fix(runtime): correct pi AgentEvent projection + add Console diagnostics

karpathy-guidelines pass — never guess at types, read the source.

Root cause of the silent "等待日志输出" Console:

1. runtime-bridge's projectAgentEventToLogChunk switched on event types
   that never exist in pi. Real types per pi-mono-reference/packages/
   agent/src/types.ts `AgentEvent`:
     agent_start / agent_end / turn_start / turn_end
     message_start / message_update / message_end
     tool_execution_start / tool_execution_update / tool_execution_end
   (NOT the `assistant_message_delta` / `tool_call_start` / `error` I
   invented.) Every case therefore fell through to `default → null`,
   so zero log chunks were emitted even when the agent was streaming
   tokens.

   Rewrote the switch to match the real union. Streaming assistant
   text and thinking now route through `message_update` ->
   `assistantMessageEvent.text_delta` / `thinking_delta` (see
   pi-mono-reference/packages/ai/src/types.ts `AssistantMessageEvent`
   for the inner protocol). Tool activity routes through
   `tool_execution_start` / `tool_execution_end`.

2. worker-runner had no Console-visible breadcrumbs between
   `run-status: running` and eventual turn output, so when the agent
   stalled (credential fetch, model resolution, first API call) the
   user saw a bare "RUNNING" with no clue where time was being spent.
   Added 5 targeted `run-log` probes:
     [runner] starting run ...
     [runner] prepareRun ok, resolving model
     [runner] model=... provider=... — constructing Agent
     [pi-event] <type>        (inside agent.subscribe, one per event)
     [runner] calling agent.prompt (N chars)
     [runner] agent.prompt resolved
   These are structured runtime signals, not test/mockup data. They
   surface the exact phase a stuck run is in.

typecheck 0 / lint 0-0 / test:unit 508/508 / build:verify 0-0.
This commit is contained in:
ZacharyZhang-NY committed 2026-04-17 10:35:23 -04:00
1 parent 7fc707ab8c
commit 806200b3d1
2 files changed
+72 -15

No files matched your search

+9
View File
@@ -45,6 +45,7 @@ export class WorkerRunner {
this.abortControllers.set(job.runId, abortController);
post({ type: "run-status", runId: job.runId, status: "running", message: `Running agent ${job.agentName}.` });
post({ type: "run-log", runId: job.runId, stream: "stdout", chunk: `[runner] starting run for ${job.agentName} (${job.connectorId}/${job.model ?? "?"})\n` });
let resolvedModelId: string | null = job.model;
let finalUsage: { inputTokens: number | null; outputTokens: number | null; costUsd: number | null } = {
@@ -72,6 +73,7 @@ export class WorkerRunner {
}
const prepared = prepareResponse.result as PrepareRunResult;
post({ type: "run-log", runId: job.runId, stream: "stdout", chunk: `[runner] prepareRun ok, resolving model\n` });
const restoreEnv = applyEnvOverrides(prepared.extraEnv);
try {
const model = resolveModel(prepared.provider, job.model);
@@ -80,6 +82,7 @@ export class WorkerRunner {
}
resolvedModelId = model.id;
const extraHeaders = resolveExtraStreamHeaders(prepared.provider, prepared.apiKey);
post({ type: "run-log", runId: job.runId, stream: "stdout", chunk: `[runner] model=${model.id} provider=${prepared.provider} — constructing Agent\n` });
const cwd = job.workspacePath ?? process.cwd();
const tools = createCodingTools(cwd) as never;
@@ -123,6 +126,10 @@ export class WorkerRunner {
const unsubscribe = agent.subscribe(async (event) => {
const timestamp = new Date().toISOString();
// Surface every pi AgentEvent type as a raw Console probe so the
// operator can see the live event stream even when projection
// misses a type or the agent is stuck inside a particular phase.
post({ type: "run-log", runId: job.runId, stream: "stdout", chunk: `[pi-event] ${event.type}\n` });
post({
type: "agent-event",
runId: job.runId,
@@ -159,9 +166,11 @@ export class WorkerRunner {
content: [{ type: "text", text: promptContent }],
timestamp: Date.now(),
};
post({ type: "run-log", runId: job.runId, stream: "stdout", chunk: `[runner] calling agent.prompt (${promptContent.length} chars)\n` });
const runPromise = agent.prompt(promptMessage);
abortController.signal.addEventListener("abort", () => agent.abort(), { once: true });
await runPromise;
post({ type: "run-log", runId: job.runId, stream: "stdout", chunk: `[runner] agent.prompt resolved\n` });
} finally {
unsubscribe();
}