Skip to content

feat: sync running tasks over WebSocket and drop the REST task API - #4535

Open
wbxl2000 wants to merge 5 commits into
mainfrom
qer/task-live
Open

wbxl2000 wants to merge 5 commits into
mainfrom
qer/task-live

Conversation

@wbxl2000

@wbxl2000 wbxl2000 commented Oct 29, 2026 •

Copy link
Copy Markdown
Collaborator

Requirement or Bug

任务改为只同步正在跑的:每个 agent 的运行中任务通过 WebSocket task.list / task 帧同步,状态直接来自引擎的任务服务;停止任务和转后台改为 WebSocket 请求 task.stop / task.background;REST 任务接口删除。已结束任务的结果留在会话历史里(工具结果和完成通知),不再作为任务列表推送。

取代 #4362(随后关闭)。客户端配套:MoonshotAI/kimi-code-app#1057。

Bug Reproduction Steps

N/A

Root Cause

N/A

Code Changes

核心思路

  • 任务只存在于它运行的时候。 实时视图里只有正在跑的任务;任务结束时推送它的最后一帧,之后不再出现在 task.list 里。结果交给等它的一方:前台任务的结果是工具结果,后台任务的结果是完成通知,两者都在会话历史里。
  • 前台和后台只看一件事:发起它的工具调用还在不在等它。 这就是 detached。前台 bash、前台 subagent、swarm 成员都是 detached: false 的任务,也都推送 task 帧;转后台就是把 detached 变成 true。
  • 状态由引擎直接给出。 feat(kap-server): send tasks as task events and stream task output #4362 在 kap-server 里用 SessionTasks 把引擎事件和 wire 记录折叠成任务状态,现在改为订阅引擎任务服务的变化事件,SessionTasks 删掉。swarm 成员在创建时就登记为任务,不再由服务端从 subagent.* 事件拼出来。

源码(不含测试和文档)+971 / −1195:agent-core-v2 +253 / −22,transcript +47 / −346,kap-server +671 / −827(其中删掉 REST 任务路由 341 行、coreEventMap 的任务投影 319 行)。

协议

server → client  { type: "task.list", session_id, agent_id, tasks: TaskState[] }
server → client  { type: "task", session_id, agent_id, event_created_at, ...TaskState }
client → server  { type: "task.stop" | "task.background", request_id, session_id, agent_id, task_id }
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?, task?: TaskState }
server → client  { type: "task.output", session_id, agent_id, task_id, offset, text }

TaskState = { task_id, kind: "shell" | "subagent" | "other", status: "running" | "ended",
              outcome?, phase?, detached, description?, child_agent_id?, parent_tool_call_id?,
              swarm_index?, command?, started_at?, ended_at?, result_summary?, error?,
              state_reason?, model?, thinking? }

和 #4362 相比:去掉 usage,加 swarm_index;已结束任务不再补发;新增 task.list、task.stop、task.background,以及应答里的 task。

  • task.list:连接开始接收某个 agent 时发一次(subscribe_v2 里该 agent 的粒度从 off 或未订阅变为非 off),内容是该 agent 此刻正在跑的全部任务,客户端用它整份替换本地列表。之后加入会话的 agent 直接从 task 帧开始。
  • task:任务每次变化发一帧完整状态,发给所有对该 agent 粒度不为 off 的连接。
  • 应答里的 task:task.watch 成功、task.stop / task.background 成功时带上任务当前状态。需要已结束任务的最终状态(例如重启后恢复一个输出面板)时,对它发 task.watch 即可。
  • task.stop / task.background 和 task.watch / task.unwatch 走同一个按任务排队的处理器:同一任务的请求按到达顺序执行,不同任务互不等待。会话不存在回 40401,该 agent 下没有这个任务回 40406,内部错误回 50001;停止或转后台一个已结束的任务回 0 并带上它的最终状态。
  • 失联提醒(应用退出后,上一轮的后台任务失去联系)在转录里显示为 level: "warning" 的 notice marker,lostTasks 列出每个失联任务 { taskId, description, kind },客户端据此渲染,不必显示写给模型的提醒原文。

结构

agent-core-v2
  IAgentTaskService
+   onDidChangeTask: Event<AgentTaskInfo>     # register / track / detach / settle / reconcile marks lost
+   refresh(taskId)                           # re-publish a running task after its own state changed
  SessionSwarmService.run
+   SwarmMemberTasks                          # one foreground task per member, registered at birth
+     AgentRunBatch.onStarted / onSettled     # started → agent id; suspended → refresh; settled → outcome
+     abort of a member task                  # cancels the whole swarm

kap-server
- services/transcript/sessionTasks.ts         # folding engine events + wire records into task state
- routes/tasks.ts, protocol/rest-task.ts, protocol/task.ts
+ transport/ws/v1/sessionTaskFeed.ts          # per live session: watch every agent's onDidChangeTask
+ transport/ws/v1/taskState.ts                # AgentTaskInfo → TaskState
  transport/ws/v1/sessionEventBroadcaster.ts
+   per connection: the agents it already got a task.list for
+   subscribe / grade change / reattach → task.list for newly received agents
+   task change → task frame to every target whose grade for that agent is not off
  transport/ws/v1/taskRequests.ts             # watch / unwatch / stop / background, one queue per task

transcript
- TranscriptTask, taskref items, task ops, snapshot.tasks   # from #4362
+ lost-task reminder → notice marker with lostTasks
  • 每个连接记录它已经收到过 task.list 的 agent,和 transcript 的 seeded 状态无关。所以再次订阅(例如调高某个 subagent 的粒度)只给新收到的 agent 发 task.list,其他 agent 的 task 帧照常流动,不会丢也不会重复。
  • 落盘格式不变:wire.jsonl 仍只追加,后台任务仍写 tasks/<id>.json 和 tasks/<id>/output.log;前台任务仍不落盘,所以 swarm 成员的结果摘要只在内存里,不会把长文本写进任务文件。
  • /init(CLI 生成 AGENTS.md)起的 subagent 没有登记为任务:它的 parentToolCallId 是占位值 generate-agents-md,会话评分依赖这个值;desktop / web 也没有消费它的地方。
  • 从 feat(kap-server): send tasks as task events and stream task output #4362 带过来的 task.watch 修复都保留:连接关闭后跳过排队中的 watch;建立推送出错时回 50001 并释放 watch;首段准备好之后才回复成功;尾部读取遇到写到一半的字符时把这段留给下一段。

提交

  1. feat(agent-core):任务变化事件,swarm 成员登记为任务。
  2. refactor(transcript):任务从转录里去掉(来自 feat(kap-server): send tasks as task events and stream task output #4362),失联提醒显示为 warning notice。
  3. feat(kap-server):实时任务帧、task.stop / task.background,删除 REST 任务接口和 SessionTasks,更新 API 文档。
  4. feat(transcript):失联提醒带上 lostTasks。
  5. chore(agent-core):按新的任务字段重新生成 docs/state-manifest.d.ts。

Behavior Changes and Affected Users

行为 之前(main) 之后 依赖旧行为的人群 退路
转录里的任务(snapshot.tasks、taskref、task.upsert 等 op) 有 没有 main 上的 desktop / web 客户端;读转录的外部脚本 无。客户端随配套 PR 一起改;desktop 内嵌 server 随包发布
REST GET /sessions/{id}/tasks、GET …/tasks/{task_id}、…:cancel、…:detach 可用 删除(404) main 上的 desktop / web(任务列表、停止、转后台);调用这些接口的外部脚本 无 REST 替代;改用 WebSocket 的 task.list / task 帧和 task.stop / task.background。changeset 写明了接口删除,bump 级别待维护者确认
任务状态推送 混在 transcript.ops 里 独立的 task.list + task 帧 — —
已结束任务 一直留在转录里 只推送最后一帧,之后不在 task.list 里 想在客户端列出历史任务的 UI 会话历史(工具结果、完成通知);task.watch 的应答带最终状态
前台任务(前台 bash、前台 subagent、swarm 成员) 只有 subagent 经转录可见 都有 task 帧(detached: false) — —
内置在 CLI 里的 web 产物(apps/kimi-code/dist-web) 读转录任务和 REST 任务接口 这些数据不再有,kimi web 的任务栏在新产物同步前是空的,停止 / 转后台按钮报错 kimi web 用户 配套 code-app PR 合入后同步新的 web 产物(另开 dist PR),与本 PR 同一版本发布
失联提醒 作为隐藏的注入消息,界面上看不到 转录里的 warning notice,带 lostTasks — —
docs/state-manifest.d.ts 里的 SubagentTaskInfo 没有这三个字段 新增可选的 swarmIndex、stateReason、resultSummary 读任务 JSON 文件的外部工具 不需要:只有前台 swarm 成员会带这三个字段,前台任务不落盘,磁盘上的任务记录形状不变,旧数据照常可读
模型侧的 TaskList 看不到 swarm 成员 swarm 运行期间,并行调用 TaskList 时能看到运行中的成员 很少见:swarm 工具调用阻塞本轮,只有同一步里并行调用 TaskList 才会看到 无,成员结束后不再列出

受影响的模块与测试:

  • agent-core-v2:taskManager.test.ts(注册、刷新、转后台、结束都发布变化,前台任务也发布);sessionSwarm.test.ts(每个成员登记为调用方的前台任务,跟随挂起和完成);swarm、task、tools 相关测试 21 个文件 475 条通过。
  • transcript:layers.test.ts(失联提醒显示为 warning notice,lostTasks 按引擎的行格式解析,描述里带引号也能解析);119 条通过。
  • kap-server:sessionEventBroadcaster.test.ts(先发 task.list 再发变化;再次订阅只给新 agent 发列表且不打断已有的流;后加入的 agent 直接收到变化);wsConnectionV1.test.ts(watch 应答带状态;stop / background 各自应答,任务不存在回 40406;缺 request_id 以 1008 断开);transcript.test.ts(任务事件不进转录和日志);apiSurface 快照去掉三条任务路由;72 个文件 1436 条通过。
  • 文档:docs/{en,zh}/reference/server-api.md 删掉 REST 任务一节,补上 task.list、task.stop、task.background 和应答的 task。
  • 本地 tsc、pnpm lint(0 error)通过。端到端在 code-app 配套分支上跑:desktop e2e 的后台 bash、后台 subagent、任务与待办三个流程在本地连续两次全部通过,帧序列(前台任务帧 → task.background → 带状态的应答 → task.watch → 输出 → 结束帧)在 trace 里核对过。

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.(只有 server API 参考文档受影响,已手动更新中英两份;CLI 用户文档里的后台任务描述没有变化)

The task service now publishes onDidChangeTask whenever a task is
registered (foreground tasks included), moved to the background, ended, or
marked lost after a restart, and refresh(taskId) republishes a running
task whose details changed. Clients get live task state straight from the
engine instead of folding wire records.

Each swarm member is registered as a foreground agent task of the caller
when the swarm starts. The task records the member's child agent, spawning
tool call and swarm index, its suspension reason while rate limited, and
its result summary when it settles. Stopping a member stops the whole
swarm, which is the only cancellation the swarm supports. Foreground tasks
are not persisted and send no notifications, so the model and the disk
see nothing new.

Background task output is read by range instead of loading the whole
file, readOutputTail and onDidAppendOutput serve task output streaming,
and task.terminated records no longer carry outputTail.
Tasks are not conversation content, so the transcript no longer models
them: TranscriptTask, taskref items, the task.upsert / taskref.upsert /
task output append ops, AgentTranscriptSnapshot.tasks, the projection of
task / subagent / shell events into task rows, the subagent task seeding on
bind, and the lost marking on cold reads are all removed. The REST
transcript endpoints no longer return tasks.

The one task fact that belongs to the conversation stays visible: the
injected reminder about background tasks lost when the app exited is now
shown as a warning notice marker instead of being hidden with other
injected messages.
…sk API

A connection that starts receiving an agent (its grade in subscribe_v2 is
not off) gets one task.list frame with every task that agent is running
right now, foreground commands and swarm members included, and then a
task frame on every change straight from the engine's task service. Ended
tasks are not resent: the last task frame of a task reports its outcome,
the outcome stays in the session history, and task.watch replies with the
task's state. The per connection list state no longer borrows the
transcript's seeded flag, so a re-subscribe that raises one agent's grade
neither drops nor resends the other agents' tasks.

task.stop and task.background join task.watch / task.unwatch as flat
requests on the same per-task queue, and every reply can carry the task's
current state. They replace the REST task endpoints, which are removed
together with their schemas: list, detail, :cancel and :detach.

task.watch keeps the fixes made during review: queued watches are skipped
after the connection closes, setup errors reply 50001 and release the
watch, success is sent only once the first chunk is ready, and a chunk the
tail read caught mid-write is forwarded instead of dropped.
The notice for background tasks that lost contact when the app exited now carries lostTasks, one { taskId, description, kind } entry per task line of the reminder, so clients can show which tasks were lost without displaying the model-facing reminder text.
@changeset-bot

changeset-bot Bot commented Oct 29, 2026 •

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 15304e5

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

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

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 29, 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 ✅ Completed 2026-10-29T20:07:32.228109Z 90230d2 PR opened
ℹ️ 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 29, 2026 •

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

commit: 15304e5

@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: 90230d2a06

ℹ️ 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 157 to 158
registerSearchRoutes(apiV1 as unknown as Parameters<typeof registerSearchRoutes>[0], core);
registerTasksRoutes(apiV1 as unknown as Parameters<typeof registerTasksRoutes>[0], core);
registerInteractionsRoutes(

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 Ship the matching web bundle with the new task protocol

Removing the REST task route and transcript task fields without updating the tracked apps/kimi-code/dist-web bundle leaves the web UI shipped by kimi web on the old contract: its task list becomes empty and stop/background actions fail as soon as this server is released. The changed-file list contains no bundle assets, so sync and commit the compatible code-app build in this change rather than relying on a later PR.

AGENTS.md reference: AGENTS.md:L18-L18

Useful? React with 👍 / 👎.

Comment on lines +17 to +19
const task = new SwarmMemberTask(member, cancelSwarm);
try {
this.members.set(member, { task, taskId: tasks.registerTask(task, { detached: member.runInBackground }) });

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 Release the swarm tool call when backgrounding a member

When a client sends task.background for one of these foreground AgentSwarm members, tasks.detach() only flips this registered task's detached state. Unlike the regular Agent tool, AgentSwarmTool directly awaits swarmService.run() and never observes this task's foreground-release promise, so the WebSocket request reports success while the parent tool call and turn remain blocked until the entire swarm completes.

Useful? React with 👍 / 👎.

Comment on lines 157 to 158
registerSearchRoutes(apiV1 as unknown as Parameters<typeof registerSearchRoutes>[0], core);
registerTasksRoutes(apiV1 as unknown as Parameters<typeof registerTasksRoutes>[0], core);
registerInteractionsRoutes(

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 Add a changeset for the removed task contracts

This commit removes user-visible REST endpoints and transcript fields but adds no .changeset entry, so the next CLI release will omit the breaking loss from its changelog and versioning flow. Add a CLI changeset that explicitly names the removed behavior and replacement; if a major bump is considered necessary, obtain the required explicit confirmation instead of selecting it here.

AGENTS.md reference: AGENTS.md:L90-L92

Useful? React with 👍 / 👎.

Comment on lines +103 to +107
subagentType: this.member.profileName,
parentToolCallId: this.member.parentToolCallId,
model: this.member.kind === 'spawn' ? this.member.plan.model : undefined,
swarmIndex: this.member.swarmIndex,
stateReason: this.stateReason,

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 Populate model metadata for every swarm member task

For thinking-enabled AgentSwarm members, this projection never sets thinkingEffort, and for resumed members it also forces model to undefined even though resumeAttempt resolves the effective profile and model. Consequently the new task.list and task frames omit the advertised thinking field for spawned members and both model and thinking for resumed members, so clients cannot render the same model metadata they receive for ordinary subagent tasks.

Useful? React with 👍 / 👎.

…k fields

SubagentTaskInfo gained the optional swarmIndex, stateReason and resultSummary fields; only foreground swarm member tasks set them, and foreground tasks are never written to disk, so stored task records keep their shape.
@wbxl2000

Copy link
Copy Markdown
Collaborator Author

CI 说明:剩下的失败只有 kimi-oauth 的 refreshProviderModels > ignores user-defined aliases when custom-registry metadata is unchanged,main 上最近几次 CI 也失败在这一条,与本 PR 无关。第一轮里的 state-manifest.d.ts 过期已在 15304e5 修复;当时另外失败的任务截止计时器、Tower 并发、remote-control 重连三条在重跑中通过。

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