Skip to content

feat(kap-server): send tasks as task events and stream task output - #4362

Open
wbxl2000 wants to merge 24 commits into
mainfrom
qer/agent-state-events
Open

wbxl2000 wants to merge 24 commits into
mainfrom
qer/agent-state-events

Conversation

@wbxl2000

@wbxl2000 wbxl2000 commented Oct 23, 2026 •

Copy link
Copy Markdown
Collaborator

Requirement or Bug

任务从 transcript 里拿出来,改成独立的 WebSocket task 帧;任务输出改为用 task.watch 按任务订阅推送,客户端不再轮询,REST 也不再返回任务输出。

客户端配套:MoonshotAI/kimi-code-app#967。本 PR 先合,code-app 那边再把 submodule 指到合并提交。

Bug Reproduction Steps

N/A

Root Cause

N/A

Code Changes

协议形状参考 KAP 草案 的「任务」和「任务输出」两节,与草案不一致的地方以本 PR 为准,草案随后更新。改动分两块:任务状态改由 kap-server 新增的 SessionTasks 维护并通过 task 帧发送,transcript 层不再有任务;新增 task.watch / task.unwatch 推送任务输出,REST 任务详情不再返回输出。

源码(不含测试和文档)合计 +944 / −869 行:

  • 任务状态净减约 280 行:新增 sessionTasks.ts 295 行和 task 帧的 schema,删掉 transcript 包的任务模型、coreEventMap.ts 的任务投影等 820 行。
  • 任务输出是新功能,净增约 390 行,主要是 taskOutputStream.ts 233 行、帧 schema 和引擎侧的两个成员。
  • REST 任务详情去掉输出参数,净减 33 行。

一、任务状态:SessionTasks + task 帧

帧的形状:

server → client  { type: "task", session_id, agent_id, event_created_at, task_id,
                   kind: "shell" | "subagent" | "other",
                   status: "running" | "ended",
                   outcome?: "completed" | "failed" | "timed_out" | "killed" | "lost",
                   phase?: "working" | "suspended", detached,
                   description?, child_agent_id?, parent_tool_call_id?, command?,
                   started_at?, ended_at?, result_summary?, error?, state_reason?,
                   usage?: { input_other, output, input_cache_read, input_cache_creation },
                   model?, thinking? }
  • 一帧是一个任务的完整状态,不是增量,客户端按 (session_id, agent_id, task_id) 整条覆盖。
  • phase 只出现在运行中的 subagent 上,有 state_reason 时是 suspended,否则是 working。
  • 草案里的 blocked、phase: "queued"、has_output 不发送。

任务状态从哪来:每个活跃会话一个 SessionTasks,按 agent 存一张任务表,表里的值就是去掉信封字段的 task 帧。引擎实时事件和磁盘上的 wire 记录形状相同({ type, time, ...payload }),走同一套折叠逻辑:

SessionTasks (one per live session)
  apply(agentId, event)     live bus events: task.*, subagent.*, shell.*
                            the first task event or subscription for an agent starts loading its wire records;
                            live events that arrive meanwhile are buffered
  load(agentId)             fold the agent's wire records with the same functions
                            mark lost: a running foreground subagent whose child agent is not running,
                              unless the parent is running and spawned it after its last turn.prompt
                              (the start of the current turn)
                            apply the buffered events, then report every task
  list(agentId)             every task of the agent, ended ones included
  onChange(frame)           → TranscriptService listeners → SessionEventBroadcaster

发送时机:

 subscribe_v2 (first subscription, re-subscribe, grade change)
   wait until the task records of every subscribed agent are loaded
   transcript.reset or catch-up transcript.ops where needed   # no tasks inside
+  task × every task of every subscribed agent, ended ones included
 agent joins the roster
+  load its task records; once loaded, task × each of its tasks
 engine event that changes a task
-  projector → task.upsert inside transcript.ops
+  SessionTasks → task × the changed task → every seeded connection whose grade for that agent is not off
  • 任务帧只发给订阅了该 agent 的连接,不向祖先 agent 冒泡;粒度为 off 的 agent 不发。
  • 任务不跟着 transcript 的 reset 走:每次 subscribe_v2 完成后都发全部任务,不管这次有没有 reset 或补拉。订阅期间连接暂不接收实时变化,最后这一轮补发保证再次订阅(例如调高某个 subagent 的粒度)时,其他 agent 在这段时间里的任务变化不会丢。transcript.ops 里撤销产生的 reset 操作不影响任务。
  • 原来只含任务变化的 transcript.ops 批次不再产生,seq 也不再因此递增。

transcript 层删掉的东西:

 packages/transcript
-  TranscriptTask / TaskKind / TaskState                   # model/task.ts
-  TranscriptItem: TranscriptTaskRef (kind: "taskref")
-  ops: task.upsert, taskref.upsert, append { target: { type: "task" } }
-  AgentTranscriptSnapshot.tasks, the store's task map, foldFacts task folding
 packages/kap-server
-  coreEventMap: task.* / subagent.* / shell.* → task.upsert projection
-  coreBinding: seed running subagent tasks from IAgentTaskService.list() on bind
-  transcriptService: markLostSubagentTasks on cold reads
-  transcript.reset snapshot.tasks, REST GET /sessions/{id}/transcript tasks

agent-core-v2:task.terminated 记录不再写 outputTail(wire-manifest.d.ts 同步删掉这个字段),旧会话里带这个字段的记录照常回放。任务状态这块没有其他引擎改动。

二、任务输出:task.watch / task.unwatch

改动前 desktop 和 web 只要会话里有任务在跑,就每秒对每个运行中的任务请求一次 GET /sessions/{session_id}/tasks/{task_id}?with_output=true&output_bytes=4096,不管任务面板有没有打开。这块给 v1 WebSocket 加一条按任务订阅输出的通道,并删掉 REST 的这条输出路径:

client → server  { type: "task.watch", request_id, session_id, agent_id, task_id, since_offset?, tail_bytes? }
client → server  { type: "task.unwatch", request_id, session_id, agent_id, task_id }
server → client  { type: "response", request_id, code, msg? }
server → client  { type: "task.output", session_id, agent_id, task_id, offset, text }
  • offset 是 text 在任务完整输出中的 UTF-8 字节位置,前后两条连续。
  • agent_id 是任务所属的 agent,也就是该任务 task 帧上的 agent_id,主 agent 是 main。subagent 也能起后台 shell,服务端只在 agent_id 这个 agent 里找任务,传错 agent 回复 40406。
  • watch 成功后先回复 response,再发首段:输出的最后至多 tail_bytes 字节(默认 32 KiB,最大 1 MiB);since_offset 落在这段范围内且在字符边界上时,只发它之后的部分;早于这段的起点、晚于末尾或落在多字节字符中间时发整段。首段两端都对齐到字符边界,读文件时碰上写到一半的字符留给下一段;没有可发送的输出时首段 text 为空。之后每段新输出推一条。
  • 同一个任务再次 watch 时,先释放上一次,再按新请求发首段。
  • watch 按连接维护,连接断开时全部释放,还在排队的 watch 不再执行;客户端重连后带本地末尾作为 since_offset 重新 watch。
  • 会话不存在回复 40401,该 agent 下没有这个任务回复 40406,字段不合法(含空的 session_id / agent_id / task_id、tail_bytes 超出范围)回复 40001;查找任务或建立推送时出错回复 50001,读输出失败回复 50003,失败时这次 watch 不保留;读不出 request_id 时以关闭码 1008 断开连接。
  • 输出超过 16 MiB 上限后任务被停止,超出部分既不写盘也不推送,推送的 offset 不会超出已保存的输出。
  • REST GET /sessions/{id}/tasks/{task_id} 不再读 with_output / output_bytes(传了也忽略),返回的任务上没有 output_preview / output_bytes;任务输出只能通过 task.watch 读取。

引擎侧给任务服务加了两个成员:

interface IAgentTaskService {
+ readonly onDidAppendOutput: Event<{ taskId: string; offset: number; text: string }>;
+ readOutputTail(taskId: string, maxBytes: number): Promise<{ offset: number; data: Uint8Array }>;
}
  • onDidAppendOutput 的 offset 取追加前的 outputSizeBytes,与输出文件的字节位置一致;只在这段输出被保留时触发,超过输出上限后不再触发。
  • readOutputTail 返回原始字节和起点:输出已写到磁盘时按范围只读文件末尾,否则读内存里保留的部分。不复用 getOutputSnapshot,因为它先解码成字符串,切到多字节字符中间会出现替换字符。

watch 的顺序:

watch(request)
  release an earlier watch of the same task on this connection
  resolve the task service of agent_id: main → ensureMainAgent (resumes a closed session),
                                        other agents → getOrRestoreAgent
  subscribe to onDidAppendOutput, buffer chunks
  readOutputTail(task, tail_bytes ?? 32 KiB)
  build the first chunk from since_offset or the tail start; drop a trailing incomplete character
  respond 0, send the first chunk
  send the part of each buffered chunk past the first chunk's end, then forward live chunks
  any error after the lookup → release, respond 50001

task.watch / task.unwatch 不进连接的 control 队列,而是每个任务各排一队:同一个任务的请求按到达顺序执行,watch 还没回复时发来的 unwatch 在它回复之后生效;不同任务之间、以及它们和订阅等其他控制帧之间互不等待,冷会话 resume 慢也挡不住别的请求。

文件

 packages/kap-server/src/
 ├── protocol/
+│   ├── ws-task.ts                 # task / task.output / task.watch / task.unwatch schemas
 │   ├── ws-control.ts              # register the four task frames in wsOperations (AsyncAPI)
 │   └── rest-task.ts, task.ts      # drop with_output / output_bytes and output_preview / output_bytes
 ├── services/transcript/
+│   ├── sessionTasks.ts            # per-session task table, fed by live events and wire records
 │   ├── transcriptService.ts       # one SessionTasks per live session; task listener; listTasks
 │   ├── coreBinding.ts             # forward bus events to SessionTasks; no subagent seeding
 │   └── coreEventMap.ts            # task projection removed
 ├── routes/
 │   ├── tasks.ts                   # task detail without output
 │   └── transcript.ts              # REST transcript without tasks
 └── transport/ws/v1/
+    ├── taskOutputStream.ts        # task.watch / task.unwatch, per-task queue, first chunk, live output
     ├── sessionEventBroadcaster.ts # send every subscribed agent's tasks per subscribe and on change
     ├── registerWsV1.ts            # create the task output source from the core scope
     └── wsConnectionV1.ts          # route task.* frames, release watches on close
 packages/transcript/src/
-├── model/task.ts
 └── contract/, ops/, store/, history/foldFacts.ts, view/   # task shapes removed
 packages/agent-core-v2/src/agent/task/
 └── task.ts, taskService.ts, persist.ts   # onDidAppendOutput / readOutputTail (range read); no outputTail

另外:

  • docs/{en,zh}/reference/server-api.md 补上 task 帧、task.watch / task.unwatch / task.output,REST transcript 去掉 tasks,REST 任务详情去掉输出参数和字段。
  • changeset 分两条:task-ws-events.md 写任务状态改走 task 帧、transcript 不再带任务;task-output-watch.md 写任务输出改走 task.watch、REST 任务详情不再返回输出。
  • 配套的 Web 产物拆到 chore: sync web dist from code-app #4487 单独同步,需要紧接着本 PR 合入,原因见下文。

Behavior Changes and Affected Users

行为 改动前 改动后 谁依赖旧行为 退路
transcript.reset 的 snapshot.tasks 该 agent 的任务列表 字段删除,任务由紧随其后的 task 帧发送 desktop / web(MoonshotAI/kimi-code-app#967 已改读 task 帧);直接连 WebSocket 的外部脚本 无,旧行为不再提供;改读 task 帧
transcript.ops 里的 task.upsert 任务每次变化推一条 不再出现;只含任务变化的批次不再产生、不占 seq 同上 无;改读 task 帧
REST GET /sessions/{id}/transcript 的 tasks、/transcript/ops 补拉里的任务操作 任务列表 / task.upsert tasks 字段删除,补拉结果里没有任务操作 main 上内置的旧 Web 产物:它按旧 schema 校验响应,tasks 是必填字段,缺了会解析失败;用 REST 读 transcript 任务的外部脚本 Web 产物由 #4487 同步;外部脚本改用 GET /sessions/{id}/tasks
REST GET /sessions/{id}/tasks/{task_id} 的 with_output / output_bytes 返回的任务带 output_preview / output_bytes 参数被忽略,任务上没有这两个字段 main 上内置的旧 Web 产物:任务详情每秒用它读运行中任务的输出,合入后读不到;用 REST 读任务输出的外部脚本 Web 产物由 #4487 同步;外部脚本改用 task.watch
transcript 条目里的 taskref 时间线上的任务引用条目 删除 没有已知客户端:code-app 从未渲染 taskref;直接解析 transcript 的外部脚本 无
TranscriptTask.outputTail 与任务输出的 append 操作 shell 输出末尾随 transcript 推送 删除,输出只通过 task.watch 推送 desktop / web 任务详情里的输出摘要(#967 已改用 task.watch);外部脚本 task.watch
服务端重启前中断的前台 subagent 任务 读 transcript 时标为 lost task 帧里是 status: "ended", outcome: "lost";判定条件不变:子 agent 没在运行,且不是父 agent 当前 turn 里发起的 无 不需要
新增 task 帧 — 订阅了该 agent 且粒度不为 off 的连接收到 无;旧客户端按未知帧类型忽略 不需要
v1 WebSocket 收到 task.watch / task.unwatch 未知类型,忽略 处理并回复 response 无:此前没有客户端发送这两种消息 不需要
收到没有 request_id 的 task.watch / task.unwatch 忽略 以关闭码 1008 断开连接 无:同上 不需要
task.terminated 记录 带 outputTail 不再写;旧记录照常回放 无:旧会话数据照常读取 不需要
IAgentTaskService 接口 — 新增 onDidAppendOutput、readOutputTail 自行实现这个接口的代码需要补这两个成员;仓库里只有测试替身,已补上 不需要

旧路径做的事和它们在新路径里的位置:

旧路径 新位置
task.started / task.terminated → 任务行:kind 映射(process → shell,agent → subagent,其他 → other)、状态、detached、command、parentToolCallId、model / thinking foldTaskInfo
subagent.spawned:只收父 agent 是自己的;带 taskId 时记下子 agent → taskId,不带时按子 agent id 建行并删掉旧映射 foldSubagentSpawned
subagent.started / suspended / completed / failed / cancelled:同时更新 taskId 行和子 agent id 行;cancelled → killed;已结束的行再收到非终止事件时开始新一轮,清掉上一轮的结果、用量和错误 foldSubagentRun
shell.started / shell.completed:commandId → taskId,isError → failed;shell.output 不处理 foldShell
用量只收非负整数;缺 taskId、时间无效等畸形记录跳过 usageOf / text / isoTime
绑定时用 IAgentTaskService.list() 补种运行中的 subagent 任务 首次使用时读该 agent 的 wire 记录,这些任务的 task.started 都在记录里
冷读 transcript 时把中断的前台 subagent 标为 lost SessionTasks 读完记录后按同一条件判定
shell 输出末尾放进 transcript(outputTail) 删除,改走 task.watch
REST 任务详情按 with_output / output_bytes 返回输出末尾(默认 32 KiB) task.watch 的首段(tail_bytes,默认 32 KiB),之后实时推送

旧客户端连新服务端的情况:desktop 内嵌的 server 随安装包发布,不会出现版本错配。web 产物由同一个 server 提供:本 PR 合入之后、#4487 合入之前,main 上内置的旧 Web 产物加载会话时 REST transcript 解析失败,会话内容显示不出来,任务详情也读不到输出,所以 #4487 要紧接着合入,这段时间不要发版。删掉的几项直接替换,不保留兼容层,与 #4428、#4470 的处理方式一致。

没有变化的部分:REST /tasks 列表、任务详情的其他字段、取消和转后台接口、subscribe_v2 的粒度语义、interaction 帧、其他 transcript 操作和 v1 现有帧。

受影响模块与测试覆盖:

  • kap-server/src/services/transcript:
    • test/services/transcript.test.ts 的 SessionTasks 用例覆盖:process 任务与它的 shell 命令合成一行;前台 shell 结束、漏掉开始事件时按事件里的 taskId 建行;swarm 成员按 agent id 跟踪挂起、失败和用量;未知 kind 归为 other、畸形记录跳过;Agent 工具发起的 subagent 按 taskId 建行并折叠生命周期;子 agent 不带 taskId 重新发起时丢掉旧映射;已结束的 subagent 被重新拉起时两行都开始新一轮、清掉终止时间;运行中重复的 subagent.started 不改开始时间;从 task.started 恢复子 agent 与 taskId 的对应;wire 记录只折叠一次、读完后报告全部任务;中断的前台 subagent 标为 lost,子 agent 仍在运行或父 agent 当前 turn 发起的除外,server 在 turn 中途崩溃、重启后父 agent 已开始新 turn 时,崩溃那一轮发起的也标为 lost;读记录期间到达的实时事件排在记录之后。
    • 同文件覆盖 binding 把每个 bus 事件按发出它的 agent 转给 SessionTasks,以及任务事件交给任务监听方而不进 journal。
  • kap-server/src/transport/ws/v1:
    • test/sessionEventBroadcaster.test.ts 覆盖订阅后发送全部任务、每次变化发送变化的任务、reset 快照里没有 tasks;再次订阅调高某个 subagent 粒度期间主 agent 的任务结束,最后的补发带上结束状态;agent 加入 roster 时读取它的任务记录;
    • test/wsConnectionV1.test.ts 覆盖回复与首段顺序、实时推送、since_offset 在末尾内 / 已在末尾 / 过旧或落在字符中间时回退并对齐字符边界、没有输出时发空首段、读末尾期间到达的输出不重复、读末尾时碰上写到一半的块(首段停在完整字符处,剩余部分补发)、unwatch、watch 未回复时发来的 unwatch 在它之后生效、慢的 watch 不挡其他任务、tail_bytes 限制首段、subagent 的任务按它的 agent id 推送、错误码(会话或任务不存在;空 ID、缺 agent_id、tail_bytes 超范围回 40001;查找出错或建立推送时出错 50001 并释放;读输出失败 50003 并释放)、1008 断开、连接关闭时释放、关闭后排队中的 watch 不再查找任务;
    • test/transcriptContract.e2e.test.ts 用真实服务端覆盖:后台 subagent 的 task 帧,且 transcript 推送里没有任务(S4);REST 快照和 WS reset 里都没有 tasks(S6);task.watch 推送运行中任务的输出、重连后带 since_offset 续传,subagent 的任务要带它自己的 agent id 才能 watch(S9)。
  • kap-server/src/routes/tasks.ts:test/tasks.test.ts 删掉 with_output 输出预览的用例。
  • packages/transcript:test/layers.test.ts、test/store.test.ts 删掉任务、taskref 和任务输出追加的用例。
  • agent-core-v2/src/agent/task:test/agent/task/output-access.test.ts 覆盖多字节字符下 offset 按字节连续、已落盘输出的末尾与起点、未落盘时读内存末尾、未知任务;test/agent/task/taskManager.test.ts 覆盖超过输出上限后推送的末尾不超过上限,并与已保存输出的末尾一致;taskOps.test.ts / taskService.test.ts 删掉 outputTail 的用例。

本地 kap-server、transcript、agent-core-v2 的 tsc 和 pnpm lint 通过;kap-server、transcript 的测试全部通过(73 个文件、1571 条),agent-core-v2 的任务测试全部通过(14 个文件、241 条)。agent-core-v2 全量测试本地有 2 条超时(tool.test.ts 的前台 subagent 超时用例、tower/store.test.ts 的状态锁用例),把本 PR 对 agent/task 的改动撤回后同样超时,CI 上通过,与本 PR 无关。

改名之后,在 #967 分支的 dev desktop 上验证过(内嵌 server 是本分支的 72b56454b):已结束的 bash 任务打开详情面板后通过 task.watch 显示完整的最终输出;用脚本按客户端的帧格式直连同一个 server,task.watch 回复 0 并收到带 agent_id: "main" 的首段,task.unwatch 回复 0,传错 agent_id 回复 40406,REST 任务详情带 with_output 时不再返回输出字段,旧的 task.attach 不回复。

改名之前,还用本分支的 server 加 #967 分支的 web 手动验证过运行中任务的完整流程:后台 bash 任务出现在任务栏并显示命令;详情面板实时追加输出,结束后保留最终输出;停止任务后显示已取消;前台 subagent 只出现一次;刷新页面后任务状态和输出都在。再强杀 server 并重启(当时还有一个 sleep 600 在跑),刷新后已结束的两个任务原样恢复,sleep 600 显示为失败(服务端记为 lost),没有重复行,控制台没有报错。

Checklist

  • I have read the CONTRIBUTING document.
  • I have linked a related issue (external PRs: issue must have a maintainer's /approve).
  • I have added tests that prove my feature works.
  • The behavior-change table above is complete, and every removed behavior or flipped default is named in the changeset and either has an escape hatch or was explicitly approved by a maintainer in this PR.
  • Ran gen-changesets skill, or this PR needs no changeset.
  • Ran gen-docs skill, or this PR needs no doc update.

Add task.attach / task.detach client messages and task.output events. Output offsets are UTF-8 byte positions taken from the agent task service, which now exposes onDidAppendOutput and readOutputTail. The first chunk is at most the last 4 KiB, aligned to a character boundary, and since_offset resumes from the client's local end when it is still within that tail. Attachments are per connection and are released when the connection closes.
@changeset-bot

changeset-bot Bot commented Oct 23, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 27b09a7

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

This PR includes changesets to release 1 package
Name Type
@moonshot-ai/kimi-code 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

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 23, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review 🔄 Running since 2026-10-29T10:30:34.537610Z 27b09a7 New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@pkg-pr-new

pkg-pr-new Bot commented Oct 23, 2026 •

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

commit: 27b09a7

@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: 8485e2782b

ℹ️ 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".

void core;
const wss = new WebSocketServer({ noServer: true, handleProtocols: selectWsBearerProtocol });
const { registry, broadcaster } = opts;
const taskOutputSource = createTaskOutputSource(core);

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 Gate task-output streaming behind an experimental flag

This installs the new task-output protocol for every kap-server instance even though the commit describes the client integration as a future change. Because this is a not-yet-public feature, declare an owning-domain flag and only pass the task-output source when that flag is enabled, defaulting it off.

AGENTS.md reference: AGENTS.md:L64-L67

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Not gating this one. task.attach / task.detach only take effect when a client sends them, and no released client does, so a default-off flag would protect no user. It would only force the desktop embedded server to opt in through an env var and require another PR to flip the default once the client adopts it. The client side lands in the follow-up code-app PR.

Comment on lines +169 to +171
const tail = await resolved.tasks
.readOutputTail(taskId, TASK_OUTPUT_TAIL_BYTES)
.catch(() => ({ offset: 0, data: new Uint8Array() }));

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 Report tail-read failures instead of returning success

When reading persisted output fails—for example because of a transient filesystem or permission error—this converts the failure into an empty tail and then sends a successful response. A completed task consequently appears to have no output, and the client receives no indication that data was dropped; detach the attachment and return an error response instead of fabricating an empty result.

AGENTS.md reference: AGENTS.md:L130-L132

Useful? React with 👍 / 👎.

Comment on lines +131 to +135
let resolved: TaskOutputSourceResult;
try {
resolved = await this.source(sessionId, taskId);
} catch {
resolved = { kind: 'session_not_found' };

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 source errors instead of claiming the session is absent

If resuming the session or initializing its main agent throws because of an I/O, restoration, or service error, this catch rewrites the failure as session_not_found and returns 40401. Clients can then incorrectly treat an existing session as deleted; only return 40401 for an actual missing-session result and surface unexpected failures as an internal error.

Useful? React with 👍 / 👎.

const offset = entry.outputSizeBytes;
entry.outputSizeBytes += chunkBytes;
this.appendRetainedOutput(entry, chunk, chunkBytes);
this._onDidAppendOutput.fire({ taskId: entry.taskId, offset, text: chunk });

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 Apply the output limit before publishing chunks

When a process crosses the 16 MiB task-output limit, this event fires before outputLimitTripped is checked, and every later chunk continues to fire while asynchronous termination is in progress. A large or SIGTERM-ignoring process can therefore send arbitrarily more data through each WebSocket attachment even though those bytes are deliberately not persisted; this defeats the output cap and leaves reconnect offsets ahead of the available tail. Only publish chunks that pass the same limit check as persistence.

Useful? React with 👍 / 👎.

Comment on lines +22 to +23
session_id: z.string(),
task_id: z.string(),

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 Reject empty task attachment identifiers

Both attachment schemas accept empty session_id and task_id strings, so requests such as { session_id: "", task_id: "" } pass validation and are answered as missing resources rather than with the documented 40001 invalid-request response. The existing REST task contract requires these identifiers to be nonempty; apply the same .min(1) validation here and in the detach schema.

Useful? React with 👍 / 👎.

Comment on lines +212 to +216
sinceOffset !== undefined && sinceOffset >= tailOffset && sinceOffset <= end
? sinceOffset
: tailOffset;
let start = requested - tailOffset;
while (start < data.byteLength && isUtf8Continuation(data[start]!)) start += 1;

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 Fall back when since_offset splits a UTF-8 character

When a reconnect supplies a since_offset that lands on a UTF-8 continuation byte, this treats it as valid and advances to the next character boundary. That silently skips bytes rather than applying the documented invalid-offset fallback; if the offset falls inside the final character, firstChunk even returns no output. Detect a non-boundary requested offset and restart from the character-aligned tail instead.

Useful? React with 👍 / 👎.

Publish appended task output only when it is kept, so nothing past the 16 MiB output cap is streamed and live offsets stay within the persisted output. Reply 50001 when the task lookup throws and 50003 when the output cannot be read, instead of reporting a missing session or an empty output. Reject empty session and task ids, and treat a since_offset inside a multi-byte character as invalid.

@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: 57dff2e134

ℹ️ 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 +18 to +23
const taskAttachSchema = z.object({
type: z.literal('task.attach'),
request_id: z.string(),
session_id: z.string().min(1),
task_id: z.string().min(1),
since_offset: z.number().int().nonnegative().optional(),

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 Publish task-output frames in the WebSocket contract

These transport-local schemas add a new wire protocol, but protocol/ws-control.ts does not include task.attach, task.detach, response, or task.output in its message unions or wsOperations. Because createAsyncApiDocument() builds /asyncapi.json exclusively from wsOperations, external WebSocket consumers relying on the documented discovery/code-generation contract cannot discover these frames and schema-based clients may reject them; define and export the frames through the canonical protocol operations and reuse those schemas here.

AGENTS.md reference: AGENTS.md:L122-L124

Useful? React with 👍 / 👎.

Move the task output end-to-end case into transcriptContract.e2e.test.ts now
that wsV1Resync.test.ts is gone, and drop the task.output coalescing that lived
in the removed coalesceFrames.
Tasks leave the transcript stream and get their own WebSocket event:

- Each subscribed agent's tasks are sent as `task` events: every task
  (ended ones included) right after a transcript reset or catch-up, then
  the changed task on every change. Agents at grade `off` get none, and
  task events do not bubble to ancestor agents. Besides the transcript
  task fields, an event carries `command` and `parent_tool_call_id` from
  the owning agent's task service when it knows the task.
- The transcript no longer carries tasks on the wire: WebSocket resets and
  both REST `/transcript` paths answer `tasks: []`, `task.upsert` ops are
  split off before journaling, and task-only batches consume no seq.
- `@moonshot-ai/transcript` drops `taskref` items, `TranscriptTask.outputTail`
  and the `task` append target; shell output only flows through
  `task.attach`.
- `task.terminated` records no output tail anymore; older records that
  still carry one replay unchanged.
- The AsyncAPI document lists `task`, `task.output`, `task.attach` and
  `task.detach`.
- kimi-inspect reads task state from `task` events and keeps it across
  REST refreshes.
code-app: 80156e1a2fc3771a0107644a54ec0640c4a20097
@wbxl2000 wbxl2000 changed the title feat(kap-server): stream task output over the v1 WebSocket feat(kap-server): send tasks as task events and stream task output Oct 28, 2026

@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: c409d9e0c8

ℹ️ 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".

@@ -156,7 +156,7 @@ export function registerTranscriptRoutes(app: TranscriptRouteHost, deps: Transcr
agent_id: query.agent_id,
items: page.items,
has_more: page.hasMore,
tasks: [...transcript.getTasks().values()],
tasks: [],

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 tasks for existing transcript clients

When a server is upgraded while a REST-only or older WebSocket client is still deployed, every transcript request now returns tasks: [] even though the live store or cold snapshot contains task records. The replacement task frames only reach clients that subscribe and understand the new protocol, so existing consumers silently lose running and historical tasks; keep populating this field during the compatibility window or version the contract.

AGENTS.md reference: AGENTS.md:L111-L117

Useful? React with 👍 / 👎.

Comment on lines +99 to +101
output: (frame) => {
this.sendSubscribedFrame(frame);
},

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 Coalesce contiguous task-output chunks before flushing

For commands that emit many small chunks, this callback queues one task.output frame per onDidAppendOutput event, while sendSubscribedFrame merely appends each frame to outbound. Consequently the advertised 16 ms batching still serializes and sends every JSON frame, and reaching maxBatchSize forces additional flushes; merge contiguous chunks for the same session/task before enqueueing or flushing them.

Useful? React with 👍 / 👎.

Comment on lines +309 to +312
onTask: (aid, task) => {
if (aid !== agentId) return;
store.applyOps([{ op: 'task.upsert', task }]);
trail.recordEvent('task', `${task.taskId} ${task.state}`, store.getState());

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 Clear stale tasks when a transcript reset is received

When reconnect catch-up is no longer possible, the server sends a transcript.reset followed by the current full set of task frames. This handler only upserts those frames, while the reset handler ignores the snapshot and REST replacement preserves the existing task map, so a task removed by an undo while the client was disconnected remains visible indefinitely. Clear the task map when the reset arrives before applying the replayed task frames.

AGENTS.md reference: AGENTS.md:L127-L129

Useful? React with 👍 / 👎.

@wbxl2000 wbxl2000 mentioned this pull request Oct 28, 2026
2 of 6 tasks

@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: af1516a8b4

ℹ️ 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".

}

if (entry.outputLimitTripped) return;
this._onDidAppendOutput.fire({ taskId: entry.taskId, offset, text: chunk });

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 Keep streamed offsets aligned with persisted output

When detached output persistence is active and IFileSystemStorageService.append rejects, this publishes the chunk before the write attempt while the existing write queue silently swallows the failure. Later chunks can still be appended, so live clients observe logical offsets that include the missing bytes, whereas a reconnect or fresh attach derives its tail offset from the shorter persisted file; this silently loses output and can make clients splice subsequent bytes at incorrect positions. Publish only successfully retained chunks or propagate the failed write and terminate the attachment.

AGENTS.md reference: AGENTS.md:L131-L133

Useful? React with 👍 / 👎.

agent_id: agentId,
event_created_at: createdAt,
task_id: task.taskId,
kind: task.kind,

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 question tasks with the tool kind

When AskUserQuestion runs in the background, QuestionBackgroundTask records info.kind as question, but the transcript projectors map every non-process/non-agent kind to other, and this new frame copies that value verbatim. Clients therefore receive kind: "other" instead of the documented "tool" classification—even though the existing REST task projection maps the same engine kind to tool—so clients cannot reliably group or render these long-running tool tasks. Map question tasks to tool when projecting or emitting the frame.

AGENTS.md reference: AGENTS.md:L127-L129

Useful? React with 👍 / 👎.

<pre className="max-h-40 overflow-auto whitespace-pre-wrap text-neutral-400">
{task.outputTail}
</pre>
) : null}

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 Continue loading task output in kimi-inspect

For a background shell whose tool frame contains only the task id/status, this now renders nothing when frame.output is absent: the previous branch displayed task.outputTail, while the new onTask path stores task metadata but never sends task.attach or retains task.output frames. As a result, kimi-inspect users silently lose both live and final shell output in the chat view; integrate the new attachment stream before removing the old output fallback.

AGENTS.md reference: AGENTS.md:L111-L113

Useful? React with 👍 / 👎.

onTask: (aid, task) => {
if (aid !== agentId) return;
store.applyOps([{ op: 'task.upsert', task }]);
trail.recordEvent('task', `${task.taskId} ${task.state}`, store.getState());

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 raw task frames in the audit trail

When a task frame arrives, this records only a synthesized taskId state string rather than the received frame. The Audit panel's raw-event view therefore cannot inspect the task's kind, outcome, timestamps, usage, error, or other wire fields, so the newly added frame type violates the audit trail's guarantee to retain every WebSocket frame and its raw payload. Add a task-specific audit entry that carries the complete frame instead of reducing it to a summary.

AGENTS.md reference: apps/kimi-inspect/AGENTS.md:L44-L44

Useful? React with 👍 / 👎.

SessionTasks keeps one task list per agent for each live session and folds
live engine events and wire records through the same apply, so the transcript
package no longer models tasks: the task type, task.upsert / taskref.upsert
ops, task append targets and snapshot tasks are gone, and the REST transcript
and reset snapshots drop their tasks field. kimi-inspect keeps the latest task
frame per task id.
Keep only the removal of TaskRefView: transcripts no longer contain taskref items, and the TranscriptTask type it referenced is gone, which fails type-aware lint.

@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: db2cacbcaf

ℹ️ 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".

{ sessionId, agentId, err: error instanceof Error ? error.message : error },
'transcript: task history read failed, continuing without it',
);
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.

P1 Badge Do not cache an empty task history after a read failure

When the session index or wire read fails transiently, this returns an empty history, allowing SessionTasks.load() to mark the agent as permanently loaded and send a successful subscription with no historical or currently running tasks. Because that SessionTasks instance never retries, desktop/web clients silently lose the task list until the live session is dropped and recreated; propagate the failure so the subscription can fail or retry rather than caching fabricated emptiness.

AGENTS.md reference: AGENTS.md:L131-L133

Useful? React with 👍 / 👎.

…32 KiB

Task frames never emitted kind tool, and the larger first chunk matches the
previous REST final-output window so ended-task panels stay useful after
clients stop fetching output over HTTP.

@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: 3f2d3797c0

ℹ️ 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 thread docs/en/reference/server-api.md Outdated
| `task.attach` | flat: `{ request_id, session_id, task_id, since_offset? }` | Stream one main-agent task's output to this connection, no session subscription needed; `since_offset` resumes after output the client already has |
| `task.detach` | flat: `{ request_id, session_id, task_id }` | Stop streaming that task's output |

`task.attach` and `task.detach` are flat: their fields sit next to `type` instead of under `payload`, `request_id` replaces `id`, and the reply is `{ "type": "response", "request_id", "code", "msg"? }` instead of an ack. A successful `task.attach` replies with code 0, then sends the last 4 KiB of output (only the part after `since_offset` when it falls inside that window) as one `task.output` frame, followed by live output. Errors: `40001` malformed frame, `40401` session not found, `40406` task not found, `50003` output unreadable.

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 Document the actual 32 KiB attach tail

The English and Chinese API references still say that a successful task.attach sends at most the last 4 KiB and treats older since_offset values as outside that window, but TASK_OUTPUT_TAIL_BYTES is now 32 KiB. For example, an offset 20 KiB behind the end resumes from that offset and sends roughly 20 KiB rather than falling back to 4 KiB, so clients and contract tests following the published recovery semantics will make incorrect assumptions about frame size and replay behavior. Update both references to the 32 KiB window.

Useful? React with 👍 / 👎.

},
task: (message) => {
for (const [target, spec] of state.targets) {
if (!state.transcriptSeeded.has(target) || gradeFor(spec, message.agent_id) === 'off') 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.

P1 Badge Buffer task changes until subscription seeding completes

When a task changes after sendTasks() captures the baseline but before the awaited subscribeTranscript() returns and adds the target to transcriptSeeded, this guard silently drops the change. Unlike transcript operations, task frames have no journal or sequence cursor, so a task that finishes in this window remains shown as running until another change or a reconnect. Buffer task changes during seeding, or establish the target before taking and sending an ordered baseline.

AGENTS.md reference: AGENTS.md:L131-L133

Useful? React with 👍 / 👎.

result_summary: rerun ? undefined : (text(event['resultSummary']) ?? prev.result_summary),
usage: rerun ? undefined : (usageOf(event['usage']) ?? prev.usage),
error: rerun ? undefined : (text(event['error']) ?? prev.error),
state_reason: text(event['reason']) ?? (rerun ? undefined : prev.state_reason),

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 Clear the suspension reason when a subagent resumes

When a suspended subagent emits subagent.started on resumption, outcome is undefined but the previous task is still running, so rerun is false and this expression preserves the old state_reason. Because toMessage() derives phase: "suspended" from the presence of that field, clients continue to show the resumed subagent as suspended until it terminates. Handle subagent.started by clearing state_reason while retaining the existing start timestamp.

Useful? React with 👍 / 👎.

…nt resume

- Mark a subscriber as seeded in the same tick that sends its task baseline,
  so a task change landing right after the baseline is no longer dropped.
- Clear state_reason when a suspended subagent resumes, so its phase returns
  to working.
- Document the 32 KiB task.attach tail.
A successful task.attach now always yields one first task.output frame,
with empty text when there is nothing to send. Clients can then tell
"first chunk not received yet" apart from "the task has no output".
@wbxl2000

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: 9a43157fe2

ℹ️ 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 +35 to +36
const agent = await ensureMainAgent(session);
const tasks = agent.accessor.get(IAgentTaskService);

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 Resolve task output from the owning agent

When a subscribed subagent starts a background shell, the transcript binding emits its task frame under that subagent, but task.attach always looks in the main agent's IAgentTaskService. Attaching the emitted task ID therefore returns TASK_NOT_FOUND; because this change also removes task output from transcript snapshots, clients have no remaining way to retrieve that subagent task's output. Resolve the task through its owning agent rather than unconditionally calling ensureMainAgent.

AGENTS.md reference: AGENTS.md:L127-L129

Useful? React with 👍 / 👎.

Comment on lines +118 to +121
return;
}
if (resolved.kind === 'task_not_found') {
fail(ErrorCode.TASK_NOT_FOUND, 'task not found');

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 Serialize attach and detach operations

When task.detach arrives while an earlier attach is still waiting for session restoration or task lookup, the detach removes the placeholder and replies successfully, but the stale attach later enters this branch and also reports code 0 despite no attachment being active. A client processing responses can therefore believe the final successful attach is streaming even though the later detach already cancelled it; serialize these operations per connection or return a cancellation/conflict response for the superseded attach.

Useful? React with 👍 / 👎.

task.attach, task.detach and task.output now carry agent_id. Subagents
can own background shells, so the server resolves the task inside that
agent instead of always looking in the main agent.

Attach and detach run through the connection's control queue, so a
detach sent while an attach is still reading the output tail is applied
after that attach replies, and replies arrive in send order.

GET /sessions/{id}/tasks/{task_id} drops with_output / output_bytes and
the output_preview / output_bytes task fields; task output is read over
task.attach only.

@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: 80e87c5f0a

ℹ️ 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".

}

reply.send(okEnvelope(toWireTask(session_id, found, output), req.id));
reply.send(okEnvelope(toWireTask(session_id, found), req.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.

P1 Badge Preserve output in the existing REST task response

When an existing REST client requests ?with_output=true&output_bytes=..., this branch now returns a successful metadata-only task because the removed query schema lets those parameters be ignored. The endpoint previously returned output_preview and output_bytes, and task.attach is not a compatible fallback for REST-only clients, so upgrades silently discard requested output even though the changeset only declares removing tasks from transcript endpoints. Keep the existing query and response fields during the compatibility window, or version the endpoint as a breaking change.

AGENTS.md reference: AGENTS.md:L111-L117

Useful? React with 👍 / 👎.

…hes per task

task.attach / task.detach become task.watch / task.unwatch. Watch and
unwatch of one task still run in arrival order, but different tasks and
the other control frames no longer wait on each other. task.watch takes an
optional tail_bytes (32 KiB by default, at most 1 MiB) for the first chunk.
# Conflicts:
#	apps/kimi-inspect/src/audit/serialize.ts
#	apps/kimi-inspect/src/components/ChatView.tsx

@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: 72b56454bc

ℹ️ 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 thread .changeset/task-ws-events.md Outdated
"@moonshot-ai/kimi-code": patch
---

Send task state over the server's WebSocket as a flat `task` frame instead of inside the transcript, and add `task.attach` / `task.detach` to stream a task's output as `task.output` frames; transcript frames and the REST transcript endpoints no longer carry tasks or task references, and their `tasks` field is removed.

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 Rename the controls in the changeset

When this changeset is published, the generated changelog will tell users to send task.attach and task.detach, but the committed protocol only accepts task.watch and task.unwatch. Users following the release note will therefore receive no task output; update this sentence to use the actual wire names.

AGENTS.md reference: AGENTS.md:L89-L90

Useful? React with 👍 / 👎.

@wbxl2000

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Keep them coming!

Reviewed commit: 55f80cd6b5

ℹ️ 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".

@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: 55f80cd6b5

ℹ️ 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".

@@ -136,6 +146,7 @@ export class TranscriptService {
this.deps.logger,
(event) => this.handleLiveOps(sessionId, event),
(agentId) => this.rebuildAfterUndo(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.

P1 Badge Rebuild the task cache after undo

When :undo removes the turn that created a shell or subagent task, this callback rebuilds only the transcript store; the new SessionTasks instance is never cleared or reloaded. The old reset path replaced snapshot.tasks, but the replacement protocol has no task-deletion frame, so existing subscribers and later subscription baselines keep the undone task until the live session is dropped. Fresh evidence in this version is that rebuildAfterUndo() ends by dispatching transcript ops while leaving entry.tasks untouched; rebuild the per-agent task state during undo reconciliation.

AGENTS.md reference: AGENTS.md:L126-L128

Useful? React with 👍 / 👎.

…ut watch

Send every subscribed agent's tasks once subscribe_v2 completes instead of
only after a transcript reset or catch-up. A task change that landed while a
re-subscribe raised another agent's grade was dropped for agents whose grade
did not change, with no seq to reveal the gap. Tasks no longer depend on
whether the transcript was reset.

Load the task history of an agent that joins the roster, so its earlier
tasks reach subscribers even if it emits no new task event.

For task.watch: skip watches still queued when the connection closes, reply
50001 and release the watch when streaming setup throws, reply success only
once the first chunk is ready, and forward the unread rest of a chunk the
tail read caught mid-write instead of dropping it. Read the output tail by
range instead of loading the whole output file.

This branch has not been deployed

No deployments
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