@treeseed/dsh-chapters 0.2.3 → 0.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/client.js +2 -2
- package/lib/client.js.map +1 -1
- package/lib/commands.js +23 -5
- package/lib/commands.js.map +1 -1
- package/lib/enrich.js +13 -3
- package/lib/enrich.js.map +1 -1
- package/lib/index.d.ts +2 -0
- package/lib/index.js +92 -6
- package/lib/index.js.map +1 -1
- package/lib/prompt-loop.js +7 -1
- package/lib/prompt-loop.js.map +1 -1
- package/lib/prompt.d.ts +119 -18
- package/lib/prompt.js +203 -72
- package/lib/prompt.js.map +1 -1
- package/lib/version.d.ts +1 -1
- package/lib/version.js +1 -1
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -94,7 +94,7 @@ window.__ModuleLoader__.load({
|
|
|
94
94
|
case "goal": return {
|
|
95
95
|
...state,
|
|
96
96
|
goalPhase: ev.phase,
|
|
97
|
-
autorun: ev.phase === "complete" ? "disarmed" : state.autorun
|
|
97
|
+
autorun: ev.phase === "complete" || ev.phase === "blocked" ? "disarmed" : state.autorun
|
|
98
98
|
};
|
|
99
99
|
}
|
|
100
100
|
return state;
|
|
@@ -119,7 +119,7 @@ window.__ModuleLoader__.load({
|
|
|
119
119
|
if (s.goal !== null && s.goal.phase === "active" && s.goal.activation === "armed") return false;
|
|
120
120
|
if (s.autorun !== "armed") return false;
|
|
121
121
|
if (s.running) return false;
|
|
122
|
-
if (s.goalPhase === "complete") return false;
|
|
122
|
+
if (s.goalPhase === "complete" || s.goalPhase === "blocked") return false;
|
|
123
123
|
if (s.hiddenTurnSeq !== null && s.hiddenTurnSeq === s.turnSeq) return false;
|
|
124
124
|
return true;
|
|
125
125
|
}
|
package/lib/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","names":["CSS","h","Tooltip","useReducer","useRef","useState","useCallback","useState","useRef","useCallback","h","Tooltip"],"sources":["../src/client/prompt-bar.ts","../src/client/prompt-dock.ts","../src/client/doors.ts","../src/client/index.ts"],"sourcesContent":["/**\n * The Suggested Next Step bar — every decision the row makes, kept out of React (#113).\n *\n * Deliberately shaped like the goal bar, because that implementation is tested, already fits, and gets the\n * split right: the **durable** facts (`status`, `text`, `turnSeq` — the server's one-generation-per-turn\n * identity) versus the **process-local** intent (`autorun`, `expanded` — the goal's `activation` in\n * everything but name). Nothing transient is persisted, and nothing persisted is guessed here.\n *\n * The failures the maintainer rejected are all decision bugs, not rendering bugs: firing while a turn is\n * running, shouting a refusal that should have been silence, an autorun that outlives the purpose that\n * justified it. Each has a row in `tests/unit/prompt-bar.test.ts`.\n */\n\nexport type PromptBarStatus = 'off' | 'idle' | 'analyzing' | 'ready' | 'failed'\nexport type PromptBarAutorun = 'armed' | 'disarmed'\n/** Mirrors the host's `GoalPhase` vocabulary (`dsh-goal` types) so goal composition needs no translation. */\nexport type GoalPhaseView = 'none' | 'active' | 'paused' | 'blocked' | 'complete'\n\nexport interface PromptBarState {\n status: PromptBarStatus\n /** The offer, verbatim. Never truncated — an over-cap answer is refused upstream. */\n text: string | null\n /** #113: what kind of step the server classified this as. `goal-edit` is held for the human's keystroke,\n * because goal edits require a direct human turn in the host and an autorun message is not one. */\n intent: 'worker-step' | 'goal-edit'\n /** The `turn/end` seq this offer belongs to; its identity, not a timestamp. */\n turnSeq: number | null\n /** Set only with `status: 'failed'`; rendered as `role=\"alert\"` (#61: delivered, not swallowed). */\n error: string | null\n /** Why an action did not happen, on the bar's face (#113). Empty when there is nothing to explain. */\n note: string\n autorun: PromptBarAutorun\n expanded: boolean\n running: boolean\n goalPhase: GoalPhaseView\n /** The offer a human dismissed or interrupted. A NEW `turnSeq` clears it; this one stays hidden. */\n hiddenTurnSeq: number | null\n /** Live goal facts, when the caller can read them (the goal driver owns the next turn when active+armed). */\n goal: { phase: string; activation: string; revision: number } | null\n}\n\nexport type PromptBarEvent =\n | { t: 'polled', status: PromptBarStatus, text?: string | null, turnSeq?: number | null, error?: string | null, intent?: 'worker-step' | 'goal-edit' }\n | { t: 'expand' } | { t: 'collapse' }\n /** Why an action did not happen, on the bar's own face (#113). Cleared when a new offer lands. */\n | { t: 'note'; note: string }\n | { t: 'autorun', on: boolean }\n | { t: 'human/prompt' }\n | { t: 'off' }\n | { t: 'goal', phase: GoalPhaseView }\n | { t: 'running', running: boolean }\n\nexport function initialBarState(autorun: PromptBarAutorun = 'disarmed'): PromptBarState {\n return { status: 'off', text: null, turnSeq: null, error: null, note: '', intent: 'worker-step', goalPhase: 'none', goal: null, autorun, expanded: false, running: false, hiddenTurnSeq: null }\n}\n\nexport function promptBarReducer(state: PromptBarState, ev: PromptBarEvent): PromptBarState {\n if (ev.t === 'note') return { ...state, note: ev.note }\n const next = baseBarReducer(state, ev)\n // A note is about the gesture that just failed, so a NEW offer retires it. A poll that repeats the same\n // offer (the bar re-reads every few seconds) does not — otherwise the explanation would blink out before\n // the human could read it.\n return ev.t === 'polled' && (next.turnSeq !== state.turnSeq || next.text !== state.text) ? { ...next, note: '' } : next\n}\n\nfunction baseBarReducer(state: PromptBarState, ev: PromptBarEvent): PromptBarState {\n switch (ev.t) {\n case 'polled': {\n if (ev.status === 'off') return { ...state, status: 'off', text: null, turnSeq: null, error: null, expanded: false, autorun: 'disarmed' }\n // A fresh generation invalidates the previous offer: acting on a stale one is how a bar lies.\n if (ev.status === 'analyzing') return { ...state, status: 'analyzing', text: null, error: null }\n return {\n ...state,\n status: ev.status,\n text: ev.status === 'ready' ? (ev.text ?? null) : null,\n intent: ev.status === 'ready' ? (ev.intent ?? 'worker-step') : 'worker-step',\n turnSeq: ev.turnSeq ?? state.turnSeq,\n error: ev.status === 'failed' ? (ev.error ?? 'the suggestion call failed') : null,\n }\n }\n case 'expand': return { ...state, expanded: true }\n // Collapsing is a reading action. It keeps the offer — only a human prompt or `Off` dismisses it.\n case 'collapse': return { ...state, expanded: false }\n case 'autorun': return { ...state, autorun: ev.on ? 'armed' : 'disarmed' }\n case 'running': return { ...state, running: ev.running }\n case 'human/prompt':\n // The human took the wheel: yield the loop, and drop the offer that was on screen. Generation itself\n // continues on the NEXT turn boundary — suspension is not disable.\n return { ...state, autorun: 'disarmed', hiddenTurnSeq: state.turnSeq, expanded: false }\n case 'off': return { ...state, status: 'off', text: null, turnSeq: null, error: null, expanded: false, autorun: 'disarmed' }\n case 'goal':\n // Per spec: autorun runs ALONGSIDE an active goal and disarms itself when the goal completes — the\n // suggestion loop exists to serve the session's purpose, and stops when that purpose stops.\n return { ...state, goalPhase: ev.phase, autorun: ev.phase === 'complete' ? 'disarmed' : state.autorun }\n }\n // Exhaustive fallback: a new event without a case must return the state unchanged rather than\n // compile into `undefined` and blank the bar.\n return state\n}\n\n/** The status label. `failed` keeps the ready label so the error span is the delta, not a mystery row. */\nexport function barLabel(s: PromptBarState): string | null {\n if (s.status === 'analyzing') return 'Analyzing situation…'\n if (s.status === 'ready' || s.status === 'failed') return 'Recommended Next Step'\n return null\n}\n\nexport function barVisible(s: PromptBarState): boolean {\n // Only `off` hides the row. `idle` is enabled-and-waiting, and showing it is the difference between\n // \"the feature is live, nothing to say yet\" and silence that reads as disabled (#113).\n if (s.status === 'off') return false\n if (s.status === 'ready' && (s.text === null || s.text === '')) return false\n // The goal bar's `clearedGoalId` guard, in our terms: don't flicker back the offer just dismissed.\n if (s.hiddenTurnSeq !== null && s.hiddenTurnSeq === s.turnSeq) return false\n return true\n}\n\n/** May the bar submit its own offer? Armed, present, idle, not dismissed, and still serving a purpose. */\nexport function shouldAutoFire(s: PromptBarState): boolean {\n if (s.status !== 'ready') return false\n if (s.text === null || s.text === '') return false\n // #113: two host-imposed stops before anything else. A goal re-cut needs a human turn (dsh-tool-goal's\n // requireDirectHuman), and an armed goal driver already intends to start this turn — firing here would be\n // two turn-starters at one boundary, racing the driver's own reservation fence.\n if (s.intent === 'goal-edit') return false\n if (s.goal !== null && s.goal.phase === 'active' && s.goal.activation === 'armed') return false\n if (s.autorun !== 'armed') return false\n if (s.running) return false\n if (s.goalPhase === 'complete') return false\n if (s.hiddenTurnSeq !== null && s.hiddenTurnSeq === s.turnSeq) return false\n return true\n}\n\n/** The command the bar pulls. One string, so the client and the command plane cannot drift silently. */\nexport const BAR_STATE_COMMAND = '/chapters-prompt state'\n/** The bar's Run button: the server submits the on-screen offer as the human's own turn. */\nexport const BAR_RUN_COMMAND = '/chapters-prompt run'\n\n/**\n * Parse the `state` verb's `k: v` lines into a pull event.\n *\n * One output serving the human and the component is the design (see `docs/contract.md`); this function is\n * the cost of that choice, and it is kept honest: an unrecognised line is IGNORED, not guessed at, and a\n * payload that yields no `status` is `null` so the caller keeps its last state rather than inventing `off`\n * (which would hide a bar that is merely mid-reply). Indentation and the prompt's own lines are the reason\n * the prompt is collected separately from the scalar fields — it is the only multi-line value.\n */\nexport function parseBarState(text: string): PromptBarEvent | null {\n const lines = text.split('\\n')\n const head = lines[0] ?? ''\n const statusWord = head.startsWith('suggested next step:') ? head.slice('suggested next step:'.length).trim() : ''\n const status = statusWord === 'off' ? 'off'\n : statusWord === 'idle' ? 'idle'\n : statusWord === 'analyzing' ? 'analyzing'\n : statusWord === 'ready' ? 'ready'\n : statusWord === 'failed' ? 'failed'\n : null\n if (status === null) return null\n const scalars: Record<string, string> = {}\n const prompt: string[] = []\n let inPrompt = false\n for (const line of lines.slice(1)) {\n if (inPrompt) { prompt.push(line.replace(/^ {4}/, '')); continue }\n const m = /^ {2}([a-z]+): ?(.*)$/.exec(line)\n if (m === null) continue\n const key = m[1]!, value = m[2]!.trim()\n if (key === 'prompt') { inPrompt = true; continue }\n scalars[key] !== undefined ? (scalars[key] += ` ${value}`) : (scalars[key] = value)\n }\n const turnRaw = scalars['turn'] ?? ''\n const turnSeq = /^\\d+$/.test(turnRaw) ? Number(turnRaw) : null\n const errorRaw = scalars['error'] ?? ''\n const intentWord = (scalars['intent'] ?? '').split(' ')[0] ?? ''\n return {\n t: 'polled',\n intent: intentWord === 'goal-edit' ? 'goal-edit' : 'worker-step',\n status,\n ...(status === 'ready' ? { text: prompt.join('\\n'), turnSeq } : {}),\n ...(status === 'failed' ? { error: errorRaw, turnSeq } : {}),\n }\n}\n\n/** The autorun flag, read from the same payload — the row is durable, the flag is not. */\nexport function parseBarAutorun(text: string): 'armed' | 'disarmed' | null {\n const m = /^ {2}autorun: (armed|disarmed)\\b/m.exec(text)\n return m === null ? null : (m[1] as 'armed' | 'disarmed')\n}\n\n/**\n * Does the host say this session is busy right now?\n *\n * The dock's props are HOOKS (`useSessionStatus`, `useChat`, `useInput`, …) — documented in\n * docs/contract.md § \"What a dock component is handed\" — so a component that waits for a scalar\n * `props.running` waits forever, which is precisely why Autorun declared itself unavailable while the host\n * was publishing the signal one field away. Shapes differ across hosts and versions, so every form is folded\n * here, in one pure function, with `undefined` (unknown) kept distinct from `false` (idle): the loop refuses\n * to start a turn on an unknown, and that distinction is the whole safety property.\n */\nexport function deriveRunning(status: unknown): boolean | undefined {\n if (status === undefined || status === null) return undefined\n if (typeof status === 'boolean') return status\n if (typeof status === 'string') {\n if (/^(running|streaming|busy|active)$/i.test(status)) return true\n if (/^(idle|done|stopped|finished|ready|waiting)$/i.test(status)) return false\n return undefined\n }\n if (typeof status === 'object') {\n const s = status as { status?: unknown, state?: unknown, running?: unknown, phase?: unknown }\n if (s.running !== undefined) return typeof s.running === 'boolean' ? s.running : undefined\n return deriveRunning(s.status ?? s.state ?? s.phase)\n }\n return undefined\n}\n","/**\n * The Suggested Next Step dock row (#113) — thin by design.\n *\n * Every decision lives in `prompt-bar.ts` where a row can pin it; this file only moves pixels and bytes\n * between the host slot and that reducer. That split is the reason it exists: the ghost was rejected for\n * behaviour that had no home, and \"one turn, one offer\" cannot be tested in a component.\n *\n * Placement is measured, not styled: `conversation.input.dock` is the host's full-width stack rendered\n * ABOVE the composer card (todo `order: 0`, goal `10`, queue `20`), so `order: 30` puts this row under every\n * other dock entry and directly above the input — and because it is a docked row rather than an overlay, it\n * can neither cover the placeholder nor collide with the send buttons. The ghost's ugliest defect was\n * structural, and the structure fixes it.\n *\n * The bar is PUSH-SHAPED, PULL-FED. A registrant can own a reactive observable (`hooks: { x }` → prop\n * `useX`) — that is how the goal bar gets its `activation` — but a plugin cannot publish a projection, and\n * the only plugin-reachable call path is `remote.commands.execute`. So the component pulls the `state` verb\n * on edges it can actually observe. Stated here because the alternative is a reader assuming a subscription\n * that does not exist.\n *\n * @module dsh-chapters/src/client/prompt-dock\n */\n\nimport { createElement as h, useCallback, useEffect, useReducer, useRef, useState, type ReactElement } from 'react'\nimport { Tooltip } from '@deepseek-ai/dsh-client-ui-primitives'\nimport {\n BAR_RUN_COMMAND,\n deriveRunning,\n BAR_STATE_COMMAND,\n barLabel,\n barVisible,\n initialBarState,\n parseBarAutorun,\n parseBarState,\n promptBarReducer,\n shouldAutoFire,\n type PromptBarEvent,\n type PromptBarState,\n} from './prompt-bar.ts'\n\n/** The dock's owner + standard-kit props we read. Everything is optional-typed on purpose: this is a host\n * shape crossing a plugin boundary, and an absent field must degrade visibly, not throw at boot (#58). */\nexport interface DockProps {\n sessionId?: string\n inputActions?: { insertText?: (text: string) => void; setDraft?: (text: string) => void; submit?: () => void }\n /** The host's session-status hook, when this build hands docks one (`useSessionStatus` is in the dock's\n * documented prop list). Folded through `deriveRunning` because its shape is not ours to assume. */\n useSessionStatus?: (selector?: unknown) => unknown\n /** The session snapshot hook; its snapshot carries `running`, which is Autorun's idle edge. */\n useSession?: (selector?: unknown) => unknown\n /** The host publishes running/idle through the composer kit on some builds; if it is absent, autorun\n * refuses to fire rather than guessing that the session is idle. Fail-safe, not fail-open. */\n running?: boolean\n /** Run a host command line for THIS session and return its text. One verb for every call the bar makes\n * (the `state` pull and the `off` write), because the bar is a mouth for the command plane — a second\n * private channel would be a second contract to keep in step, which is what `doors.ts` already avoids. */\n exec?: (line: string) => Promise<string>\n}\n\nconst CSS = [\n // Metrics copied from the goal bar's measured module so the two rows read as one family: a 36px card,\n // host elevation + radius tokens, and the host's own composer width vars so it tracks the card above it.\n '.dsh-chapters_promptDock{box-sizing:border-box;width:calc(100% - 4 * var(--dsh-composer-dock-inset));max-width:calc(var(--dsh-composer-card-max-width) - 4 * var(--dsh-composer-dock-inset));margin:0 auto}',\n '.dsh-chapters_promptBar{isolation:isolate;box-sizing:border-box;width:100%;border-radius:var(--dsw-radius-md);height:36px;box-shadow:var(--dsw-elevation-panel);border:0;align-items:center;gap:10px;padding:4px 5px 4px 12px;display:flex;position:relative}',\n '.dsh-chapters_promptBar::before{z-index:-1;border-radius:inherit;background:var(--dsw-specific-menu);backdrop-filter:var(--dsw-menu-backdrop-filter);content:\"\";pointer-events:none;position:absolute;inset:0}',\n '.dsh-chapters_promptExpanded::before{align-items:flex-start;position:absolute;inset:0}',\n '.dsh-chapters_promptGlyph{color:var(--dsw-alias-label-tertiary);flex:none;display:inline-flex}',\n '.dsh-chapters_promptLabel{color:var(--dsw-alias-label-primary);flex:none;font-size:13px;font-weight:500;line-height:24px}',\n '.dsh-chapters_promptText{min-width:0;color:var(--dsw-alias-label-primary-dimmed);font-size:13px;line-height:20px;cursor:pointer;border:0;background:0 0;padding:0;text-align:left;flex:1}',\n '.dsh-chapters_promptEllipsis{white-space:nowrap;text-overflow:ellipsis;overflow:hidden;display:block}',\n '.dsh-chapters_promptFull{white-space:pre-wrap;overflow:auto;max-height:9rem;display:block}',\n '.dsh-chapters_promptError{color:var(--dsw-alias-state-error-primary);font-size:12px;line-height:20px;flex:none;max-width:34%}', '.dsh-chapters_promptNote{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:20px;min-width:0}',\n '.dsh-chapters_promptIntent{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:20px}',\n\n '.dsh-chapters_promptActions{flex:none;align-items:center;gap:10px;display:flex}',\n '.dsh-chapters_promptBtn{corner-shape:round;width:28px;height:28px;color:var(--dsw-alias-label-tertiary);cursor:pointer;background:0 0;border:none;border-radius:999px;justify-content:center;align-items:center;padding:0;display:inline-flex;font-size:13px}',\n '.dsh-chapters_promptBtn:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary)}',\n '.dsh-chapters_promptBtn:disabled{opacity:.4;cursor:default}',\n].join('\\n')\nconst CSS_ID = 'dsh-chapters/prompt-dock.css'\n\nconst ensureCss = (): void => {\n if (typeof document === 'undefined') return\n if (document.getElementById(CSS_ID) !== null) return\n const style = document.createElement('style')\n style.id = CSS_ID\n style.setAttribute('data-plugin-css', CSS_ID)\n style.textContent = CSS\n document.head.appendChild(style)\n}\n\n/** The analysis glyph, drawn to the goal icon's metric (16 viewBox, stroke currentColor, width 1).\n * A prism-and-arrow: the feature reads the situation and points at the next move. */\nconst Glyph = (): ReactElement => h(\n 'svg', { viewBox: '0 0 16 16', width: 14, height: 14, fill: 'none', 'aria-hidden': 'true', 'stroke-width': '1' },\n h('path', { d: 'M2.5 3.5h11L8 9.5 2.5 3.5Z', stroke: 'currentColor' }),\n h('path', { d: 'M8 9.5V14', stroke: 'currentColor' }),\n h('path', { d: 'M6 12l2 2 2-2', stroke: 'currentColor' }),\n)\n\n/** One action button, labelled through the host tooltip exactly as the goal bar labels its own. */\nconst Action = (props: { label: string; glyph: string; onClick: () => void; disabled?: boolean; title?: string }): ReactElement => {\n const button = h('button', {\n type: 'button', className: 'dsh-chapters_promptBtn', 'aria-label': props.label,\n ...(props.disabled === true ? { disabled: true } : {}),\n ...(props.title !== undefined ? { title: props.title } : {}),\n onClick: props.onClick,\n }, props.glyph)\n // Props matched to the call `doors.ts` already makes successfully: the host Tooltip takes\n // { label, side, children } and nothing else that I have verified. Inventing extra props (portal, delayMs)\n // failed the overload on the first compile, which is the right way to learn it.\n return h(Tooltip, { label: props.label, side: 'bottom' as const, children: button as ReactElement<Record<string, unknown>> })\n}\n\n/** Run is allowed whenever there is an offer; AUTORUN is what the intent and the goal gate. Keeping the\n * human's own button enabled is deliberate — the point of the protocol is to route a goal edit to a keystroke,\n * never to disable the human's ability to send it. */\n/**\n * The words the collapsed row shows, per state. `idle` and `analyzing` must say something, because an empty\n * row is indistinguishable from a broken one — the whole reason this feature took a day to read: the bar was\n * live, enabled, and rendering nothing but whitespace.\n */\nfunction lineFor(state: PromptBarState): string {\n if (state.status === 'ready') return state.text ?? ''\n if (state.status === 'analyzing') return 'Analyzing the session…'\n if (state.status === 'failed') return state.error !== null && state.error !== '' ? `Cannot recommend: ${state.error}` : 'Cannot recommend a next step right now'\n return 'No suggestion yet — I will recommend one after your next turn'\n}\n\nconst canRunHere = (state: PromptBarState): boolean => state.text !== null && state.text !== ''\n\n/** How long a state pull may take before we call it failed. Generous on purpose: this is a local RPC that\n * answers in milliseconds when the session is attachable, and the failure it guards against is a hang. */\nconst PULL_TIMEOUT_MS = 15_000\n\n/** Retry ceiling for the FIRST pull only, in attempts and spacing. A mount-time race must not be able to\n * starve the bar forever, and a real `off` answer stops the loop on the first reply either way. */\n/** Cadence of the keep-poll. A local RPC that answers in milliseconds; the cost is one command per\n * interval, and it ends the moment the state is `off`. */\nconst POLL_SPACING_MS = 10_000\n\nfunction execWithTimeout(exec: (line: string) => Promise<string>, line: string, ms: number): Promise<string> {\n return new Promise<string>((resolve, reject) => {\n const timer = setTimeout(() => { reject(new Error(`no answer within ${String(ms)}ms`)) }, ms)\n exec(line).then(\n (text) => { clearTimeout(timer); resolve(text) },\n (error: unknown) => { clearTimeout(timer); reject(error instanceof Error ? error : new Error(String(error))) },\n )\n })\n}\n\nexport function PromptDock(props: DockProps): ReactElement | null {\n const { sessionId, inputActions, exec } = props\n const [state, dispatch] = useReducer(promptBarReducer, initialBarState(), (s) => ({ ...s, running: props.running ?? true }))\n // #79's discipline, kept because the failure it prevents was real: one pull per mount, then per edge.\n // A fetch in an effect with no such guard re-fires on every render.\n const pulling = useRef(false)\n // TWO different facts, two different refs — conflating them is the bug I just shipped: `pulled` is the\n // once-per-mount pull guard, `alive` is \"may this component still call dispatch/setState\". Initializing the\n // pull guard to true made every mount skip its first pull, so the bar sat at its `off` default and rendered\n // nothing at all — a regression that looked, from outside, like the feature having stopped working.\n const probed = useRef(false)\n const pulled = useRef(false)\n const alive = useRef(true)\n useEffect(() => () => { alive.current = false }, [])\n const [busy, setBusy] = useState(false)\n void 0\n const answered = useRef(false)\n const replyStatus = useRef<string>('unknown')\n const stateRef = useRef<PromptBarState>(initialBarState())\n stateRef.current = state\n if (!probed.current) {\n probed.current = true\n // One print, on the first mount: which composer verbs does THIS host actually hand a dock? The click-to-draft\n // requirement needs one, `setDraft` is accepted but never reaches the editor, and guessing at the surface\n // again would be a third wrong theory in a row.\n console.info(`[client:prompt] inputActions keys: ${Object.keys(inputActions ?? {}).join(',') || 'NONE'}`)\n }\n\n const refresh = useCallback(async (): Promise<void> => {\n if (exec === undefined || pulling.current) return\n pulling.current = true\n try {\n // Bounded, and loudly so. Tonight's whole diagnosis cost was paid because this await had no ceiling:\n // a wire that never answers produced no dispatch, no catch and no `finally`, which looked identical to\n // \"the feature is switched off\". A hung command is now a named failure with a number on it (#61).\n const text = await execWithTimeout(exec, BAR_STATE_COMMAND, PULL_TIMEOUT_MS)\n // Handed to the leg through the page, because the state block is the contract and I need to see the\n // bytes the browser parsed, not the bytes the server meant. Overwritten each pull; read on failure.\n try { (window as unknown as { __dshPromptReply?: string }).__dshPromptReply = text } catch { /* test seam only */ }\n const ev = parseBarState(text) as PromptBarEvent | null\n if (ev !== null) { answered.current = true; replyStatus.current = (ev as { status?: string }).status ?? replyStatus.current; dispatch(ev) }\n const autorun = parseBarAutorun(text)\n if (autorun !== null) dispatch({ t: 'autorun', on: autorun === 'armed' })\n } catch (error) {\n // #61: a detected failure is a DELIVERED failure. Keeping the last known state is right; saying\n // nothing was not — with a bare `catch {}` a wire that never answers is indistinguishable from a\n // feature that is switched off, and that is exactly how this bar looked \"unverifiable\" in a browser\n // while the real fault was one line of swallowed output.\n // A failed pull is not a state: the bar keeps what it last knew rather than rendering `off`,\n // which would read as \"suggestions are disabled\" when the wire merely hiccoughed.\n console.warn(`[client:prompt] pull failed: ${String((error as Error)?.message ?? error).slice(0, 180)}`)\n } finally { pulling.current = false }\n }, [exec])\n\n useEffect(() => {\n ensureCss()\n if (pulled.current) return\n pulled.current = true\n // Retry until SOMETHING answers. `answered` flips on any parseable reply, including an honest `off`, so\n // this loop cannot make a disabled session look enabled — it only closes the window where a pull fired\n // before the session could take a command and the bar then stayed blank for the rest of the session.\n // Keep reading while the feature is live. This host publishes no idle edge — the same absence that\n // makes Autorun refuse to fire — so a pull that only ran at mount would answer `idle` once and then go\n // quiet for the rest of the session. The poll stops on `off`, which is the only state that means the\n // human (or the config) switched it away, so a disabled session costs one read and nothing more.\n let timer: ReturnType<typeof setTimeout> | undefined\n const tick = async (): Promise<void> => {\n await refresh()\n if (replyStatus.current === 'off') return\n // The reply, not the React state. The old condition read `stateRef.current`, which is only advanced by a\n // render — and while the row is hidden there is no render to wait for, so the poll stopped after its\n // first read and the bar could never observe the offer that landed a minute later. Measured: every\n // `state read` in the boot log precedes its `ready` write, with none following. \n timer = setTimeout(() => { void tick() }, POLL_SPACING_MS)\n }\n void tick()\n return () => { if (timer !== undefined) clearTimeout(timer) }\n }, [refresh])\n\n // Autorun fires off the IDLE edge, never off a render. `running` comes from the host when it publishes it;\n // absent means unknown, and unknown means do not start a turn the human cannot see coming.\n useEffect(() => {\n dispatch({ t: 'running', running: props.running ?? true })\n }, [props.running])\n\n // Run is a COMMAND, not a keystroke. The composer-write path (`inputActions.setDraft` + `submit`) looked\n // like the obvious implementation and does not work on this host build: `setDraft` is accepted, and the\n // editor stays empty — measured, twice, after I had convinced myself it was the test's locator or a focus\n // ordering problem. The plugin already has a dependable wire to its own server plane, and the server has\n // exactly one human-authored submission path (the follow-up autorun uses), so Run rides that. The\n // consequence is also the point: what the human clicks and what the loop arms are the same code.\n const run = useCallback((): void => {\n if (state.text === null || state.text === '' || exec === undefined) return\n setBusy(true)\n void exec(BAR_RUN_COMMAND)\n .then((out) => {\n if (!alive.current) return\n if (/^cannot run:/i.test(out.trim())) dispatch({ t: 'polled', status: 'failed', text: state.text, error: out.trim().slice(0, 200), turnSeq: state.turnSeq } as PromptBarEvent)\n else dispatch({ t: 'collapse' })\n })\n .catch((error: unknown) => { console.warn(`[client:prompt] run failed: ${String((error as Error)?.message ?? error).slice(0, 160)}`) })\n .finally(() => { if (alive.current) setBusy(false) })\n }, [exec, state.text, state.turnSeq])\n\n useEffect(() => {\n if (!shouldAutoFire(state)) return\n run()\n }, [state, run])\n\n // Autorun's idle edge stays UNWIRED on this host, and that is now a measured fact rather than a guess:\n // `useSessionStatus` is in the dock's documented prop list, but calling it here threw twice — first a React\n // hook-order abort (#310, because it sat below the early return), then `TypeError: l is not a function` once\n // hoisted. Two blind attempts is the limit; the contract says which hooks exist, not their arity or context.\n // So the gate keeps its original, correct behavior: with no signal, `running` is undefined, Autorun is\n // visibly unavailable and says why, and `deriveRunning` (9 rows) stands ready for whichever shape the host\n // does publish. A loop that starts turns the human cannot foresee is the one failure mode not on offer.\n // `useSessionStatus` is `SnapshotSelectorHook<SessionStatusSnapshot>` — a SELECTOR hook (declared in\n // dsh-client-ui-session's client types), which is why calling it with a session id threw\n // `TypeError: l is not a function`: the host used my string as the selector. An identity selector takes\n // whatever the snapshot's shape happens to be, and `deriveRunning` folds it, so this code does not bet on\n // field names I have not read. Still hoisted above the early return: hook count must not change per render.\n // The idle signal IS published — on `useSession`, whose snapshot carries `running` (measured by printing the\n // hook surfaces once: `useSession=sessionId|pendingSubmissions|running|subagent|removed|openState`). My\n // first two attempts were wrong in different ways: a scalar `props.running` that no host supplies, then\n // `useSessionStatus`, which is provided but yields no readable keys under an identity selector. `deriveRunning`\n // folds `{running}`, so this is the last mile, and the hook is called unconditionally through a stable\n // fallback because hook order must not change between renders.\n const useSessionHook = props.useSession ?? (() => undefined)\n const sessionSnapshot = useSessionHook((snapshot: unknown) => snapshot)\n const running = props.running ?? deriveRunning(sessionSnapshot)\n const autorunDisabled = running === undefined\n\n if (sessionId === undefined || !barVisible(state as PromptBarState)) return null\n\n const populate = (): void => {\n // Collapsed: reading. Expanded: the maintainer's click-to-edit gesture — setDraft REPLACES the draft,\n // which is what they asked for (\"the prompt replaces the chat input contents so you can edit it\"), and\n // nothing is submitted. Run is a separate, deliberate button.\n // Printed on every branch, because \"the button did nothing\" and \"the button took the other path\" looked\n // identical from outside for three runs.\n console.info(`[client:prompt] populate expanded=${String(state.expanded)} hasInsertText=${String(typeof inputActions?.insertText)} hasSetDraft=${String(typeof inputActions?.setDraft)} textLen=${String(state.text?.length ?? -1)}`)\n if (!state.expanded) { dispatch({ t: 'expand' }); return }\n if (state.text === null || state.text === '') return\n // Delivered, not assumed. The click-to-draft gesture is the interaction the maintainer asked for, so a\n // silent no-op here is indistinguishable from \"the feature does not work\" — and that ambiguity cost the\n // rest of this leg's diagnosis. Absent verb, absent text: both say so.\n // The host's composer kit is `{ captureInsertion, insertText, setDraft, addAttachments, removeAttachment,\n // pruneAttachments, submit }` (measured on 0.2.0-rc.2). `setDraft` writes the STORE, and the store is\n // mirrored into the editor only at mount while the editor is empty — which is why a click that \"worked\"\n // (no refusal, no throw) left the box visibly empty. `insertText` is the verb that reaches the editor, so\n // click-to-draft uses it and keeps `setDraft` only as a fallback for builds that lack it.\n if (typeof inputActions?.insertText === 'function') {\n // `insertText` inserts AT THE CARET, and clicking this button moves the caret out of the editor — so the\n // call ran and put the words nowhere. Focusing the composer first is the whole difference, and it is done\n // with a tolerant query rather than a class name: the host's own module hashes (`wSkVaW_…`) are not mine\n // to depend on, and a bar that silently stops drafting after a host restyle is worse than one that tries.\n try {\n const box = document.querySelector<HTMLElement>('[contenteditable=\"true\"][role=\"textbox\"]')\n box?.focus()\n } catch { /* a host without the editor mounted: the insert below still records the intent */ }\n // Measured on host 0.2.0-rc.2: called from a dock this throws\n // `Cannot read properties of undefined (reading 'draftRev')`, and the kit's other verb (`setDraft`) writes\n // a store the editor mirrors only at mount. Neither reaches the composer. A silent no-op is what cost the\n // last six runs, so the refusal now appears on the bar and points at Run, which does work.\n const refuse = (why: string): void => {\n console.warn(`[client:prompt] click-to-draft refused: ${why}`)\n dispatch({ t: 'note', note: 'This host does not let a plugin write the composer, so the prompt cannot be edited before sending — Run submits it as your turn' })\n }\n try {\n inputActions.insertText(state.text)\n // Throwing is the easy case. The harder one is the verb that returns successfully and changes nothing,\n // which is what `setDraft` does on 0.2.0-rc.2 and what a silent button looks like forever. Verify the\n // editor actually holds the words; if it does not, say so on the bar.\n const box = document.querySelector<HTMLElement>('[contenteditable=\"true\"][role=\"textbox\"]')\n const head = state.text.slice(0, 24)\n if ((box?.innerText ?? '').indexOf(head) === -1) {\n // One animation frame: the host may write the DOM after its own microtask.\n setTimeout(() => {\n const b2 = document.querySelector<HTMLElement>('[contenteditable=\"true\"][role=\"textbox\"]')\n if ((b2?.innerText ?? '').indexOf(head) === -1) refuse('insertText reported success and left the editor empty')\n }, 400)\n }\n } catch (error) {\n refuse(String((error as Error)?.message ?? error).slice(0, 160))\n }\n // Read the editor back from inside the component, so \"insertText is a no-op on this host\" is a fact with\n // a number next to it rather than a conclusion the test reaches after the fact.\n setTimeout(() => {\n try {\n const box = document.querySelector<HTMLElement>('[contenteditable=\"true\"][role=\"textbox\"]')\n console.info(`[client:prompt] after insertText: editorText=${String(box?.innerText?.length ?? 'NO-BOX')} focused=${String(document.activeElement === box)}`)\n } catch { /* diagnostics never break the gesture */ }\n }, 250)\n return\n }\n if (typeof inputActions?.setDraft === 'function') {\n inputActions.setDraft(state.text)\n return\n }\n console.warn('[client:prompt] click-to-draft refused: this host exposes neither insertText nor setDraft')\n }\n\n // Called unconditionally through a stable fallback, so the hook order cannot change between renders.\n return h('div', { className: 'dsh-chapters_promptDock', 'data-prompt-bar': 'true' },\n h('div', { className: `dsh-chapters_promptBar${state.expanded ? ' dsh-chapters_promptExpanded' : ''}` },\n h('span', { className: 'dsh-chapters_promptGlyph' }, h(Glyph)),\n h('span', { className: 'dsh-chapters_promptLabel' }, barLabel(state as PromptBarState) ?? ''),\n // A goal re-cut is held for the human's keystroke: the host reserves goal edits for a turn containing a\n // direct human message, and an autorun message is not one. The bar says so on its face rather than\n // letting Run appear to be a normal button that silently fails in the tool.\n ...(state.intent === 'goal-edit' ? [h('span', { key: 'intent', className: 'dsh-chapters_promptIntent', 'aria-label': 'This step edits the goal' }, 'edits the goal · run it yourself')] : []),\n h('button', {\n type: 'button', className: 'dsh-chapters_promptText',\n 'aria-label': state.expanded ? 'Put this suggested prompt in the input box' : 'Show the full suggested prompt',\n onClick: populate,\n }, state.expanded\n ? h('span', { className: 'dsh-chapters_promptFull' }, state.text ?? '')\n : h('span', { className: 'dsh-chapters_promptEllipsis' }, lineFor(state))),\n ...(state.note === '' ? [] : [h('span', { key: 'note', className: 'dsh-chapters_promptNote', role: 'status' }, state.note)]),\n ...(state.error === '' ? [] : [h('span', { key: 'err', className: 'dsh-chapters_promptError', role: 'alert' }, state.error)]),\n h('div', { className: 'dsh-chapters_promptActions' },\n h(Action, {\n label: 'Run the suggested next step', glyph: '▶', onClick: run, disabled: busy,\n ...(state.text === null || state.text === '' || !canRunHere(state) ? { disabled: true } : {}),\n ...(state.intent === 'goal-edit'\n ? { title: 'Goal edits need a human turn. Run still works; autorun will never do this one.' }\n : {}),\n }),\n h(Action, {\n label: state.autorun === 'armed' ? 'Autorun off' : 'Autorun',\n glyph: state.autorun === 'armed' ? '⟳' : '⟲',\n disabled: autorunDisabled,\n ...(autorunDisabled\n ? { title: 'Autorun needs the host to publish an idle signal; this build does not, so nothing would fire safely.' }\n : {}),\n // A SERVER write, like `off` below. Flipping local state alone was the defect: `refresh()` re-reads the\n // loop state (`:193`), the server still says `disarmed`, and the label snaps back — so the user watched\n // their own click be refused and the loop could never be started from the bar. The command surface is\n // `/chapters-prompt autorun on|off` (src/commands.ts:462-478), which also reports the goal displacement.\n onClick: () => {\n void (async () => {\n const arm = state.autorun !== 'armed'\n if (exec !== undefined) await exec(`/chapters-prompt autorun ${arm ? 'on' : 'off'}`)\n dispatch({ t: 'autorun', on: arm })\n await refresh()\n })()\n },\n }),\n h(Action, {\n label: 'Turn prompt suggestions off for this session', glyph: '✕',\n // `off` is a server write, not a local hide: generation stops, the configured model is KEPT so\n // back-on is one action, and the bar disappears instead of lingering with a stale offer.\n onClick: () => { void (async () => { if (exec !== undefined) await exec('/chapters-prompt off'); dispatch({ t: 'off' }) })() },\n }),\n ),\n ),\n )\n}\n","/**\n * The client door runner: the two deterministic archive doors, executed over\n * `remote.commands` and followed by a SWITCH to the fresh child.\n *\n * ONE runner, two rows. The server side keeps the same discipline — both doors\n * come from `deterministicCommand(door)` in src/tools.ts, and a unit row pins\n * that they archive identically. Copy-pasting this body is how the two would\n * drift apart, so there is exactly one implementation and a table of rows.\n *\n * `chapters-continue` ships only as a composer command; without a button here\n * the child is created correctly and the UI shows nothing, which reads as\n * \"nothing happened\". The row is the missing affordance, not new logic.\n *\n * React-free on purpose: the primitives package pulls CSS modules the node\n * test runner cannot load, so the testable half lives here and index.ts is the\n * thin chrome that renders these rows as buttons (tests/unit/client-doors.test.ts).\n *\n * @module dsh-chapters/src/client/doors\n */\n\n/** Structural slice of the aggregated remote service (@deepseek-ai/dsh-api-remotes).\n * `remote.commands` must ALSO appear in inject — the federation gates each\n * sub-service individually (the live feedback package declares\n * remote.messageFeedback beside remote for exactly this reason). */\nexport interface ClientRemote {\n commands?: {\n execute(sessionId: string, line: string, attachments: readonly unknown[]): Promise<{\n ok?: boolean\n value?: { commandId?: string; result?: { kind?: string; text?: string } }\n error?: { code?: string; message?: string }\n }>\n }\n}\n\n/** The client sessions service (dsh-client-runtime): open() lives on the\n * prototype; everything else on the surface stays untrusted and unused. */\nexport interface ClientSessions {\n open(sessionId: string): unknown\n}\n\nexport interface DoorResult {\n ok: boolean\n message: string\n}\n\n/**\n * One assistant-action row. `noun` is the server's own word for the child in\n * its success text (`branch` / `continuation`), so the fallback wording and the\n * title regex both track the door without a second implementation.\n */\nexport interface DoorAction {\n /** The slot registration id. */\n id: string\n /** Position in the assistant action row. */\n order: number\n /** Button + tooltip label, and the accessible name the e2e specs select on. */\n label: string\n /** The host command this row executes — the ONLY semantic difference at runtime. */\n command: string\n /** The noun in `${noun} “title” is session ch-…`. */\n noun: string\n /** The row's glyph (16×16 viewBox, single path). */\n iconPath: string\n}\n\nexport const DOOR_ACTIONS: readonly DoorAction[] = [\n {\n id: 'chapters-fork',\n order: 30,\n label: 'Fork with chapters',\n command: '/chapters-fork',\n noun: 'branch',\n iconPath: 'M5 3.25a2.25 2.25 0 1 0-1.5 2.12v5.26a2.25 2.25 0 1 0 1.5 0V9h5a2.25 2.25 0 0 0 2.25-2.25v-1.4a2.25 2.25 0 1 0-1.5 0v1.4A.75.75 0 0 1 10 7.5H5V5.37A2.25 2.25 0 0 0 5 3.25Z',\n },\n {\n id: 'chapters-continue',\n order: 31,\n label: 'Continue with chapters',\n command: '/chapters-continue',\n noun: 'continuation',\n iconPath: 'M1 8a.75.75 0 0 1 0-1.5h9.44L7.72 3.28a.75.75 0 1 1 1.06-1.06l3.5 3.5.53.53-.53.53-3.5 3.5a.75.75 0 1 1-1.06-1.06L10.44 9.5H1Zm13-5.25v10.5a.75.75 0 0 1-1.5 0v-10.5a.75.75 0 0 1 1.5 0Z',\n },\n]\n\n/** Pull the child id out of either door's durable success text — both end in\n * `… is session ch-…` (src/tools.ts), so this regex is door-agnostic. */\n/**\n * Which build of THIS file produced a door note (#77). When the live probe reported a roster\n * trace of `missing → missing → missing` — meaning adoption should have fired three times — while\n * showing no adoption outcome at all, the first question stopped being \"why did the client refuse\"\n * and became \"did the browser run the code I am reading?\". A note that names its own build makes\n * that a glance instead of an archaeology session into symlinked plugin trees.\n */\nexport const DOOR_BUILD = '2026-10-22T23:55 door-build-stamp'\n\nexport const CHILD_ID = /is session (ch-[\\w-]+)/\n\n/** The quoted title the door gave the child, matched by its OWN noun so a\n * refusal or an unrelated quote cannot pass for it. */\nexport const doorTitle = (noun: string): RegExp => new RegExp(`${noun} \\\\u201C([^\\\\u201D]+)\\\\u201D is session`)\n\n/**\n * A refusal reason that can never render as nothing. `String(err.message ?? err)` is `''` for a\n * host error built with an empty message, and an empty reason disappears from the note — the\n * reader then concludes \"no refusal happened\", which is the opposite of the truth (#77).\n */\nfunction refusalReason(error: unknown): string {\n const raw = error instanceof Error ? error.message : String(error)\n const trimmed = raw.trim()\n const named = trimmed === '' ? (error instanceof Error && error.name.trim() !== '' ? error.name : '') : trimmed\n const base = named === '' ? (error === null ? 'null thrown' : typeof error) : named\n return base.replace(/\\s+/g, ' ').slice(0, 90)\n}\n\n/**\n * Is the child in the client's OWN roster? This is the question that separates two very\n * different failures, and the door used to report the symptom without it:\n * - the client never learned the session exists (roster: missing) → the fix is server-side\n * publication, and no amount of clicking `open` will ever work;\n * - the client knows it and `open` still did nothing (roster: contains) → a navigation bug.\n * Measured host 0.2.0-rc.2: the sessions service exposes `projectList`, `refresh`, `retain`,\n * `handleSessionAdded` — the roster is a projection the client fills from control frames.\n */\n/**\n * The client's own session ids, or `null` when the store cannot be read. ONE reader for both\n * the note and the adoption decision, so the door never acts on a verdict it did not report.\n */\nfunction rosterIds(sessions: ClientSessions | undefined): Set<string> | null {\n const store = (sessions as unknown as { list?: { getSnapshot?: () => unknown } })?.list\n if (typeof store?.getSnapshot !== 'function') return null\n const snap = store.getSnapshot()\n const ids = new Set<string>()\n if (Array.isArray((snap as { ids?: unknown[] } | undefined)?.ids)) {\n for (const id of (snap as { ids: unknown[] }).ids) ids.add(String(id))\n } else if ((snap as { byId?: object } | undefined)?.byId !== undefined) {\n for (const k of Object.keys((snap as { byId: object }).byId)) ids.add(k)\n } else return null\n return ids\n}\n\nfunction rosterNote(sessions: ClientSessions | undefined, child: string): string {\n try {\n // The host's client store shape is `list.getSnapshot() → { ids, byId, phase }`.\n // `projectList()` turned out to be a MUTATOR that returns undefined (measured), so the first\n // version of this note printed \"0 sessions\" about a conversation that visibly had one — the\n // repo's own `listEvents` lesson (\"measure the SHAPE, not the presence\"), repeated by me in\n // the same week. Read the store; when it isn't reachable, presence is UNKNOWN and the note\n // says that rather than inventing an absence.\n const ids = rosterIds(sessions)\n if (ids === null) {\n const store = (sessions as unknown as { list?: { getSnapshot?: () => unknown } })?.list\n const shape = typeof store?.getSnapshot !== 'function' ? 'no list store on the service' : 'snapshot unreadable'\n return ` (roster: ${shape} — presence of ${child.slice(0, 11)}… is UNKNOWN, not absent)`\n }\n if (ids.size === 0) return ` (roster: empty — presence of ${child.slice(0, 11)}… is UNKNOWN, not absent)`\n const n = ids.size\n return ids.has(child)\n ? ` (roster: contains ${child.slice(0, 11)}…, ${String(n)} sessions — open() is the failure)`\n : ` (roster: MISSING, ${String(n)} sessions — the client never catalogued this session)`\n } catch { return '' }\n}\n\n/** The service's own method names, as a hint attached to a failure note (never a guess). */\nfunction verbsNote(sessions: ClientSessions | undefined): string {\n try {\n const svc = sessions as unknown as Record<string, unknown> | undefined\n if (svc === null || svc === undefined || typeof svc !== 'object') return ' (sessions service: absent)'\n // Measured: `Object.keys(svc)` returned NOTHING for the real service, because the host\n // puts its verbs on the PROTOTYPE (a class instance), not as own keys. A diagnostic that\n // reports \"no methods\" about a working service is worse than none — walk the chain.\n const found = new Set<string>()\n let node: unknown = svc\n // Stop at Object.prototype: its four legacy accessors are not service verbs, and listing\n // them pushed the real names past the cap on the first live run.\n for (let depth = 0; depth < 6 && node !== null && node !== undefined && node !== Object.prototype; depth++) {\n for (const key of Object.getOwnPropertyNames(node as object)) {\n if (key === 'constructor') continue\n try { if (typeof (svc as Record<string, unknown>)[key] === 'function') found.add(key) } catch { /* getter that throws — not a verb we can use either */ }\n }\n node = Object.getPrototypeOf(node)\n }\n const verbs = [...found].sort().slice(0, 24)\n return verbs.length === 0 ? ' (sessions service: no methods on the chain)' : ` (sessions verbs: ${verbs.join(', ')})`\n } catch { return ' (sessions service: unreadable)' }\n}\n\nfunction openByService(\n sessions: ClientSessions | undefined,\n sessionId: string,\n /** #77: adopt (and re-project) only when the client's own roster is missing this session.\n * `onAfter` reports the roster at the instant after adoption, before any later refresh can\n * discard the mutation — the difference between an ordering bug and a publication gap. */\n adopt?: { needed: boolean, onRefused: (reason: string) => void, onSubscribeRefused?: (reason: string) => void, onAfter: (seen: boolean | null) => void, onStep?: (step: string) => void },\n nav?: { openSession?: (id: string) => unknown },\n): Promise<boolean> {\n return (async () => {\n const svc = sessions as unknown as {\n open?: (id: string) => unknown; refresh?: () => unknown; projectList?: () => unknown\n create?: (opts: { sessionId?: string }) => unknown\n using?: (id: string, opts: { source: string }, fn: () => Promise<void>) => unknown\n } | undefined\n // #77, the actual root cause: `sessions.open` DOES NOT EXIST on this host. The sessions\n // service offers create/fork/using/retain/binding/refresh; the host's own fork button calls\n // `ctx.uiWorkspace.openSession(childId)` (`dsh-client-ui-chat/lib/client.js`). The line here\n // used to be `if (typeof svc?.open !== 'function') return false`, so every service attempt\n // since the door was written returned false on line one and the note blamed the client's\n // roster for a verb that was never there.\n if (svc === undefined) return false\n const navigate = typeof nav?.openSession === 'function'\n ? (id: string): unknown => nav.openSession!(id)\n : typeof svc?.open === 'function'\n ? (id: string): unknown => svc.open!(id)\n : null\n if (navigate === null) {\n adopt?.onStep?.('no-verb')\n return false\n }\n try { await svc.refresh?.() } catch { /* refresh best-effort */ }\n // #57: re-project the client's session catalog. The host does this ITSELF after every\n // create and fork — `dsh-api-session-controller/lib/client.js`, `create()` and `fork()`\n // both end in `this.projectList()` — because a session the client has never catalogued is\n // not addressable by `open()` no matter how often you call it. A plugin-created child is\n // exactly that case: durable, workspace-attached, absent from the client's roster. Measured\n // on 0.2.0-rc.2: without this the door created the child, `open` resolved, and the app\n // stayed on the parent (the door's own note now says `roster: …` so this is never a guess).\n try { svc.projectList?.() } catch { /* best-effort, like refresh */ }\n // #77: adopt-and-catalogue. `manager.create()` is documented \"on success merge into\n // summaries immediately (no wait for the next refresh)\" and forwards a caller-owned\n // `sessionId`, while the server ADOPTS a persisted id instead of authoring a second session\n // (`create(request)` → `ensureSession(sessionId, cwd, request.sessionId !== undefined, …)`).\n // Nothing about the child's content is authored here — the seed notice was committed\n // host-plane long before this call — this only lets the client see what the host already\n // knows. A refusal is reported to the caller, never swallowed.\n if (adopt?.needed === true && typeof svc.create === 'function') {\n // the block narrates itself: entry first, then whichever way it left. The live note once\n // said \"try\" from the runner and showed nothing downstream — with this line, an absent\n // `enter-create` IS the answer instead of a mystery.\n adopt.onStep?.('enter-create')\n try {\n await svc.create({ sessionId })\n adopt.onStep?.('settle-create')\n try { svc.projectList?.() } catch { /* re-project again, now that a mutation exists */ }\n // the momentary read is the whole point: a later `refreshList()` reassigns the manager's\n // mutations from the server result, so an adoption that DID land is invisible by the time\n // the last attempt fails. Report what was true at the instant it mattered.\n const after = rosterIds(sessions)\n adopt.onAfter?.(after === null ? null : after.has(sessionId))\n } catch (error) {\n adopt.onStep?.('refuse-create')\n adopt.onRefused(refusalReason(error))\n }\n }\n // #77, second candidate: if adoption left the child out of the roster, SUBSCRIBE to it.\n // `using(target, {source}, fn)` is the host's own way to address a session that is not\n // currently displayed (`sessions/service.js`: retain → `manager.get(id).open()`). A refusal is\n // reported exactly like adoption's. No URL route exists as an alternative — checked every\n // dsh-client-* bundle: the only `/session/` strings are resource addresses for files.\n if (adopt?.needed === true && typeof svc.using === 'function' && rosterIds(sessions)?.has(sessionId) === false) {\n adopt.onStep?.('enter-using')\n try {\n await svc.using(sessionId, { source: 'dsh-chapters-door' }, async () => undefined)\n adopt.onStep?.('settle-using')\n try { svc.projectList?.() } catch { /* best-effort, like the others */ }\n const seen = rosterIds(sessions)\n adopt.onAfter?.(seen === null ? null : seen.has(sessionId))\n } catch (error) {\n adopt.onStep?.('refuse-using')\n adopt.onSubscribeRefused?.(refusalReason(error))\n }\n }\n // The store must know the session before open() resolves it; a child\n // created seconds ago is only server-known until refresh lands. Retry\n // open briefly across that window.\n const started = Date.now()\n for (;;) {\n try { navigate(sessionId); return true } catch {\n if (Date.now() - started > 4_000) return false\n await new Promise((r) => setTimeout(r, 250))\n }\n }\n })()\n}\n\n/**\n * Fallback path: expand collapsed groups / show-more buttons, then click the\n * newest treeitem whose label contains the child title (newest-updated sorts\n * first, so the first match is the fresh child).\n */\nasync function openBranchRow(title: string, budgetMs = 4_000): Promise<boolean> {\n if (typeof document === 'undefined') return false\n const needle = title.toLowerCase()\n const tryClick = (): boolean => {\n const rows = Array.from(document.querySelectorAll<HTMLElement>('[role=\"treeitem\"]'))\n const hit = rows.find((row) => (row.textContent ?? '').toLowerCase().includes(needle))\n if (hit === undefined) return false\n hit.click()\n return true\n }\n const clickMore = (): number => {\n const buttons = Array.from(document.querySelectorAll<HTMLElement>('button'))\n .filter((b) => /more sessions|ungrouped/i.test(b.textContent ?? '') && b.getAttribute('aria-expanded') !== 'true')\n for (const b of buttons) b.click()\n return buttons.length\n }\n const started = Date.now()\n let expansions = 0\n while (Date.now() - started < budgetMs) {\n if (tryClick()) return true\n if (expansions < 4 && clickMore() > 0) { expansions += 1; await new Promise((r) => setTimeout(r, 250)); continue }\n await new Promise((resolve) => setTimeout(resolve, 150))\n }\n return tryClick()\n}\n\nexport function makeDoorRunner(\n remote: ClientRemote,\n sessions: ClientSessions | undefined,\n door: DoorAction,\n /**\n * \"Is THIS session the one the client is now showing?\" #54: `sessions.open()` returns\n * void, so a call that resolves proves the request was accepted, not that the app\n * switched. Measured on host 0.2.0-rc.2: no note element (open resolved), yet\n * `dsh.sessions.current` still named the parent and the child was absent from the\n * sidebar after a full reload — the user got silence and a missing session. Absent\n * (a client that cannot answer) the runner keeps the old behavior, and the wiring is\n * pinned by a row so production can never quietly drop it.\n */\n verifySwitched?: (childSessionId: string) => Promise<boolean>,\n /**\n * #77: how many times to re-pull the client's list, re-project and re-open. The session\n * manager documents `refreshList()` as \"single-flight within one Host generation\"\n * (`dsh-api-session-controller/lib/types/client/sessions/manager.js`), so a pull issued while\n * another is in flight can resolve from the STALE result — precisely the moment a session\n * created microseconds ago is missing. Retrying is both the fix candidate and the measurement:\n * which attempt lands tells us whether this is ordering (ours) or silence (host-side).\n * A client with no `refresh` verb gets exactly ONE attempt — re-asking `open` against a roster\n * we cannot re-pull would be theater.\n */\n opts?: { attempts?: number, attemptPauseMs?: number, navigator?: { openSession?: (id: string) => unknown } },\n): (sessionId: string) => Promise<DoorResult> {\n return async function run(sessionId: string): Promise<DoorResult> {\n try {\n if (remote.commands?.execute === undefined) return { ok: false, message: 'commands remote unavailable' }\n const res = await remote.commands.execute(sessionId, door.command, [])\n if (res.ok !== true) return { ok: false, message: (res.error?.message ?? `the ${door.noun} call failed`).slice(0, 90) }\n const result = res.value?.result\n const text = typeof result?.text === 'string' ? result.text : ''\n if (result?.kind !== 'success') return { ok: false, message: (text || `the ${door.noun} was refused — see the command result`).slice(0, 90) }\n const child = CHILD_ID.exec(text)?.[1]\n if (child === undefined) return { ok: true, message: `${door.noun} created — appearing in the sidebar` }\n const switchedOnce = async (): Promise<boolean> => {\n if (verifySwitched === undefined) return true\n try { return await verifySwitched(child) } catch { return false }\n }\n const canRepull = typeof (sessions as unknown as { refresh?: unknown } | undefined)?.refresh === 'function'\n const maxAttempts = canRepull ? (opts?.attempts ?? 3) : 1\n let tried = 0\n let adoptRefusal = ''\n let subscribeRefusal = ''\n // #77: what the roster reader answered AT EACH DECISION, not only at the end. The live\n // probe reported MISSING after three attempts while neither candidate fired, which can\n // only mean the reader disagreed with itself over time — and a note that says one number\n // cannot show that.\n const rosterTrace: string[] = []\n const steps: string[] = []\n let adoptSaw: boolean | null | undefined\n for (let i = 0; i < maxAttempts; i++) {\n tried += 1\n // decide adoption from the SAME reader the note reports, so the door never acts on a\n // verdict it did not tell the user about; an unreadable roster is not \"missing\"\n const ids = rosterIds(sessions)\n // Loaded-but-empty is a DECISION (the child is not there → try the sanctioned paths);\n // unreadable is UNKNOWN and must not invent a session (#77: the strict form here meant\n // neither candidate ever ran in the live probe, and its silence looked like a result).\n const needed = ids !== null && !ids.has(child)\n // `+c` / `+u` = the candidate verbs are present on the injected service AT THAT MOMENT.\n // Without this, a note that says the roster was missing while no adoption outcome appears\n // is uninterpretable: either the code never ran, or the service has no create to call.\n const svcShape = sessions as unknown as { create?: unknown, using?: unknown, open?: unknown } | undefined\n // +c create, +u using, +o sessions.open, +w workspace navigator — the exact predicates\n // the door branches on, so a note can be read instead of re-derived from source\n rosterTrace.push(`${ids === null ? 'unknown' : ids.size === 0 ? 'empty' : ids.has(child) ? 'has' : 'missing'}${typeof svcShape?.create === 'function' ? '+c' : '-c'}${typeof svcShape?.using === 'function' ? '+u' : '-u'}${typeof svcShape?.open === 'function' ? '+o' : '-o'}${typeof opts?.navigator?.openSession === 'function' ? '+w' : '-w'}`)\n if (await openByService(sessions, child, {\n needed,\n onRefused: (reason) => { adoptRefusal = reason },\n onSubscribeRefused: (reason) => { subscribeRefusal = reason },\n onAfter: (seen) => { adoptSaw = seen },\n onStep: (step) => { if (!steps.includes(step)) steps.push(step) },\n }, opts?.navigator) && await switchedOnce()) return { ok: true, message: '' }\n if (i + 1 < maxAttempts) await new Promise((r) => { setTimeout(r, opts?.attemptPauseMs ?? 300).unref?.() })\n }\n // the service path did not take — the sidebar row is the same affordance a human uses\n const title = doorTitle(door.noun).exec(text)?.[1]\n if (title !== undefined && await openBranchRow(title) && await switchedOnce()) return { ok: true, message: '' }\n // #54: a child that is created but not shown is a DIFFERENT fact from one the user\n // has to open by hand, and the message must not blur them.\n // #56/#57: the note must be ACTIONABLE. \"the app did not switch\" is true and useless;\n // naming the verbs the injected service actually exposes turns one failure into the\n // diagnosis (is there a refresh/reload to call, or is `open` the only verb and the\n // omission server-side?). A detected failure that isn't delivered is the same bug.\n return verifySwitched === undefined\n ? { ok: true, message: `${door.noun} created — open it in the sidebar` }\n : { ok: true, message: `${door.noun} created — the app did not switch after ${String(tried)} attempt${tried === 1 ? '' : 's'}; open it from the sidebar [roster per attempt: ${rosterTrace.join(' → ')}] [tried: ${steps.length === 0 ? 'none' : steps.includes('no-verb') ? 'no open or openSession verb on any injected service' : steps.join(', ')}] (build: ${DOOR_BUILD})${adoptRefusal === '' ? '' : ` (adoption refused: ${adoptRefusal})`}${subscribeRefusal === '' ? '' : ` (subscribe refused: ${subscribeRefusal})`}${adoptSaw === undefined ? '' : ` (right after adopt: ${adoptSaw ? 'contains' : 'missing'} ${child.slice(0, 15)})`}${rosterNote(sessions, child)}${verbsNote(sessions)}` }\n } catch (error) {\n return { ok: false, message: String((error as Error)?.message ?? error).slice(0, 90) }\n }\n }\n}\n\n/**\n * Execute a host command line for one session and return its text, or '' when the command plane is\n * unavailable. The bar's `state` pull and `off` write ride this, because it is the ONE place the client\n * talks to the wire: the guard row \"there is no second runner body in the client chrome\" exists to keep\n * `index.ts` as registration chrome, and a duplicated `commands.execute` in two files is how a wire\n * behaviour drifts from its own test (#59's one-predicate rule, applied to the transport).\n */\nexport async function runCommandText(remote: ClientRemote | undefined, sessionId: string, line: string): Promise<string> {\n const exec = remote?.commands?.execute\n if (exec === undefined) return ''\n const res = await exec(sessionId, line, [])\n if (res.ok !== true) return ''\n return res.value?.result?.text ?? ''\n}\n","/**\n * dsh-chapters client entry: the per-message door buttons.\n *\n * Registers the two deterministic archive doors into the assistant action row\n * (`conversation.chat.assistant-actions` — the slot the official feedback\n * package populates beside copy/branch). Clicking one executes its host\n * command — `/chapters-fork`, `/chapters-continue` — through `remote.commands`,\n * the exact wire the composer's input line rides, then SWITCHES the app to the\n * fresh child:\n *\n * 1. `sessions.open(child)` — the client sessions service carries open() on\n * its prototype (Playwright-verified against this host; a keys-dump alone\n * hid it, which is why an earlier round wrongly gave up on this path);\n * 2. fallback: click the child's sidebar row after expanding collapsed\n * groups (plugin children can render under collapsed 'Ungrouped' heads);\n * 3. last resort: an inline note — the child is durable either way.\n *\n * There is deliberately NO second implementation of door logic here: the\n * buttons are mouths for the host commands (watermark, segmentation, budget,\n * titles, refusal-with-numbers all server-side, all tested), and the runner\n * they share is `src/client/doors.ts` — one body, one row per door. The purity\n * gate in tsdown.client.config.ts keeps this file cross-plugin-free.\n *\n * @module dsh-chapters/src/client\n */\n\nimport { createElement as h, useCallback, useEffect, useRef, useState, type ReactElement } from 'react'\n// The host's own tooltip bubble — a platform module served by the loader table,\n// external like react. Every action in this row labels through it.\nimport { Tooltip } from '@deepseek-ai/dsh-client-ui-primitives'\nimport { PromptDock } from './prompt-dock.ts'\nimport { DOOR_ACTIONS, makeDoorRunner, runCommandText, type ClientRemote, type ClientSessions, type DoorAction, type DoorResult } from './doors.ts'\n\ninterface ClientSlots {\n inject(name: string, factory: () => unknown): void\n register(registration: Record<string, unknown>, component: unknown): unknown\n}\n\ninterface Ctx {\n slots: ClientSlots\n get(name: string): unknown\n}\n\n// #77: `uiWorkspace` is not optional in practice — it owns `openSession(target)`, the verb the\n// host's own fork button uses (`ctx.uiWorkspace.openSession(childId)` in dsh-client-ui-chat).\n// `sessions` has no `open` at all, so without this row the door has never had a way to switch.\nexport const inject = ['slots', 'remote', 'remote.commands', 'sessions', 'uiWorkspace'] as const\n\n/** Props composed by the slot framework: owner (`messageId`) + our injected verb. */\ninterface DoorActionProps {\n messageId: string\n run: () => Promise<DoorResult>\n}\n\n/**\n * Style parity with the native IconActions row: rules copied verbatim from the\n * host's MessageIconActions module (`._xzv4MW_action` in dsh-client-ui-chat's\n * compiled client + the feedback package's :disabled) — same metrics, tokens,\n * hover. Update this string if the host restyles its action row. Both doors\n * share these metrics.\n *\n * The final rule hides the native branch action IN THE MESSAGE ROW only\n * (user-approved cosmetic decision, docs/architecture.md): built-in chrome, so\n * the selector is its accessible label scoped to action-row buttons; both\n * locale spellings covered. If the host rewords the label the button simply\n * reappears — fails open, visibly.\n */\nconst CSS = [\n '.dsh-chapters_forkAction{width:calc(28px + var(--dsh-content-font-delta,0px));height:calc(28px + var(--dsh-content-font-delta,0px));color:var(--dsw-alias-label-tertiary);cursor:pointer;background:0 0;border:none;border-radius:28px;justify-content:center;align-items:center;padding:6px;display:inline-flex}',\n '.dsh-chapters_forkAction svg{width:calc(15px + var(--dsh-content-font-delta,0px));height:calc(15px + var(--dsh-content-font-delta,0px))}',\n '.dsh-chapters_forkAction:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary)}',\n '.dsh-chapters_forkAction:disabled{cursor:default;opacity:.4}',\n '.dsh-chapters_forkNote{color:var(--dsw-alias-label-tertiary);padding-left:4px;font-size:13px;line-height:20px}',\n 'button[class*=\"_action\"][aria-label=\"Branch into a new conversation\"],button[class*=\"_action\"][aria-label=\"\\u5728\\u65b0\\u5bf9\\u8bdd\\u4e2d\\u5206\\u652f\"]{display:none !important}',\n].join('\\n')\nconst CSS_TAG_ID = 'dsh-chapters/assistant-action.css'\n\nfunction injectStyles(): void {\n try {\n if (typeof document === 'undefined' || document.querySelector(`style[data-plugin-css=${JSON.stringify(CSS_TAG_ID)}]`) !== null) return\n const tag = document.createElement('style')\n tag.dataset.plugin = 'dsh-chapters'\n tag.dataset.pluginCss = CSS_TAG_ID\n tag.textContent = CSS\n document.head.appendChild(tag)\n } catch { /* styling is cosmetic; the action still works without it */ }\n}\n\n/** One component body for every door row; only the label and glyph differ. */\nfunction makeDoorAction(door: DoorAction) {\n return function ChaptersDoorAction({ run }: DoorActionProps) {\n const [busy, setBusy] = useState(false)\n const [note, setNote] = useState<string | null>(null)\n const alive = useRef(true)\n useEffect(() => () => { alive.current = false }, [])\n const onClick = useCallback(() => {\n if (busy) return\n setBusy(true)\n setNote(null)\n void run().then((result) => {\n if (!alive.current) return\n setBusy(false)\n setNote(result.message === '' ? null : result.message)\n setTimeout(() => { if (alive.current) setNote(null) }, 8000)\n })\n }, [busy, run])\n const button = h('button', {\n type: 'button',\n 'aria-label': door.label,\n disabled: busy,\n onClick,\n className: 'dsh-chapters_forkAction',\n },\n h('svg', { viewBox: '0 0 16 16', width: '15', height: '15', fill: 'currentColor', 'aria-hidden': 'true' },\n h('path', { d: door.iconPath })))\n return h('span', { style: { display: 'inline-flex', alignItems: 'center', gap: '4px' } },\n h(Tooltip, { label: door.label, side: 'bottom' as const, children: button as ReactElement<Record<string, unknown>> }),\n note !== null\n ? h('span', { className: 'dsh-chapters_forkNote' }, note)\n : null)\n }\n}\n\nexport function apply(ctx: Ctx): void {\n injectStyles()\n const slots = ctx.slots\n const remote = (ctx.get('remote') ?? {}) as ClientRemote\n let sessions: ClientSessions | undefined\n try { sessions = ctx.get('sessions') as ClientSessions | undefined } catch { sessions = undefined }\n // Defensive: a host that cannot provide the navigator must leave the door registering and\n // reporting its own limitation, not failing at boot.\n let uiWorkspace: { openSession?: (id: string) => unknown } | undefined\n try { uiWorkspace = ctx.get('uiWorkspace') as { openSession?: (id: string) => unknown } | undefined } catch { uiWorkspace = undefined }\n // No early return on missing descriptors: the buttons register regardless\n // and the click reports the gap. A missing button is worse than a button\n // that explains itself. Components and runners are built ONCE here — slot\n // factories may re-run, and a fresh component identity per call would\n // remount the row.\n // #54: the switch verifier. `dsh.sessions.current` is published by the host's own\n // dsh-client-ui-workspace (grep-confirmed, not folklore) — the same key the e2e specs\n // read to name a session exactly, which is the point: the door now checks the fact the\n // user would notice, not whether a void call threw.\n const currentSessionId = (): string | null => {\n try {\n const raw = window.localStorage.getItem('dsh.sessions.current')\n if (raw === null) return null\n const parsed = JSON.parse(raw) as { sessionId?: string } | null\n return parsed?.sessionId ?? null\n } catch { return null }\n }\n const rows = DOOR_ACTIONS.map((door) => ({\n door,\n run: makeDoorRunner(remote, sessions, door, async (child) => {\n for (let i = 0; i < 8; i++) {\n if (currentSessionId() === child) return true\n await new Promise((r) => setTimeout(r, 250))\n }\n return currentSessionId() === child\n }, { ...(uiWorkspace !== undefined ? { navigator: uiWorkspace } : {} ) }),\n component: makeDoorAction(door),\n }))\n slots.inject('conversation.chat.assistant-actions', () => rows.map(({ door, run, component }) => slots.register({\n name: 'conversation.chat.assistant-actions',\n id: door.id,\n order: door.order,\n inject: (sessionId: string) => ({ run: () => run(sessionId) }),\n }, component)))\n slots.inject('conversation.input.dock', () => slots.register({\n name: 'conversation.input.dock',\n id: 'chapters-prompt',\n // Under every other dock entry and directly above the composer card: todo 0, goal 10, queue 20, and the\n // host sorts ascending by priority then order (measured in dsh-client-ui-slots).\n order: 30,\n inject: (sessionId: string) => ({\n // The bar is a mouth for the command plane, and the ONE wire body in `doors.ts` carries it: a plugin has\n // no declarable RPC namespace, so this pull transport is why the bar is push-shaped and pull-fed.\n exec: (line: string) => runCommandText(remote, sessionId, line),\n }),\n }, PromptDock))\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"client.js","names":["CSS","h","Tooltip","useReducer","useRef","useState","useCallback","useState","useRef","useCallback","h","Tooltip"],"sources":["../src/client/prompt-bar.ts","../src/client/prompt-dock.ts","../src/client/doors.ts","../src/client/index.ts"],"sourcesContent":["/**\n * The Suggested Next Step bar — every decision the row makes, kept out of React (#113).\n *\n * Deliberately shaped like the goal bar, because that implementation is tested, already fits, and gets the\n * split right: the **durable** facts (`status`, `text`, `turnSeq` — the server's one-generation-per-turn\n * identity) versus the **process-local** intent (`autorun`, `expanded` — the goal's `activation` in\n * everything but name). Nothing transient is persisted, and nothing persisted is guessed here.\n *\n * The failures the maintainer rejected are all decision bugs, not rendering bugs: firing while a turn is\n * running, shouting a refusal that should have been silence, an autorun that outlives the purpose that\n * justified it. Each has a row in `tests/unit/prompt-bar.test.ts`.\n */\n\nexport type PromptBarStatus = 'off' | 'idle' | 'analyzing' | 'ready' | 'failed'\nexport type PromptBarAutorun = 'armed' | 'disarmed'\n/** Mirrors the host's `GoalPhase` vocabulary (`dsh-goal` types) so goal composition needs no translation. */\nexport type GoalPhaseView = 'none' | 'active' | 'paused' | 'blocked' | 'complete'\n\nexport interface PromptBarState {\n status: PromptBarStatus\n /** The offer, verbatim. Never truncated — an over-cap answer is refused upstream. */\n text: string | null\n /** #113: what kind of step the server classified this as. `goal-edit` is held for the human's keystroke,\n * because goal edits require a direct human turn in the host and an autorun message is not one. */\n intent: 'worker-step' | 'goal-edit'\n /** The `turn/end` seq this offer belongs to; its identity, not a timestamp. */\n turnSeq: number | null\n /** Set only with `status: 'failed'`; rendered as `role=\"alert\"` (#61: delivered, not swallowed). */\n error: string | null\n /** Why an action did not happen, on the bar's face (#113). Empty when there is nothing to explain. */\n note: string\n autorun: PromptBarAutorun\n expanded: boolean\n running: boolean\n goalPhase: GoalPhaseView\n /** The offer a human dismissed or interrupted. A NEW `turnSeq` clears it; this one stays hidden. */\n hiddenTurnSeq: number | null\n /** Live goal facts, when the caller can read them (the goal driver owns the next turn when active+armed). */\n goal: { phase: string; activation: string; revision: number } | null\n}\n\nexport type PromptBarEvent =\n | { t: 'polled', status: PromptBarStatus, text?: string | null, turnSeq?: number | null, error?: string | null, intent?: 'worker-step' | 'goal-edit' }\n | { t: 'expand' } | { t: 'collapse' }\n /** Why an action did not happen, on the bar's own face (#113). Cleared when a new offer lands. */\n | { t: 'note'; note: string }\n | { t: 'autorun', on: boolean }\n | { t: 'human/prompt' }\n | { t: 'off' }\n | { t: 'goal', phase: GoalPhaseView }\n | { t: 'running', running: boolean }\n\nexport function initialBarState(autorun: PromptBarAutorun = 'disarmed'): PromptBarState {\n return { status: 'off', text: null, turnSeq: null, error: null, note: '', intent: 'worker-step', goalPhase: 'none', goal: null, autorun, expanded: false, running: false, hiddenTurnSeq: null }\n}\n\nexport function promptBarReducer(state: PromptBarState, ev: PromptBarEvent): PromptBarState {\n if (ev.t === 'note') return { ...state, note: ev.note }\n const next = baseBarReducer(state, ev)\n // A note is about the gesture that just failed, so a NEW offer retires it. A poll that repeats the same\n // offer (the bar re-reads every few seconds) does not — otherwise the explanation would blink out before\n // the human could read it.\n return ev.t === 'polled' && (next.turnSeq !== state.turnSeq || next.text !== state.text) ? { ...next, note: '' } : next\n}\n\nfunction baseBarReducer(state: PromptBarState, ev: PromptBarEvent): PromptBarState {\n switch (ev.t) {\n case 'polled': {\n if (ev.status === 'off') return { ...state, status: 'off', text: null, turnSeq: null, error: null, expanded: false, autorun: 'disarmed' }\n // A fresh generation invalidates the previous offer: acting on a stale one is how a bar lies.\n if (ev.status === 'analyzing') return { ...state, status: 'analyzing', text: null, error: null }\n return {\n ...state,\n status: ev.status,\n text: ev.status === 'ready' ? (ev.text ?? null) : null,\n intent: ev.status === 'ready' ? (ev.intent ?? 'worker-step') : 'worker-step',\n turnSeq: ev.turnSeq ?? state.turnSeq,\n error: ev.status === 'failed' ? (ev.error ?? 'the suggestion call failed') : null,\n }\n }\n case 'expand': return { ...state, expanded: true }\n // Collapsing is a reading action. It keeps the offer — only a human prompt or `Off` dismisses it.\n case 'collapse': return { ...state, expanded: false }\n case 'autorun': return { ...state, autorun: ev.on ? 'armed' : 'disarmed' }\n case 'running': return { ...state, running: ev.running }\n case 'human/prompt':\n // The human took the wheel: yield the loop, and drop the offer that was on screen. Generation itself\n // continues on the NEXT turn boundary — suspension is not disable.\n return { ...state, autorun: 'disarmed', hiddenTurnSeq: state.turnSeq, expanded: false }\n case 'off': return { ...state, status: 'off', text: null, turnSeq: null, error: null, expanded: false, autorun: 'disarmed' }\n case 'goal':\n // Per spec: autorun runs ALONGSIDE an active goal and disarms itself when the goal completes — the\n // suggestion loop exists to serve the session's purpose, and stops when that purpose stops.\n // #188: blocked joins complete — both planes stop together when the goal reaches a terminal\n // phase; a loop still firing offers at a blocked goal is the autorun that outlived its purpose.\n return { ...state, goalPhase: ev.phase, autorun: ev.phase === 'complete' || ev.phase === 'blocked' ? 'disarmed' : state.autorun }\n }\n // Exhaustive fallback: a new event without a case must return the state unchanged rather than\n // compile into `undefined` and blank the bar.\n return state\n}\n\n/** The status label. `failed` keeps the ready label so the error span is the delta, not a mystery row. */\nexport function barLabel(s: PromptBarState): string | null {\n if (s.status === 'analyzing') return 'Analyzing situation…'\n if (s.status === 'ready' || s.status === 'failed') return 'Recommended Next Step'\n return null\n}\n\nexport function barVisible(s: PromptBarState): boolean {\n // Only `off` hides the row. `idle` is enabled-and-waiting, and showing it is the difference between\n // \"the feature is live, nothing to say yet\" and silence that reads as disabled (#113).\n if (s.status === 'off') return false\n if (s.status === 'ready' && (s.text === null || s.text === '')) return false\n // The goal bar's `clearedGoalId` guard, in our terms: don't flicker back the offer just dismissed.\n if (s.hiddenTurnSeq !== null && s.hiddenTurnSeq === s.turnSeq) return false\n return true\n}\n\n/** May the bar submit its own offer? Armed, present, idle, not dismissed, and still serving a purpose. */\nexport function shouldAutoFire(s: PromptBarState): boolean {\n if (s.status !== 'ready') return false\n if (s.text === null || s.text === '') return false\n // #113: two host-imposed stops before anything else. A goal re-cut needs a human turn (dsh-tool-goal's\n // requireDirectHuman), and an armed goal driver already intends to start this turn — firing here would be\n // two turn-starters at one boundary, racing the driver's own reservation fence.\n if (s.intent === 'goal-edit') return false\n if (s.goal !== null && s.goal.phase === 'active' && s.goal.activation === 'armed') return false\n if (s.autorun !== 'armed') return false\n if (s.running) return false\n if (s.goalPhase === 'complete' || s.goalPhase === 'blocked') return false\n if (s.hiddenTurnSeq !== null && s.hiddenTurnSeq === s.turnSeq) return false\n return true\n}\n\n/** The command the bar pulls. One string, so the client and the command plane cannot drift silently. */\nexport const BAR_STATE_COMMAND = '/chapters-prompt state'\n/** The bar's Run button: the server submits the on-screen offer as the human's own turn. */\nexport const BAR_RUN_COMMAND = '/chapters-prompt run'\n\n/**\n * Parse the `state` verb's `k: v` lines into a pull event.\n *\n * One output serving the human and the component is the design (see `docs/contract.md`); this function is\n * the cost of that choice, and it is kept honest: an unrecognised line is IGNORED, not guessed at, and a\n * payload that yields no `status` is `null` so the caller keeps its last state rather than inventing `off`\n * (which would hide a bar that is merely mid-reply). Indentation and the prompt's own lines are the reason\n * the prompt is collected separately from the scalar fields — it is the only multi-line value.\n */\nexport function parseBarState(text: string): PromptBarEvent | null {\n const lines = text.split('\\n')\n const head = lines[0] ?? ''\n const statusWord = head.startsWith('suggested next step:') ? head.slice('suggested next step:'.length).trim() : ''\n const status = statusWord === 'off' ? 'off'\n : statusWord === 'idle' ? 'idle'\n : statusWord === 'analyzing' ? 'analyzing'\n : statusWord === 'ready' ? 'ready'\n : statusWord === 'failed' ? 'failed'\n : null\n if (status === null) return null\n const scalars: Record<string, string> = {}\n const prompt: string[] = []\n let inPrompt = false\n for (const line of lines.slice(1)) {\n if (inPrompt) { prompt.push(line.replace(/^ {4}/, '')); continue }\n const m = /^ {2}([a-z]+): ?(.*)$/.exec(line)\n if (m === null) continue\n const key = m[1]!, value = m[2]!.trim()\n if (key === 'prompt') { inPrompt = true; continue }\n scalars[key] !== undefined ? (scalars[key] += ` ${value}`) : (scalars[key] = value)\n }\n const turnRaw = scalars['turn'] ?? ''\n const turnSeq = /^\\d+$/.test(turnRaw) ? Number(turnRaw) : null\n const errorRaw = scalars['error'] ?? ''\n const intentWord = (scalars['intent'] ?? '').split(' ')[0] ?? ''\n return {\n t: 'polled',\n intent: intentWord === 'goal-edit' ? 'goal-edit' : 'worker-step',\n status,\n ...(status === 'ready' ? { text: prompt.join('\\n'), turnSeq } : {}),\n ...(status === 'failed' ? { error: errorRaw, turnSeq } : {}),\n }\n}\n\n/** The autorun flag, read from the same payload — the row is durable, the flag is not. */\nexport function parseBarAutorun(text: string): 'armed' | 'disarmed' | null {\n const m = /^ {2}autorun: (armed|disarmed)\\b/m.exec(text)\n return m === null ? null : (m[1] as 'armed' | 'disarmed')\n}\n\n/**\n * Does the host say this session is busy right now?\n *\n * The dock's props are HOOKS (`useSessionStatus`, `useChat`, `useInput`, …) — documented in\n * docs/contract.md § \"What a dock component is handed\" — so a component that waits for a scalar\n * `props.running` waits forever, which is precisely why Autorun declared itself unavailable while the host\n * was publishing the signal one field away. Shapes differ across hosts and versions, so every form is folded\n * here, in one pure function, with `undefined` (unknown) kept distinct from `false` (idle): the loop refuses\n * to start a turn on an unknown, and that distinction is the whole safety property.\n */\nexport function deriveRunning(status: unknown): boolean | undefined {\n if (status === undefined || status === null) return undefined\n if (typeof status === 'boolean') return status\n if (typeof status === 'string') {\n if (/^(running|streaming|busy|active)$/i.test(status)) return true\n if (/^(idle|done|stopped|finished|ready|waiting)$/i.test(status)) return false\n return undefined\n }\n if (typeof status === 'object') {\n const s = status as { status?: unknown, state?: unknown, running?: unknown, phase?: unknown }\n if (s.running !== undefined) return typeof s.running === 'boolean' ? s.running : undefined\n return deriveRunning(s.status ?? s.state ?? s.phase)\n }\n return undefined\n}\n","/**\n * The Suggested Next Step dock row (#113) — thin by design.\n *\n * Every decision lives in `prompt-bar.ts` where a row can pin it; this file only moves pixels and bytes\n * between the host slot and that reducer. That split is the reason it exists: the ghost was rejected for\n * behaviour that had no home, and \"one turn, one offer\" cannot be tested in a component.\n *\n * Placement is measured, not styled: `conversation.input.dock` is the host's full-width stack rendered\n * ABOVE the composer card (todo `order: 0`, goal `10`, queue `20`), so `order: 30` puts this row under every\n * other dock entry and directly above the input — and because it is a docked row rather than an overlay, it\n * can neither cover the placeholder nor collide with the send buttons. The ghost's ugliest defect was\n * structural, and the structure fixes it.\n *\n * The bar is PUSH-SHAPED, PULL-FED. A registrant can own a reactive observable (`hooks: { x }` → prop\n * `useX`) — that is how the goal bar gets its `activation` — but a plugin cannot publish a projection, and\n * the only plugin-reachable call path is `remote.commands.execute`. So the component pulls the `state` verb\n * on edges it can actually observe. Stated here because the alternative is a reader assuming a subscription\n * that does not exist.\n *\n * @module dsh-chapters/src/client/prompt-dock\n */\n\nimport { createElement as h, useCallback, useEffect, useReducer, useRef, useState, type ReactElement } from 'react'\nimport { Tooltip } from '@deepseek-ai/dsh-client-ui-primitives'\nimport {\n BAR_RUN_COMMAND,\n deriveRunning,\n BAR_STATE_COMMAND,\n barLabel,\n barVisible,\n initialBarState,\n parseBarAutorun,\n parseBarState,\n promptBarReducer,\n shouldAutoFire,\n type PromptBarEvent,\n type PromptBarState,\n} from './prompt-bar.ts'\n\n/** The dock's owner + standard-kit props we read. Everything is optional-typed on purpose: this is a host\n * shape crossing a plugin boundary, and an absent field must degrade visibly, not throw at boot (#58). */\nexport interface DockProps {\n sessionId?: string\n inputActions?: { insertText?: (text: string) => void; setDraft?: (text: string) => void; submit?: () => void }\n /** The host's session-status hook, when this build hands docks one (`useSessionStatus` is in the dock's\n * documented prop list). Folded through `deriveRunning` because its shape is not ours to assume. */\n useSessionStatus?: (selector?: unknown) => unknown\n /** The session snapshot hook; its snapshot carries `running`, which is Autorun's idle edge. */\n useSession?: (selector?: unknown) => unknown\n /** The host publishes running/idle through the composer kit on some builds; if it is absent, autorun\n * refuses to fire rather than guessing that the session is idle. Fail-safe, not fail-open. */\n running?: boolean\n /** Run a host command line for THIS session and return its text. One verb for every call the bar makes\n * (the `state` pull and the `off` write), because the bar is a mouth for the command plane — a second\n * private channel would be a second contract to keep in step, which is what `doors.ts` already avoids. */\n exec?: (line: string) => Promise<string>\n}\n\nconst CSS = [\n // Metrics copied from the goal bar's measured module so the two rows read as one family: a 36px card,\n // host elevation + radius tokens, and the host's own composer width vars so it tracks the card above it.\n '.dsh-chapters_promptDock{box-sizing:border-box;width:calc(100% - 4 * var(--dsh-composer-dock-inset));max-width:calc(var(--dsh-composer-card-max-width) - 4 * var(--dsh-composer-dock-inset));margin:0 auto}',\n '.dsh-chapters_promptBar{isolation:isolate;box-sizing:border-box;width:100%;border-radius:var(--dsw-radius-md);height:36px;box-shadow:var(--dsw-elevation-panel);border:0;align-items:center;gap:10px;padding:4px 5px 4px 12px;display:flex;position:relative}',\n '.dsh-chapters_promptBar::before{z-index:-1;border-radius:inherit;background:var(--dsw-specific-menu);backdrop-filter:var(--dsw-menu-backdrop-filter);content:\"\";pointer-events:none;position:absolute;inset:0}',\n '.dsh-chapters_promptExpanded::before{align-items:flex-start;position:absolute;inset:0}',\n '.dsh-chapters_promptGlyph{color:var(--dsw-alias-label-tertiary);flex:none;display:inline-flex}',\n '.dsh-chapters_promptLabel{color:var(--dsw-alias-label-primary);flex:none;font-size:13px;font-weight:500;line-height:24px}',\n '.dsh-chapters_promptText{min-width:0;color:var(--dsw-alias-label-primary-dimmed);font-size:13px;line-height:20px;cursor:pointer;border:0;background:0 0;padding:0;text-align:left;flex:1}',\n '.dsh-chapters_promptEllipsis{white-space:nowrap;text-overflow:ellipsis;overflow:hidden;display:block}',\n '.dsh-chapters_promptFull{white-space:pre-wrap;overflow:auto;max-height:9rem;display:block}',\n '.dsh-chapters_promptError{color:var(--dsw-alias-state-error-primary);font-size:12px;line-height:20px;flex:none;max-width:34%}', '.dsh-chapters_promptNote{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:20px;min-width:0}',\n '.dsh-chapters_promptIntent{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:20px}',\n\n '.dsh-chapters_promptActions{flex:none;align-items:center;gap:10px;display:flex}',\n '.dsh-chapters_promptBtn{corner-shape:round;width:28px;height:28px;color:var(--dsw-alias-label-tertiary);cursor:pointer;background:0 0;border:none;border-radius:999px;justify-content:center;align-items:center;padding:0;display:inline-flex;font-size:13px}',\n '.dsh-chapters_promptBtn:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary)}',\n '.dsh-chapters_promptBtn:disabled{opacity:.4;cursor:default}',\n].join('\\n')\nconst CSS_ID = 'dsh-chapters/prompt-dock.css'\n\nconst ensureCss = (): void => {\n if (typeof document === 'undefined') return\n if (document.getElementById(CSS_ID) !== null) return\n const style = document.createElement('style')\n style.id = CSS_ID\n style.setAttribute('data-plugin-css', CSS_ID)\n style.textContent = CSS\n document.head.appendChild(style)\n}\n\n/** The analysis glyph, drawn to the goal icon's metric (16 viewBox, stroke currentColor, width 1).\n * A prism-and-arrow: the feature reads the situation and points at the next move. */\nconst Glyph = (): ReactElement => h(\n 'svg', { viewBox: '0 0 16 16', width: 14, height: 14, fill: 'none', 'aria-hidden': 'true', 'stroke-width': '1' },\n h('path', { d: 'M2.5 3.5h11L8 9.5 2.5 3.5Z', stroke: 'currentColor' }),\n h('path', { d: 'M8 9.5V14', stroke: 'currentColor' }),\n h('path', { d: 'M6 12l2 2 2-2', stroke: 'currentColor' }),\n)\n\n/** One action button, labelled through the host tooltip exactly as the goal bar labels its own. */\nconst Action = (props: { label: string; glyph: string; onClick: () => void; disabled?: boolean; title?: string }): ReactElement => {\n const button = h('button', {\n type: 'button', className: 'dsh-chapters_promptBtn', 'aria-label': props.label,\n ...(props.disabled === true ? { disabled: true } : {}),\n ...(props.title !== undefined ? { title: props.title } : {}),\n onClick: props.onClick,\n }, props.glyph)\n // Props matched to the call `doors.ts` already makes successfully: the host Tooltip takes\n // { label, side, children } and nothing else that I have verified. Inventing extra props (portal, delayMs)\n // failed the overload on the first compile, which is the right way to learn it.\n return h(Tooltip, { label: props.label, side: 'bottom' as const, children: button as ReactElement<Record<string, unknown>> })\n}\n\n/** Run is allowed whenever there is an offer; AUTORUN is what the intent and the goal gate. Keeping the\n * human's own button enabled is deliberate — the point of the protocol is to route a goal edit to a keystroke,\n * never to disable the human's ability to send it. */\n/**\n * The words the collapsed row shows, per state. `idle` and `analyzing` must say something, because an empty\n * row is indistinguishable from a broken one — the whole reason this feature took a day to read: the bar was\n * live, enabled, and rendering nothing but whitespace.\n */\nfunction lineFor(state: PromptBarState): string {\n if (state.status === 'ready') return state.text ?? ''\n if (state.status === 'analyzing') return 'Analyzing the session…'\n if (state.status === 'failed') return state.error !== null && state.error !== '' ? `Cannot recommend: ${state.error}` : 'Cannot recommend a next step right now'\n return 'No suggestion yet — I will recommend one after your next turn'\n}\n\nconst canRunHere = (state: PromptBarState): boolean => state.text !== null && state.text !== ''\n\n/** How long a state pull may take before we call it failed. Generous on purpose: this is a local RPC that\n * answers in milliseconds when the session is attachable, and the failure it guards against is a hang. */\nconst PULL_TIMEOUT_MS = 15_000\n\n/** Retry ceiling for the FIRST pull only, in attempts and spacing. A mount-time race must not be able to\n * starve the bar forever, and a real `off` answer stops the loop on the first reply either way. */\n/** Cadence of the keep-poll. A local RPC that answers in milliseconds; the cost is one command per\n * interval, and it ends the moment the state is `off`. */\nconst POLL_SPACING_MS = 10_000\n\nfunction execWithTimeout(exec: (line: string) => Promise<string>, line: string, ms: number): Promise<string> {\n return new Promise<string>((resolve, reject) => {\n const timer = setTimeout(() => { reject(new Error(`no answer within ${String(ms)}ms`)) }, ms)\n exec(line).then(\n (text) => { clearTimeout(timer); resolve(text) },\n (error: unknown) => { clearTimeout(timer); reject(error instanceof Error ? error : new Error(String(error))) },\n )\n })\n}\n\nexport function PromptDock(props: DockProps): ReactElement | null {\n const { sessionId, inputActions, exec } = props\n const [state, dispatch] = useReducer(promptBarReducer, initialBarState(), (s) => ({ ...s, running: props.running ?? true }))\n // #79's discipline, kept because the failure it prevents was real: one pull per mount, then per edge.\n // A fetch in an effect with no such guard re-fires on every render.\n const pulling = useRef(false)\n // TWO different facts, two different refs — conflating them is the bug I just shipped: `pulled` is the\n // once-per-mount pull guard, `alive` is \"may this component still call dispatch/setState\". Initializing the\n // pull guard to true made every mount skip its first pull, so the bar sat at its `off` default and rendered\n // nothing at all — a regression that looked, from outside, like the feature having stopped working.\n const probed = useRef(false)\n const pulled = useRef(false)\n const alive = useRef(true)\n useEffect(() => () => { alive.current = false }, [])\n const [busy, setBusy] = useState(false)\n void 0\n const answered = useRef(false)\n const replyStatus = useRef<string>('unknown')\n const stateRef = useRef<PromptBarState>(initialBarState())\n stateRef.current = state\n if (!probed.current) {\n probed.current = true\n // One print, on the first mount: which composer verbs does THIS host actually hand a dock? The click-to-draft\n // requirement needs one, `setDraft` is accepted but never reaches the editor, and guessing at the surface\n // again would be a third wrong theory in a row.\n console.info(`[client:prompt] inputActions keys: ${Object.keys(inputActions ?? {}).join(',') || 'NONE'}`)\n }\n\n const refresh = useCallback(async (): Promise<void> => {\n if (exec === undefined || pulling.current) return\n pulling.current = true\n try {\n // Bounded, and loudly so. Tonight's whole diagnosis cost was paid because this await had no ceiling:\n // a wire that never answers produced no dispatch, no catch and no `finally`, which looked identical to\n // \"the feature is switched off\". A hung command is now a named failure with a number on it (#61).\n const text = await execWithTimeout(exec, BAR_STATE_COMMAND, PULL_TIMEOUT_MS)\n // Handed to the leg through the page, because the state block is the contract and I need to see the\n // bytes the browser parsed, not the bytes the server meant. Overwritten each pull; read on failure.\n try { (window as unknown as { __dshPromptReply?: string }).__dshPromptReply = text } catch { /* test seam only */ }\n const ev = parseBarState(text) as PromptBarEvent | null\n if (ev !== null) { answered.current = true; replyStatus.current = (ev as { status?: string }).status ?? replyStatus.current; dispatch(ev) }\n const autorun = parseBarAutorun(text)\n if (autorun !== null) dispatch({ t: 'autorun', on: autorun === 'armed' })\n } catch (error) {\n // #61: a detected failure is a DELIVERED failure. Keeping the last known state is right; saying\n // nothing was not — with a bare `catch {}` a wire that never answers is indistinguishable from a\n // feature that is switched off, and that is exactly how this bar looked \"unverifiable\" in a browser\n // while the real fault was one line of swallowed output.\n // A failed pull is not a state: the bar keeps what it last knew rather than rendering `off`,\n // which would read as \"suggestions are disabled\" when the wire merely hiccoughed.\n console.warn(`[client:prompt] pull failed: ${String((error as Error)?.message ?? error).slice(0, 180)}`)\n } finally { pulling.current = false }\n }, [exec])\n\n useEffect(() => {\n ensureCss()\n if (pulled.current) return\n pulled.current = true\n // Retry until SOMETHING answers. `answered` flips on any parseable reply, including an honest `off`, so\n // this loop cannot make a disabled session look enabled — it only closes the window where a pull fired\n // before the session could take a command and the bar then stayed blank for the rest of the session.\n // Keep reading while the feature is live. This host publishes no idle edge — the same absence that\n // makes Autorun refuse to fire — so a pull that only ran at mount would answer `idle` once and then go\n // quiet for the rest of the session. The poll stops on `off`, which is the only state that means the\n // human (or the config) switched it away, so a disabled session costs one read and nothing more.\n let timer: ReturnType<typeof setTimeout> | undefined\n const tick = async (): Promise<void> => {\n await refresh()\n if (replyStatus.current === 'off') return\n // The reply, not the React state. The old condition read `stateRef.current`, which is only advanced by a\n // render — and while the row is hidden there is no render to wait for, so the poll stopped after its\n // first read and the bar could never observe the offer that landed a minute later. Measured: every\n // `state read` in the boot log precedes its `ready` write, with none following. \n timer = setTimeout(() => { void tick() }, POLL_SPACING_MS)\n }\n void tick()\n return () => { if (timer !== undefined) clearTimeout(timer) }\n }, [refresh])\n\n // Autorun fires off the IDLE edge, never off a render. `running` comes from the host when it publishes it;\n // absent means unknown, and unknown means do not start a turn the human cannot see coming.\n useEffect(() => {\n dispatch({ t: 'running', running: props.running ?? true })\n }, [props.running])\n\n // Run is a COMMAND, not a keystroke. The composer-write path (`inputActions.setDraft` + `submit`) looked\n // like the obvious implementation and does not work on this host build: `setDraft` is accepted, and the\n // editor stays empty — measured, twice, after I had convinced myself it was the test's locator or a focus\n // ordering problem. The plugin already has a dependable wire to its own server plane, and the server has\n // exactly one human-authored submission path (the follow-up autorun uses), so Run rides that. The\n // consequence is also the point: what the human clicks and what the loop arms are the same code.\n const run = useCallback((): void => {\n if (state.text === null || state.text === '' || exec === undefined) return\n setBusy(true)\n void exec(BAR_RUN_COMMAND)\n .then((out) => {\n if (!alive.current) return\n if (/^cannot run:/i.test(out.trim())) dispatch({ t: 'polled', status: 'failed', text: state.text, error: out.trim().slice(0, 200), turnSeq: state.turnSeq } as PromptBarEvent)\n else dispatch({ t: 'collapse' })\n })\n .catch((error: unknown) => { console.warn(`[client:prompt] run failed: ${String((error as Error)?.message ?? error).slice(0, 160)}`) })\n .finally(() => { if (alive.current) setBusy(false) })\n }, [exec, state.text, state.turnSeq])\n\n useEffect(() => {\n if (!shouldAutoFire(state)) return\n run()\n }, [state, run])\n\n // Autorun's idle edge stays UNWIRED on this host, and that is now a measured fact rather than a guess:\n // `useSessionStatus` is in the dock's documented prop list, but calling it here threw twice — first a React\n // hook-order abort (#310, because it sat below the early return), then `TypeError: l is not a function` once\n // hoisted. Two blind attempts is the limit; the contract says which hooks exist, not their arity or context.\n // So the gate keeps its original, correct behavior: with no signal, `running` is undefined, Autorun is\n // visibly unavailable and says why, and `deriveRunning` (9 rows) stands ready for whichever shape the host\n // does publish. A loop that starts turns the human cannot foresee is the one failure mode not on offer.\n // `useSessionStatus` is `SnapshotSelectorHook<SessionStatusSnapshot>` — a SELECTOR hook (declared in\n // dsh-client-ui-session's client types), which is why calling it with a session id threw\n // `TypeError: l is not a function`: the host used my string as the selector. An identity selector takes\n // whatever the snapshot's shape happens to be, and `deriveRunning` folds it, so this code does not bet on\n // field names I have not read. Still hoisted above the early return: hook count must not change per render.\n // The idle signal IS published — on `useSession`, whose snapshot carries `running` (measured by printing the\n // hook surfaces once: `useSession=sessionId|pendingSubmissions|running|subagent|removed|openState`). My\n // first two attempts were wrong in different ways: a scalar `props.running` that no host supplies, then\n // `useSessionStatus`, which is provided but yields no readable keys under an identity selector. `deriveRunning`\n // folds `{running}`, so this is the last mile, and the hook is called unconditionally through a stable\n // fallback because hook order must not change between renders.\n const useSessionHook = props.useSession ?? (() => undefined)\n const sessionSnapshot = useSessionHook((snapshot: unknown) => snapshot)\n const running = props.running ?? deriveRunning(sessionSnapshot)\n const autorunDisabled = running === undefined\n\n if (sessionId === undefined || !barVisible(state as PromptBarState)) return null\n\n const populate = (): void => {\n // Collapsed: reading. Expanded: the maintainer's click-to-edit gesture — setDraft REPLACES the draft,\n // which is what they asked for (\"the prompt replaces the chat input contents so you can edit it\"), and\n // nothing is submitted. Run is a separate, deliberate button.\n // Printed on every branch, because \"the button did nothing\" and \"the button took the other path\" looked\n // identical from outside for three runs.\n console.info(`[client:prompt] populate expanded=${String(state.expanded)} hasInsertText=${String(typeof inputActions?.insertText)} hasSetDraft=${String(typeof inputActions?.setDraft)} textLen=${String(state.text?.length ?? -1)}`)\n if (!state.expanded) { dispatch({ t: 'expand' }); return }\n if (state.text === null || state.text === '') return\n // Delivered, not assumed. The click-to-draft gesture is the interaction the maintainer asked for, so a\n // silent no-op here is indistinguishable from \"the feature does not work\" — and that ambiguity cost the\n // rest of this leg's diagnosis. Absent verb, absent text: both say so.\n // The host's composer kit is `{ captureInsertion, insertText, setDraft, addAttachments, removeAttachment,\n // pruneAttachments, submit }` (measured on 0.2.0-rc.2). `setDraft` writes the STORE, and the store is\n // mirrored into the editor only at mount while the editor is empty — which is why a click that \"worked\"\n // (no refusal, no throw) left the box visibly empty. `insertText` is the verb that reaches the editor, so\n // click-to-draft uses it and keeps `setDraft` only as a fallback for builds that lack it.\n if (typeof inputActions?.insertText === 'function') {\n // `insertText` inserts AT THE CARET, and clicking this button moves the caret out of the editor — so the\n // call ran and put the words nowhere. Focusing the composer first is the whole difference, and it is done\n // with a tolerant query rather than a class name: the host's own module hashes (`wSkVaW_…`) are not mine\n // to depend on, and a bar that silently stops drafting after a host restyle is worse than one that tries.\n try {\n const box = document.querySelector<HTMLElement>('[contenteditable=\"true\"][role=\"textbox\"]')\n box?.focus()\n } catch { /* a host without the editor mounted: the insert below still records the intent */ }\n // Measured on host 0.2.0-rc.2: called from a dock this throws\n // `Cannot read properties of undefined (reading 'draftRev')`, and the kit's other verb (`setDraft`) writes\n // a store the editor mirrors only at mount. Neither reaches the composer. A silent no-op is what cost the\n // last six runs, so the refusal now appears on the bar and points at Run, which does work.\n const refuse = (why: string): void => {\n console.warn(`[client:prompt] click-to-draft refused: ${why}`)\n dispatch({ t: 'note', note: 'This host does not let a plugin write the composer, so the prompt cannot be edited before sending — Run submits it as your turn' })\n }\n try {\n inputActions.insertText(state.text)\n // Throwing is the easy case. The harder one is the verb that returns successfully and changes nothing,\n // which is what `setDraft` does on 0.2.0-rc.2 and what a silent button looks like forever. Verify the\n // editor actually holds the words; if it does not, say so on the bar.\n const box = document.querySelector<HTMLElement>('[contenteditable=\"true\"][role=\"textbox\"]')\n const head = state.text.slice(0, 24)\n if ((box?.innerText ?? '').indexOf(head) === -1) {\n // One animation frame: the host may write the DOM after its own microtask.\n setTimeout(() => {\n const b2 = document.querySelector<HTMLElement>('[contenteditable=\"true\"][role=\"textbox\"]')\n if ((b2?.innerText ?? '').indexOf(head) === -1) refuse('insertText reported success and left the editor empty')\n }, 400)\n }\n } catch (error) {\n refuse(String((error as Error)?.message ?? error).slice(0, 160))\n }\n // Read the editor back from inside the component, so \"insertText is a no-op on this host\" is a fact with\n // a number next to it rather than a conclusion the test reaches after the fact.\n setTimeout(() => {\n try {\n const box = document.querySelector<HTMLElement>('[contenteditable=\"true\"][role=\"textbox\"]')\n console.info(`[client:prompt] after insertText: editorText=${String(box?.innerText?.length ?? 'NO-BOX')} focused=${String(document.activeElement === box)}`)\n } catch { /* diagnostics never break the gesture */ }\n }, 250)\n return\n }\n if (typeof inputActions?.setDraft === 'function') {\n inputActions.setDraft(state.text)\n return\n }\n console.warn('[client:prompt] click-to-draft refused: this host exposes neither insertText nor setDraft')\n }\n\n // Called unconditionally through a stable fallback, so the hook order cannot change between renders.\n return h('div', { className: 'dsh-chapters_promptDock', 'data-prompt-bar': 'true' },\n h('div', { className: `dsh-chapters_promptBar${state.expanded ? ' dsh-chapters_promptExpanded' : ''}` },\n h('span', { className: 'dsh-chapters_promptGlyph' }, h(Glyph)),\n h('span', { className: 'dsh-chapters_promptLabel' }, barLabel(state as PromptBarState) ?? ''),\n // A goal re-cut is held for the human's keystroke: the host reserves goal edits for a turn containing a\n // direct human message, and an autorun message is not one. The bar says so on its face rather than\n // letting Run appear to be a normal button that silently fails in the tool.\n ...(state.intent === 'goal-edit' ? [h('span', { key: 'intent', className: 'dsh-chapters_promptIntent', 'aria-label': 'This step edits the goal' }, 'edits the goal · run it yourself')] : []),\n h('button', {\n type: 'button', className: 'dsh-chapters_promptText',\n 'aria-label': state.expanded ? 'Put this suggested prompt in the input box' : 'Show the full suggested prompt',\n onClick: populate,\n }, state.expanded\n ? h('span', { className: 'dsh-chapters_promptFull' }, state.text ?? '')\n : h('span', { className: 'dsh-chapters_promptEllipsis' }, lineFor(state))),\n ...(state.note === '' ? [] : [h('span', { key: 'note', className: 'dsh-chapters_promptNote', role: 'status' }, state.note)]),\n ...(state.error === '' ? [] : [h('span', { key: 'err', className: 'dsh-chapters_promptError', role: 'alert' }, state.error)]),\n h('div', { className: 'dsh-chapters_promptActions' },\n h(Action, {\n label: 'Run the suggested next step', glyph: '▶', onClick: run, disabled: busy,\n ...(state.text === null || state.text === '' || !canRunHere(state) ? { disabled: true } : {}),\n ...(state.intent === 'goal-edit'\n ? { title: 'Goal edits need a human turn. Run still works; autorun will never do this one.' }\n : {}),\n }),\n h(Action, {\n label: state.autorun === 'armed' ? 'Autorun off' : 'Autorun',\n glyph: state.autorun === 'armed' ? '⟳' : '⟲',\n disabled: autorunDisabled,\n ...(autorunDisabled\n ? { title: 'Autorun needs the host to publish an idle signal; this build does not, so nothing would fire safely.' }\n : {}),\n // A SERVER write, like `off` below. Flipping local state alone was the defect: `refresh()` re-reads the\n // loop state (`:193`), the server still says `disarmed`, and the label snaps back — so the user watched\n // their own click be refused and the loop could never be started from the bar. The command surface is\n // `/chapters-prompt autorun on|off` (src/commands.ts:462-478), which also reports the goal displacement.\n onClick: () => {\n void (async () => {\n const arm = state.autorun !== 'armed'\n if (exec !== undefined) await exec(`/chapters-prompt autorun ${arm ? 'on' : 'off'}`)\n dispatch({ t: 'autorun', on: arm })\n await refresh()\n })()\n },\n }),\n h(Action, {\n label: 'Turn prompt suggestions off for this session', glyph: '✕',\n // `off` is a server write, not a local hide: generation stops, the configured model is KEPT so\n // back-on is one action, and the bar disappears instead of lingering with a stale offer.\n onClick: () => { void (async () => { if (exec !== undefined) await exec('/chapters-prompt off'); dispatch({ t: 'off' }) })() },\n }),\n ),\n ),\n )\n}\n","/**\n * The client door runner: the two deterministic archive doors, executed over\n * `remote.commands` and followed by a SWITCH to the fresh child.\n *\n * ONE runner, two rows. The server side keeps the same discipline — both doors\n * come from `deterministicCommand(door)` in src/tools.ts, and a unit row pins\n * that they archive identically. Copy-pasting this body is how the two would\n * drift apart, so there is exactly one implementation and a table of rows.\n *\n * `chapters-continue` ships only as a composer command; without a button here\n * the child is created correctly and the UI shows nothing, which reads as\n * \"nothing happened\". The row is the missing affordance, not new logic.\n *\n * React-free on purpose: the primitives package pulls CSS modules the node\n * test runner cannot load, so the testable half lives here and index.ts is the\n * thin chrome that renders these rows as buttons (tests/unit/client-doors.test.ts).\n *\n * @module dsh-chapters/src/client/doors\n */\n\n/** Structural slice of the aggregated remote service (@deepseek-ai/dsh-api-remotes).\n * `remote.commands` must ALSO appear in inject — the federation gates each\n * sub-service individually (the live feedback package declares\n * remote.messageFeedback beside remote for exactly this reason). */\nexport interface ClientRemote {\n commands?: {\n execute(sessionId: string, line: string, attachments: readonly unknown[]): Promise<{\n ok?: boolean\n value?: { commandId?: string; result?: { kind?: string; text?: string } }\n error?: { code?: string; message?: string }\n }>\n }\n}\n\n/** The client sessions service (dsh-client-runtime): open() lives on the\n * prototype; everything else on the surface stays untrusted and unused. */\nexport interface ClientSessions {\n open(sessionId: string): unknown\n}\n\nexport interface DoorResult {\n ok: boolean\n message: string\n}\n\n/**\n * One assistant-action row. `noun` is the server's own word for the child in\n * its success text (`branch` / `continuation`), so the fallback wording and the\n * title regex both track the door without a second implementation.\n */\nexport interface DoorAction {\n /** The slot registration id. */\n id: string\n /** Position in the assistant action row. */\n order: number\n /** Button + tooltip label, and the accessible name the e2e specs select on. */\n label: string\n /** The host command this row executes — the ONLY semantic difference at runtime. */\n command: string\n /** The noun in `${noun} “title” is session ch-…`. */\n noun: string\n /** The row's glyph (16×16 viewBox, single path). */\n iconPath: string\n}\n\nexport const DOOR_ACTIONS: readonly DoorAction[] = [\n {\n id: 'chapters-fork',\n order: 30,\n label: 'Fork with chapters',\n command: '/chapters-fork',\n noun: 'branch',\n iconPath: 'M5 3.25a2.25 2.25 0 1 0-1.5 2.12v5.26a2.25 2.25 0 1 0 1.5 0V9h5a2.25 2.25 0 0 0 2.25-2.25v-1.4a2.25 2.25 0 1 0-1.5 0v1.4A.75.75 0 0 1 10 7.5H5V5.37A2.25 2.25 0 0 0 5 3.25Z',\n },\n {\n id: 'chapters-continue',\n order: 31,\n label: 'Continue with chapters',\n command: '/chapters-continue',\n noun: 'continuation',\n iconPath: 'M1 8a.75.75 0 0 1 0-1.5h9.44L7.72 3.28a.75.75 0 1 1 1.06-1.06l3.5 3.5.53.53-.53.53-3.5 3.5a.75.75 0 1 1-1.06-1.06L10.44 9.5H1Zm13-5.25v10.5a.75.75 0 0 1-1.5 0v-10.5a.75.75 0 0 1 1.5 0Z',\n },\n]\n\n/** Pull the child id out of either door's durable success text — both end in\n * `… is session ch-…` (src/tools.ts), so this regex is door-agnostic. */\n/**\n * Which build of THIS file produced a door note (#77). When the live probe reported a roster\n * trace of `missing → missing → missing` — meaning adoption should have fired three times — while\n * showing no adoption outcome at all, the first question stopped being \"why did the client refuse\"\n * and became \"did the browser run the code I am reading?\". A note that names its own build makes\n * that a glance instead of an archaeology session into symlinked plugin trees.\n */\nexport const DOOR_BUILD = '2026-10-22T23:55 door-build-stamp'\n\nexport const CHILD_ID = /is session (ch-[\\w-]+)/\n\n/** The quoted title the door gave the child, matched by its OWN noun so a\n * refusal or an unrelated quote cannot pass for it. */\nexport const doorTitle = (noun: string): RegExp => new RegExp(`${noun} \\\\u201C([^\\\\u201D]+)\\\\u201D is session`)\n\n/**\n * A refusal reason that can never render as nothing. `String(err.message ?? err)` is `''` for a\n * host error built with an empty message, and an empty reason disappears from the note — the\n * reader then concludes \"no refusal happened\", which is the opposite of the truth (#77).\n */\nfunction refusalReason(error: unknown): string {\n const raw = error instanceof Error ? error.message : String(error)\n const trimmed = raw.trim()\n const named = trimmed === '' ? (error instanceof Error && error.name.trim() !== '' ? error.name : '') : trimmed\n const base = named === '' ? (error === null ? 'null thrown' : typeof error) : named\n return base.replace(/\\s+/g, ' ').slice(0, 90)\n}\n\n/**\n * Is the child in the client's OWN roster? This is the question that separates two very\n * different failures, and the door used to report the symptom without it:\n * - the client never learned the session exists (roster: missing) → the fix is server-side\n * publication, and no amount of clicking `open` will ever work;\n * - the client knows it and `open` still did nothing (roster: contains) → a navigation bug.\n * Measured host 0.2.0-rc.2: the sessions service exposes `projectList`, `refresh`, `retain`,\n * `handleSessionAdded` — the roster is a projection the client fills from control frames.\n */\n/**\n * The client's own session ids, or `null` when the store cannot be read. ONE reader for both\n * the note and the adoption decision, so the door never acts on a verdict it did not report.\n */\nfunction rosterIds(sessions: ClientSessions | undefined): Set<string> | null {\n const store = (sessions as unknown as { list?: { getSnapshot?: () => unknown } })?.list\n if (typeof store?.getSnapshot !== 'function') return null\n const snap = store.getSnapshot()\n const ids = new Set<string>()\n if (Array.isArray((snap as { ids?: unknown[] } | undefined)?.ids)) {\n for (const id of (snap as { ids: unknown[] }).ids) ids.add(String(id))\n } else if ((snap as { byId?: object } | undefined)?.byId !== undefined) {\n for (const k of Object.keys((snap as { byId: object }).byId)) ids.add(k)\n } else return null\n return ids\n}\n\nfunction rosterNote(sessions: ClientSessions | undefined, child: string): string {\n try {\n // The host's client store shape is `list.getSnapshot() → { ids, byId, phase }`.\n // `projectList()` turned out to be a MUTATOR that returns undefined (measured), so the first\n // version of this note printed \"0 sessions\" about a conversation that visibly had one — the\n // repo's own `listEvents` lesson (\"measure the SHAPE, not the presence\"), repeated by me in\n // the same week. Read the store; when it isn't reachable, presence is UNKNOWN and the note\n // says that rather than inventing an absence.\n const ids = rosterIds(sessions)\n if (ids === null) {\n const store = (sessions as unknown as { list?: { getSnapshot?: () => unknown } })?.list\n const shape = typeof store?.getSnapshot !== 'function' ? 'no list store on the service' : 'snapshot unreadable'\n return ` (roster: ${shape} — presence of ${child.slice(0, 11)}… is UNKNOWN, not absent)`\n }\n if (ids.size === 0) return ` (roster: empty — presence of ${child.slice(0, 11)}… is UNKNOWN, not absent)`\n const n = ids.size\n return ids.has(child)\n ? ` (roster: contains ${child.slice(0, 11)}…, ${String(n)} sessions — open() is the failure)`\n : ` (roster: MISSING, ${String(n)} sessions — the client never catalogued this session)`\n } catch { return '' }\n}\n\n/** The service's own method names, as a hint attached to a failure note (never a guess). */\nfunction verbsNote(sessions: ClientSessions | undefined): string {\n try {\n const svc = sessions as unknown as Record<string, unknown> | undefined\n if (svc === null || svc === undefined || typeof svc !== 'object') return ' (sessions service: absent)'\n // Measured: `Object.keys(svc)` returned NOTHING for the real service, because the host\n // puts its verbs on the PROTOTYPE (a class instance), not as own keys. A diagnostic that\n // reports \"no methods\" about a working service is worse than none — walk the chain.\n const found = new Set<string>()\n let node: unknown = svc\n // Stop at Object.prototype: its four legacy accessors are not service verbs, and listing\n // them pushed the real names past the cap on the first live run.\n for (let depth = 0; depth < 6 && node !== null && node !== undefined && node !== Object.prototype; depth++) {\n for (const key of Object.getOwnPropertyNames(node as object)) {\n if (key === 'constructor') continue\n try { if (typeof (svc as Record<string, unknown>)[key] === 'function') found.add(key) } catch { /* getter that throws — not a verb we can use either */ }\n }\n node = Object.getPrototypeOf(node)\n }\n const verbs = [...found].sort().slice(0, 24)\n return verbs.length === 0 ? ' (sessions service: no methods on the chain)' : ` (sessions verbs: ${verbs.join(', ')})`\n } catch { return ' (sessions service: unreadable)' }\n}\n\nfunction openByService(\n sessions: ClientSessions | undefined,\n sessionId: string,\n /** #77: adopt (and re-project) only when the client's own roster is missing this session.\n * `onAfter` reports the roster at the instant after adoption, before any later refresh can\n * discard the mutation — the difference between an ordering bug and a publication gap. */\n adopt?: { needed: boolean, onRefused: (reason: string) => void, onSubscribeRefused?: (reason: string) => void, onAfter: (seen: boolean | null) => void, onStep?: (step: string) => void },\n nav?: { openSession?: (id: string) => unknown },\n): Promise<boolean> {\n return (async () => {\n const svc = sessions as unknown as {\n open?: (id: string) => unknown; refresh?: () => unknown; projectList?: () => unknown\n create?: (opts: { sessionId?: string }) => unknown\n using?: (id: string, opts: { source: string }, fn: () => Promise<void>) => unknown\n } | undefined\n // #77, the actual root cause: `sessions.open` DOES NOT EXIST on this host. The sessions\n // service offers create/fork/using/retain/binding/refresh; the host's own fork button calls\n // `ctx.uiWorkspace.openSession(childId)` (`dsh-client-ui-chat/lib/client.js`). The line here\n // used to be `if (typeof svc?.open !== 'function') return false`, so every service attempt\n // since the door was written returned false on line one and the note blamed the client's\n // roster for a verb that was never there.\n if (svc === undefined) return false\n const navigate = typeof nav?.openSession === 'function'\n ? (id: string): unknown => nav.openSession!(id)\n : typeof svc?.open === 'function'\n ? (id: string): unknown => svc.open!(id)\n : null\n if (navigate === null) {\n adopt?.onStep?.('no-verb')\n return false\n }\n try { await svc.refresh?.() } catch { /* refresh best-effort */ }\n // #57: re-project the client's session catalog. The host does this ITSELF after every\n // create and fork — `dsh-api-session-controller/lib/client.js`, `create()` and `fork()`\n // both end in `this.projectList()` — because a session the client has never catalogued is\n // not addressable by `open()` no matter how often you call it. A plugin-created child is\n // exactly that case: durable, workspace-attached, absent from the client's roster. Measured\n // on 0.2.0-rc.2: without this the door created the child, `open` resolved, and the app\n // stayed on the parent (the door's own note now says `roster: …` so this is never a guess).\n try { svc.projectList?.() } catch { /* best-effort, like refresh */ }\n // #77: adopt-and-catalogue. `manager.create()` is documented \"on success merge into\n // summaries immediately (no wait for the next refresh)\" and forwards a caller-owned\n // `sessionId`, while the server ADOPTS a persisted id instead of authoring a second session\n // (`create(request)` → `ensureSession(sessionId, cwd, request.sessionId !== undefined, …)`).\n // Nothing about the child's content is authored here — the seed notice was committed\n // host-plane long before this call — this only lets the client see what the host already\n // knows. A refusal is reported to the caller, never swallowed.\n if (adopt?.needed === true && typeof svc.create === 'function') {\n // the block narrates itself: entry first, then whichever way it left. The live note once\n // said \"try\" from the runner and showed nothing downstream — with this line, an absent\n // `enter-create` IS the answer instead of a mystery.\n adopt.onStep?.('enter-create')\n try {\n await svc.create({ sessionId })\n adopt.onStep?.('settle-create')\n try { svc.projectList?.() } catch { /* re-project again, now that a mutation exists */ }\n // the momentary read is the whole point: a later `refreshList()` reassigns the manager's\n // mutations from the server result, so an adoption that DID land is invisible by the time\n // the last attempt fails. Report what was true at the instant it mattered.\n const after = rosterIds(sessions)\n adopt.onAfter?.(after === null ? null : after.has(sessionId))\n } catch (error) {\n adopt.onStep?.('refuse-create')\n adopt.onRefused(refusalReason(error))\n }\n }\n // #77, second candidate: if adoption left the child out of the roster, SUBSCRIBE to it.\n // `using(target, {source}, fn)` is the host's own way to address a session that is not\n // currently displayed (`sessions/service.js`: retain → `manager.get(id).open()`). A refusal is\n // reported exactly like adoption's. No URL route exists as an alternative — checked every\n // dsh-client-* bundle: the only `/session/` strings are resource addresses for files.\n if (adopt?.needed === true && typeof svc.using === 'function' && rosterIds(sessions)?.has(sessionId) === false) {\n adopt.onStep?.('enter-using')\n try {\n await svc.using(sessionId, { source: 'dsh-chapters-door' }, async () => undefined)\n adopt.onStep?.('settle-using')\n try { svc.projectList?.() } catch { /* best-effort, like the others */ }\n const seen = rosterIds(sessions)\n adopt.onAfter?.(seen === null ? null : seen.has(sessionId))\n } catch (error) {\n adopt.onStep?.('refuse-using')\n adopt.onSubscribeRefused?.(refusalReason(error))\n }\n }\n // The store must know the session before open() resolves it; a child\n // created seconds ago is only server-known until refresh lands. Retry\n // open briefly across that window.\n const started = Date.now()\n for (;;) {\n try { navigate(sessionId); return true } catch {\n if (Date.now() - started > 4_000) return false\n await new Promise((r) => setTimeout(r, 250))\n }\n }\n })()\n}\n\n/**\n * Fallback path: expand collapsed groups / show-more buttons, then click the\n * newest treeitem whose label contains the child title (newest-updated sorts\n * first, so the first match is the fresh child).\n */\nasync function openBranchRow(title: string, budgetMs = 4_000): Promise<boolean> {\n if (typeof document === 'undefined') return false\n const needle = title.toLowerCase()\n const tryClick = (): boolean => {\n const rows = Array.from(document.querySelectorAll<HTMLElement>('[role=\"treeitem\"]'))\n const hit = rows.find((row) => (row.textContent ?? '').toLowerCase().includes(needle))\n if (hit === undefined) return false\n hit.click()\n return true\n }\n const clickMore = (): number => {\n const buttons = Array.from(document.querySelectorAll<HTMLElement>('button'))\n .filter((b) => /more sessions|ungrouped/i.test(b.textContent ?? '') && b.getAttribute('aria-expanded') !== 'true')\n for (const b of buttons) b.click()\n return buttons.length\n }\n const started = Date.now()\n let expansions = 0\n while (Date.now() - started < budgetMs) {\n if (tryClick()) return true\n if (expansions < 4 && clickMore() > 0) { expansions += 1; await new Promise((r) => setTimeout(r, 250)); continue }\n await new Promise((resolve) => setTimeout(resolve, 150))\n }\n return tryClick()\n}\n\nexport function makeDoorRunner(\n remote: ClientRemote,\n sessions: ClientSessions | undefined,\n door: DoorAction,\n /**\n * \"Is THIS session the one the client is now showing?\" #54: `sessions.open()` returns\n * void, so a call that resolves proves the request was accepted, not that the app\n * switched. Measured on host 0.2.0-rc.2: no note element (open resolved), yet\n * `dsh.sessions.current` still named the parent and the child was absent from the\n * sidebar after a full reload — the user got silence and a missing session. Absent\n * (a client that cannot answer) the runner keeps the old behavior, and the wiring is\n * pinned by a row so production can never quietly drop it.\n */\n verifySwitched?: (childSessionId: string) => Promise<boolean>,\n /**\n * #77: how many times to re-pull the client's list, re-project and re-open. The session\n * manager documents `refreshList()` as \"single-flight within one Host generation\"\n * (`dsh-api-session-controller/lib/types/client/sessions/manager.js`), so a pull issued while\n * another is in flight can resolve from the STALE result — precisely the moment a session\n * created microseconds ago is missing. Retrying is both the fix candidate and the measurement:\n * which attempt lands tells us whether this is ordering (ours) or silence (host-side).\n * A client with no `refresh` verb gets exactly ONE attempt — re-asking `open` against a roster\n * we cannot re-pull would be theater.\n */\n opts?: { attempts?: number, attemptPauseMs?: number, navigator?: { openSession?: (id: string) => unknown } },\n): (sessionId: string) => Promise<DoorResult> {\n return async function run(sessionId: string): Promise<DoorResult> {\n try {\n if (remote.commands?.execute === undefined) return { ok: false, message: 'commands remote unavailable' }\n const res = await remote.commands.execute(sessionId, door.command, [])\n if (res.ok !== true) return { ok: false, message: (res.error?.message ?? `the ${door.noun} call failed`).slice(0, 90) }\n const result = res.value?.result\n const text = typeof result?.text === 'string' ? result.text : ''\n if (result?.kind !== 'success') return { ok: false, message: (text || `the ${door.noun} was refused — see the command result`).slice(0, 90) }\n const child = CHILD_ID.exec(text)?.[1]\n if (child === undefined) return { ok: true, message: `${door.noun} created — appearing in the sidebar` }\n const switchedOnce = async (): Promise<boolean> => {\n if (verifySwitched === undefined) return true\n try { return await verifySwitched(child) } catch { return false }\n }\n const canRepull = typeof (sessions as unknown as { refresh?: unknown } | undefined)?.refresh === 'function'\n const maxAttempts = canRepull ? (opts?.attempts ?? 3) : 1\n let tried = 0\n let adoptRefusal = ''\n let subscribeRefusal = ''\n // #77: what the roster reader answered AT EACH DECISION, not only at the end. The live\n // probe reported MISSING after three attempts while neither candidate fired, which can\n // only mean the reader disagreed with itself over time — and a note that says one number\n // cannot show that.\n const rosterTrace: string[] = []\n const steps: string[] = []\n let adoptSaw: boolean | null | undefined\n for (let i = 0; i < maxAttempts; i++) {\n tried += 1\n // decide adoption from the SAME reader the note reports, so the door never acts on a\n // verdict it did not tell the user about; an unreadable roster is not \"missing\"\n const ids = rosterIds(sessions)\n // Loaded-but-empty is a DECISION (the child is not there → try the sanctioned paths);\n // unreadable is UNKNOWN and must not invent a session (#77: the strict form here meant\n // neither candidate ever ran in the live probe, and its silence looked like a result).\n const needed = ids !== null && !ids.has(child)\n // `+c` / `+u` = the candidate verbs are present on the injected service AT THAT MOMENT.\n // Without this, a note that says the roster was missing while no adoption outcome appears\n // is uninterpretable: either the code never ran, or the service has no create to call.\n const svcShape = sessions as unknown as { create?: unknown, using?: unknown, open?: unknown } | undefined\n // +c create, +u using, +o sessions.open, +w workspace navigator — the exact predicates\n // the door branches on, so a note can be read instead of re-derived from source\n rosterTrace.push(`${ids === null ? 'unknown' : ids.size === 0 ? 'empty' : ids.has(child) ? 'has' : 'missing'}${typeof svcShape?.create === 'function' ? '+c' : '-c'}${typeof svcShape?.using === 'function' ? '+u' : '-u'}${typeof svcShape?.open === 'function' ? '+o' : '-o'}${typeof opts?.navigator?.openSession === 'function' ? '+w' : '-w'}`)\n if (await openByService(sessions, child, {\n needed,\n onRefused: (reason) => { adoptRefusal = reason },\n onSubscribeRefused: (reason) => { subscribeRefusal = reason },\n onAfter: (seen) => { adoptSaw = seen },\n onStep: (step) => { if (!steps.includes(step)) steps.push(step) },\n }, opts?.navigator) && await switchedOnce()) return { ok: true, message: '' }\n if (i + 1 < maxAttempts) await new Promise((r) => { setTimeout(r, opts?.attemptPauseMs ?? 300).unref?.() })\n }\n // the service path did not take — the sidebar row is the same affordance a human uses\n const title = doorTitle(door.noun).exec(text)?.[1]\n if (title !== undefined && await openBranchRow(title) && await switchedOnce()) return { ok: true, message: '' }\n // #54: a child that is created but not shown is a DIFFERENT fact from one the user\n // has to open by hand, and the message must not blur them.\n // #56/#57: the note must be ACTIONABLE. \"the app did not switch\" is true and useless;\n // naming the verbs the injected service actually exposes turns one failure into the\n // diagnosis (is there a refresh/reload to call, or is `open` the only verb and the\n // omission server-side?). A detected failure that isn't delivered is the same bug.\n return verifySwitched === undefined\n ? { ok: true, message: `${door.noun} created — open it in the sidebar` }\n : { ok: true, message: `${door.noun} created — the app did not switch after ${String(tried)} attempt${tried === 1 ? '' : 's'}; open it from the sidebar [roster per attempt: ${rosterTrace.join(' → ')}] [tried: ${steps.length === 0 ? 'none' : steps.includes('no-verb') ? 'no open or openSession verb on any injected service' : steps.join(', ')}] (build: ${DOOR_BUILD})${adoptRefusal === '' ? '' : ` (adoption refused: ${adoptRefusal})`}${subscribeRefusal === '' ? '' : ` (subscribe refused: ${subscribeRefusal})`}${adoptSaw === undefined ? '' : ` (right after adopt: ${adoptSaw ? 'contains' : 'missing'} ${child.slice(0, 15)})`}${rosterNote(sessions, child)}${verbsNote(sessions)}` }\n } catch (error) {\n return { ok: false, message: String((error as Error)?.message ?? error).slice(0, 90) }\n }\n }\n}\n\n/**\n * Execute a host command line for one session and return its text, or '' when the command plane is\n * unavailable. The bar's `state` pull and `off` write ride this, because it is the ONE place the client\n * talks to the wire: the guard row \"there is no second runner body in the client chrome\" exists to keep\n * `index.ts` as registration chrome, and a duplicated `commands.execute` in two files is how a wire\n * behaviour drifts from its own test (#59's one-predicate rule, applied to the transport).\n */\nexport async function runCommandText(remote: ClientRemote | undefined, sessionId: string, line: string): Promise<string> {\n const exec = remote?.commands?.execute\n if (exec === undefined) return ''\n const res = await exec(sessionId, line, [])\n if (res.ok !== true) return ''\n return res.value?.result?.text ?? ''\n}\n","/**\n * dsh-chapters client entry: the per-message door buttons.\n *\n * Registers the two deterministic archive doors into the assistant action row\n * (`conversation.chat.assistant-actions` — the slot the official feedback\n * package populates beside copy/branch). Clicking one executes its host\n * command — `/chapters-fork`, `/chapters-continue` — through `remote.commands`,\n * the exact wire the composer's input line rides, then SWITCHES the app to the\n * fresh child:\n *\n * 1. `sessions.open(child)` — the client sessions service carries open() on\n * its prototype (Playwright-verified against this host; a keys-dump alone\n * hid it, which is why an earlier round wrongly gave up on this path);\n * 2. fallback: click the child's sidebar row after expanding collapsed\n * groups (plugin children can render under collapsed 'Ungrouped' heads);\n * 3. last resort: an inline note — the child is durable either way.\n *\n * There is deliberately NO second implementation of door logic here: the\n * buttons are mouths for the host commands (watermark, segmentation, budget,\n * titles, refusal-with-numbers all server-side, all tested), and the runner\n * they share is `src/client/doors.ts` — one body, one row per door. The purity\n * gate in tsdown.client.config.ts keeps this file cross-plugin-free.\n *\n * @module dsh-chapters/src/client\n */\n\nimport { createElement as h, useCallback, useEffect, useRef, useState, type ReactElement } from 'react'\n// The host's own tooltip bubble — a platform module served by the loader table,\n// external like react. Every action in this row labels through it.\nimport { Tooltip } from '@deepseek-ai/dsh-client-ui-primitives'\nimport { PromptDock } from './prompt-dock.ts'\nimport { DOOR_ACTIONS, makeDoorRunner, runCommandText, type ClientRemote, type ClientSessions, type DoorAction, type DoorResult } from './doors.ts'\n\ninterface ClientSlots {\n inject(name: string, factory: () => unknown): void\n register(registration: Record<string, unknown>, component: unknown): unknown\n}\n\ninterface Ctx {\n slots: ClientSlots\n get(name: string): unknown\n}\n\n// #77: `uiWorkspace` is not optional in practice — it owns `openSession(target)`, the verb the\n// host's own fork button uses (`ctx.uiWorkspace.openSession(childId)` in dsh-client-ui-chat).\n// `sessions` has no `open` at all, so without this row the door has never had a way to switch.\nexport const inject = ['slots', 'remote', 'remote.commands', 'sessions', 'uiWorkspace'] as const\n\n/** Props composed by the slot framework: owner (`messageId`) + our injected verb. */\ninterface DoorActionProps {\n messageId: string\n run: () => Promise<DoorResult>\n}\n\n/**\n * Style parity with the native IconActions row: rules copied verbatim from the\n * host's MessageIconActions module (`._xzv4MW_action` in dsh-client-ui-chat's\n * compiled client + the feedback package's :disabled) — same metrics, tokens,\n * hover. Update this string if the host restyles its action row. Both doors\n * share these metrics.\n *\n * The final rule hides the native branch action IN THE MESSAGE ROW only\n * (user-approved cosmetic decision, docs/architecture.md): built-in chrome, so\n * the selector is its accessible label scoped to action-row buttons; both\n * locale spellings covered. If the host rewords the label the button simply\n * reappears — fails open, visibly.\n */\nconst CSS = [\n '.dsh-chapters_forkAction{width:calc(28px + var(--dsh-content-font-delta,0px));height:calc(28px + var(--dsh-content-font-delta,0px));color:var(--dsw-alias-label-tertiary);cursor:pointer;background:0 0;border:none;border-radius:28px;justify-content:center;align-items:center;padding:6px;display:inline-flex}',\n '.dsh-chapters_forkAction svg{width:calc(15px + var(--dsh-content-font-delta,0px));height:calc(15px + var(--dsh-content-font-delta,0px))}',\n '.dsh-chapters_forkAction:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary)}',\n '.dsh-chapters_forkAction:disabled{cursor:default;opacity:.4}',\n '.dsh-chapters_forkNote{color:var(--dsw-alias-label-tertiary);padding-left:4px;font-size:13px;line-height:20px}',\n 'button[class*=\"_action\"][aria-label=\"Branch into a new conversation\"],button[class*=\"_action\"][aria-label=\"\\u5728\\u65b0\\u5bf9\\u8bdd\\u4e2d\\u5206\\u652f\"]{display:none !important}',\n].join('\\n')\nconst CSS_TAG_ID = 'dsh-chapters/assistant-action.css'\n\nfunction injectStyles(): void {\n try {\n if (typeof document === 'undefined' || document.querySelector(`style[data-plugin-css=${JSON.stringify(CSS_TAG_ID)}]`) !== null) return\n const tag = document.createElement('style')\n tag.dataset.plugin = 'dsh-chapters'\n tag.dataset.pluginCss = CSS_TAG_ID\n tag.textContent = CSS\n document.head.appendChild(tag)\n } catch { /* styling is cosmetic; the action still works without it */ }\n}\n\n/** One component body for every door row; only the label and glyph differ. */\nfunction makeDoorAction(door: DoorAction) {\n return function ChaptersDoorAction({ run }: DoorActionProps) {\n const [busy, setBusy] = useState(false)\n const [note, setNote] = useState<string | null>(null)\n const alive = useRef(true)\n useEffect(() => () => { alive.current = false }, [])\n const onClick = useCallback(() => {\n if (busy) return\n setBusy(true)\n setNote(null)\n void run().then((result) => {\n if (!alive.current) return\n setBusy(false)\n setNote(result.message === '' ? null : result.message)\n setTimeout(() => { if (alive.current) setNote(null) }, 8000)\n })\n }, [busy, run])\n const button = h('button', {\n type: 'button',\n 'aria-label': door.label,\n disabled: busy,\n onClick,\n className: 'dsh-chapters_forkAction',\n },\n h('svg', { viewBox: '0 0 16 16', width: '15', height: '15', fill: 'currentColor', 'aria-hidden': 'true' },\n h('path', { d: door.iconPath })))\n return h('span', { style: { display: 'inline-flex', alignItems: 'center', gap: '4px' } },\n h(Tooltip, { label: door.label, side: 'bottom' as const, children: button as ReactElement<Record<string, unknown>> }),\n note !== null\n ? h('span', { className: 'dsh-chapters_forkNote' }, note)\n : null)\n }\n}\n\nexport function apply(ctx: Ctx): void {\n injectStyles()\n const slots = ctx.slots\n const remote = (ctx.get('remote') ?? {}) as ClientRemote\n let sessions: ClientSessions | undefined\n try { sessions = ctx.get('sessions') as ClientSessions | undefined } catch { sessions = undefined }\n // Defensive: a host that cannot provide the navigator must leave the door registering and\n // reporting its own limitation, not failing at boot.\n let uiWorkspace: { openSession?: (id: string) => unknown } | undefined\n try { uiWorkspace = ctx.get('uiWorkspace') as { openSession?: (id: string) => unknown } | undefined } catch { uiWorkspace = undefined }\n // No early return on missing descriptors: the buttons register regardless\n // and the click reports the gap. A missing button is worse than a button\n // that explains itself. Components and runners are built ONCE here — slot\n // factories may re-run, and a fresh component identity per call would\n // remount the row.\n // #54: the switch verifier. `dsh.sessions.current` is published by the host's own\n // dsh-client-ui-workspace (grep-confirmed, not folklore) — the same key the e2e specs\n // read to name a session exactly, which is the point: the door now checks the fact the\n // user would notice, not whether a void call threw.\n const currentSessionId = (): string | null => {\n try {\n const raw = window.localStorage.getItem('dsh.sessions.current')\n if (raw === null) return null\n const parsed = JSON.parse(raw) as { sessionId?: string } | null\n return parsed?.sessionId ?? null\n } catch { return null }\n }\n const rows = DOOR_ACTIONS.map((door) => ({\n door,\n run: makeDoorRunner(remote, sessions, door, async (child) => {\n for (let i = 0; i < 8; i++) {\n if (currentSessionId() === child) return true\n await new Promise((r) => setTimeout(r, 250))\n }\n return currentSessionId() === child\n }, { ...(uiWorkspace !== undefined ? { navigator: uiWorkspace } : {} ) }),\n component: makeDoorAction(door),\n }))\n slots.inject('conversation.chat.assistant-actions', () => rows.map(({ door, run, component }) => slots.register({\n name: 'conversation.chat.assistant-actions',\n id: door.id,\n order: door.order,\n inject: (sessionId: string) => ({ run: () => run(sessionId) }),\n }, component)))\n slots.inject('conversation.input.dock', () => slots.register({\n name: 'conversation.input.dock',\n id: 'chapters-prompt',\n // Under every other dock entry and directly above the composer card: todo 0, goal 10, queue 20, and the\n // host sorts ascending by priority then order (measured in dsh-client-ui-slots).\n order: 30,\n inject: (sessionId: string) => ({\n // The bar is a mouth for the command plane, and the ONE wire body in `doors.ts` carries it: a plugin has\n // no declarable RPC namespace, so this pull transport is why the bar is push-shaped and pull-fed.\n exec: (line: string) => runCommandText(remote, sessionId, line),\n }),\n }, PromptDock))\n}\n"],"mappings":"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"}
|
package/lib/commands.js
CHANGED
|
@@ -380,8 +380,26 @@ export function registerHostCommands(ctx, domain, config) {
|
|
|
380
380
|
? 'prompt model override cleared — the config default is unset, so suggestions are OFF'
|
|
381
381
|
: `prompt model override cleared — now following the config default (${spec}, ON)` };
|
|
382
382
|
}
|
|
383
|
+
// #188: the caps ride WITH the model — `/chapters-prompt model <id> [maxChars [contextChars]]`.
|
|
384
|
+
// Explicit numbers store beside prompt.model in the settings table; omitted numbers derive from the
|
|
385
|
+
// selected model's real context window (window × promptContextRatio) at suggestion time, and the
|
|
386
|
+
// config keys are the no-window fallback. Resolution and sources: `/chapters-prompt state`.
|
|
387
|
+
const maxArg = parts[2];
|
|
388
|
+
const ctxArg = parts[3];
|
|
389
|
+
if (parts.length > 4 || (maxArg !== undefined && !/^[1-9][0-9]*$/.test(maxArg)) || (ctxArg !== undefined && !/^[1-9][0-9]*$/.test(ctxArg))) {
|
|
390
|
+
return { kind: 'error', text: `model takes <provider/model> [maxChars [contextChars]] — positive integers (got: ${parts.slice(1).join(' ')})` };
|
|
391
|
+
}
|
|
383
392
|
await config.prompt.setModel(arg);
|
|
384
|
-
|
|
393
|
+
if (config.prompt.setCaps !== undefined && (maxArg !== undefined || ctxArg !== undefined)) {
|
|
394
|
+
await config.prompt.setCaps({
|
|
395
|
+
...(maxArg !== undefined ? { maxChars: maxArg } : {}),
|
|
396
|
+
...(ctxArg !== undefined ? { contextChars: ctxArg } : {}),
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
const capsNote = maxArg !== undefined || ctxArg !== undefined
|
|
400
|
+
? ` Caps stored beside the model${maxArg !== undefined ? `: suggestion ${maxArg} chars` : ''}${ctxArg !== undefined ? `${maxArg !== undefined ? ',' : ':'} context budget ${ctxArg} chars` : ''}.`
|
|
401
|
+
: " Caps derive from this model's context window (window × promptContextRatio); pass numbers to pin them.";
|
|
402
|
+
return { kind: 'success', text: `prompt model set to ${arg} — this ENABLES the composer ghost (one bounded call per turn boundary; /chapters-prompt off disables).${capsNote}` };
|
|
385
403
|
}
|
|
386
404
|
if (verb === 'off') {
|
|
387
405
|
await config.prompt.setModel('');
|
|
@@ -476,7 +494,7 @@ export function registerHostCommands(ctx, domain, config) {
|
|
|
476
494
|
const events = (sess.snapshotEvents?.() ?? []);
|
|
477
495
|
const titleEvent = [...events].reverse().find((e) => e.type === 'session/title');
|
|
478
496
|
const title = titleEvent !== undefined && typeof titleEvent.data.title === 'string' ? titleEvent.data.title : undefined;
|
|
479
|
-
const r = await config.prompt.suggest({ id: sess.id, title, events: [...events] });
|
|
497
|
+
const r = await config.prompt.suggest({ id: sess.id, title, cwd: cwdOf(invocation.agent), events: [...events] });
|
|
480
498
|
// #113: this returned `text: r.text` — ONE bare line, which the composer ellipsized with no way to
|
|
481
499
|
// recover it. /chapters-status gets an expander because it answers with a labelled multi-line block,
|
|
482
500
|
// so this does the same: the prompt verbatim inside a fence, then the facts that bound it (which
|
|
@@ -484,11 +502,11 @@ export function registerHostCommands(ctx, domain, config) {
|
|
|
484
502
|
// over-cap answer is refused and retried, never truncated — the number belongs in the output.
|
|
485
503
|
const F = String.fromCharCode(96).repeat(3);
|
|
486
504
|
const { spec, source } = config.prompt.effective();
|
|
487
|
-
const lim = config.prompt.limits !== undefined ? config.prompt.limits() : undefined;
|
|
505
|
+
const lim = config.prompt.limits !== undefined ? await config.prompt.limits(null) : undefined;
|
|
488
506
|
const who = `${title !== undefined && title !== '' ? `\u201c${title}\u201d ` : ''}(${sess.id})`;
|
|
489
507
|
const capLine = lim !== undefined
|
|
490
|
-
? `
|
|
491
|
-
: '
|
|
508
|
+
? ` caps: suggestion ${String(lim.maxChars)} chars (${lim.sources.maxChars}) · context budget ${String(lim.contextChars)} chars (${lim.sources.contextChars}) — over-cap is refused and retried, never truncated`
|
|
509
|
+
: ' caps: (not reported by this surface)';
|
|
492
510
|
const meta = ` model: ${spec !== '' ? spec : 'unset'} (${source})\n${capLine}`;
|
|
493
511
|
if (r.ok) {
|
|
494
512
|
return { kind: 'success', text: [
|