docs: make technical prose concrete
This commit is contained in:
@@ -1,20 +1,21 @@
|
||||
/**
|
||||
* Plan mode is logged per-agent collaboration state: while active, a
|
||||
* deployment-owned guidance section shapes each model request, and
|
||||
* deployment-owned guidance section is included in each model request, and
|
||||
* `exit_plan_mode` presents the completed plan for user review, while the
|
||||
* `/plan off` command lets a user leave directly. Plan mode is independent of
|
||||
* sandbox mode and approval policy; those enforcement axes do not read or
|
||||
* write plan state.
|
||||
* `/plan off` command lets a user leave directly. Sandbox mode and approval
|
||||
* policy enforce restrictions independently and do not read or write plan
|
||||
* state.
|
||||
*
|
||||
* 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 an in-turn step boundary. The service
|
||||
* projects pending intent into the proposed step assembly, then flushes it
|
||||
* remain pending until the next accepted in-turn pre-step. The service includes
|
||||
* the selected state in the proposed step assembly, then appends `plan/mode`
|
||||
* from `agent/pre-step` only when the step is accepted. Same-step request
|
||||
* retries reuse their assembly.
|
||||
*
|
||||
* The exit tool remains registered while plan mode is inactive so crossing a
|
||||
* boundary changes only the prompt section, not the request tool catalog.
|
||||
* The exit tool remains registered while plan mode is inactive, so entering
|
||||
* or leaving plan mode changes only the prompt section, not the request tool
|
||||
* catalog.
|
||||
*
|
||||
* Agent Note:
|
||||
* - .agents/notes/implemented/simplification/2026-07-22-plan-specific-collaboration-state.md
|
||||
@@ -97,7 +98,7 @@ function firstHeading(plan: string): string | undefined {
|
||||
|
||||
/**
|
||||
* Validate deployment-owned plan guidance. Missing, blank, non-string, or
|
||||
* unknown fields fail at plugin load rather than silently shaping nothing.
|
||||
* unknown fields fail at plugin load rather than being ignored.
|
||||
*
|
||||
* @param config Raw plugin config.
|
||||
* @returns A detached validated config.
|
||||
@@ -176,7 +177,7 @@ function planModeAtLastHeader(events: readonly SessionEvent[]): boolean | undefi
|
||||
}
|
||||
|
||||
/**
|
||||
* `ctx.planMode`: owns logged plan state, boundary application and narration,
|
||||
* `ctx.planMode`: owns logged plan state, applies and narrates selected state at step start,
|
||||
* the `plan:policy` section, the `/plan` command, and the stable exit tool.
|
||||
* UIs observe committed flips through `session/event`; there is no live mirror.
|
||||
*/
|
||||
@@ -187,7 +188,7 @@ export class PlanModeService extends Service {
|
||||
private readonly section: string
|
||||
|
||||
/**
|
||||
* Latest selection per session awaiting an in-turn request-boundary flush.
|
||||
* Latest selection per session awaiting the next accepted in-turn pre-step.
|
||||
* `narrate` is true for user selections and false for the exit tool, whose
|
||||
* result already narrates the transition.
|
||||
*/
|
||||
@@ -197,10 +198,10 @@ export class PlanModeService extends Service {
|
||||
super(ctx, 'planMode')
|
||||
this.section = resolveConfig(config).section
|
||||
let disposed = false
|
||||
// Pre-step is outside Session.append publication, so its log-only mode
|
||||
// event can land between turns or inside an open turn without re-entering
|
||||
// the session. A failed append remains pending for a later boundary, and
|
||||
// policy cannot block the step.
|
||||
// Pre-step is outside Session.append publication, so it can append the
|
||||
// log-only mode event inside an open turn without re-entering the session.
|
||||
// A failed append remains pending for a later accepted in-turn pre-step,
|
||||
// and policy cannot block the step.
|
||||
ctx.on('agent/pre-step', async (
|
||||
{ agent, signal },
|
||||
next,
|
||||
@@ -212,7 +213,7 @@ export class PlanModeService extends Service {
|
||||
try {
|
||||
this.onBoundary(agent.session)
|
||||
} catch (error) {
|
||||
ctx.logger.warn('dsh-plan-mode: boundary flush failed: %o', error)
|
||||
ctx.logger.warn('dsh-plan-mode: failed to append selected plan mode at step start: %o', error)
|
||||
return decision
|
||||
}
|
||||
return !pending.narrate || narration === undefined
|
||||
@@ -234,8 +235,9 @@ export class PlanModeService extends Service {
|
||||
// The plan projection unit (session-projection RFC): a pure double-event
|
||||
// fold serving clients the whole {active, pending} value. `command/run`
|
||||
// records the user's logged /plan selection (the handler calls `set()`
|
||||
// before any failing path, so log and run-plane cannot fork); `plan/mode`
|
||||
// is the boundary commit that resolves it. Pending is thereby a pure
|
||||
// before any failing path, so a failed handler cannot leave the recorded
|
||||
// command without its plan selection); `plan/mode` records that selection
|
||||
// and clears it. Pending is thereby a pure
|
||||
// replay quantity: host restarts, other tabs, and cold reads all recover
|
||||
// it from the log alone. The unit child activates only when a projection
|
||||
// registry is composed (headless assemblies stay unaffected).
|
||||
@@ -280,8 +282,9 @@ export class PlanModeService extends Service {
|
||||
case 'cancelled':
|
||||
return { kind: 'success', text: 'Plan mode entry cancelled.' }
|
||||
case 'noop':
|
||||
// Repeat the queued wording while an exit still awaits its
|
||||
// boundary; only a truly inactive session reads idempotent.
|
||||
// Repeat the queued wording while an exit still awaits the
|
||||
// next accepted pre-step; only a truly inactive session reads
|
||||
// idempotent.
|
||||
return foldPlanMode(agent.session.events)
|
||||
? { kind: 'success', text: 'Leaving plan mode (applies from the next step).' }
|
||||
: { kind: 'success', text: 'Plan mode is already inactive.' }
|
||||
@@ -357,8 +360,8 @@ export class PlanModeService extends Service {
|
||||
}
|
||||
throw cause
|
||||
})
|
||||
// A review may outlive this plugin fiber. Without boundary listeners,
|
||||
// an approved result could never land, so fail and keep planning.
|
||||
// A review may outlive this plugin fiber. Without its pre-step listener,
|
||||
// an approved selection could never be appended, so fail and keep planning.
|
||||
if (disposed) {
|
||||
throw new Error('the plan-mode service was reloaded while the plan was under review; present the plan again')
|
||||
}
|
||||
@@ -371,7 +374,8 @@ export class PlanModeService extends Service {
|
||||
: `The user chose to keep planning; their feedback: ${feedback}`)
|
||||
}
|
||||
// Keep plan guidance for the rest of this assistant tool batch. The
|
||||
// silent intent flushes after the step, before the next assembly.
|
||||
// silent selection is appended at the next accepted in-turn pre-step,
|
||||
// before its request assembly.
|
||||
this.pendingIntents.set(agent.session, { active: false, narrate: false })
|
||||
return { approved: true }
|
||||
},
|
||||
@@ -390,7 +394,8 @@ export class PlanModeService extends Service {
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the logged plan state and any selected state awaiting a boundary.
|
||||
* Read the logged plan state and any selected state awaiting the next
|
||||
* accepted in-turn pre-step.
|
||||
*
|
||||
* @param agent The agent to read.
|
||||
* @returns Current logged state plus a pending selection, when present.
|
||||
@@ -402,20 +407,20 @@ export class PlanModeService extends Service {
|
||||
}
|
||||
|
||||
/**
|
||||
* Select whether plan mode should be active. Between turns the change
|
||||
* commits immediately — no request boundary would arrive until the next
|
||||
* prompt, so a queued intent would hang (the open-turn fold is the idle
|
||||
* signal: agent status stays `running` through post-turn checkpointing,
|
||||
* where a boundary equally never comes). During an open turn the
|
||||
* selection is held as pending intent for the next in-turn request
|
||||
* boundary. Repeated selection of the current or already-pending state is
|
||||
* a no-op.
|
||||
* Select whether plan mode should be active. Between turns the method
|
||||
* appends the change immediately because no in-turn pre-step will run until
|
||||
* another prompt starts a turn. The open-turn fold is the idle signal:
|
||||
* agent status stays `running` through post-turn checkpointing, when no
|
||||
* further in-turn pre-step runs. During an open turn the selection remains
|
||||
* pending until the next accepted in-turn pre-step. Repeated selection of
|
||||
* the current or already-pending state is a no-op.
|
||||
*
|
||||
* @param agent The agent to switch.
|
||||
* @param active Whether plan mode should be active.
|
||||
* @returns what happened: `committed` (logged now), `queued` (awaiting the
|
||||
* next boundary), `cancelled` (an opposite pending selection was cleared;
|
||||
* the logged state already matches), or `noop` (already in that state).
|
||||
* next accepted in-turn pre-step), `cancelled` (an opposite pending selection
|
||||
* was cleared; the logged state already matches), or `noop` (already in that
|
||||
* state).
|
||||
*/
|
||||
set(agent: Agent, active: boolean): 'committed' | 'queued' | 'cancelled' | 'noop' {
|
||||
const session = agent.session
|
||||
@@ -439,7 +444,7 @@ export class PlanModeService extends Service {
|
||||
return 'committed'
|
||||
}
|
||||
|
||||
/** Flush one pending selection before the next request assembly. */
|
||||
/** Append one pending selection before the next request assembly. */
|
||||
private onBoundary(session: Session): void {
|
||||
const pending = this.pendingIntents.get(session)
|
||||
if (pending === undefined) return
|
||||
@@ -449,8 +454,8 @@ export class PlanModeService extends Service {
|
||||
return
|
||||
}
|
||||
session.append('plan/mode', { active: target })
|
||||
// Delete only after append succeeds so a later boundary can retry a failed
|
||||
// durable write.
|
||||
// Delete only after append succeeds so a later accepted in-turn pre-step
|
||||
// can retry a failed durable write.
|
||||
this.pendingIntents.delete(session)
|
||||
}
|
||||
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
/**
|
||||
* The plan projection's wire value. `active` is the logged state in force
|
||||
* (the last `plan/mode`, inactive before the first); `pending` is true while
|
||||
* a logged `/plan` selection (`command/run`) awaits its request-boundary
|
||||
* `plan/mode` commit and targets a state other than `active`. Capability
|
||||
* a logged `/plan` selection (`command/run`) targets a state other than
|
||||
* `active` and no later `plan/mode` event has recorded that state. Capability
|
||||
* absence (plan-mode not composed) is the key's absence, never a value.
|
||||
*/
|
||||
export interface PlanProjection {
|
||||
|
||||
Reference in New Issue
Block a user