diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4678dd1..5a8cc83 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,6 +12,7 @@ concurrency: jobs: test: runs-on: ubuntu-latest + timeout-minutes: 15 steps: - uses: actions/checkout@v5 - uses: oven-sh/setup-bun@v2 diff --git a/bun.lockb b/bun.lockb index 48ace75..b79cd09 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index 51ede25..66aee5b 100644 --- a/package.json +++ b/package.json @@ -25,9 +25,9 @@ }, "dependencies": { "@clack/prompts": "^1.3.0", - "@earendil-works/pi-agent-core": "^0.74.1", - "@earendil-works/pi-ai": "^0.79.7", - "@earendil-works/pi-coding-agent": "^0.79.7", + "@earendil-works/pi-agent-core": "0.84.4", + "@earendil-works/pi-ai": "0.84.4", + "@earendil-works/pi-coding-agent": "0.84.4", "discord.js": "^14.16.3", "mdast-util-from-markdown": "^2.0.3", "remark-gfm": "^4.0.1", diff --git a/src/agent/installStreamingSender.ts b/src/agent/installStreamingSender.ts index b074ef6..beced4d 100644 --- a/src/agent/installStreamingSender.ts +++ b/src/agent/installStreamingSender.ts @@ -83,6 +83,17 @@ export function installStreamingSender(args: { const stopReason = (message as { stopReason?: string }).stopReason; const errorMessage = (message as { errorMessage?: string }).errorMessage; if (stopReason === "error" && errorMessage) { + // Log to journal too — the channel-side "agent error" line is + // easy to miss (no debug-channel copy, no stderr) so operators + // digging into `journalctl -u …` after the fact see nothing. + // Include provider/model for post-mortem: 401/403s here almost + // always trace back to a specific provider's credential or a + // stale models.json format (e.g. the pi 0.79→0.84 config-value + // syntax change). + const provider = (message as { provider?: string }).provider; + const model = (message as { model?: string }).model; + const modelTag = provider || model ? ` [${provider ?? "?"}/${model ?? "?"}]` : ""; + console.error(`[stream] agent turn errored${modelTag}: ${errorMessage}`); sender .sendError(new Error(errorMessage)) .catch((error) => console.error("[stream] sendError failed:", error)); diff --git a/src/createAgentPool.ts b/src/createAgentPool.ts index 679fde4..b1774a6 100644 --- a/src/createAgentPool.ts +++ b/src/createAgentPool.ts @@ -299,7 +299,7 @@ export function createAgentPool(args: { // match. const sessionModel = defaultProvider && defaultModel - ? services.modelRegistry?.find(defaultProvider, defaultModel) ?? undefined + ? services.modelRuntime?.getModel(defaultProvider, defaultModel) ?? undefined : undefined; const { session } = await createAgentSessionFromServices({ diff --git a/src/io/createDebugLogger.ts b/src/io/createDebugLogger.ts index 0547c79..3084093 100644 --- a/src/io/createDebugLogger.ts +++ b/src/io/createDebugLogger.ts @@ -171,9 +171,51 @@ export function createDebugLogger(args: { const startLog = pendingCompactionStartLog; pendingCompactionStartLog = null; postCompactionEnd(event, startLog); + return; + } + // Surface model-turn errors (auth failures, provider 5xx, malformed + // responses) into the debug channel. The channel-facing `sendError` + // is a single terse line in the *user* channel; without a copy here, + // operators monitoring debug have no signal that anything went wrong. + if (event.type === "turn_end" && isAssistantMessageEnd(event.message)) { + const msg = event.message as { + stopReason?: string; + errorMessage?: string; + provider?: string; + model?: string; + }; + if (msg.stopReason === "error" && msg.errorMessage) { + postTurnError({ + errorMessage: msg.errorMessage, + provider: msg.provider, + model: msg.model, + }).catch((error) => console.error("[debugLogger] turn-error post failed:", error)); + } } }); + async function postTurnError(args: { + errorMessage: string; + provider?: string; + model?: string; + }): Promise { + const channel = await getDebugChannel(); + if (!channel) return; + const link = sourceMessageUrl ?? (await getChannelLink()); + const head = link ? `[agent error](<${link}>)` : "agent error"; + const modelTag = + args.provider || args.model ? ` · ${args.provider ?? "?"}/${args.model ?? "?"}` : ""; + const text = `-# ⚠️ ${head}${modelTag}: ${sanitizeBackticks(args.errorMessage)}`.slice( + 0, + hardCharLimit, + ); + await sendDebugMessage({ + channel, + content: text, + errorContext: "turn-error post failed", + }); + } + async function postCompactionStart(reason: string): Promise { const channel = await getDebugChannel(); if (!channel) return null;