@treeseed/dsh-chapters 0.2.4 → 0.2.6

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.
@@ -0,0 +1,248 @@
1
+ # The autorun guide — goal-directed sessions that keep themselves moving
2
+
3
+ > Status: draft for the maintainer's process review (#186). Promotion to the documentation site
4
+ > (a numbered section beside §28) happens after that review. Every command verb, config key, and
5
+ > default named here was read out of the shipped source at 0.2.3, not remembered.
6
+
7
+ ## 1. What this system is for
8
+
9
+ A coding agent stops when its turn ends. Everything in this guide exists to close the gap between
10
+ "the work needs forty turns" and "a human is pressing Enter forty times" — without handing the
11
+ machine unchecked authority over the objective:
12
+
13
+ - **The goal** is the standing directive: what done means, and the stopping rules around it. The
14
+ host's goal driver re-prompts the model at every turn boundary until the goal is complete,
15
+ blocked, cleared, or its round budget is spent.
16
+ - **The Suggested Next Step bar** is a second model — the *prompt model*, whose whole job is to
17
+ write the ONE next instruction — consulting the session's live tail and its archived-chapter
18
+ summaries after each completed turn.
19
+ - **Autorun** is the clutch between them: armed, each suggestion runs itself as a human-authored
20
+ turn, so the session keeps working without you. With a goal armed, arming autorun *displaces*
21
+ the goal driver's own continuation — the goal remains the objective, but the suggestion loop
22
+ becomes the engine. Without a goal, autorun simply keeps iterating suggestions.
23
+ - **Chapters** make the loop sustainable: when context pressure arrives, the conversation is
24
+ archived verbatim into chapters and the work continues in a fresh session seeded with nothing
25
+ but a table of contents. A 32K-context model can run a hundred-turn objective because its
26
+ context is renewable.
27
+ - **Enrichment and rule distillation** make the loop *learn*: idle-time model passes annotate the
28
+ archived chapters (title, summary, topics — so retrieval works), and mine them for standing
29
+ orders (rules with an honest warrant: `stated` only when a human literally said it, `inferred`
30
+ when the model judged it useful). Rules then assemble into every future task's context.
31
+
32
+ The two-by-two that orients everything else:
33
+
34
+ | | no autorun | autorun armed |
35
+ |---|---|---|
36
+ | **no goal** | ordinary chat; the bar offers suggestions, you press Run (or ignore them) | the suggestion loop iterates on its own, up to its round budget |
37
+ | **goal armed** | the host's goal driver re-prompts each turn boundary (rounds cascade until complete/blocked/cleared/budget) | the suggestion loop drives instead — the goal driver's continuation is displaced, the goal stays the objective |
38
+
39
+ ## 2. How it works, turn by turn
40
+
41
+ 1. A turn completes. If a prompt model is set, it is asked for the next step: one imperative
42
+ instruction, at most `promptMaxChars` (default **240** today) characters, composed from a
43
+ `promptContextChars` (default **4000**) budget covering the session's chapter summaries, the
44
+ live conversation tail, and the active goal. An over-cap answer is *refused, never truncated* —
45
+ a suggestion you can't see whole is a suggestion you can't judge. As of 0.2.4 (#188, shipped): the suggestion cap defaults to **4096** characters, and both caps
46
+ belong to the model — `/chapters-prompt model <provider/model> [maxChars] [contextChars]` stores
47
+ explicit numbers beside the model, and with no explicit numbers the context budget **derives from
48
+ the model's real window** (`window × promptContextRatio`, default 0.5, at 4 chars/token — a 32K
49
+ model and a 1M model get budgets 30× apart from one rule). The input itself became the session's
50
+ memory: the goal whole, the active plan, the core rules, every chapter on the ancestor path as
51
+ title + detailed summary + every plot line, and the last turn whole — with **zero truncation** as
52
+ an invariant: over budget, the oldest chapter drops (announced) until the history fits, and a
53
+ protected set that cannot fit refuses with the numbers instead of sending a starved input.
54
+ 2. The offer is classified. Plain text = a **worker-step** (autorun may fire it). A `GOAL:`
55
+ prefix = a **goal-edit**: the model is proposing to re-cut the objective itself. Autorun must
56
+ never act on a goal-edit — the host reserves objective changes for direct human turns — so it
57
+ waits in the bar for you to read and Run yourself.
58
+ 3. If autorun is armed and the offer is a worker-step, the loop fires **at most one turn per
59
+ offer**, submitted through the same path as the bar's Run button: a human-authored user turn.
60
+ Fired rounds count against `promptMaxRounds` (default **24** per session); offers nobody armed
61
+ never spend the budget.
62
+ 4. Arming autorun while a goal is active calls the host's goal *disarm* seam: the goal driver
63
+ stops re-injecting its rounds while the goal itself stays durable and unchanged (phase,
64
+ objective, revision all intact). You can see it: the goal bar's `Pause goal` becomes
65
+ `Resume goal`. One engine owns each turn boundary — never two.
66
+ 5. Context pressure arrives eventually; that is the design, not a failure. The host's relief
67
+ compacts the surface, chapters archive the span verbatim (oversized tool results defer to
68
+ content-addressed artifacts), and a continuation opens with the TOC notice as its only seed.
69
+ The goal rides the session tree into the continuation; **autorun does not** — armed state is
70
+ process-local per session and nothing arrives armed after a restart or in a child. Re-arm
71
+ deliberately; a loop that starts turns must never resume itself.
72
+ 6. While the session is idle, enrichment drains its queue in batches (`enrichmentIdleMs`,
73
+ `enrichmentBatchCap`): one structured call per archived chapter asks for
74
+ `{title, summary, topics, rules[]}` as validated JSON — retry once, and on second failure the
75
+ deterministic values stand (a slow or failing model never blocks or blanks a chapter). The
76
+ chapter's body is never touched; annotations land in frontmatter with a `generated:`
77
+ provenance chain.
78
+ 7. Mining rides the same call (or a dedicated one when `rules.model` is set — one extra bounded
79
+ call per chapter, which is exactly what you pay a better model for). Each candidate rule
80
+ carries a `quote`; the quote is checked **verbatim against human bytes only** — a quote found
81
+ in assistant prose is the model restating itself, never evidence you said it. That decides the
82
+ warrant: `basis: stated` (your words are behind it) or `basis: inferred` (the model judged it
83
+ useful and nobody said it). With `rulesAutoActivate` on (the default), mined rules govern the
84
+ session tree on arrival and `revoke` is the undo; with it off, they are held `reviewOnly`
85
+ until this machine approves them.
86
+ 8. Rules close the loop: at the start of every task the model can assemble the standing orders in
87
+ force (`chapters_rules`), and rebuilt heads render the core set — so what the loop learned
88
+ yesterday shapes what the loop suggests tomorrow.
89
+
90
+ ## 3. Before you start
91
+
92
+ 1. **Install/update** the plugin in your harness profile and restart the harness:
93
+ `npm i @treeseed/dsh-chapters@latest` (site §23 covers every path).
94
+ 2. **Optionally link a knowledge pool** so chapters and project rules are shared across machines:
95
+ `/chapters-link <path-or-url>`. Everything in this guide works without it; with it, project
96
+ rules and the archive travel.
97
+ 3. **Pick your models** — this is what `/chapters-models` exists for:
98
+
99
+ ```
100
+ /chapters-models # providers + advertised models as paste-ready ids
101
+ /chapters-models <provider> <model> # one route: context window, reasoning efforts
102
+ ```
103
+
104
+ The footer shows what each of the three settings currently resolves to. Three knobs, three
105
+ different jobs:
106
+
107
+ | setting | command | job | default |
108
+ |---|---|---|---|
109
+ | annotation | `/chapters-enrich model <provider/model|clear>` | summarize archived chapters for retrieval | unset = rides the conversation route |
110
+ | mining | `/chapters-rule model <provider/model|clear>` | distill standing orders — judgment-heavy | unset = rides the annotation call |
111
+ | prompt | `/chapters-prompt model <provider/model>` | **defines the next work**; setting it IS enabling the bar | unset = the feature does not exist |
112
+
113
+ The bar's visibility rule is exactly this setting: with no prompt model, the feature does not
114
+ exist anywhere; the moment one is set, the bar appears docked above the composer (its own dock
115
+ slot, order 30) and lives there per session — `Analyzing…` after each completed turn, then
116
+ `Recommended Next Step`. `/chapters-prompt off` hides it for the session (`on` brings it back),
117
+ and an idle bar stays visible on purpose: "live, nothing to say yet" must not look like
118
+ "disabled".
119
+
120
+ Tuning intuition: annotation is volume work — a cheap, fast model is right. Mining reads
121
+ between the lines for instructions worth keeping — this is where a stronger model earns its
122
+ extra call. The prompt model writes the instruction that *becomes the next turn*; under
123
+ autorun it is effectively the loop's director, so give it the best model you can afford.
124
+ 4. **Decide your review posture.** `rulesAutoActivate: true` (default) means mined rules govern
125
+ on arrival — fast, and `revoke` undoes any mistake per machine. Setting it `false` holds every
126
+ mined rule inert until `/chapters-rule approve <id>` — right for shared pools where a
127
+ mis-generalization would bind teammates. Config keys live on the preset row in your profile's
128
+ `cordis.patch.yml` (site §16).
129
+
130
+ ## 4. The arc: from a need to a completed goal
131
+
132
+ **Phase 1 — Identify the work.** Either bring a decision you made outside the session ("we're
133
+ migrating the auth layer; here's the plan") or think it through with the model by ordinary
134
+ conversation. Either is fine; what matters is that the objective ends up stated in words a
135
+ stranger could judge completion by. When the model needs a decision from you it should ask with
136
+ the question tool — which suspends until you answer — rather than burying a question in prose
137
+ and proceeding on a guess.
138
+
139
+ **Phase 2 — Arm the goal.** Say it plainly: *"Create a goal: <objective>."* The model calls
140
+ `create_goal`; the host's goal bar appears with `Pause goal`. Two numbers matter:
141
+ `max_goal_rounds` caps the automatic continuation rounds (default **256** — a round is itself a
142
+ turn, so rounds cascade until something stops them); ask for a smaller budget when the objective
143
+ is bounded. The goal can be edited, paused, resumed, completed, or marked blocked through the
144
+ model (`update_goal`) — blocked requires the same concrete condition to persist for at least
145
+ three consecutive rounds, so it can't be used to give up early.
146
+
147
+ **Phase 3 — Hand over the wheel.** With the bar's model set (step 3 above), arm the loop:
148
+
149
+ ```
150
+ /chapters-prompt autorun on
151
+ ```
152
+
153
+ The reply tells you which engine you got: with a goal armed it reports the displacement ("the
154
+ goal remains the objective; its own auto-continuation is disarmed, so the suggestion loop
155
+ drives"), and the goal bar flips to `Resume goal`. Autorun needs a prompt model — arming without
156
+ one is refused, loudly.
157
+
158
+ **Phase 4 — The loop.** Now watch it work. The bar is your primary surface here — everything
159
+ per-turn happens on it, and the commands you typed in Phase 3 were configuration, not the
160
+ workflow. After each completed turn the bar shows *Analyzing…* then *Recommended Next Step*;
161
+ expand it to read the offer in full; **Run** submits it as your own turn; **Autorun** arms the
162
+ loop so worker-step offers run themselves; **Off** hides the bar for the session. Armed autorun
163
+ fires worker-steps as human-authored turns; pressure points become checkpoints and continuations;
164
+ idle time annotates and mines. Your role shifts from author to editor — which brings us to the
165
+ situations.
166
+
167
+ **Phase 5 — Steering (everything in between).** See the table in §5. The one rule of thumb:
168
+ **typing anything takes the wheel.** A human turn disarms autorun immediately (the offer stays
169
+ visible, and the reply says why), so interrupting is always safe and never loses the suggestion.
170
+
171
+ **Phase 6 — Completion.** The model marks the goal complete only when the objective is actually
172
+ achieved, with evidence gathered before the claim; you'll get a closing report. Or it marks
173
+ blocked with the concrete persisting condition — and **blocked disarms autorun exactly like
174
+ complete does** (0.2.4, #188: both planes stop together; a loop firing offers at a dead goal
175
+ outlived its purpose). You can end it yourself any time (`/goal clear`, or Pause on the goal
176
+ bar). While a goal is active or paused, the round budget **stands down** — the pursuit is bounded
177
+ by the goal's lifecycle, never cut off by `promptMaxRounds`. With no goal (or after one is
178
+ cleared), an armed loop keeps iterating suggestions within the budget (default 24): open-ended
179
+ iteration is a feature, but know it's there — the last thing you do is usually `autorun off`.
180
+
181
+ **Phase 7 — After.** The work outlives the session: chapters are searchable (`chapters_search`
182
+ for the model, the pool for your team if linked), artifacts remain addressable by hash, and mined
183
+ rules persist — session-tier ones stay machine-local and bind that session tree; promote a good
184
+ one to the shared tier with `/chapters-rule add <category> "<text>"` (a new project-tier rule)
185
+ and `approve` it per machine.
186
+
187
+ ## 5. Situations and possible actions
188
+
189
+ | Situation | What's happening | Action |
190
+ |---|---|---|
191
+ | The suggestion is wrong | the prompt model misread the state | Just type your own instruction — a human turn disarms autorun and the offer stays visible. Or Run a different pace: `autorun off`, steer manually |
192
+ | The suggestion starts with `GOAL:` | the director proposes re-cutting the objective — autorun will never fire these | Read it. If right, press Run yourself (objective changes need a human turn — that's the host's rule, not a limitation of the loop). If wrong, ignore it; the next turn gets a fresh offer |
193
+ | The loop is circling | offers repeat variations; no state changes | `autorun off`, then either re-cut the goal (ask the model to `update_goal` edit) or give one concrete instruction that breaks the cycle |
194
+ | The objective is stale | the work changed shape | Ask the model to edit the goal (a revision bump invalidates outstanding offers — last cycle's directive can't fire against a re-cut objective), or `/goal clear` and create a new one |
195
+ | It won't stop | rounds cascade by design | Three brakes, finest first: `autorun off` (loop), `Pause goal` / ask to pause (driver), round budgets (`promptMaxRounds` 24, `max_goal_rounds` at create) |
196
+ | A mined rule is wrong or over-general | inference happens; that's why the warrant exists | `/chapters-rule list --scope session` → `view <id>` (full text + basis) → `revoke <id>`. Machine-local, one command, file bytes untouched |
197
+ | A mined rule is good and general | worth binding beyond this tree | `/chapters-rule add <category> "<text>"` creates a project-tier proposal; `approve <id>` makes it core on your machine; the pool carries it to teammates |
198
+ | You keep repeating yourself | say it once, durably | Start a line with `ADD RULE: <text>` in your own message — deterministic capture, zero model tokens, active for your session tree, exactly your words |
199
+ | Mined rules worry you | shared pool, high stakes | `rulesAutoActivate: false` in config → REVIEW posture: every mined rule waits inert for `approve`; `/chapters-rule active` shows the posture and what a rebuilt head will render |
200
+ | Annotations are shallow | annotation model too weak, or wrong for the corpus | `/chapters-enrich model <better/id>`; re-run with `/chapters-enrich run` |
201
+ | Mining misses the point | mining rides a cheap annotation call | `/chapters-rule model <stronger/id>` — one extra bounded call per chapter, the intended lever |
202
+ | Enrichment backlog | idle batches haven't drained | `/chapters-enrich report` (status + pending count), `/chapters-enrich run` (drain now) |
203
+ | Session feels long/slow | pressure is normal; relief is automatic | Nothing required — checkpoints and continuations are the design. Optionally `/chapters-save` (archive without a child) or the message doors: **Continue with chapters** (same line of work, fresh context) / **Fork with chapters** (branch an experiment; the parent stays untouched) |
204
+ | The bar is in the way | you want a quiet session | `/chapters-prompt off` hides it; `on` brings it back. Off is per session |
205
+ | Something looks broken | sync, store, or diagnostics | `/chapters-status` (prints the whole diagnostics block in every state); `/chapters-repair` for archive damage (supersedes, never rewrites); `/chapters-rule conflicts` for contradictory standing orders (report-only — it never edits a rule) |
206
+ | Restarted the harness | armed state is process-local by design | The goal persists (durable); autorun does not (nothing arrives armed — a loop that starts turns must never resume itself). Re-arm deliberately |
207
+
208
+ ## 6. Command summary
209
+
210
+ ```
211
+ Discovery /chapters-models [<provider> [<model>]]
212
+ Suggestions /chapters-prompt model <provider/model> | on | off | state | autorun on|off
213
+ /chapters-prompt suggest (compose a fresh offer now — the pull the bar rides)
214
+ /chapters-prompt run (submit the offer on screen — the bar's Run button as a command;
215
+ Enrichment /chapters-enrich run | model <provider/model|clear> | report
216
+ Rules /chapters-rule add <category> <text> | list [--all|--proposed|--category <c>|--scope project|session]
217
+ /chapters-rule view <id> | active | approve <id> | revoke <id> | pin <id> | unpin <id>
218
+ /chapters-rule model [<provider/model|clear>] | conflicts
219
+ Archive & health /chapters-status | /chapters-save | /chapters-repair | /chapters-link
220
+ both are session-scoped: "the offer on screen" is a per-session fact)
221
+ Doors (UI) Continue with chapters · Fork with chapters · the Suggested Next Step bar (expand, Run, Autorun, Off)
222
+ Goals (host) the goal bar (Pause/Resume) · /goal · the model's create_goal / get_goal / update_goal tools
223
+ Marker (chat) ADD RULE: <text> — at the start of a line in your own message
224
+ ```
225
+
226
+ Config keys you're most likely to touch (preset row in `cordis.patch.yml`, site §16):
227
+ `promptMaxRounds` (24), `promptContextChars` (4000), `promptMaxChars` (240), `rulesAutoActivate`
228
+ (true), `rulesEnrichment` (true — mining on/off), `enrichmentEnabled` (true),
229
+ `enrichmentIdleMs` / `enrichmentBatchCap` / `enrichmentMaxTokens`.
230
+
231
+ ## 7. Honest limits (stated, not buried)
232
+
233
+ - **Run submits as-is.** No current host lets a docked plugin write the composer draft, so
234
+ click-to-edit a suggestion before running it is not available — the bar explains this where you'd
235
+ expect editing, and Run submits (#126 tracks the host ask). To change a suggestion: turn autorun
236
+ off and type your own version.
237
+ - **Autorun never fires a `GOAL:` offer** — objective changes are human turns by host design.
238
+ - **Nothing arrives armed.** Autorun is per-session, process-local; restarts and continuations
239
+ start disarmed. The goal is the durable half of the pair.
240
+ - **A blocked or completed goal renders no strip** in the goal bar — if the bar vanished, the goal
241
+ reached a terminal state; `/chapters-prompt state` reports the whole picture (suggestion, model,
242
+ autorun, intent, goal) in one block.
243
+ - **Displacement needs a host seam.** Where `GoalService.disarm` is absent, arming autorun
244
+ degrades safely: the loop yields to the goal driver instead of racing it, and the command's
245
+ reply tells you which behavior you got.
246
+ - **The loop is as good as the prompt model.** Under autorun that model is the director of every
247
+ next step — the single most consequential of the three settings. `/chapters-models <provider>
248
+ <model>` shows its context window before you commit.
package/docs/contract.md CHANGED
@@ -604,7 +604,17 @@ and does not let a plugin do, with anchors:
604
604
  `{value.result.kind/text}`). New **wire events are NOT available to plugins**: the forwarded
605
605
  allowlist is app-owned ("the one home of this application's forwarded-Host-event allowlist",
606
606
  `packages/api/remotes/src/remote-events.ts:24`).
607
- - **Push transport is 0.2.x-only.** `appendPluginRecord(session, 'plugin:<name>', data)`
607
+ **Every execution is recorded** (measured, `packages/interaction/commands/src/index.ts:365-385`):
608
+ `command/run` + `command/done` lifecycle events, log-only (never model surface) but rendered as
609
+ history nodes by the web UI; `recordInput: false` (definition flag, `index.ts:71-75`) suppresses
610
+ only the `args` payload, NOT the node pair. There is no quiet-invocation mode — a polling client
611
+ on this wire writes visible history every pull, which is why the bar's cadence is a tested
612
+ decision (#193) and the push swap (#194) is the structural fix.
613
+ - **Push transport is 0.2.1+ (measured 2026-10-30, #193 — this line said "0.2.x-only" and was
614
+ wrong):** `appendPluginRecord` has **zero occurrences** at tag `dsh-v0.2.0-rc.2`
615
+ (`packages/core/session/src/index.ts`) and is present at `dsh-v0.2.1-rc.2` (`:492`). The pinned
616
+ rig host (0.2.0-rc.2) therefore CANNOT serve the push swap; #194 gates on a host-pin bump plus
617
+ feature detection with pull fallback. `appendPluginRecord(session, 'plugin:<name>', data)`
608
618
  (`packages/core/session/src/index.ts:492`, grammar/types at `types.ts:525-543`, reader
609
619
  `pluginRecordOf` at `index.ts:508`): durable, append-only, "never enters the model-visible
610
620
  surface", retained by fork/resume, skipped by unknown readers. The `verify-plugin-record-callers`
package/lib/client.js CHANGED
@@ -105,6 +105,23 @@ window.__ModuleLoader__.load({
105
105
  if (s.status === "ready" || s.status === "failed") return "Recommended Next Step";
106
106
  return null;
107
107
  }
108
+ /**
109
+ * #193: the pull cadence, as a decision instead of an interval constant. Every pull is two recorded
110
+ * host lifecycle events (`command/run` + `command/done`) the UI renders as history nodes — cadence
111
+ * IS the litter, so it belongs in this module with a row for every arm. Active = the state can
112
+ * plausibly change on a short clock (generation in flight, an armed loop to fire, a turn running
113
+ * that will end into an offer). Quiet = stable; one pull a minute is plenty for a state that only
114
+ * changes at turn boundaries. Hidden-and-quiet = pull NOTHING (a background tab should write zero
115
+ * history); hidden-and-active still pulls, because walking away from a running loop is the point of
116
+ * autorun and pausing it silently would be the loop dying with nobody told.
117
+ */
118
+ const ACTIVE_POLL_MS = 1e4;
119
+ const QUIET_POLL_MS = 6e4;
120
+ function pollSpacingFor(s) {
121
+ const active = s.status === "analyzing" || s.autorun === "armed" || s.running;
122
+ if (!active && s.hidden) return null;
123
+ return active ? ACTIVE_POLL_MS : QUIET_POLL_MS;
124
+ }
108
125
  function barVisible(s) {
109
126
  if (s.status === "off") return false;
110
127
  if (s.status === "ready" && (s.text === null || s.text === "")) return false;
@@ -310,9 +327,6 @@ window.__ModuleLoader__.load({
310
327
  const PULL_TIMEOUT_MS = 15e3;
311
328
  /** Retry ceiling for the FIRST pull only, in attempts and spacing. A mount-time race must not be able to
312
329
  * starve the bar forever, and a real `off` answer stops the loop on the first reply either way. */
313
- /** Cadence of the keep-poll. A local RPC that answers in milliseconds; the cost is one command per
314
- * interval, and it ends the moment the state is `off`. */
315
- const POLL_SPACING_MS = 1e4;
316
330
  function execWithTimeout(exec, line, ms) {
317
331
  return new Promise((resolve, reject) => {
318
332
  const timer = setTimeout(() => {
@@ -341,6 +355,7 @@ window.__ModuleLoader__.load({
341
355
  alive.current = false;
342
356
  }, []);
343
357
  const [busy, setBusy] = (0, react.useState)(false);
358
+ const kick = (0, react.useRef)(void 0);
344
359
  const answered = (0, react.useRef)(false);
345
360
  const replyStatus = (0, react.useRef)("unknown");
346
361
  const stateRef = (0, react.useRef)(initialBarState());
@@ -380,22 +395,43 @@ window.__ModuleLoader__.load({
380
395
  pulled.current = true;
381
396
  let timer;
382
397
  const tick = async () => {
383
- await refresh();
398
+ const spacing = pollSpacingFor({
399
+ status: replyStatus.current,
400
+ autorun: stateRef.current.autorun,
401
+ running: stateRef.current.running,
402
+ hidden: typeof document !== "undefined" && document.hidden === true
403
+ });
404
+ if (spacing !== null) await refresh();
384
405
  if (replyStatus.current === "off") return;
385
406
  timer = setTimeout(() => {
386
407
  tick();
387
- }, POLL_SPACING_MS);
408
+ }, spacing ?? 6e4);
409
+ };
410
+ kick.current = () => {
411
+ if (timer !== void 0) clearTimeout(timer);
412
+ tick();
413
+ };
414
+ const onVisible = () => {
415
+ if (typeof document !== "undefined" && !document.hidden) kick.current?.();
388
416
  };
417
+ if (typeof document !== "undefined") document.addEventListener("visibilitychange", onVisible);
389
418
  tick();
390
419
  return () => {
391
420
  if (timer !== void 0) clearTimeout(timer);
421
+ kick.current = void 0;
422
+ if (typeof document !== "undefined") document.removeEventListener("visibilitychange", onVisible);
392
423
  };
393
424
  }, [refresh]);
425
+ const prevRunning = (0, react.useRef)(props.running ?? true);
394
426
  (0, react.useEffect)(() => {
427
+ const next = props.running ?? true;
428
+ const prev = prevRunning.current;
429
+ prevRunning.current = next;
395
430
  dispatch({
396
431
  t: "running",
397
- running: props.running ?? true
432
+ running: next
398
433
  });
434
+ if (prev && !next) kick.current?.();
399
435
  }, [props.running]);
400
436
  const run = (0, react.useCallback)(() => {
401
437
  if (state.text === null || state.text === "" || exec === void 0) return;
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 // #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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"}
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\n/**\n * #193: the pull cadence, as a decision instead of an interval constant. Every pull is two recorded\n * host lifecycle events (`command/run` + `command/done`) the UI renders as history nodes — cadence\n * IS the litter, so it belongs in this module with a row for every arm. Active = the state can\n * plausibly change on a short clock (generation in flight, an armed loop to fire, a turn running\n * that will end into an offer). Quiet = stable; one pull a minute is plenty for a state that only\n * changes at turn boundaries. Hidden-and-quiet = pull NOTHING (a background tab should write zero\n * history); hidden-and-active still pulls, because walking away from a running loop is the point of\n * autorun and pausing it silently would be the loop dying with nobody told.\n */\nexport const ACTIVE_POLL_MS = 10_000\nexport const QUIET_POLL_MS = 60_000\n\nexport function pollSpacingFor(s: { status: PromptBarStatus; autorun: PromptBarAutorun; running: boolean; hidden: boolean }): number | null {\n const active = s.status === 'analyzing' || s.autorun === 'armed' || s.running\n if (!active && s.hidden) return null\n return active ? ACTIVE_POLL_MS : QUIET_POLL_MS\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 pollSpacingFor, QUIET_POLL_MS, type PromptBarStatus,\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\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 // #193: the mount effect's reschedule handle, so the idle edge and the visibility edge can pull NOW\n // instead of waiting out a possibly-quiet cadence. Undefined while the poll is not mounted.\n const kick = useRef<(() => void) | undefined>(undefined)\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, at a cadence that is a DECISION (#193): every pull is two\n // recorded host lifecycle events (`command/run` + `command/done`) the UI renders as history nodes, so\n // the old unconditional 10s poll wrote ~360 visible nodes an hour into every bar-enabled session —\n // the maintainer's \"littered with chapters-prompt runs, one right after another\". `pollSpacingFor`\n // paces it: fast while state can change (analyzing / armed / running), slow while stable, and NOTHING\n // in a hidden tab unless the loop is armed. On hosts that publish no running prop, `running` stays\n // conservatively true and the cadence stays active — the old behavior, unchanged. `off` still ends it.\n let timer: ReturnType<typeof setTimeout> | undefined\n const tick = async (): Promise<void> => {\n const spacing = pollSpacingFor({\n status: replyStatus.current as PromptBarStatus,\n autorun: stateRef.current.autorun,\n running: stateRef.current.running,\n hidden: typeof document !== 'undefined' && document.hidden === true,\n })\n if (spacing !== null) 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\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() }, spacing ?? QUIET_POLL_MS)\n }\n // The idle edge and the tab-foreground edge both mean \"something just changed; look NOW\" — a scheduled\n // quiet pull is up to a minute stale, and staleness at exactly the moment an offer lands is how a bar\n // looks dead. Kick reschedules rather than stacks: it clears the pending timer first.\n kick.current = () => {\n if (timer !== undefined) clearTimeout(timer)\n void tick()\n }\n const onVisible = (): void => {\n if (typeof document !== 'undefined' && !document.hidden) kick.current?.()\n }\n if (typeof document !== 'undefined') document.addEventListener('visibilitychange', onVisible)\n void tick()\n return () => {\n if (timer !== undefined) clearTimeout(timer)\n kick.current = undefined\n if (typeof document !== 'undefined') document.removeEventListener('visibilitychange', onVisible)\n }\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 const prevRunning = useRef<boolean>(props.running ?? true)\n useEffect(() => {\n const next = props.running ?? true\n const prev = prevRunning.current\n prevRunning.current = next\n dispatch({ t: 'running', running: next })\n // #193: the IDLE edge — a turn just ended, which is precisely when a new offer is born. Pull now\n // instead of waiting out the cadence. Hosts that publish no running prop never produce the\n // transition, and their cadence stays active anyway.\n if (prev && !next) kick.current?.()\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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"}
@@ -59,7 +59,12 @@ export function goalDirectiveOf(goal) {
59
59
  if (goal === null)
60
60
  return 'No goal is active for this session. Recommend the next step the work itself calls for.';
61
61
  return [
62
- `An ACTIVE GOAL governs this session (revision ${String(goal.revision)}): ${goal.objective.slice(0, 600)}`,
62
+ // #188 invariant: the objective rides WHOLE. The slice(0, 600) that stood here was itself the
63
+ // truncation the contract forbids — real objectives exceed 600 chars (the one this repo's own
64
+ // session ran under was ~1,200), and a director steering against half an objective is the exact
65
+ // hallucination invite zero-truncation exists to forbid. The assembly's budget handles size:
66
+ // the goal block is protected, and a protected set that cannot fit refuses with the numbers.
67
+ `An ACTIVE GOAL governs this session (revision ${String(goal.revision)}): ${goal.objective}`,
63
68
  'The recommended next step MUST advance that goal — a step that merely continues current work does not count.',
64
69
  'If the goal is already accomplished, recommend the step that closes it out (mark it complete) rather than another round.',
65
70
  ].join('\n');
@@ -1 +1 @@
1
- {"version":3,"file":"prompt-state.js","sourceRoot":"","sources":["../src/prompt-state.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"prompt-state.js","sourceRoot":"","sources":["../src/prompt-state.ts"],"names":[],"mappings":"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"}
package/lib/version.d.ts CHANGED
@@ -6,4 +6,4 @@
6
6
  * reader cannot recover later, so it belongs in the bytes. A row in tests asserts this literal equals
7
7
  * `package.json`'s version — the one place a hand-mirrored constant must never be allowed to drift.
8
8
  */
9
- export declare const PLUGIN_VERSION = "0.2.4";
9
+ export declare const PLUGIN_VERSION = "0.2.6";
package/lib/version.js CHANGED
@@ -6,5 +6,5 @@
6
6
  * reader cannot recover later, so it belongs in the bytes. A row in tests asserts this literal equals
7
7
  * `package.json`'s version — the one place a hand-mirrored constant must never be allowed to drift.
8
8
  */
9
- export const PLUGIN_VERSION = '0.2.4';
9
+ export const PLUGIN_VERSION = '0.2.6';
10
10
  //# sourceMappingURL=version.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@treeseed/dsh-chapters",
3
- "version": "0.2.4",
3
+ "version": "0.2.6",
4
4
  "description": "dsh plugin: verbatim chapter archive + zero-token index for small-context models",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",