Repository navigation
Conversation
🦋 Changeset detectedLatest commit: 60981bd The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
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 |
Codex Review Summary: ✅ CompletedThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
commit: |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: acba0bcead
ℹ️ 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 (turn.steerInterruptStep !== undefined) { | ||
| const current = turn.current; | ||
| if (current?.number === turn.steerInterruptStep) { | ||
| this.closeFailedMachineStep(turn, current, 'interrupted'); | ||
| turn.current = undefined; | ||
| turn.interruptStep = undefined; | ||
| this.emitStepInterrupted(turn.id, current.number, 'aborted'); | ||
| } | ||
| turn.steerInterruptStep = undefined; |
There was a problem hiding this comment.
Preserve steers when the step cap is exhausted
When max_steps_per_turn equals the current step number (for example, it is 1 during the first request), this new interruption path proceeds to another gate, but the existing stepOrdinal > maxSteps check rejects that gate because the steer nudge has bypassMaxSteps: false. Before this change, steering during the request left the prompt queued to seed a new turn; now the same configured users get a max_steps failure after their input has been moved into the active turn, with no model response. Either defer the steer to a new turn when the cap is exhausted or explicitly allow its continuation step.
AGENTS.md reference: AGENTS.md:L109-L111
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Leaving the step-cap behavior unchanged in this PR. max_steps_per_turn remains a hard cap: steering does not grant an extra step or reroute the input into a new turn. This is an intentional scope decision, not an attempt to bypass the configured limit.
| "@moonshot-ai/kimi-code": patch | ||
| --- | ||
|
|
||
| Make steering interrupt active model requests and continue in the same turn. |
There was a problem hiding this comment.
Include the direct-steer API in the changeset
The release note only describes interrupting an active model request, so it omits the other user-facing feature added by this commit: clients can now use POST /api/v1/sessions/{session_id}/steer instead of the two-request submit-and-steer flow. Mention the new endpoint and how to use it so the published changelog states the complete perceptible change.
AGENTS.md reference: AGENTS.md:L88-L89
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Updated .changeset/calm-pandas-steer.md to include POST /api/v1/sessions/{session_id}/steer and the one-request message-steering behavior.
| 'turn.steer': { | ||
| target: 'draining', | ||
| actions: [ | ||
| ({ context }) => { | ||
| context.llmScope.abort(); | ||
| }, | ||
| 'salvageSteered', |
There was a problem hiding this comment.
Wait for the interrupted gate before starting the next step
When a steer arrives while the request is still awaiting an asynchronous onWillBeginStep hook, this transition immediately enters draining and starts the continuation gate, but aborting llmScope does not abort the hook because gate() passes the turn-level step.signal. The old and new gates can therefore run concurrently; if the old hook later throws, failMachineGate() records a stale failedStep and the continued turn fails even though that step was already interrupted. This is reachable during asynchronous pre-step work such as automatic compaction, so wait for the old gate to settle or give it a steer-abortable signal before starting the continuation.
AGENTS.md reference: AGENTS.md:L109-L111
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in d7893f7e14. onWillBeginStep now receives a signal composed from turn cancellation and steering. A steer aborts the pending gate before advancing, and late failures from an obsolete gate no longer populate failedStep. The regression test drives two overlapping gates and verifies that the stale failure does not fail the continued turn. promptService is 33/33 and core typecheck passes.
| tags: ['prompts'], | ||
| operationId: 'steerPromptContent', | ||
| }, | ||
| submit(true), |
There was a problem hiding this comment.
Separate direct-steer idempotency from prompt submission
When a client first sends a body with a fixed prompt_id to /prompts and then sends the identical body to the new /steer endpoint, both requests are fingerprinted as { route: 'prompts', body } by the shared handler. beginPromptAttempt() therefore returns the first cached response and this submit(true) path never runs, so the second request reports success while leaving the prompt queued rather than steering it. Give the direct endpoint a distinct route discriminator so cross-endpoint prompt-id reuse is rejected as a conflict instead of silently replaying a semantically different operation.
AGENTS.md reference: AGENTS.md:L121-L123
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Leaving the shared idempotency behavior unchanged in this PR. The direct Ctrl-S client creates a fresh prompt ID and sends only /steer, so it never reuses an ID across /prompts and /steer. Cross-endpoint ID reuse is out of scope for this change.
- interrupt only the current llm step and consume steer in the same turn - add a direct session steer endpoint using the prompt submission flow - update API documentation and regression coverage
- distinguish steered steps from user cancellation - preserve streaming tool calls until the drain boundary - allow steer delivery to active untracked turns
- abort pending step hooks when steer advances the gate - prevent stale hook failures from failing the active step - document the direct steer endpoint in the changeset
acba0bc to
d7893f7
Compare
- keep in-turn user messages separate during request projection - avoid rewriting the previous request prompt-cache prefix - drop a test that depends on skill variants unavailable in this release
- preserve the accumulator part returned to the loop engine - retain tool-call detection without changing the streaming contract
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 60981bd617
ℹ️ 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 (turn.controller.signal.aborted || isAbortError(error) || step.signal.aborted) { | ||
| turn.abortReason = turn.controller.signal.aborted ? turn.controller.signal.reason : error; | ||
| if (turn.controller.signal.aborted || isAbortError(error) || gateSignal.aborted) { | ||
| if (turn.controller.signal.aborted) turn.abortReason = turn.controller.signal.reason; |
There was a problem hiding this comment.
Preserve hook-originated abort reasons
When an onWillBeginStep hook rejects with an AbortError without the turn or steer signal being aborted—for example, because the hook cancels its own asynchronous work—this branch returns fail but no longer records error as turn.abortReason. The requester consequently converts the decision into the generic GATE_FAILURE, so the turn is reported as failed instead of retaining the previous cancellation behavior and original reason. Suppress the reason only for an obsolete steer-aborted gate, while preserving independent hook aborts.
AGENTS.md reference: AGENTS.md:L110-L112
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Acknowledged. This is reachable in the TUI when automatic compaction blocks a step and the user cancels only that compaction with Esc/Ctrl+C, without cancelling the turn. We did not find an equivalent desktop/web flow. We’re treating this as a corner case and leaving it out of scope for this PR rather than changing the gate behavior again.
Requirement or Bug
使用者在 LLM 流式请求期间 steer 时,此前必须等待当前 step 自然结束;客户端直接 steer 新消息也需要先 submit,再按 prompt id 发起第二次请求。这个 PR 让有效 steer 只中断当前 LLM 请求,并在原 turn 的下一 step 消费消息,同时提供单请求 direct steer REST 接口。工具调用的流式生成与正式执行阶段仍保持原来的 drain 边界,不取消工具。
Bug Reproduction Steps
N/A
Root Cause
N/A
Code Changes
核心思路是把“是否打断”留在 turn 状态机内部:只有 agent 处于
running.active且 steer 命中有效排队 prompt 时,才向当前 turn 发送内部turn.steer。LLM 文本输出或重试阶段进入draining,而不是结束整个 turn;一旦 accumulator 出现 tool-call,或已经进入正式acting,则不中断当前请求或工具。flowchart TD A[有效 steer 命中排队 prompt] --> B{当前 step 阶段} B -->|文本 thinking / retrying| C[结束 LLM 请求并进入 draining] B -->|tool-call 流式生成 / acting| D[不取消请求或工具,等待 step 自然结束] C --> E[保留已流出的 thinking / text] E --> F[Loop 以 steered 原因关闭当前 step] D --> G[按原 drain 边界物化 steer] F --> H[原 turn 的下一 step 消费 steer] G --> HLoopService 用独立的
steerInterruptStep标记这次由 steer 触发的中断,并发出独立的steeredstep 结束原因,避免影响 retry、recovery 和用户 cancel。异步onWillBeginStep尚未结算时,gate 同时监听 turn 与 steer signal;旧 hook 失败后不会把已经推进的 step 写成 stalefailedStep。TUI 对steered只完成流式 UI 收尾,不再显示“用户取消”或清理活动 Agent/Swarm;transcript 将 step 记为 interrupted 并保留steeredendReason,最终 turn 仍正常 completed。steer 消息仍带inTurn,turn.steer仍在原 turn 确认,prompt 最终按正常结果结算。tracked prompt、goal 和后台任务等 untracked message 启动的活动 turn 都采用同一 steer 准入规则:是否可 steer 由当前是否存在活动 turn 决定,不再要求 prompt tracked;普通 queued 输入的归属与结算逻辑不变。
kap-server 将普通 prompt 提交处理器抽成共享的
submit(directSteer):原POST /sessions/{session_id}/prompts使用普通提交,新增POST /sessions/{session_id}/steer启用 direct steer。两条路径共享请求 schema、媒体、Skill、profile、环境、幂等和错误处理;原按 id steer 接口不变。阅读顺序:
packages/agent-core-v2/src/human/agent/machine.ts:有效 steer 的队列搬移与中断闸门。packages/agent-core-v2/src/human/agent/turn.ts与human/llm/message.ts:文本、tool-call 流式生成和正式工具阶段的边界。packages/agent-core-v2/src/agent/loop/loopService.ts:steeredstep 收尾、异步 gate 竞态处理和 untracked 活动 turn 准入。packages/agent-core-v2/src/agent/loop/projection.ts:inTurn消息保持独立,避免改写已发送请求的 prompt-cache 前缀。apps/kimi-code/src/tui/controllers/session-event-handler.ts:steer 与真实用户取消的不同展示。packages/kap-server/src/routes/prompts.ts:direct steer 与普通提交共享的完整链路。其余改动是对应行为测试、API surface 快照、双语文档和一条 patch changeset。
Behavior Changes and Affected Users
steered收尾,并在原 turn 的下一 step 消费;已流出的 thinking / text 按现有规则保留inTurn消息保持独立,下一请求只在原历史后追加,不改写已发送内容aborted,TUI 显示用户取消steered;TUI 不显示用户取消,transcript 保留该 endReason,最终 turn 正常 completedabortedfailedSteponWillBeginStep的 hooks 与使用 steer 的客户端running.active与有效匹配项双重闸门避免误中断POST /api/v1/sessions/{session_id}/steer一次完成,复用普通提交字段与返回结构受影响模块与覆盖:
turn.aborted、tool-call 流式阶段不断流,以及正式工具 signal 不取消。steered收尾一次、同 turnturn.steer、下一请求消费 steer、prompt 正常 completed、连续 steer 的 FIFO 和身份归属、untracked 活动 turn、stale gate 失败后继续执行,以及 release prompt-cache guard 的 append-only 前缀检查。steered与真实aborted的区别;TUI 不显示用户取消、不清理活动 Agent/Swarm,transcript 记录 interrupted step 的steeredendReason。steerIfActive=true、消息进入历史,以及 API surface 快照。验证记录
remote/process两项进程组清理时序问题,全量结果不宣称全绿。Checklist
gen-changesetsskill, or this PR needs no changeset.