Merge remote-tracking branch 'origin/master' into worktree-guifork

This commit is contained in:
imccyu
2026-07-28 10:23:48 +08:00
710 changed files with 24719 additions and 6603 deletions

View File

@@ -8,9 +8,9 @@
*
* The state in force is folded from the session log (`plan/mode`, last one
* wins), so resume and fork restore it without a live mirror. User selections
* are held as pending intent until a turn boundary because every session event
* is turn-enclosed. The service flushes before the affected request assembly
* on prompt submission and each request step (including retry turns).
* are held as pending intent until an in-turn request boundary because every
* session event is turn-enclosed. The service flushes at `agent/step` before
* the affected request assembly, including retry turns.
*
* The exit tool remains registered while plan mode is inactive so crossing a
* boundary changes only the prompt section, not the request tool catalog.
@@ -145,9 +145,9 @@ export class PlanModeService extends Service {
private readonly section: string
/**
* Latest selection per session awaiting a turn-boundary flush. `narrate` is
* true for user selections and false for the exit tool, whose result already
* narrates the transition.
* Latest selection per session awaiting an in-turn request-boundary flush.
* `narrate` is true for user selections and false for the exit tool, whose
* result already narrates the transition.
*/
private readonly pendingIntents = new WeakMap<Session, { active: boolean; narrate: boolean }>()
@@ -298,7 +298,7 @@ export class PlanModeService extends Service {
}
/**
* Select whether plan mode should be active from the next turn boundary.
* Select whether plan mode should be active from the next request boundary.
* Repeated selection of the current or already-pending state is a no-op.
*
* @param agent The agent to switch.

View File

@@ -13,7 +13,7 @@ const PLAN_CONFIG = { section: 'Test plan mode instructions.' }
/**
* Full-loop integration: a scripted mock model drives the REAL plan-mode plugin
* through the agent loop — the pending-intent flush at the turn boundary, the
* through the agent loop — the pending-intent flush at the request boundary, the
* assembly the soft layer shapes (the exit tool + mode section), and the
* `request/header` snapshots every transition leaves.
* Only the model is mocked; the loop, the session log, and the plugin are
@@ -72,7 +72,7 @@ describe('plan mode through the agent loop', () => {
const ctx = await harness(adapter)
const agent = ctx.agentLoop.create(SessionId('it-plan-seed'), { provider: 'mock', model: 'mock' })
// Selected while idle: the pending intent flushes at the first
// prompt-submit, BEFORE the first assembly.
// in-turn agent/step seam, before the first assembly.
ctx.planMode.set(agent, true)
agent.followup({ content: [{ type: 'text', text: 'explore the repo' }], source: { kind: 'user' } })
@@ -136,7 +136,9 @@ describe('plan mode through the agent loop', () => {
const adapter = new MockAdapter([failedRequest, textResponse('Recovered in plan mode.')])
const ctx = await harness(adapter)
const agent = ctx.agentLoop.create(SessionId('it-plan-retry-flip'), { provider: 'mock', model: 'mock' })
ctx.on('agent/request-error', async (subject, _turn, _step, _error, _failure, _signal, next) => {
ctx.on('agent/request-error', async (
subject, _turn, _step, _error, _failure, _priorFailures, _retryPolicy, _signal, next,
) => {
if (subject !== agent) return next()
ctx.planMode.set(agent, true)
return { kind: 'retry' }