Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
d9459ea
fix(transcript): fold mid-turn task notifications into the current tu…
wbxl2000 Aug 19, 2026
19e513c
chore: changeset for the notification fold fix
wbxl2000 Aug 19, 2026
599d0d9
fix(transcript): key the notification fold on persisted task-turn bou…
wbxl2000 Aug 19, 2026
0ccda12
fix(transcript): collect background_task turn origins too, and fall b…
wbxl2000 Aug 19, 2026
04255a7
fix(transcript): fold consecutive task notifications into the same turn
wbxl2000 Aug 19, 2026
2f2b2c5
fix(transcript): normalize folded notification text to the live title…
wbxl2000 Aug 19, 2026
9905e3b
fix(transcript): stop folded notification text before child blocks, p…
wbxl2000 Aug 19, 2026
163ec98
fix(transcript): truncate folded notification text at the first child…
wbxl2000 Aug 19, 2026
b43e971
fix(transcript): drop folded notifications without a persisted step, …
wbxl2000 Aug 19, 2026
bbb2559
fix(transcript): attach mid-turn task notifications to the following …
wbxl2000 Aug 19, 2026
7c36d45
fix(transcript): keep other tasks' notifications in task-origin turns
wbxl2000 Aug 19, 2026
ea6d64d
fix(transcript): derive task-turn boundaries from durable turn.prompt…
wbxl2000 Aug 19, 2026
57ee405
feat(transcript): carry subagent model and thinking effort on task en…
wbxl2000 Aug 19, 2026
609d849
fix(transcript): mirror turn liveness into meta.activity, live and cold
wbxl2000 Aug 20, 2026
4733e9e
fix(transcript): populate the prompts entity from prompt.accepted/que…
wbxl2000 Aug 20, 2026
d86f3e3
fix(transcript): reconcile liveness and the prompt queue at backfill …
wbxl2000 Aug 20, 2026
3e5976e
fix(transcript): include the prompts entity in the REST transcript re…
wbxl2000 Aug 20, 2026
2ea5f79
fix(agent-core-v2): publish prompt.accepted on the event bus
wbxl2000 Aug 20, 2026
281ebf8
test(transcript): add the contract-level e2e covering every entity th…
wbxl2000 Aug 20, 2026
eae2920
fix(transcript): guard the prompt backfill against missing services; …
wbxl2000 Aug 20, 2026
7daf99e
test(agent-core-v2): re-record event stream snapshots with prompt.acc…
wbxl2000 Aug 20, 2026
11f9cd0
fix(transcript): settle the spawned agent row when its lifecycle redi…
wbxl2000 Aug 20, 2026
2f795cc
test(transcript): move the contract e2e timeout to the describe arg (…
wbxl2000 Aug 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/transcript-notification-fold.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@moonshot-ai/kimi-code": patch
---

Fix the cold transcript rebuild splitting a turn at background-task completion notices; they now fold into the current turn like the live stream does.
1 change: 1 addition & 0 deletions packages/agent-core-v2/src/agent/prompt/promptOps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const promptAcceptedSchema = z.object({
export class PromptAccepted extends AgentEvent2<z.infer<typeof promptAcceptedSchema>> {
static override readonly type = 'prompt.accepted';
static override readonly durable = true;
static override readonly observable = true;
static override readonly schema = promptAcceptedSchema;
}
export interface PromptAccepted {
Expand Down
3 changes: 3 additions & 0 deletions packages/agent-core-v2/test/agent/loop/loop.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ describe('Agent loop', () => {
expect(await ctx.untilTurnEnd()).toMatchInlineSnapshot(`
[wire] tools.set_active_tools { "agentId": "main", "names": [], "time": "<time>" }
[wire] prompt.accepted { "agentId": "main", "promptId": "<msg-1>", "time": "<time>" }
[emit] prompt.accepted { "time": "<time>", "agentId": "main", "promptId": "<msg-1>" }
[wire] turn.prompt { "agentId": "main", "input": [ { "type": "text", "text": "Hello" } ], "origin": { "kind": "user" }, "time": "<time>" }
[emit] turn.started { "time": "<time>", "agentId": "main", "turnId": 0, "origin": { "kind": "user" }, "prompt": "Hello" }
[emit] agent.activity.updated { "time": "<time>", "lifecycle": "ready", "turn": { "turnId": 0, "origin": { "kind": "user" }, "phase": "running", "step": 0, "ending": false, "pendingApprovals": [], "activeToolCalls": [], "since": "<time>" }, "background": [], "agentId": "main" }
Expand Down Expand Up @@ -124,6 +125,7 @@ describe('Agent loop', () => {

expect(await ctx.untilTurnEnd()).toMatchInlineSnapshot(`
[wire] prompt.accepted { "agentId": "main", "promptId": "<msg-1>", "time": "<time>" }
[emit] prompt.accepted { "time": "<time>", "agentId": "main", "promptId": "<msg-1>" }
[wire] turn.prompt { "agentId": "main", "input": [ { "type": "text", "text": "Hello" } ], "origin": { "kind": "user" }, "time": "<time>" }
[emit] turn.started { "time": "<time>", "agentId": "main", "turnId": 0, "origin": { "kind": "user" }, "prompt": "Hello" }
[emit] agent.activity.updated { "time": "<time>", "lifecycle": "ready", "turn": { "turnId": 0, "origin": { "kind": "user" }, "phase": "running", "step": 0, "ending": false, "pendingApprovals": [], "activeToolCalls": [], "since": "<time>" }, "background": [], "agentId": "main" }
Expand Down Expand Up @@ -346,6 +348,7 @@ describe('Agent loop', () => {
expect(await ctx.untilApproval(true)).toMatchInlineSnapshot(`
[wire] tools.set_active_tools { "agentId": "main", "names": [ "Lookup" ], "time": "<time>" }
[wire] prompt.accepted { "agentId": "main", "promptId": "<msg-1>", "time": "<time>" }
[emit] prompt.accepted { "time": "<time>", "agentId": "main", "promptId": "<msg-1>" }
[wire] turn.prompt { "agentId": "main", "input": [ { "type": "text", "text": "Look up moon" } ], "origin": { "kind": "user" }, "time": "<time>" }
[emit] turn.started { "time": "<time>", "agentId": "main", "turnId": 0, "origin": { "kind": "user" }, "prompt": "Look up moon" }
[emit] agent.activity.updated { "time": "<time>", "lifecycle": "ready", "turn": { "turnId": 0, "origin": { "kind": "user" }, "phase": "running", "step": 0, "ending": false, "pendingApprovals": [], "activeToolCalls": [], "since": "<time>" }, "background": [], "agentId": "main" }
Expand Down
2 changes: 2 additions & 0 deletions packages/agent-core-v2/test/app/config/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,7 @@ describe('Agent config', () => {
});
expect(await ctx.untilApproval(true)).toMatchInlineSnapshot(`
[wire] prompt.accepted { "agentId": "main", "promptId": "<msg-1>", "time": "<time>" }
[emit] prompt.accepted { "time": "<time>", "agentId": "main", "promptId": "<msg-1>" }
[wire] turn.prompt { "agentId": "main", "input": [ { "type": "text", "text": "Look up before config changes" } ], "origin": { "kind": "user" }, "time": "<time>" }
[emit] turn.started { "time": "<time>", "agentId": "main", "turnId": 0, "origin": { "kind": "user" }, "prompt": "Look up before config changes" }
[emit] agent.activity.updated { "time": "<time>", "lifecycle": "ready", "turn": { "turnId": 0, "origin": { "kind": "user" }, "phase": "running", "step": 0, "ending": false, "pendingApprovals": [], "activeToolCalls": [], "since": "<time>" }, "background": [], "agentId": "main" }
Expand Down Expand Up @@ -402,6 +403,7 @@ describe('Agent config', () => {
[emit] agent.status.updated { "time": "<time>", "agentId": "main", "contextTokens": 102 }
[emit] prompt.completed { "time": "<time>", "agentId": "main", "promptId": "<msg-1>", "finishedAt": "<time>", "reason": "completed" }
[wire] prompt.accepted { "agentId": "main", "promptId": "<msg-2>", "time": "<time>" }
[emit] prompt.accepted { "time": "<time>", "agentId": "main", "promptId": "<msg-2>" }
[wire] turn.prompt { "agentId": "main", "input": [ { "type": "text", "text": "Start a fresh turn" } ], "origin": { "kind": "user" }, "time": "<time>" }
[emit] turn.started { "time": "<time>", "agentId": "main", "turnId": 1, "origin": { "kind": "user" }, "prompt": "Start a fresh turn" }
[emit] agent.activity.updated { "time": "<time>", "lifecycle": "ready", "turn": { "turnId": 1, "origin": { "kind": "user" }, "phase": "running", "step": 0, "ending": false, "pendingApprovals": [], "activeToolCalls": [], "since": "<time>" }, "background": [], "agentId": "main" }
Expand Down
2 changes: 2 additions & 0 deletions packages/agent-core-v2/test/features/plan/plan.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -717,6 +717,7 @@ describe('Plan service', () => {
[wire] plan_mode.enter { "agentId": "main", "id": "test-plan", "time": "<time>" }
[emit] agent.status.updated { "time": "<time>", "agentId": "main", "planMode": true }
[wire] prompt.accepted { "agentId": "main", "promptId": "<msg-1>", "time": "<time>" }
[emit] prompt.accepted { "time": "<time>", "agentId": "main", "promptId": "<msg-1>" }
[wire] turn.prompt { "agentId": "main", "input": [ { "type": "text", "text": "Inspect without mutating files" } ], "origin": { "kind": "user" }, "time": "<time>" }
[emit] turn.started { "time": "<time>", "agentId": "main", "turnId": 0, "origin": { "kind": "user" }, "prompt": "Inspect without mutating files" }
[emit] agent.activity.updated { "time": "<time>", "lifecycle": "ready", "turn": { "turnId": 0, "origin": { "kind": "user" }, "phase": "running", "step": 0, "ending": false, "pendingApprovals": [], "activeToolCalls": [], "since": "<time>" }, "background": [], "agentId": "main" }
Expand Down Expand Up @@ -797,6 +798,7 @@ describe('Plan service', () => {
[wire] plan_mode.enter { "agentId": "main", "id": "test-plan", "time": "<time>" }
[emit] agent.status.updated { "time": "<time>", "agentId": "main", "planMode": true }
[wire] prompt.accepted { "agentId": "main", "promptId": "<msg-1>", "time": "<time>" }
[emit] prompt.accepted { "time": "<time>", "agentId": "main", "promptId": "<msg-1>" }
[wire] turn.prompt { "agentId": "main", "input": [ { "type": "text", "text": "Remove forbidden.txt" } ], "origin": { "kind": "user" }, "time": "<time>" }
[emit] turn.started { "time": "<time>", "agentId": "main", "turnId": 0, "origin": { "kind": "user" }, "prompt": "Remove forbidden.txt" }
[emit] agent.activity.updated { "time": "<time>", "lifecycle": "ready", "turn": { "turnId": 0, "origin": { "kind": "user" }, "phase": "running", "step": 0, "ending": false, "pendingApprovals": [], "activeToolCalls": [], "since": "<time>" }, "background": [], "agentId": "main" }
Expand Down
2 changes: 2 additions & 0 deletions packages/agent-core-v2/test/tool/tool.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3806,6 +3806,7 @@ describe('Agent tools', () => {
[wire] permission.set_mode { "agentId": "main", "mode": "auto", "time": "<time>" }
[wire] tools.register_user_tool { "name": "Lookup", "description": "Look up a short test value.", "parameters": { "type": "object", "properties": { "query": { "type": "string" } }, "required": [ "query" ], "additionalProperties": false }, "agentId": "main", "time": "<time>" }
[wire] prompt.accepted { "agentId": "main", "promptId": "<msg-1>", "time": "<time>" }
[emit] prompt.accepted { "time": "<time>", "agentId": "main", "promptId": "<msg-1>" }
[wire] turn.prompt { "agentId": "main", "input": [ { "type": "text", "text": "Look up moon" } ], "origin": { "kind": "user" }, "time": "<time>" }
[emit] turn.started { "time": "<time>", "agentId": "main", "turnId": 0, "origin": { "kind": "user" }, "prompt": "Look up moon" }
[emit] agent.activity.updated { "time": "<time>", "lifecycle": "ready", "turn": { "turnId": 0, "origin": { "kind": "user" }, "phase": "running", "step": 0, "ending": false, "pendingApprovals": [], "activeToolCalls": [], "since": "<time>" }, "background": [], "agentId": "main" }
Expand Down Expand Up @@ -3882,6 +3883,7 @@ describe('Agent tools', () => {
[wire] tools.unregister_user_tool { "agentId": "main", "name": "Lookup", "time": "<time>" }
[emit] prompt.completed { "time": "<time>", "agentId": "main", "promptId": "<msg-1>", "finishedAt": "<time>", "reason": "completed" }
[wire] prompt.accepted { "agentId": "main", "promptId": "<msg-2>", "time": "<time>" }
[emit] prompt.accepted { "time": "<time>", "agentId": "main", "promptId": "<msg-2>" }
[wire] turn.prompt { "agentId": "main", "input": [ { "type": "text", "text": "Can you still use Lookup?" } ], "origin": { "kind": "user" }, "time": "<time>" }
[emit] turn.started { "time": "<time>", "agentId": "main", "turnId": 1, "origin": { "kind": "user" }, "prompt": "Can you still use Lookup?" }
[emit] agent.activity.updated { "time": "<time>", "lifecycle": "ready", "turn": { "turnId": 1, "origin": { "kind": "user" }, "phase": "running", "step": 0, "ending": false, "pendingApprovals": [], "activeToolCalls": [], "since": "<time>" }, "background": [], "agentId": "main" }
Expand Down
2 changes: 2 additions & 0 deletions packages/kap-server/src/routes/transcript.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ export function registerTranscriptRoutes(app: TranscriptRouteHost, deps: Transcr
interactions: [...transcript.getInteractions().values()],
attachments: [...transcript.getAttachments().values()],
todos: [...transcript.getTodos().values()],
prompts: [...transcript.getPrompts().values()],
meta: transcript.getMeta(),
agents: store.agents(),
pending_interactions: transcript.listPendingInteractions(),
Expand Down Expand Up @@ -197,6 +198,7 @@ export function registerTranscriptRoutes(app: TranscriptRouteHost, deps: Transcr
interactions: snapshot.interactions,
attachments: snapshot.attachments,
todos: snapshot.todos,
prompts: snapshot.prompts,
meta: snapshot.meta,
agents: roster,
pending_interactions: [],
Expand Down
113 changes: 97 additions & 16 deletions packages/kap-server/src/services/transcript/coreEventMap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ import type {
PromptCompleted,
PromptSteered,
} from '@moonshot-ai/agent-core-v2/agent/prompt/promptService';
import type { PromptAccepted } from '@moonshot-ai/agent-core-v2/agent/prompt/promptOps';
import type { PromptQueued } from '@moonshot-ai/agent-core-v2/agent/prompt/promptService';
import type {
ShellCompleted,
ShellOutput,
Expand Down Expand Up @@ -89,6 +91,8 @@ export interface ProjectorInteraction {
type PlanRevisionEvent = { readonly type: 'plan.revision' } & PlanRevision;

type AgentActivityUpdatedEvent = { readonly type: 'agent.activity.updated' } & AgentActivityUpdated;
type PromptAcceptedEvent = { readonly type: 'prompt.accepted' } & PromptAccepted;
type PromptQueuedEvent = { readonly type: 'prompt.queued' } & PromptQueued;
type PromptCompletedEvent = { readonly type: 'prompt.completed' } & PromptCompleted;
type PromptAbortedEvent = { readonly type: 'prompt.aborted' } & PromptAborted;
type PromptSteeredEvent = { readonly type: 'prompt.steered' } & PromptSteered;
Expand Down Expand Up @@ -121,6 +125,8 @@ export type ProjectorBusEvent =
| ({ readonly type: 'goal.updated' } & GoalUpdated)
| ({ readonly type: 'agent.status.updated' } & AgentStatusUpdated)
| AgentActivityUpdatedEvent
| PromptAcceptedEvent
| PromptQueuedEvent
| PromptCompletedEvent
| PromptAbortedEvent
| PromptSteeredEvent
Expand Down Expand Up @@ -201,6 +207,7 @@ export class AgentTranscriptProjector {
/** Latest header of the in-flight (or most recent) turn; kept whole so terminal upserts preserve `origin` / `startedAt` by reference. */
private currentTurn: TurnHeader | undefined;
private currentStep: StepHeader | undefined;
private pendingTaskNotifications: { text: string; taskId: string | undefined }[] = [];
/** turnId → highest step ordinal seen (engine-reported placement hint). */
private readonly stepOrdinals = new Map<string, number>();
private frameOrdinal = 0;
Expand Down Expand Up @@ -312,6 +319,10 @@ export class AgentTranscriptProjector {
return this.onAgentStatusUpdated(event);
case 'agent.activity.updated':
return this.onAgentActivityUpdated(event);
case 'prompt.accepted':
return this.onPromptAccepted(event);
case 'prompt.queued':
return this.onPromptQueued(event);
case 'prompt.submitted':
return this.onPromptSubmitted(event);
case 'prompt.completed':
Expand Down Expand Up @@ -379,9 +390,11 @@ export class AgentTranscriptProjector {
startedAt: nowIso(),
};
this.currentStep = undefined;
this.pendingTaskNotifications = [];
this.openText = undefined;
this.openThinking = undefined;
ops.push({ op: 'turn.upsert', turn: this.currentTurn });
ops.push({ op: 'meta.merge', meta: { activity: 'turn' } });
return ops;
}

Expand Down Expand Up @@ -419,7 +432,9 @@ export class AgentTranscriptProjector {
usage: this.takeTurnUsage(turnId),
};
ops.push({ op: 'turn.upsert', turn: this.currentTurn });
ops.push({ op: 'meta.merge', meta: { activity: 'idle' } });
this.currentStep = undefined;
this.pendingTaskNotifications = [];
if (event.reason === 'cancelled' && event.interruptReason === 'user_cancelled') {
ops.push(
this.markerOp('interruption', { turnId: event.turnId, reason: event.interruptReason }),
Expand Down Expand Up @@ -473,7 +488,23 @@ export class AgentTranscriptProjector {
this.frameOrdinal = 0;
this.openText = undefined;
this.openThinking = undefined;
return [{ op: 'step.upsert', turnId, step: this.currentStep }];
const ops: TranscriptOperation[] = [{ op: 'step.upsert', turnId, step: this.currentStep }];
for (const pending of this.pendingTaskNotifications) {
ops.push({
op: 'frame.upsert',
turnId,
stepId,
frame: {
kind: 'text',
frameId: `${stepId}.f${++this.frameOrdinal}`,
role: 'user',
text: pending.text,
taskId: pending.taskId,
},
});
}
this.pendingTaskNotifications = [];
return ops;
}

private onStepCompleted(event: {
Expand Down Expand Up @@ -865,20 +896,21 @@ export class AgentTranscriptProjector {
}): TranscriptOperation[] {
const step = this.currentStep;
const turn = this.currentTurn;
const midTurn =
step !== undefined &&
turn !== undefined &&
step.state === 'running' &&
turn.state === 'running';
if (!midTurn) return [];
const frame: TextFrame = {
kind: 'text',
frameId: `${step.stepId}.f${++this.frameOrdinal}`,
role: 'user',
text: `${event.title}\n${event.body}`.trim(),
taskId: event.sourceId,
};
return [{ op: 'frame.upsert', turnId: turn.turnId, stepId: step.stepId, frame }];
if (turn === undefined || turn.state !== 'running') return [];
const text = `${event.title}\n${event.body}`.trim();
if (step !== undefined && step.state === 'running') {
const frame: TextFrame = {
kind: 'text',
frameId: `${step.stepId}.f${++this.frameOrdinal}`,
role: 'user',
text,
taskId: event.sourceId,
};
return [{ op: 'frame.upsert', turnId: turn.turnId, stepId: step.stepId, frame }];
}
if (turn.origin?.kind === 'task' && (turn.origin.taskId === undefined || turn.origin.taskId === event.sourceId)) return [];
this.pendingTaskNotifications.push({ text, taskId: event.sourceId });
return [];
}

private onTaskLifecycle(event: {
Expand Down Expand Up @@ -1046,6 +1078,8 @@ export class AgentTranscriptProjector {
swarmIndex?: number;
runInBackground: boolean;
taskId?: string;
model?: string;
thinkingEffort?: string;
}): TranscriptOperation[] {
const taskKey = event.taskId ?? event.subagentId;
if (event.taskId !== undefined) {
Expand All @@ -1063,6 +1097,8 @@ export class AgentTranscriptProjector {
outputTail: prev?.outputTail ?? '',
startedAt: prev?.startedAt ?? nowIso(),
endedAt: prev?.endedAt,
model: event.model ?? prev?.model,
thinkingEffort: event.thinkingEffort ?? prev?.thinkingEffort,
}));
const ops: TranscriptOperation[] = [{ op: 'task.upsert', task }];
const hit =
Expand Down Expand Up @@ -1114,8 +1150,34 @@ export class AgentTranscriptProjector {
usage: event.usage ?? prev?.usage,
error: event.error ?? prev?.error,
stateReason: event.reason ?? prev?.stateReason,
model: prev?.model,
thinkingEffort: prev?.thinkingEffort,
}));
return [{ op: 'task.upsert', task }];
const ops: TranscriptOperation[] = [{ op: 'task.upsert', task }];
if (taskKey !== event.subagentId && this.tasks.has(event.subagentId)) {
const agentTask = this.upsertTask(event.subagentId, (prev) => ({
taskId: event.subagentId,
kind: 'subagent',
state,
detached: prev?.detached ?? true,
description: prev?.description,
agentId: event.subagentId,
outputTail: prev?.outputTail ?? '',
startedAt: prev?.startedAt ?? nowIso(),
endedAt:
event.type === 'subagent.completed' || event.type === 'subagent.failed'
? nowIso()
: prev?.endedAt,
resultSummary: event.resultSummary ?? prev?.resultSummary,
usage: event.usage ?? prev?.usage,
error: event.error ?? prev?.error,
stateReason: event.reason ?? prev?.stateReason,
model: prev?.model,
thinkingEffort: prev?.thinkingEffort,
}));
ops.push({ op: 'task.upsert', task: agentTask });
}
return ops;
}

private onGoalUpdated(event: {
Expand Down Expand Up @@ -1269,6 +1331,25 @@ export class AgentTranscriptProjector {
return this.markerOp('notice', { level, message, event: eventPayload });
}

private onPromptAccepted(event: PromptAcceptedEvent): TranscriptOperation[] {
const prompt = this.upsertPrompt(event.promptId, () => ({
promptId: event.promptId,
status: 'running',
createdAt: nowIso(),
}));
Comment on lines +1335 to +1339

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve active prompt details on the event path

When the transcript binding already exists before a normal prompt submission, the first immediately launched prompt is initialized only by prompt.accepted; prompt.queued is not emitted, and the terminal handlers merely preserve the previous fields. This initializer omits both userMessageId and content, so the same prompt is complete when discovered through livePromptBackfill but remains permanently partial when observed from submission onward. Carry the admitted prompt details in the event or project the submission into the transcript store.

AGENTS.md reference: packages/transcript/AGENTS.md:L23-L23

Useful? React with 👍 / 👎.

return [{ op: 'prompt.upsert', prompt }];
}

private onPromptQueued(event: PromptQueuedEvent): TranscriptOperation[] {
const prompt = this.upsertPrompt(event.promptId, () => ({
promptId: event.promptId,
status: 'queued',
content: event.content,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Normalize queued prompt content before exposing it

When a queued prompt contains uploaded image, video, or session-media content, this stores the engine ContentPart[] directly, including internal image_url/kimi-file:// representations, instead of the public message-content shape used by the prompt routes and steer projection. The late-attach backfill repeats the same direct assignment, so transcript clients receive internal and path-dependent content; apply projectPromptContentParts in both paths.

AGENTS.md reference: packages/transcript/AGENTS.md:L23-L23

Useful? React with 👍 / 👎.

createdAt: nowIso(),
}));
return [{ op: 'prompt.upsert', prompt }];
}

private onPromptSubmitted(event: ProjectorPromptSubmittedEvent): TranscriptOperation[] {
const prompt = this.upsertPrompt(event.promptId, () => ({
promptId: event.promptId,
Expand Down
Loading
Loading