Skip to content

feat(transcript): add unified transcript layer, drop the /api/v2 RPC surface#1888

Merged
sailist merged 47 commits into
MoonshotAI:mainfrom
sailist:feat/unified-transcript-layer
Jul 20, 2026
Merged

feat(transcript): add unified transcript layer, drop the /api/v2 RPC surface#1888
sailist merged 47 commits into
MoonshotAI:mainfrom
sailist:feat/unified-transcript-layer

Conversation

@sailist

@sailist sailist commented Jul 18, 2026

Copy link
Copy Markdown
Collaborator

Related Issue

No linked issue — the problem is explained below.

Problem

Session rendering state (turns, steps, tool calls, tasks) was re-derived independently by every consumer: kap-server re-projected context memory for REST, the v1 WS broadcaster pushed raw engine events, and each client rebuilt its own timeline from those events. Every new frame kind or edge case — cold rebuilds, session reloads, subagent frame adoption, missed tool results — had to be fixed in several places at once, and the surfaces still disagreed with each other.

At the same time the RPC surface existed three times over (/api/v2 REST, /api/v2/ws, and the dev-only /api/v1/debug/*) with a channel whitelist to maintain, and the klient http transport plus the dual/v2 e2e suites existed only to serve it.

What changed

1. Unified transcript layer (@moonshot-ai/transcript)

Problem: every consumer re-derived rendering state on its own, so fixes and new frame kinds had to be replicated across server and clients.

What was done:

  • New packages/transcript: a pure-TypeScript, browser-safe transcript data layer — agent-granular L1 store, idempotent L2 operations, off/turn/block/delta L3 subscription granularity, framework-free L4 view registry, and turn-cursor pagination. It is the sole owner of all transcript wire types.
  • kap-server: an engine-event-driven TranscriptService projects core events into transcript ops, backfills history from the persisted per-agent wire records (cold sessions rebuild any agent, with 0-based turn ordinals matching the engine's), and exposes GET /api/v1/sessions/{id}/transcript (cursor pages) plus delta-only transcript.ops over /api/v1/ws with per-connection granularity control.
  • kimi-inspect: ChatView renders turn-granularly from the transcript surface (REST pages + delta-only WS) instead of context memory, reusing the package's L2 reducer with no local re-implementation.
  • Follow-up correctness fixes: subscriber seeding and baseline resets, cursor-based replay, live/cold backfill merge, shell event routing by task id, subagent frame adoption, missed tool-result healing, transcript reattach after session reload, and more.

2. Drop the /api/v2 RPC surface and the klient http transport

Problem: the RPC surface was triplicated (v2 REST, v2 WS, debug REST) with a whitelist to keep in sync, and the klient http transport plus the dual/v2 e2e suites existed only to serve it.

What was done:

  • kap-server: removed the /api/v2 REST routes and /api/v2/ws socket (registerRpcRoutes renamed to serviceDispatcherRoutes; the v2 ws modules deleted). /api/v1/debug/* is now the only RPC surface — a reflection dispatcher over the entire scoped DI registry with no whitelist, mounted only with --debug-endpoints on a loopback bind and gated by the global bearer auth. /api/v1/ws is the only WS endpoint.
  • klient: dropped the http transport and the kap-server devDependency; transports reduce to the ipc|memory subpath entries, and the dual/v2 e2e suites were removed with them.
  • kimi-inspect: targets /api/v1/debug only (no fallback), polls react-query on a 15 s interval instead of a Service-event push channel, and shows a blocking "Debug surface unavailable" screen on connection failure.
  • transcript: added global attachment / interaction / todo entities (model, ops, wire schema), projected from engine events in kap-server's projector.

3. Supporting fixes

  • protocol: register shell.completed on the v1 event surface; node-sdk covers it in the exhaustive event switch.
  • agent-core-v2: keep system-turn steering text out of turn prompts.

Checklist

  • I have read the CONTRIBUTING document.
  • I have linked a related issue, or explained the problem above.
  • I have added tests that prove my feature works.
  • Ran gen-changesets skill, or this PR needs no changeset.
  • Ran gen-docs skill, or this PR needs no doc update.

@changeset-bot

changeset-bot Bot commented Jul 18, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 55b743f

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 3 packages
Name Type
@moonshot-ai/transcript Patch
@moonshot-ai/kap-server Patch
@moonshot-ai/agent-core-v2 Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

- add packages/transcript: agent-granular L1 store, idempotent L2 ops,
  off/turn/block/delta L3 granularity, L4 view registry, and turn-cursor
  pagination; sole owner of all transcript wire types
- kap-server: engine-event-driven TranscriptService with history backfill,
  GET /sessions/{id}/transcript, and transcript.ops WS deltas with
  per-connection granularity control
- kimi-inspect: render ChatView from the transcript surface (REST pages +
  delta-only WS) instead of context memory
- sync flake.nix workspace lists and document packages/transcript and
  packages/server-e2e in AGENTS.md
@sailist
sailist force-pushed the feat/unified-transcript-layer branch from 341402b to c2c7450 Compare July 18, 2026 15:46
@pkg-pr-new

pkg-pr-new Bot commented Jul 18, 2026

Copy link
Copy Markdown
pnpm dlx https://pkg.pr.new/@moonshot-ai/kimi-code@55b743f
npx https://pkg.pr.new/@moonshot-ai/kimi-code@55b743f

commit: 55b743f

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 341402bbf2

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +181 to +184
ordinal: n,
state: 'running',
origin: mapTurnOrigin(event.origin),
startedAt: nowIso(),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Project live user prompts into turns

When a user sends a prompt after the transcript store has been bound, the live turn header is built from turn.started, which carries only the turn id/origin; this projector also does not observe the later context.append_message that contains the user text, and live REST reads return this in-memory store rather than rebuilding from context. The new ChatView clears the textarea after submit, so every live user bubble is missing from the transcript until a cold rebuild. Please add a live projection/optimistic upsert for the prompt text, or include the prompt in the turn header before emitting turn.upsert.

Useful? React with 👍 / 👎.

if (!changed) return { state, changed: false };
return { state: { ...state, items }, changed: true };
}
return { state: { ...state, items: [...state.items, item] }, changed: true };

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 Keep standalone items ordered during backfill

When a live session is backfilled while new live turns are arriving, historical turns are inserted by ordinal, but historical markers/taskrefs replayed by snapshotToOps hit this append-only path. A marker or taskref that originally belonged near an old turn (for example a skill/compaction marker or background-task ref) is therefore appended after newer live turns, corrupting the transcript order and pagination window. Please preserve a segment/order anchor for standalone items, or merge the backfill as an ordered snapshot rather than appending these items blindly.

Useful? React with 👍 / 👎.

- agent-core-v2: carry the extracted prompt text on turn.started so the
  transcript projector can render the user input at turn open (the context
  append with the same text is not a bus event and lands later)
- kap-server: keep the prompt through turn.ended; anchor backfilled
  markers/taskrefs to their following snapshot turn so replaying history
  after live turns arrived keeps the historical order
- transcript: add an optional beforeTurn placement anchor to
  marker.upsert / taskref.upsert; anchored inserts land before the first
  turn at or past the anchor instead of appending blindly

Addresses review feedback on MoonshotAI#1888.
@sailist

sailist commented Jul 18, 2026

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: aa9ca9739f

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

let open = kind === 'assistant' ? this.openText : this.openThinking;
if (open === undefined) {
const frameId = `${step.stepId}.f${++this.frameOrdinal}`;
open = { frameId, offset: 0, text: '' };

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Preserve stream offsets when attaching mid-turn

When the transcript service is first opened after a turn has already streamed some text, the history backfill can seed tN.1.f1 with the text already persisted, but this fresh projector did not observe those earlier deltas and starts the next append at offset 0. The reducer treats offset < local.length as an overlap and can replace the seeded text with only the later chunk, so opening kimi-inspect mid-response or after a reconnect can corrupt the live transcript until another full rebuild. The projector needs to initialize from the backfilled frame state or otherwise force a reset/gap instead of emitting offset-0 appends for an existing frame.

Useful? React with 👍 / 👎.

const originKind = message.origin?.kind;

if (message.role === 'user') {
if (originKind !== undefined && HIDDEN_USER_ORIGINS.has(originKind)) continue;

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 Start a new turn for hidden system prompts

For persisted histories that contain a hidden system_trigger user message which actually opened a new turn (for example goal continuation uses a MessageStepRequest with origin: { kind: 'system_trigger' } and admission: 'newTurn'), this continue leaves the previous turn active. The following assistant messages are then folded into the prior visible user turn during cold rebuild/backfill, losing the system-triggered turn boundary and ordinal; hide the prompt if needed, but still start/advance a promptless turn or clear the current turn before consuming its assistant output.

Useful? React with 👍 / 👎.

- kap-server: on mid-stream attach, adopt the backfill-seeded stream frame
  (id + offset) instead of opening an empty one whose upsert clobbered the
  seeded text and whose offset-0 appends could not land
- transcript: group a turn-opening system_trigger (goal continuation) into
  its own turn so cold rebuilds keep the turn boundary and stay
  ordinal-aligned with the engine's live turn numbering

Addresses review feedback on MoonshotAI#1888.
@sailist

sailist commented Jul 18, 2026

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 049054661e

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

* materialized in this process — comes back established.
*/
async ensureAgentHistory(sessionId: string, agentId: string): Promise<void> {
if (agentId === MAIN_AGENT_ID) return this.whenReady(sessionId);

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 Re-read main backfill after mid-turn attachment

When the first transcript read/subscription happens after a turn has started but before the LLM request finishes, the initial backfill can only see records already flushed to wire.jsonl; LoopService.appendResponseContent() persists content.part records only after request.result completes, while earlier assistant.delta events were missed by the newly-created projector. Because the main-agent path here never schedules another backfill after ready, subsequent REST refreshes keep the suffix-only live store and the answer prefix is missing until process restart. Re-run/merge main history after the step/turn completes, or allow refresh to force a cold resnapshot.

Useful? React with 👍 / 👎.

*/
forSessionLive(sessionId: string): TranscriptStore | undefined {
const existing = this.live.get(sessionId);
if (existing !== undefined) return existing.store;

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 Drop live transcript stores when sessions close

After any REST/WS transcript access creates a live entry, this early return serves that store forever even if ISessionLifecycleService.close()/archive() later deletes and disposes the session handle. In that scenario the binding receives agent disposals and removes roster entries, so a subsequent GET /transcript for the closed/archived session hits this stale store and returns empty or stale data instead of falling through to readColdSnapshot from disk. Drop live entries on close/archive or re-check the lifecycle before returning an existing store.

Useful? React with 👍 / 👎.

- drop a session's live transcript store when the session closes or archives
  (lifecycle events plus a re-check on the cached-entry path) so reads fall
  back to the cold rebuild instead of serving a stale store
- re-read an ended turn from the persisted wire records (debounced per
  agent) and merge it back live-first: headers keep live state/timestamps
  while origin/prompt recover from disk, and truncated text/thinking frames
  from a mid-turn attach are restored only when the persisted text is
  longer — a fresh live frame or a lagging flush is never reverted

Addresses review feedback on MoonshotAI#1888.
@sailist

sailist commented Jul 18, 2026

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 75278067f0

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

return (
interaction.origin.agentId ??
(typeof payloadAgent === 'string' ? payloadAgent : undefined) ??
MAIN_AGENT_ID

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 Route subagent questions to their owning transcript

When a subagent uses AskUserQuestion, SessionQuestionService records only origin: { turnId } and QuestionRequest has no agentId, so this fallback always assigns the interaction to main. In that context the question frame/resolution is added to the main transcript while the subagent transcript that owns the turn/tool call never shows the pending question.

Useful? React with 👍 / 👎.

isError?: boolean;
}): TranscriptOperation[] {
const hit = this.toolFrames.get(event.toolCallId);
if (hit === undefined) return [];

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 Rehydrate missed tool results after mid-turn binding

If the transcript binds after tool.call.started but before tool.result, the backfill may seed the tool frame in the store but this projector map is still empty, so the result is dropped here; the later post-turn heal only replays text/thinking frames, leaving the tool output absent for live inspect clients until a cold rebuild/reload.

Useful? React with 👍 / 👎.

- kap-server: adopt the backfill-seeded tool frame when a tool.result
  arrives for a call that started before the projector attached, so the
  output lands instead of being dropped (the producer-store lookups now
  ride one projector options object)
- agent-core-v2: record the owning agent on question interactions
  (ISessionQuestionService.request gains an agentId option, passed by
  AskUserQuestionTool from its agent scope) so a subagent's questions
  route to its own transcript and WS events instead of 'main'

Addresses review feedback on MoonshotAI#1888.
@sailist

sailist commented Jul 18, 2026

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b3948c621f

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +356 to +360
const snapshot = transcript.snapshot({ tailTurns: TRANSCRIPT_RESET_TAIL_TURNS });
target.send(
this.buildTranscriptEnvelope(state, 'transcript.reset', {
agent_id: transcript.agentId,
snapshot,

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 Filter transcript reset snapshots by grade

When a client subscribes at turn or block grade, live transcript.ops are filtered by filterOpsForGrade, but the initial/upgrade transcript.reset still sends transcript.snapshot(...) unchanged. In those subscription modes the first frame can include full steps, frames, and text content even though turn is documented as headers/global state only, so consumers requesting a lower-detail stream still receive the full transcript on every reset; pass the grade into the reset path and redact/filter the snapshot consistently.

Useful? React with 👍 / 👎.

Comment on lines +692 to +696
private markerOp(marker: string, payload: unknown): TranscriptOperation {
this.markerSeq += 1;
const item: TranscriptMarker = {
kind: 'marker',
markerId: `m${this.markerSeq}`,

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 Avoid colliding live marker IDs with backfilled markers

For resumed or mid-bind live sessions, historical markers rebuilt by groupMessagesIntoSnapshot/snapshotToOps use IDs like m1, m2, but a new AgentTranscriptProjector always starts markerSeq at zero. The first live goal/skill/compaction/notice marker after backfill therefore also gets m1, and applyItemUpsert treats it as the same item and replaces the historical marker instead of appending, corrupting the timeline; seed the counter from existing markers or use a live-specific stable namespace.

Useful? React with 👍 / 👎.

Comment on lines +594 to +598
// Link the spawning tool call to the new agent (Agent / AgentSwarm tool
// frames). The spawned payload carries no task id of its own — the
// subagent task above is keyed by the agent id instead.
const hit = this.toolFrames.get(event.parentToolCallId);
if (hit !== undefined) {

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 Adopt seeded parent tool frames before linking subagents

If transcript binding starts after an Agent/AgentSwarm tool call was persisted/backfilled but before subagent.spawned is emitted, the parent tool frame exists in the store while this.toolFrames is still empty. This lookup then skips the frame, so the spawned subagent's agentRefs is never written and the parent tool card cannot link to the child even though adoptToolFrame handles the same mid-bind case elsewhere; use the lookup fallback here too.

Useful? React with 👍 / 👎.

…resets

- kap-server: fall back to store adoption when subagent.spawned links a
  parent tool call that started before the projector attached, so the
  agentRefs link is not lost on mid-bind attaches
- kap-server: id live markers in their own namespace (live-mN) — the cold
  rebuild numbers its markers m1... too, and a colliding id made the
  store's upsert replace the historical marker instead of appending
- transcript/kap-server: redact transcript.reset snapshots to the
  subscriber's grade (below 'block' the step/frame detail is stripped), so
  a 'turn'-grade subscription no longer receives full content on resets

Addresses review feedback on MoonshotAI#1888.
@sailist

sailist commented Jul 18, 2026

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4eb8ec0ad9

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +846 to +848
case 'cancelled':
case 'blocked':
return 'cancelled';

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 Map blocked turns to failed

When a turn.ended event arrives with reason blocked (the TurnEndReason contract says blocked folds into failed, and the existing v1 mapTurnReason does the same), this mapper records the transcript turn as cancelled. In blocked hook/goal scenarios, the transcript will incorrectly present the run as a user cancellation and failed-turn consumers will miss it; map blocked to failed here instead.

Useful? React with 👍 / 👎.

Comment on lines +246 to +247
// Record the owning agent so question events/frames route to this
// agent's surfaces (a subagent's question must not land on 'main').

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Move inline comment into v2 file header

packages/agent-core-v2/AGENTS.md requires comments in this package to live only in the top-of-file /** */ block and never beside methods or statements; this new inline note inside executeQuestion violates that scoped convention. Move the rationale into the file header or remove it so the v2 comment rule remains enforceable.

Useful? React with 👍 / 👎.

- kap-server: map turn.ended reason 'blocked' to the 'failed' transcript
  state, matching the engine's TurnEndReason wire contract and the v1
  mapTurnReason folding (it was presented as a user cancellation)
- agent-core-v2: move the question agentId rationale into the ask-user
  file header — package rules keep comments in the top-of-file block only

Addresses review feedback on MoonshotAI#1888.
@sailist

sailist commented Jul 18, 2026

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f9b9d4b6a1

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

kind: 'question',
payload: req,
origin: { turnId: req.turnId },
origin: { turnId: req.turnId, agentId: options?.agentId },

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 the owning agent when resolving questions

When AskUserQuestion runs inside a subagent, passing agentId here makes the requested event use the subagent, but the v1 broadcaster only remembers InteractionKind and interactionResolvedEvent() still hard-codes resolved/dismissed question events to agentId: 'main'. With an agent_filter or per-agent UI subscribed to the subagent, the request is delivered but the answer/dismissal is filtered to main, leaving the subagent question pending in the client. Store the interaction's agent id alongside the kind and pass it into the resolved event.

Useful? React with 👍 / 👎.

// Land the roster entry last, so roster-driven resets already see the
// backfilled content — this also guarantees a reset target for agents
// whose engine instance has not been created yet.
store.describeAgent({ agentId, type: agentId === MAIN_AGENT_ID ? 'main' : 'sub' });

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3 Badge Keep persisted subagent descriptors on backfill

When a subagent history is fetched after backfillMain() has already seeded store.agents() from session metadata, this unconditional descriptor replaces the richer descriptorFromMeta() entry with only {agentId, type}. Opening a subagent transcript therefore strips its parentAgentId/label from subsequent transcript REST responses and roster notifications. Reuse the existing metadata descriptor, or avoid downgrading an existing descriptor here.

Useful? React with 👍 / 👎.

- kap-server: keep the metadata-seeded roster descriptor (parentAgentId /
  label) when an on-demand history backfill lands its roster entry, instead
  of downgrading it to { agentId, type }
- kap-server: remember each interaction's owning agent and stamp it on the
  resolved question/approval v1 events — they were hard-coded to 'main', so
  an agent-filtered subscriber saw a subagent's question open but never
  close

Addresses review feedback on MoonshotAI#1888.
@sailist

sailist commented Jul 18, 2026

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6056376d98

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +179 to +182
for (const pending of interactions.listPending()) {
if (knownInteractions.has(pending.id)) continue;
knownInteractions.add(pending.id);
announceInteraction(pending);

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 Announce pending interactions after backfill

When the transcript binding is created while an approval/question is already pending, this loop announces the interaction before TranscriptService.backfillAgent has replayed the persisted tool-call frame. In that mid-turn attach case, mapInteractionRequested falls back to a synthetic step, and mapInteractionResolved cannot add the approvalId back-link to the tool frame, so approval/plan cards render detached from the tool until a cold rebuild.

Useful? React with 👍 / 👎.

Comment on lines +228 to +230
store.describeAgent({
agentId,
type: existing?.type ?? (agentId === MAIN_AGENT_ID ? 'main' : 'sub'),

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 Avoid adding empty typo agents to the roster

When a live transcript is requested for an agent id with no wire records, readColdSnapshot returns an empty snapshot, but this unconditional describeAgent still adds that id to the in-memory roster. A typo or probe such as agent_id=nope therefore pollutes later agents responses and wildcard transcript subscribers with a ghost subagent until the session is dropped.

Useful? React with 👍 / 👎.

Comment on lines +84 to +87
// The value import also loads the `DomainEventMap` augmentation for the
// `turn.*` / delta events this service publishes (the augmentation lives with
// the event definitions; without an import it would not enter every
// consumer's program).

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 Move import rationale into the file header

packages/agent-core-v2/AGENTS.md says comments in this tree must live solely in the top-of-file /** */ block and never beside functions, methods, or statements. This newly added import-adjacent comment violates that local rule; please fold the rationale into the file header or remove it.

Useful? React with 👍 / 👎.

- kap-server: announce interactions pending at bind time only after the
  initial backfill (new TranscriptBinding.seedPendingInteractions), and
  adopt the seeded tool frame on the request/resolve paths, so a
  pre-existing approval lands next to its backfilled tool call and keeps
  the approvalId back-link
- kap-server: skip the roster entry when a probed agent id has neither a
  roster presence nor persisted content (agent_id=nope no longer conjures
  a ghost subagent)
- agent-core-v2: fold the turnEvents import rationale into the loop file
  header (package rule: comments live in the top-of-file block only)

Addresses review feedback on MoonshotAI#1888.
@sailist

sailist commented Jul 18, 2026

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: cec3cecf34

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

summary.workspaceId,
sessionId,
AGENTS_DIR,
agentId,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Reject path traversal in transcript agent IDs

When /api/v1/sessions/{sid}/transcript is called with an agent_id containing .. or path separators, this unvalidated value is passed directly into the filesystem path before readWireRecords(). For both cold reads and live-session backfills, an authenticated client can therefore make the server read a wire.jsonl outside the intended agents/<agentId> directory; validate the id against the session's agent registry or reject path segments before joining it into the path.

Useful? React with 👍 / 👎.

Comment on lines +315 to +316
const grade = gradeFor(sub.transcriptGrades, agentId);
const filtered = filterOpsForGrade(grade, ops);

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 Apply agent filters to transcript fan-out

For clients that combine the existing v1 agent_filter with a transcript wildcard grade, transcript events bypass the allowlist: the normal event path later in dispatch() checks matchesAgentFilter(...), but this transcript fan-out only checks transcriptGrades, so a connection filtered to main can still receive subagent transcript ops/resets when '*': 'delta' is set. Apply the same agent filter before sending transcript frames, or document that transcript grades intentionally override the legacy filter.

Useful? React with 👍 / 👎.

- transcript/kap-server: validate agent ids as plain names (no separators,
  no dot segments) at the REST query layer and again before the id is
  joined into the wire-records path — an authenticated client could
  otherwise read a wire.jsonl outside the agents directory
- kap-server: compose the legacy v1 agent allowlist with transcript
  grades on every fan-out path (initial resets, per-ops fan-out,
  roster-driven resets), so a filtered connection no longer receives
  other agents' transcript frames

Addresses review feedback on MoonshotAI#1888.
@sailist

sailist commented Jul 19, 2026

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 843c903734

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +142 to +143
// The failure text was never streamed — emit it before completing,
// so live consumers (transcript tasks) see the output too.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Remove inline comments from agent-core-v2 code

packages/agent-core-v2/AGENTS.md requires comments in this package to live solely in the top-of-file block and says to never place them beside functions, methods, or statements. This newly added explanatory comment inside run (and the similar new inline comments in this file) violates that convention; please move any needed context into the file header or remove it.

Useful? React with 👍 / 👎.

const deferred = state.deferredTranscriptSeeds.get(target);
if (deferred === undefined) return;
state.deferredTranscriptSeeds.delete(target);
await this.subscribeTranscript(state, target, deferred.spec, deferred.prevGrades, deferred.prevFilter);

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 Force a reset after cursor-based transcript replay

When an already-subscribed connection re-subscribes with a cursor at the same transcript grade, subscribe(..., deferTranscriptReset: true) removes the target from transcriptSeeded, so any volatile transcript.ops arriving during the durable replay are dropped. flushTranscriptSeed then passes the previous grades here, so subscribeTranscript sees no upgrade and sends no reset before marking the target seeded again; that leaves the client missing the dropped transcript ops. Cursor-based flushes should force a baseline reset even when the grade/filter did not change.

Useful? React with 👍 / 👎.

A cursor re-subscribe at unchanged grades deferred its baseline and then
compared against the previous grades on flush, so no reset was sent —
while volatile ops fanned out during the deferral had been dropped,
leaving the client with a permanent gap. flushTranscriptSeed now always
seeds a full baseline (previous grades no longer tracked in the deferred
record), and a regression test covers the same-grade cursor resubscribe.

Also drop the inline comments added to shellCommandService.ts — the
agent-core-v2 convention keeps commentary in the top-of-file block; the
context moved there.
@sailist

sailist commented Jul 19, 2026

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Bravo.

Reviewed commit: 35351131f7

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@sailist

sailist commented Jul 19, 2026

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 35351131f7

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +361 to +364
await Promise.all(
Object.keys(spec)
.filter((agentId) => agentId !== '*' && gradeFor(spec, agentId) !== 'off')
.map((agentId) => service.ensureAgentHistory(state.sessionId, agentId)),

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 Backfill wildcard-selected roster agents

When a client subscribes with a wildcard grade such as { '*': 'delta' } after the live store has been seeded from session metadata, this only backfills explicitly named agents and skips '*'. Historical subagents can already be present in store.agents() from the roster, but their AgentTranscript is never materialized, so the reset loop later sees no transcript and sends no baseline for them; wildcard subscribers silently miss existing subagent transcripts unless they enumerate every agent id. Ensure admitted roster agents are backfilled before sending wildcard resets.

Useful? React with 👍 / 👎.

Comment on lines +370 to +373
const agentFilter = state.targets.get(target)?.agentFilter;
for (const descriptor of store.agents()) {
if (agentFilter !== undefined && !agentFilter.has(descriptor.agentId)) continue;
const grade = gradeFor(spec, descriptor.agentId);

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 Re-check subscription before sending resets

If two subscribe/unsubscribe frames for the same target overlap while whenReady() or ensureAgentHistory() is awaiting, this resumes with the stale spec from the earlier call and sends resets even if state.targets now has a downgraded/off subscription or no target at all. The WS handler starts subscribe work asynchronously, so a slow history backfill can deliver a delta/block reset after a client has downgraded or unsubscribed. Re-read the current subscription after the awaits and return unless it still matches before computing grades and sending resets.

Useful? React with 👍 / 👎.

… subs

Two subscribeTranscript gaps found in review:

- Re-read the target's subscription after the history awaits: subscribe
  work runs asynchronously, so an overlapping downgrade/unsubscribe used
  to be answered with resets computed from the stale spec. The reset
  loop now uses the latest grades/filter from state.targets and bails
  when the target is gone or no longer graded.
- Backfill roster agents admitted via the wildcard grade, not just
  explicitly named ones: a historical subagent seeded into the roster
  from session metadata had no materialized AgentTranscript, so
  wildcard subscribers silently never received its baseline reset.

Adds regression tests for the wildcard backfill, the mid-seed
downgrade, and the mid-seed unsubscribe (all three fail without the
fix); makeCore now accepts persisted agent metadata for roster seeding.
@sailist

sailist commented Jul 19, 2026

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0f6fd1a762

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +770 to +774
const modes: { plan?: Record<string, never>; swarm?: Record<string, never> } = {};
if (event.planMode === true) modes.plan = {};
if (event.swarmMode === true) modes.swarm = {};
if (modes.plan === undefined && modes.swarm === undefined) return [];
return [{ op: 'meta.merge', meta: { modes } }];

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 Handle mode exit events in transcript meta

When plan or swarm mode exits, the engine emits agent.status.updated with planMode: false / swarmMode: false, but this branch only emits a meta.merge for true and returns nothing for false. Once a live transcript has set meta.modes.plan or meta.modes.swarm, clients that render transcript meta will keep showing that mode as active until a full reset; emit a clearing operation for the corresponding mode instead of dropping exit events.

Useful? React with 👍 / 👎.

`agent.status.updated` with `planMode: false` / `swarmMode: false` was
dropped by the transcript projector because `meta.merge` could only set
mode badges, never clear them — clients kept rendering an exited mode
until the next full reset. The merge wire shape now accepts `null` per
mode key (set = object, clear = null, absent = keep): the reducer
deletes the key and normalizes an empty `modes` away, the zod schema
validates the nullable form, and the projector emits the clearing op
for exit events.
@sailist

sailist commented Jul 19, 2026

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0f471b428b

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

type: 'turn.started',
turnId: job.turn.id,
origin,
prompt: turnPromptText(job.seed.input),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Do not expose hidden system prompts in transcripts

When the turn origin is a system-triggered turn such as a goal continuation or subagent run, job.seed.input is internal steering text rather than displayable user input; the existing web transcript explicitly hides system triggers (apps/kimi-web/src/composables/messagesToTurns.ts:473-479). Passing it through turn.started.prompt here makes the new live REST/WS transcript render those hidden prompts whenever those turns start, and the cold rebuild mirrors the same leak via groupTurns.ts:126. Please gate prompt population to displayable user origins, or emit the hidden-origin turn boundary without prompt text.

Useful? React with 👍 / 👎.

`turn.started.prompt` was populated from the turn input for every
origin, so system-triggered turns (goal continuation, subagent run,
cron) exposed their internal steering text to live transcript
consumers; the cold rebuild mirrored the same leak when grouping
persisted history. The loop now populates the prompt only for
displayable user origins (user input, or a user-slash skill/plugin
activation) via the new isDisplayablePromptOrigin gate, and the cold
grouping opens hidden-origin turns promptless. Turns still open
normally — only the prompt text is withheld.
@sailist

sailist commented Jul 19, 2026

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d29e44153d

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".


/** Dispose the live store + binding for a session (session closed / server shutdown). */
dropSession(sessionId: string): void {
this.opsListeners.delete(sessionId);

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 Reattach transcript listeners after session reload

When a live session is closed or archived, this removes the onSessionOps listener set that SessionEventBroadcaster registered, but the broadcaster's SessionState still keeps transcriptStream populated, so ensureTranscriptStream() returns early on a later subscribe for the same session. If the session is resumed in the same daemon, the subscriber may get a fresh transcript.reset, but no mapped-op listener is re-registered and subsequent live transcript.ops are never delivered.

Useful? React with 👍 / 👎.

When the engine session closed or archived, TranscriptService dropped
the live store together with its ops listener set, but the
broadcaster's SessionState kept its transcriptStream — so
ensureTranscriptStream returned early for a later subscribe on the
resumed session, delivering a fresh reset but never the live
transcript.ops. The stream is now pinned to its TranscriptStore
instance and the fan-out re-registers whenever a rebuilt store shows
up. Adds a regression test that drops the service entry mid-stream and
asserts ops keep flowing after resubscribe (fails without the fix).
@sailist

sailist commented Jul 19, 2026

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7f15354cde

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +237 to +241
case 'task': {
const taskId = (origin as { taskId?: unknown }).taskId;
return taskId !== undefined && typeof taskId === 'string'
? { kind: 'task', taskId, payload: origin }
: { kind: 'other', payload: origin };

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 background-task origins during cold rebuilds

When rebuilding cold transcripts for legacy/v1 sessions, background task notifications are persisted with origin.kind === 'background_task' (the live mapper already handles this spelling), but this branch only maps task origins. Those messages therefore fall through to { kind: 'other' } after a restart and lose their taskId, so the transcript can no longer associate the notification turn with the background task/subagent. Include background_task in the task-origin branch.

Useful? React with 👍 / 👎.

Legacy/v1 sessions persist background-task notifications with
origin.kind === 'background_task' (the live mapper already handles that
spelling), but the cold grouping only mapped 'task' — after a restart
those turns fell through to { kind: 'other' } and lost their taskId, so
the transcript could no longer associate the notification turn with its
background task. Both spellings now share the task-origin branch.
@sailist

sailist commented Jul 19, 2026

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9f78f9feb5

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +565 to +567
const taskId = this.shellTasks.get(event.commandId) ?? event.taskId;
if (taskId === undefined) return [];
this.shellTasks.set(event.commandId, taskId);

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 shell failures before task startup

When a foreground ! command fails before onForegroundTaskStart runs, such as Bash validation/spawn/registerTask errors, shellCommandService publishes shell.output/shell.completed with taskId undefined. This guard drops those events, so the live transcript never gets a task ref, stderr, or terminal state for that failed command even though the command did run and appended shell output to context. Consider falling back to a synthetic task id (for example the command id) or otherwise projecting these no-taskId failures instead of returning no ops.

Useful? React with 👍 / 👎.

A foreground `!` command that failed before onForegroundTaskStart ran
(Bash validation/spawn/registerTask errors) published shell.output /
shell.completed with taskId undefined, and the projector's guard
dropped them — the live transcript lost the stderr and the terminal
state of a command that did run. Shell events now resolve their task as
the id learned at shell.started, else the event's own taskId, else a
synthetic per-command id (shell-<commandId>), so early failures land
like any other shell task.
@sailist

sailist commented Jul 19, 2026

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 266edaa07b

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +525 to +529
if (event.type === 'task.started') {
ops.push({
op: 'taskref.upsert',
item: { kind: 'taskref', refId: `ref-${info.taskId}`, taskId: info.taskId, at: nowIso() },
});

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 Emit taskrefs for first-seen task terminations

When the transcript binding starts after a background process/subagent/question has already emitted task.started, the later task.terminated is the first lifecycle event this projector sees. This branch emits only task.upsert for terminations, so the task exists only in the global task map with no taskref timeline item; the chat view renders standalone tasks through taskref items, so that completed background work never appears in the transcript. Mirror the shell.completed missed-start guard by emitting the ref when this is the first task event observed.

Useful? React with 👍 / 👎.

- kap-server: remove the /api/v2 REST routes and /api/v2/ws socket (registerRpcRoutes renamed to serviceDispatcherRoutes; transport/ws/{eventMap,registerWs,wsClient,wsConnection,wsProtocol} deleted). /api/v1/debug/* is now the only RPC surface — a reflection dispatcher over the entire scoped DI registry with no whitelist — and /api/v1/ws the only WS endpoint
- klient: drop the http transport (transports/http/*, transports/ws/wsSocket.ts) and the kap-server devDependency; transports reduce to the ipc|memory subpath entries, and the dual/v2 e2e suites go with them
- kimi-inspect: target /api/v1/debug only with no fallback, replace the Service-event push channel (wsChannel/wsSocket) with on-demand fetch plus 15 s polling, and show a blocking "Debug surface unavailable" screen on connection failure
- transcript: add global attachment/interaction/todo entities (model, ops, wire schema) and project them from engine events in kap-server's coreEventMap
@sailist sailist changed the title feat(transcript): add unified transcript layer and v1 surface feat(transcript): add unified transcript layer, drop the /api/v2 RPC surface Jul 20, 2026
@sailist
sailist merged commit 5ae60fa into MoonshotAI:main Jul 20, 2026
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant