@sagmans/dsh-tui 0.3.0 → 0.4.0

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.
Files changed (113) hide show
  1. package/README.md +158 -6
  2. package/cordis.patch.yml +14 -0
  3. package/lib/agent/projections.d.ts +22 -6
  4. package/lib/agent/projections.d.ts.map +1 -1
  5. package/lib/agent/projections.js +21 -10
  6. package/lib/agent/projections.js.map +1 -1
  7. package/lib/agent/prompt-history.d.ts +89 -0
  8. package/lib/agent/prompt-history.d.ts.map +1 -0
  9. package/lib/agent/prompt-history.js +380 -0
  10. package/lib/agent/prompt-history.js.map +1 -0
  11. package/lib/agent/status.d.ts +6 -0
  12. package/lib/agent/status.d.ts.map +1 -1
  13. package/lib/agent/status.js +1 -0
  14. package/lib/agent/status.js.map +1 -1
  15. package/lib/herdr/client.d.ts +57 -0
  16. package/lib/herdr/client.d.ts.map +1 -0
  17. package/lib/herdr/client.js +218 -0
  18. package/lib/herdr/client.js.map +1 -0
  19. package/lib/herdr/constants.d.ts +93 -0
  20. package/lib/herdr/constants.d.ts.map +1 -0
  21. package/lib/herdr/constants.js +79 -0
  22. package/lib/herdr/constants.js.map +1 -0
  23. package/lib/herdr/reporter.d.ts +60 -0
  24. package/lib/herdr/reporter.d.ts.map +1 -0
  25. package/lib/herdr/reporter.js +217 -0
  26. package/lib/herdr/reporter.js.map +1 -0
  27. package/lib/herdr/state.d.ts +57 -0
  28. package/lib/herdr/state.d.ts.map +1 -0
  29. package/lib/herdr/state.js +62 -0
  30. package/lib/herdr/state.js.map +1 -0
  31. package/lib/index.d.ts.map +1 -1
  32. package/lib/index.js +252 -20
  33. package/lib/index.js.map +1 -1
  34. package/lib/input/actions.d.ts +1 -1
  35. package/lib/input/actions.d.ts.map +1 -1
  36. package/lib/input/actions.js +3 -0
  37. package/lib/input/actions.js.map +1 -1
  38. package/lib/input/ghost.d.ts +46 -0
  39. package/lib/input/ghost.d.ts.map +1 -0
  40. package/lib/input/ghost.js +58 -0
  41. package/lib/input/ghost.js.map +1 -0
  42. package/lib/input/keymap.d.ts.map +1 -1
  43. package/lib/input/keymap.js +2 -0
  44. package/lib/input/keymap.js.map +1 -1
  45. package/lib/input/submission.d.ts +24 -1
  46. package/lib/input/submission.d.ts.map +1 -1
  47. package/lib/input/submission.js +28 -1
  48. package/lib/input/submission.js.map +1 -1
  49. package/lib/stash/lock.d.ts +72 -0
  50. package/lib/stash/lock.d.ts.map +1 -0
  51. package/lib/stash/lock.js +374 -0
  52. package/lib/stash/lock.js.map +1 -0
  53. package/lib/stash/paths.d.ts +22 -0
  54. package/lib/stash/paths.d.ts.map +1 -0
  55. package/lib/stash/paths.js +83 -0
  56. package/lib/stash/paths.js.map +1 -0
  57. package/lib/stash/private-fs.d.ts +61 -0
  58. package/lib/stash/private-fs.d.ts.map +1 -0
  59. package/lib/stash/private-fs.js +379 -0
  60. package/lib/stash/private-fs.js.map +1 -0
  61. package/lib/stash/schema.d.ts +57 -0
  62. package/lib/stash/schema.d.ts.map +1 -0
  63. package/lib/stash/schema.js +127 -0
  64. package/lib/stash/schema.js.map +1 -0
  65. package/lib/stash/store.d.ts +92 -0
  66. package/lib/stash/store.d.ts.map +1 -0
  67. package/lib/stash/store.js +262 -0
  68. package/lib/stash/store.js.map +1 -0
  69. package/lib/stash.d.ts +91 -0
  70. package/lib/stash.d.ts.map +1 -0
  71. package/lib/stash.js +296 -0
  72. package/lib/stash.js.map +1 -0
  73. package/lib/theme-settings.d.ts +37 -8
  74. package/lib/theme-settings.d.ts.map +1 -1
  75. package/lib/theme-settings.js +60 -3
  76. package/lib/theme-settings.js.map +1 -1
  77. package/lib/theme-tokens.d.ts +1 -1
  78. package/lib/theme-tokens.d.ts.map +1 -1
  79. package/lib/theme-tokens.js +8 -0
  80. package/lib/theme-tokens.js.map +1 -1
  81. package/lib/todo-guard.d.ts +116 -0
  82. package/lib/todo-guard.d.ts.map +1 -0
  83. package/lib/todo-guard.js +259 -0
  84. package/lib/todo-guard.js.map +1 -0
  85. package/lib/ui/editor.d.ts +41 -1
  86. package/lib/ui/editor.d.ts.map +1 -1
  87. package/lib/ui/editor.js +98 -4
  88. package/lib/ui/editor.js.map +1 -1
  89. package/lib/ui/history-picker.d.ts +15 -0
  90. package/lib/ui/history-picker.d.ts.map +1 -0
  91. package/lib/ui/history-picker.js +26 -0
  92. package/lib/ui/history-picker.js.map +1 -0
  93. package/lib/ui/picker.d.ts +11 -1
  94. package/lib/ui/picker.d.ts.map +1 -1
  95. package/lib/ui/picker.js +17 -2
  96. package/lib/ui/picker.js.map +1 -1
  97. package/lib/ui/prompt.d.ts +2 -0
  98. package/lib/ui/prompt.d.ts.map +1 -1
  99. package/lib/ui/prompt.js +4 -0
  100. package/lib/ui/prompt.js.map +1 -1
  101. package/lib/ui/stash-picker.d.ts +43 -0
  102. package/lib/ui/stash-picker.d.ts.map +1 -0
  103. package/lib/ui/stash-picker.js +78 -0
  104. package/lib/ui/stash-picker.js.map +1 -0
  105. package/lib/ui/status.d.ts +2 -0
  106. package/lib/ui/status.d.ts.map +1 -1
  107. package/lib/ui/status.js +7 -0
  108. package/lib/ui/status.js.map +1 -1
  109. package/lib/work.d.ts +3 -1
  110. package/lib/work.d.ts.map +1 -1
  111. package/lib/work.js +9 -1
  112. package/lib/work.js.map +1 -1
  113. package/package.json +5 -1
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Interactive terminal (TUI) surface for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness): use `dsh` in a terminal instead of a browser.
4
4
 
5
- Status: **v1 feature-complete; published on npm as `@sagmans/dsh-tui`.** The surface owns the alternate screen, streams assistant text as markdown, renders every tool's own card, answers approvals and questions, restores and names stored conversations, switches model mid-session, runs any of the four shipped agent modes and switches between them before a session's first turn, reads a child agent's conversation in place, keeps the goal, plan mode, todo list, delegations, and background jobs above the editor with a status line below it, and hands the terminal back on every graceful exit. Publication is tag-driven with GitHub OIDC provenance and no stored npm token; see [RELEASE.md](RELEASE.md).
5
+ Status: **v1 feature-complete; published on npm as `@sagmans/dsh-tui`.** The surface owns the alternate screen, streams assistant text as markdown, renders every tool's own card, answers approvals and questions, restores and names stored conversations, switches model mid-session, runs any of the four shipped agent modes and switches between them before a session's first turn, reads a child agent's conversation in place, keeps the goal, plan mode, todo list, delegations, and background jobs above the editor with a status line below it, nudges an agent whose plan has aged without an update, parks and restores prompt drafts per working directory, and hands the terminal back on every graceful exit. Publication is tag-driven with GitHub OIDC provenance and no stored npm token; see [RELEASE.md](RELEASE.md).
6
6
 
7
7
  ## Install
8
8
 
@@ -130,6 +130,9 @@ moves any of them — see [Keys](#keys).
130
130
  | Ctrl+Y | show or hide the calls a PTC program dispatched: one two-space-indented entry per call under its `run_code` card, named and argued from the tool's own header and wrapped at the screen edge; shown by default |
131
131
  | Shift+Tab | expand or fold the reasoning behind an answer: folded, the row names itself, its token count, and the key; opened, it adds the thought |
132
132
  | Ctrl+T | pick the reasoning effort for the next step |
133
+ | Ctrl+R | reverse-search recorded prompts: the list opens filtered by whatever is in the bar, `enter` puts one back, `esc` keeps the draft |
134
+ | Ctrl+X then S | stash the current draft |
135
+ | Ctrl+X then L | open this directory's stashed drafts |
133
136
  | Ctrl+X then M | open the model picker |
134
137
  | Ctrl+X then Y | copy the last answer to the clipboard |
135
138
  | `y` / `n` / Esc | allow once, reject, or cancel a pending approval |
@@ -160,23 +163,33 @@ moves any of them — see [Keys](#keys).
160
163
  | `/export [path]` | write the visible transcript as markdown (default `dsh-session-<id>.md`) |
161
164
  | `/resume` | open another stored session without leaving the terminal |
162
165
  | `/clear` | clear the visible transcript |
166
+ | `/history` | show how many prompts are recorded and where the file is |
167
+ | `/history clear` | forget every recorded prompt, reporting how many went |
163
168
  | `/theme` | list every styled element and the value in force |
164
169
  | `/keys` | list every action and the keys in force; `/keys <layer>` narrows it (see [Keys](#keys)) |
170
+ | `/stash <draft>` | park the text given after the command (`ctrl+x` then `s` parks the editor) |
171
+ | `/stash-pop [index\|id]` | put a stashed draft into the editor and remove it (newest by default) |
172
+ | `/stash-apply [index\|id]` | put a stashed draft into the editor and keep it |
173
+ | `/stash-list` | pick from this directory's stashed drafts; `enter` pops the marked one |
174
+ | `/stash-drop [index\|id]` | delete a stashed draft without using it |
175
+ | `/stash-clear` | delete every stashed draft for this directory, after a confirmation |
165
176
  | `/quit` | leave and print the resume command |
166
177
 
167
178
  `Ctrl+X` starts a chord. For the next two seconds the footer leads with the
168
179
  prefix alone — enough to say that a key is waiting, without reciting the map —
169
180
  and a key that finishes nothing is typed as usual rather than swallowed, so a
170
181
  prefix pressed by accident costs nothing; `/help` lists the chords, `m` for the
171
- model picker, `p` for plan mode, and `y` for the last answer.
182
+ model picker, `p` for plan mode, `y` for the last answer, `s` to stash the
183
+ draft, and `l` for the stashes.
172
184
  `keys.chord.prefix: alt+x` starts the chord with another key — or with a list of
173
185
  them, as so many ways in — and `prefixWindow: 0` waits for the next key instead
174
186
  of lapsing; every second key is a row of its own (`chord.model`, `chord.plan`,
175
- `chord.copy`), so a chord can be respelled whole. A prefix that is not a modifier chord, that
187
+ `chord.copy`, `chord.stash`, `chord.stashes`), so a chord can be respelled whole. A prefix that is not a modifier chord, that
176
188
  the surface or the prompt bar already answers (`ctrl+c`, `ctrl+s`), or that the
177
189
  terminal keeps (`ctrl+q`) is refused with the reason, and the shipped keymap
178
190
  stays in force. The chords themselves are the commands they stand for: `m`, `p`,
179
- and `y` ask the same dispatcher `/model`, `/plan`, and `/copy` do. Plan mode is
191
+ `y`, `s`, and `l` ask the same dispatcher `/model`, `/plan`, `/copy`, `/stash`,
192
+ and `/stash-list` do. Plan mode is
180
193
  the one pair that cannot share a name: `/plan` only enters, so the chord names
181
194
  `/plan off` instead when the agent is in plan mode — or is waiting for the turn
182
195
  boundary to become so — and reads that state from the plan package rather than
@@ -186,8 +199,81 @@ An approval or a question draws inline above the editor and takes the keyboard.
186
199
 
187
200
  While a turn runs, a prompt submitted into the editor waits in the agent's own inbox instead of disappearing: it is drawn above the editor in the input bar's own frame, faint and italic, and moves into the transcript when the agent takes it — where it keeps that frame in the prompt's own mint shade, so what the reader typed is never mistaken for what the agent said. `editor.queued` and `editor.queued.more` restyle or hide the waiting rows; `transcript.user` restyles the submitted prompt.
188
201
 
202
+ Every submitted line is also kept in a global prompt history at
203
+ `$DSH_HOME/prompt-history.json`. Typing the start of a prompt that was sent
204
+ before draws the rest of the newest match after the cursor in a faint shade.
205
+ `Ctrl+E` takes the whole suggestion and the word-movement key takes the next
206
+ word, and both keys fall back to their old meaning the moment nothing is
207
+ offered. The history is deliberately global — the same prompt is useful in
208
+ every checkout — so nothing records a directory. An exact repeat moves to the
209
+ front instead of being stored twice. `history.ghost: false` keeps reverse
210
+ search but stops drawing the suggestion, `history.enabled: false` stops
211
+ recording and offering, and `history.maxEntries` bounds the file. A file this
212
+ build cannot parse is left untouched and writes are refused, so a newer format
213
+ is never overwritten; `/history` names it and the count, and `/history clear`
214
+ forgets everything.
215
+
189
216
  Any other `/command` goes to the command registry, so `/plan`, `/compact`, `/goal`, and `/feedback` behave as they do on the other surfaces.
190
217
 
218
+ ## Prompt stash
219
+
220
+ `ctrl+x` then `s` parks the draft the editor is holding and clears it;
221
+ `/stash <draft>` parks a draft typed on the command line. A bare `/stash` only
222
+ says so, because submitting a command consumes the line it was typed on and there
223
+ is nothing left of the draft to park. `/stash-pop` puts a parked draft back and
224
+ removes it, so a prompt written for the wrong moment survives a restart instead
225
+ of being retyped or sent; `l` opens the list of them. A stash belongs to the exact
226
+ working directory, so the drafts parked in one checkout never appear in another;
227
+ the footer shows `stash N` while any are waiting, ranked above the context and
228
+ cache numbers it shares a row with.
229
+
230
+ A selector is the number the list shows in brackets — `0` is the newest — or the
231
+ entry's own id; leaving it out takes the newest. `apply` and `pop` refuse to
232
+ overwrite a draft already in the editor, because losing an unsent prompt to a
233
+ restore is the one outcome the feature exists to prevent. They refuse while a
234
+ question is borrowing the bar for the same reason: a draft written into an answer
235
+ would be sent as one. `pop` writes the editor first and removes the entry second,
236
+ so a crash between the two leaves the draft in the bank rather than only in a
237
+ terminal that is gone.
238
+
239
+ Nothing is cleared until the write has landed. A refusal — no room left, a bank
240
+ past its cap, another writer holding the lock — leaves the draft in the bar,
241
+ including a draft typed after `/stash`, which is written back into the bar before
242
+ the write is attempted. The bar is only cleared while it still holds that same
243
+ draft and no question has borrowed it, so an answer typed during the write is
244
+ never wiped by a stash finishing.
245
+
246
+ The bank is one JSON file per directory under `$DSH_HOME/tui-stash`, written with
247
+ owner-only permissions (`0700` directory, `0600` file) through a no-follow open,
248
+ and every directory the path passes through must be owned by the reader (or by
249
+ root) and not writable by anyone else — the sticky bit is the only exception,
250
+ since it keeps renaming to an entry's owner. Links are walked one hop at a time,
251
+ with `..` left for the filesystem to resolve against what the link points at, and
252
+ a link this user does not own ends the walk: a chain that jumps through a shared
253
+ directory is refused at the directory it jumped through. A directory
254
+ that another user or a group member could redirect the storage through is refused
255
+ rather than trusted, which is also why a group-writable home directory fails the
256
+ stash with the offending path named. Every update is a locked read-modify-write
257
+ and an atomic temp-and-rename, so two surfaces in the same directory cannot lose
258
+ each other's entries; reclaiming a lock whose owner is gone is serialized on a
259
+ per-bank claim file, and the removal only applies to the lock it judged, so a
260
+ holder that released in between cannot have its successor's live lock deleted. A
261
+ contender never deletes a lock it did not publish, so losing the name to a
262
+ successor costs a retry rather than the successor's turn.
263
+
264
+ A bank whose working directory is not this one, or whose bytes do not parse, is
265
+ moved aside as `<name>.corrupt-<time>` and reported with its path — including when
266
+ the directory holding the copy could not be synced. A bank written by a newer
267
+ format, or one past the size cap, is refused in place rather than moved, because
268
+ neither is corruption. A storage directory a save had to create is flushed
269
+ through the directory that names it before the save reports anything. If that
270
+ flush fails, the empty directories are taken back so the retry starts clean; a
271
+ directory another surface has already saved into is left exactly as it is, because
272
+ an entry left unflushed costs durability while a removed bank costs the draft. Drafts are never written to a session log, and control and
273
+ Unicode bidi controls are stripped when a draft is stored and again when it is
274
+ read, so a hand-edited bank cannot park a terminal escape or a reordering trick in
275
+ the bar.
276
+
191
277
  ## Settings
192
278
 
193
279
  Every styled element is a named token with a shipped default, and every key is
@@ -205,6 +291,10 @@ dsh-tui:
205
291
  prompt.submit: [ctrl+enter, alt+enter, ctrl+s]
206
292
  surface.effort: ctrl+t # one key, or a list of them
207
293
  tui.editor.yank: ctrl+y # any action the library draws, by the id /keys prints
294
+ history:
295
+ enabled: true # record prompts and offer them back (default true)
296
+ ghost: true # draw the dimmed completion; reverse search stays either way (default true)
297
+ maxEntries: 2000 # prompts kept, newest first (1-20000, default 2000)
208
298
  palette:
209
299
  muted: '#5c5c5c' # one shade quiets every receding element
210
300
  tokens:
@@ -230,6 +320,13 @@ card alone instead. `Ctrl+Y` toggles the same choice for the current session,
230
320
  and an edit to the document re-seeds it. An unknown key or value is
231
321
  refused with a notice naming it, so a typo cannot quietly do nothing.
232
322
 
323
+ The `history` block tunes the prompt history. `enabled: false` stops recording
324
+ and offering it; `ghost: false` keeps reverse search but stops the dimmed
325
+ completion; `maxEntries` bounds the file, and an exact repeat moves to the
326
+ front rather than being stored twice. `editor.ghost` styles the suggestion, and
327
+ `NO_COLOR` or `--no-color` suppresses it entirely, because a suggestion the
328
+ reader cannot see but could still accept is worse than none.
329
+
233
330
  A reply whose fenced block names `mermaid` is drawn as terminal box art instead
234
331
  of source, laid out at the width the transcript has. `mermaid: streaming` (the
235
332
  default) draws a diagram while the reply is still arriving, `final` waits for
@@ -339,6 +436,7 @@ The package is a Cordis plugin bundle that stacks over `@deepseek-ai/dsh-base`:
339
436
  - `@deepseek-ai/dsh-agent-presets` is the roster of modes, holding the id a session starts in when nobody names one.
340
437
  - `@deepseek-ai/dsh-code-runtime-worker-thread` and `@deepseek-ai/dsh-cordis-host-runner` are the host machinery PTC mode and creator mode need; only the Web bundle shipped them, so a terminal profile has to mount them to offer those modes at all.
341
438
  - `@sagmans/dsh-tui` owns the terminal: it creates or resumes one agent through `ctx.agents`, folds `session/event` into transcript rows and work state, renders them with `@earendil-works/pi-tui`, and releases the terminal on exit, on a boot failure, and on a signal.
439
+ - `@sagmans/dsh-tui/todo-guard` is the one advisory row this bundle adds to the agent plane: it watches the harness's own `todos` and `plan` projections and rides the next tool result with a reminder when a plan ages. See [Todo discipline](#todo-discipline).
342
440
 
343
441
  A question whose id ends in `:secret` declares its typed answer a credential: the bar hides everything but its first and last four characters, and the free-text row a question with options offers is labelled `API KEY`. Wording is not a declaration, because hiding every question that mentions a key would hide answers their authors meant to be read.
344
442
 
@@ -352,6 +450,49 @@ A card's header names the tool, then the argument the call was made with — a p
352
450
 
353
451
  The bundle also takes the base's global agent rows out of the composition, twenty-three of them. Every one is a row the shipped modes supply per session instead, so leaving it mounted registers the same tool names in two layers and doubles each prompt section it owns. What stays mounted is the host: sessions, storage, models, permissions, jobs, and the command registry.
354
452
 
453
+ ### Todo discipline
454
+
455
+ The todo tool and its list belong to the agent; this bundle owns the surface and one advisory guard. `@sagmans/dsh-tui/todo-guard` mounts host-plane, reads the harness's own `todos` and `plan` projections, and — when a non-empty list has gone a threshold of model steps without a `todo_write`, or a long turn has produced no list at all — rides the next tool result with a model-visible reminder. It never vetoes a call, never steers a stopped turn, and never adds a prompt section, so its request prefix stays stable across deployments. A reminder costs the loop one extra model step to consume; the per-turn cap bounds that. It stays silent in plan mode, in a mode whose catalog has no `todo_write`, and when the projections are absent.
456
+
457
+ | Option | Default | Effect |
458
+ |---|---|---|
459
+ | `staleSteps` | `6` | model steps a non-empty open list may age before the guard speaks |
460
+ | `missingListSteps` | `12` | steps in a turn before the guard suggests a first list |
461
+ | `maxRemindersPerTurn` | `3` | hard cap on reminders, and on the extra steps they cost, per turn |
462
+ | `previewItems` | `5` | open items quoted in a reminder; the rest become a count |
463
+
464
+ Override them from the home-level patch, which outranks the profile's own layers. An id-targeted patch replaces the whole config, so restate every field you keep:
465
+
466
+ ```yaml
467
+ # $DSH_HOME/cordis.patch.yml
468
+ - id: tui-todo-guard
469
+ config:
470
+ staleSteps: 4
471
+ missingListSteps: 12
472
+ maxRemindersPerTurn: 3
473
+ previewItems: 5
474
+ ```
475
+
476
+ The list the dock and `/todo` draw follows the same lifetime every other surface shows: it is cleared when the next turn opens, because a fresh task must not inherit the previous turn's checklist.
477
+
478
+ ## Herdr
479
+
480
+ [Herdr](https://github.com/herdrdev/herdr) is a terminal multiplexer for coding agents. When it starts this surface in one of its panes it exports `HERDR_ENV=1`, `HERDR_PANE_ID`, and `HERDR_SOCKET_PATH`, and the pane reports what it is doing over that socket — as the agent `dsh`, under the source `custom:dsh-tui`. Away from Herdr (an ordinary terminal, SSH, tmux) the reporter is inert: no socket is opened, and nothing reaches the screen the reader owns.
481
+
482
+ | This surface | Herdr |
483
+ |---|---|
484
+ | the screen is taken, before any session opens | `idle`, claiming the pane's agent row |
485
+ | `turn/start` | `working` |
486
+ | an approval, a question, or a picker takes the keyboard | `blocked`, with that card's title sent along |
487
+ | the decision settles | `working` if a turn is open, otherwise `idle` |
488
+ | `turn/end` | `idle` |
489
+ | a session opens, resumes, forks, or is switched to | its id and reason, plus the `dsh_session` / `dsh_cwd` pane tokens |
490
+ | exit, signal, or boot failure | `herdr pane release-agent`, so no row is left waiting on a process that is gone |
491
+
492
+ A wait outranks a running turn: a turn waiting on a human is not making progress, and the wait is the only thing worth acting on from a wall of panes. Reports are sequenced per source, so a delivery that arrives late cannot undo the state the surface already moved past, and a state Herdr is already showing is not sent again. A report is only counted as made when Herdr acknowledges it: one that failed is tried again — soon after, then with a growing wait — and a session identity Herdr never confirmed travels with the next report of any kind. The retry cannot wait for another state change, because the pane may have nothing left to say: a turn that ended while the socket was down produces no further event. States still waiting to be sent are collapsed into the newest one, so a socket that was down for a minute is told where the pane is rather than where it has been. The release carries the next number in that same sequence for the same reason: Herdr reads one that cannot beat the pane's last report as stale, and a stale release leaves the row waiting on a process that is gone. It also stops reporting first — a claim landing after the release would take the row back for a process that is leaving — and the report already on the wire is waited for before the row goes back, because Herdr ignores the release of a pane nothing has claimed yet and that late report would then claim it. What is still waiting behind it is dropped rather than sent.
493
+
494
+ Herdr persists a session reference only for its own built-in integrations, so this pane's session identity travels as metadata tokens instead: a script or a companion plugin reads them back with `herdr pane get <id>` and resumes that exact conversation with `dsh --profile tui --resume=<id>`. Herdr holds a token value up to 80 characters and shortens anything longer, so a session id or directory that cannot be sent whole has its token cleared instead: a shortened path would read as a different directory, and a pane must not claim one. What Herdr cannot do is identify the process itself — its detection table and its screen rules both name built-in agents only — so a pane that has not reported yet reads as an ordinary pane, and is not yet a target: `herdr agent wait` on it fails with `agent_not_found` until the first report lands. Herdr 0.9.1 also keeps the title that accompanies a `blocked` state without showing it anywhere, so a reader sees the state and reads the card on screen.
495
+
355
496
  ## Development
356
497
 
357
498
  ```sh
@@ -400,6 +541,10 @@ The automated checks drive a real PTY, but they run on this machine's terminal.
400
541
  | a reply carrying a mermaid fence | it draws as box art at the transcript width, with the prose around it untouched |
401
542
  | that reply in a terminal narrower than the drawing | the fence stays source, and widening the window draws it without a new turn |
402
543
  | `dsh-tui: { mermaid: off }` in `$DSH_HOME/settings.yaml`, then a mermaid reply | the fence stays source; editing the value to `streaming` draws a settled reply without a restart |
544
+ | type the start of a prompt already recorded | the rest of the newest match follows the cursor in a faint shade; `ctrl+e` takes it whole, the word-right key takes one word, and both keys do their old job when nothing is offered |
545
+ | `ctrl+r`, then a fragment | reverse search opens seeded with the bar's draft; `enter` puts a prompt back, `esc` keeps the draft |
546
+ | `dsh-tui: { history: { ghost: false } }`, then type a known prefix | no suggestion is drawn, and `ctrl+r` still searches |
547
+ | `/history clear`, then `/history` | the notice reports the count forgotten, and the second reports `1 prompt recorded` — the check line is itself recorded |
403
548
  | `/model` on a configured profile | the picker lists only the configured providers' advertised models, heads itself with the route in force, and typing filters it while later rows stream in; `esc` or Ctrl+C leaves without changing the route, and `enter` chains into the route's reasoning efforts |
404
549
  | `/preset` on a fresh session | the picker lists four modes, marks the current one, and the switch survives a resume |
405
550
  | `/preset minimal` after a turn | refused, naming the reason; the session keeps the mode it composed with |
@@ -412,6 +557,10 @@ The automated checks drive a real PTY, but they run on this machine's terminal.
412
557
  | press `0` on a question, type an answer, press Enter | the editor under row `0` shows the text as it is edited, and the model receives it as that question's answer |
413
558
  | type a prompt without sending it, then answer a question | the prompt bar steps aside while the question is open and holds the same prompt again afterwards |
414
559
  | `echo hi \| dsh --profile tui` | refuses with a non-zero exit and a message naming the TTY requirement |
560
+ | `/stash`, `/stash-pop` in one terminal | the footer shows `stash 1` after the stash and the draft returns to the editor after the pop |
561
+ | a second `dsh --profile tui` in the same directory | `/stash-list` shows the draft the first terminal parked |
562
+ | `dsh --profile tui` in another directory | `/stash-list` says `no stashed drafts`, even though the first directory still has one |
563
+ | hand-edit `$DSH_HOME/tui-stash/<key>.json` into invalid JSON, then `/stash-list` | the surface reports the quarantine path, starts empty, and leaves the moved file readable |
415
564
  | `/quit`, Ctrl+C while idle, `kill -TERM <pid>` | the shell returns with cursor, echo, mouse, and title restored |
416
565
 
417
566
  ## Releasing
@@ -430,15 +579,18 @@ The workflow stores no npm token: the registry trusts `release.yml` on the `npm-
430
579
  - `/model` changes the route and reasoning effort for the running session only. Catalog membership is advisory — an adapter may accept an id it does not advertise, while an explicit effort is checked against the route's own levels before it is applied. The picker offers the routes this deployment configured, not the ones it can prove credentialed: a provider whose key or sign-in is still missing appears like any other, and its first request names the missing credential.
431
580
  - Scrolling is the mouse wheel, or the terminal's own scrollback keys where it offers them.
432
581
  - A turn that ran longer than ten seconds rings the terminal bell when it ends, because the reader may have walked away; `--no-bell` turns that off.
433
- - The dock shows the goal, plan mode, the todo items still to do, and any background job or delegation still running; a settled item leaves rather than turns into a completed row. The transcript marks where older history was compacted away. `/plan` toggles plan mode; `/plan <message>` also steers that message, which is the base command's own behaviour.
582
+ - The dock shows the goal, plan mode, the todo items still to do, and any background job or delegation still running; a settled item leaves rather than turns into a completed row, and the list clears when the next turn opens so a fresh task never inherits the previous one's checklist. The transcript marks where older history was compacted away. `/plan` toggles plan mode; `/plan <message>` also steers that message, which is the base command's own behaviour.
434
583
  - Background jobs and subagent runs are live process state, not durable events: they disappear when the run ends, and a resumed session starts with an empty board and roster.
435
584
  - A card reads its tool's own render intent through the agent whose session is on screen, so a stored session with no live agent — one this process is not running, or a child that has already finished — folds to the generic card instead of the tool's own.
436
585
  - Reading a child's conversation does not move the terminal: commands, approvals, and the status line stay with the session you launched, and the transcript is the only thing that switches. The status line carries the way back, read from the map in force, so a remap shows up without reopening the view.
437
586
  - Delete is unimplemented: the session store exposes no delete, and the surface does not reach around that seam into its files. `/fork` covers the case that needs it — it branches into a new session and leaves the original alone.
587
+ - The prompt stash holds text only. It does not read Pi's `pi-stash` data, does not migrate an older key format, and keeps no pasted images: a draft larger than 1 MiB, or a bank larger than 16 MiB, is refused rather than stored — the cap is measured on the bytes the file will hold, escapes included, before anything is written, so a refusal cannot leave a bank that saves and then refuses to load. A corrupt bank is quarantined and reported, never repaired in place; a bank from a newer format is refused where it lies, so an older build cannot swallow a newer build's drafts.
438
588
  - Approvals and questions render inline and take the keyboard; a question batch is answered in order, and a question that lists options can always be answered with free text on row `0`.
589
+ - Inside Herdr the pane reports its own state, and that report is the only thing that makes it an agent there: Herdr cannot start, resume, or prompt this surface, so launching and resuming stay with `dsh` itself (or a Herdr plugin that runs it).
439
590
  - Styling is per element and overridable; see [Settings](#settings). Shipped defaults are emitted as 24-bit colour where the terminal advertises it and degraded to 256 or 16 colours otherwise, so a light or dark terminal still follows its own palette where it has one.
440
- - Tool text, model text, and file content are escaped before rendering, so a hostile result cannot inject terminal control sequences; the cost is that a literal tab shows as \x09.
591
+ - Tool text, model text, and file content are escaped before rendering, so a hostile result cannot inject terminal control sequences; the cost is that a literal tab shows as \x09. A stashed draft is stripped of control and bidi characters instead, because it is restored into a live editor rather than drawn as text.
441
592
  - Mermaid fences draw in assistant replies only, and only at the top level of one: a fence nested in a list, quoted inside another fence, or carried by a prompt, a thought, or a tool card stays source. Author `:::class` styling and diagram links are ignored — the renderer reports what each run is, and the theme decides how it looks.
593
+ - Prompt history is global to this machine, not per project: `$DSH_HOME/prompt-history.json` holds every submitted line, deduplicated exactly, and a file this build cannot parse is left untouched with writes refused so a newer format is never overwritten. Every write re-reads the file under a lock shared by sessions, so a second session's prompts are folded in rather than overwritten, and a lock whose holder stopped is reclaimed or reported instead of guessed at; control characters are spelled out before a prompt is stored. A multiline suggestion draws its first line with `↵` marking the fold. `history.ghost: false` keeps reverse search without the suggestion, `history.enabled: false` stops recording and offering it, and `NO_COLOR`/`--no-color` suppresses the ghost because text the reader cannot see but could still accept is worse than none.
442
594
 
443
595
  ## License
444
596
 
package/cordis.patch.yml CHANGED
@@ -97,6 +97,20 @@
97
97
  color: !!js ctx.tuiStartup.color
98
98
  bell: !!js ctx.tuiStartup.bell
99
99
 
100
+ # Advisory todo discipline for every agent this profile runs. The tool and
101
+ # its list already exist per session; this row only notices when a plan has
102
+ # aged and rides the next tool result with a reminder. It is host-plane on
103
+ # purpose: it registers no tool, provides no service, and never gates a
104
+ # call, so it needs no preset and no realm. `staleSteps` and the rest are
105
+ # documented in the README and overridable from a profile patch.
106
+ - id: tui-todo-guard
107
+ name: '@sagmans/dsh-tui/todo-guard'
108
+ config:
109
+ staleSteps: 6
110
+ missingListSteps: 12
111
+ maxRemindersPerTurn: 3
112
+ previewItems: 5
113
+
100
114
  # The roster of agent compositions, and the mode a session starts in when
101
115
  # nobody names one. PTC leads because a flagless run should reach every tool
102
116
  # through one program rather than one shell call at a time. A profile patch
@@ -1,12 +1,28 @@
1
1
  import type { Context } from '@deepseek-ai/cordis';
2
+ /** One projection read that keeps "not registered" apart from "cannot answer". */
3
+ export type ProjectionRead = {
4
+ readonly kind: 'state';
5
+ readonly state: unknown;
6
+ } | {
7
+ readonly kind: 'unregistered';
8
+ } | {
9
+ readonly kind: 'unavailable';
10
+ };
2
11
  /**
3
- * One session projection's state.
12
+ * One session projection's state, with the reason a missing answer is kept.
4
13
  *
5
- * A composition without a projection registry, and a session whose projection
6
- * unit is not registered, both leave the key simply absent — and the surface
7
- * reads projections while rendering, where a missing fact may cost a segment
8
- * but never the session. A registered unit that throws is treated the same way
9
- * for the same reason.
14
+ * `stateOf` answers `undefined` only for a key that is not registered; a
15
+ * composition without a registry, and a registered unit that throws, are the
16
+ * two cases that leave nothing to read at all. A caller that merely decorates a
17
+ * screen may collapse them, but one deciding whether it knows enough to speak
18
+ * must be able to tell them apart.
19
+ */
20
+ export declare function projectionRead(ctx: Context, session: unknown, key: string): ProjectionRead;
21
+ /**
22
+ * One session projection's state, or `undefined` whenever there is none to read.
23
+ *
24
+ * The surface reads projections while rendering, where a missing fact may cost a
25
+ * segment but never the session, so every unreadable case collapses here.
10
26
  */
11
27
  export declare function projectionState(ctx: Context, session: unknown, key: string): unknown;
12
28
  /** One projection's state when it is a record. */
@@ -1 +1 @@
1
- {"version":3,"file":"projections.d.ts","sourceRoot":"","sources":["../../src/agent/projections.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAOlD;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAQpF;AAED,kDAAkD;AAClD,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,OAAO,EACZ,OAAO,EAAE,OAAO,EAChB,GAAG,EAAE,MAAM,GACV,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAGrC;AAED,4DAA4D;AAC5D,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAGhG"}
1
+ {"version":3,"file":"projections.d.ts","sourceRoot":"","sources":["../../src/agent/projections.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAOlD,kFAAkF;AAClF,MAAM,MAAM,cAAc,GACtB;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAA;CAAE,GACnD;IAAE,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAA;CAAE,GACjC;IAAE,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAA;CAAE,CAAA;AAEpC;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,cAAc,CAS1F;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAGpF;AAED,kDAAkD;AAClD,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,OAAO,EACZ,OAAO,EAAE,OAAO,EAChB,GAAG,EAAE,MAAM,GACV,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAGrC;AAED,4DAA4D;AAC5D,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAGhG"}
@@ -1,23 +1,34 @@
1
1
  /**
2
- * One session projection's state.
2
+ * One session projection's state, with the reason a missing answer is kept.
3
3
  *
4
- * A composition without a projection registry, and a session whose projection
5
- * unit is not registered, both leave the key simply absent — and the surface
6
- * reads projections while rendering, where a missing fact may cost a segment
7
- * but never the session. A registered unit that throws is treated the same way
8
- * for the same reason.
4
+ * `stateOf` answers `undefined` only for a key that is not registered; a
5
+ * composition without a registry, and a registered unit that throws, are the
6
+ * two cases that leave nothing to read at all. A caller that merely decorates a
7
+ * screen may collapse them, but one deciding whether it knows enough to speak
8
+ * must be able to tell them apart.
9
9
  */
10
- export function projectionState(ctx, session, key) {
10
+ export function projectionRead(ctx, session, key) {
11
11
  const projections = ctx.get('sessionProjections');
12
12
  if (projections?.stateOf === undefined)
13
- return undefined;
13
+ return { kind: 'unavailable' };
14
14
  try {
15
- return projections.stateOf(session, key);
15
+ const state = projections.stateOf(session, key);
16
+ return state === undefined ? { kind: 'unregistered' } : { kind: 'state', state };
16
17
  }
17
18
  catch {
18
- return undefined;
19
+ return { kind: 'unavailable' };
19
20
  }
20
21
  }
22
+ /**
23
+ * One session projection's state, or `undefined` whenever there is none to read.
24
+ *
25
+ * The surface reads projections while rendering, where a missing fact may cost a
26
+ * segment but never the session, so every unreadable case collapses here.
27
+ */
28
+ export function projectionState(ctx, session, key) {
29
+ const read = projectionRead(ctx, session, key);
30
+ return read.kind === 'state' ? read.state : undefined;
31
+ }
21
32
  /** One projection's state when it is a record. */
22
33
  export function projectionRecord(ctx, session, key) {
23
34
  const state = projectionState(ctx, session, key);
@@ -1 +1 @@
1
- {"version":3,"file":"projections.js","sourceRoot":"","sources":["../../src/agent/projections.ts"],"names":[],"mappings":"AAOA;;;;;;;;GAQG;AACH,MAAM,UAAU,eAAe,CAAC,GAAY,EAAE,OAAgB,EAAE,GAAW;IACzE,MAAM,WAAW,GAAG,GAAG,CAAC,GAAG,CAAC,oBAAoB,CAA4B,CAAA;IAC5E,IAAI,WAAW,EAAE,OAAO,KAAK,SAAS;QAAE,OAAO,SAAS,CAAA;IACxD,IAAI,CAAC;QACH,OAAO,WAAW,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,CAAA;IAC1C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAA;IAClB,CAAC;AACH,CAAC;AAED,kDAAkD;AAClD,MAAM,UAAU,gBAAgB,CAC9B,GAAY,EACZ,OAAgB,EAChB,GAAW;IAEX,MAAM,KAAK,GAAG,eAAe,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,CAAC,CAAA;IAChD,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,KAAgC,CAAC,CAAC,CAAC,SAAS,CAAA;AACnG,CAAC;AAED,4DAA4D;AAC5D,MAAM,UAAU,gBAAgB,CAAC,GAAY,EAAE,OAAgB,EAAE,GAAW;IAC1E,MAAM,KAAK,GAAG,eAAe,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,CAAC,CAAA;IAChD,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAA;AACtE,CAAC"}
1
+ {"version":3,"file":"projections.js","sourceRoot":"","sources":["../../src/agent/projections.ts"],"names":[],"mappings":"AAaA;;;;;;;;GAQG;AACH,MAAM,UAAU,cAAc,CAAC,GAAY,EAAE,OAAgB,EAAE,GAAW;IACxE,MAAM,WAAW,GAAG,GAAG,CAAC,GAAG,CAAC,oBAAoB,CAA4B,CAAA;IAC5E,IAAI,WAAW,EAAE,OAAO,KAAK,SAAS;QAAE,OAAO,EAAE,IAAI,EAAE,aAAa,EAAE,CAAA;IACtE,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,WAAW,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,CAAA;QAC/C,OAAO,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,cAAc,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,CAAA;IAClF,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,IAAI,EAAE,aAAa,EAAE,CAAA;IAChC,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,eAAe,CAAC,GAAY,EAAE,OAAgB,EAAE,GAAW;IACzE,MAAM,IAAI,GAAG,cAAc,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,CAAC,CAAA;IAC9C,OAAO,IAAI,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAA;AACvD,CAAC;AAED,kDAAkD;AAClD,MAAM,UAAU,gBAAgB,CAC9B,GAAY,EACZ,OAAgB,EAChB,GAAW;IAEX,MAAM,KAAK,GAAG,eAAe,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,CAAC,CAAA;IAChD,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,KAAgC,CAAC,CAAC,CAAC,SAAS,CAAA;AACnG,CAAC;AAED,4DAA4D;AAC5D,MAAM,UAAU,gBAAgB,CAAC,GAAY,EAAE,OAAgB,EAAE,GAAW;IAC1E,MAAM,KAAK,GAAG,eAAe,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,CAAC,CAAA;IAChD,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAA;AACtE,CAAC"}
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Global prompt history for the terminal surface.
3
+ *
4
+ * The file is deliberately project-agnostic: a reader who types the same prompt
5
+ * in two checkouts wants it offered in both, so nothing here records a working
6
+ * directory. It lives beside the reader's other harness preferences rather than
7
+ * inside the profile, because a profile is replaced on install and history is
8
+ * theirs to keep.
9
+ */
10
+ /** Environment variable that overrides the harness home, matching the launcher. */
11
+ export declare const DSH_HOME_ENV = "DSH_HOME";
12
+ /** Directory name of the default harness home under the OS home. */
13
+ export declare const DSH_HOME_DIR_NAME = ".dsh";
14
+ /** File name the history is stored under, inside the harness home. */
15
+ export declare const HISTORY_FILE_NAME = "prompt-history.json";
16
+ /** Schema this build writes; a file that names another positive version is left alone. */
17
+ export declare const HISTORY_SCHEMA_VERSION = 1;
18
+ /** Entries kept when the reader configures nothing. */
19
+ export declare const DEFAULT_MAX_ENTRIES = 2000;
20
+ /** Largest cap a reader may ask for, so a typo cannot grow the file without bound. */
21
+ export declare const MAX_ENTRIES_LIMIT = 20000;
22
+ /** Why the store refuses to write, which is a reason the reader can act on. */
23
+ export type HistoryBlockReason = 'corrupt_history' | 'unsupported_schema' | 'unreadable_history';
24
+ /** One recorded prompt, newest first in the file. */
25
+ export interface PromptEntry {
26
+ readonly text: string;
27
+ readonly updatedAt: string;
28
+ readonly useCount: number;
29
+ }
30
+ /** The persisted document, versioned so a future shape can be recognized. */
31
+ export interface PromptHistoryFile {
32
+ readonly version: number;
33
+ readonly updatedAt: string;
34
+ readonly entries: readonly PromptEntry[];
35
+ }
36
+ /** A file read reduced to what a caller can act on. */
37
+ export type ParsedHistoryFile = {
38
+ readonly kind: 'ready';
39
+ readonly file: PromptHistoryFile;
40
+ } | {
41
+ readonly kind: 'blocked';
42
+ readonly reason: HistoryBlockReason;
43
+ };
44
+ /** The store the surface holds; mutations are fire-and-forget and serialized. */
45
+ export interface PromptHistory {
46
+ /** Current entries, newest first; the array identity is stable per snapshot. */
47
+ entries(): readonly PromptEntry[];
48
+ /** Persist one submitted prompt; blank input is ignored. */
49
+ record(text: string): void;
50
+ /** Remove every entry and report how many went. */
51
+ clear(): Promise<number>;
52
+ /** Resolve once every queued mutation has settled; errors are reported, not thrown. */
53
+ flush(): Promise<void>;
54
+ /** Absolute path of the file, for a status line and diagnostics. */
55
+ path(): string;
56
+ /** Why writes are refused, or undefined while the store is healthy. */
57
+ blockedReason(): HistoryBlockReason | undefined;
58
+ }
59
+ /** Inputs the surface supplies; every one is a seam a test drives. */
60
+ export interface PromptHistoryOptions {
61
+ /** Resolved harness home; defaults to $DSH_HOME or ~/.dsh. */
62
+ readonly home?: string;
63
+ /** Read per mutation so a settings edit takes effect without a restart. */
64
+ readonly cap: () => number;
65
+ readonly now?: () => Date;
66
+ readonly warn?: (message: string) => void;
67
+ /** Lock wait a test can shorten; defaults to LOCK_WAIT_MS. */
68
+ readonly lockWaitMs?: number;
69
+ }
70
+ /**
71
+ * Resolve the harness home.
72
+ *
73
+ * Precedence is the launcher's own: $DSH_HOME, then ~/.dsh. A blank override is
74
+ * treated as unset, because resolving to the working directory would scatter
75
+ * private history into whatever tree the reader happened to start from.
76
+ */
77
+ export declare function resolveDshHome(env?: NodeJS.ProcessEnv): string;
78
+ /** Read one persisted document, refusing anything this build cannot safely rewrite. */
79
+ export declare function parseHistoryFile(text: string): ParsedHistoryFile;
80
+ /**
81
+ * Put one prompt at the front, replacing an exact duplicate.
82
+ *
83
+ * A duplicate is moved rather than copied so a repeated prompt resurfaces as the
84
+ * newest suggestion without ever appearing twice in the list.
85
+ */
86
+ export declare function upsertEntry(entries: readonly PromptEntry[], text: string, now: string, maxEntries: number): PromptEntry[];
87
+ /** Build the store the surface records into and completes from. */
88
+ export declare function createPromptHistory(options: PromptHistoryOptions): PromptHistory;
89
+ //# sourceMappingURL=prompt-history.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompt-history.d.ts","sourceRoot":"","sources":["../../src/agent/prompt-history.ts"],"names":[],"mappings":"AAMA;;;;;;;;GAQG;AAEH,mFAAmF;AACnF,eAAO,MAAM,YAAY,aAAa,CAAA;AACtC,oEAAoE;AACpE,eAAO,MAAM,iBAAiB,SAAS,CAAA;AACvC,sEAAsE;AACtE,eAAO,MAAM,iBAAiB,wBAAwB,CAAA;AACtD,0FAA0F;AAC1F,eAAO,MAAM,sBAAsB,IAAI,CAAA;AACvC,uDAAuD;AACvD,eAAO,MAAM,mBAAmB,OAAO,CAAA;AACvC,sFAAsF;AACtF,eAAO,MAAM,iBAAiB,QAAS,CAAA;AAmBvC,+EAA+E;AAC/E,MAAM,MAAM,kBAAkB,GAAG,iBAAiB,GAAG,oBAAoB,GAAG,oBAAoB,CAAA;AAShG,qDAAqD;AACrD,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;CAC1B;AAED,6EAA6E;AAC7E,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,OAAO,EAAE,SAAS,WAAW,EAAE,CAAA;CACzC;AAED,uDAAuD;AACvD,MAAM,MAAM,iBAAiB,GACzB;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,iBAAiB,CAAA;CAAE,GAC5D;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,kBAAkB,CAAA;CAAE,CAAA;AAErE,iFAAiF;AACjF,MAAM,WAAW,aAAa;IAC5B,gFAAgF;IAChF,OAAO,IAAI,SAAS,WAAW,EAAE,CAAA;IACjC,4DAA4D;IAC5D,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,mDAAmD;IACnD,KAAK,IAAI,OAAO,CAAC,MAAM,CAAC,CAAA;IACxB,uFAAuF;IACvF,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IACtB,oEAAoE;IACpE,IAAI,IAAI,MAAM,CAAA;IACd,uEAAuE;IACvE,aAAa,IAAI,kBAAkB,GAAG,SAAS,CAAA;CAChD;AAED,sEAAsE;AACtE,MAAM,WAAW,oBAAoB;IACnC,8DAA8D;IAC9D,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;IACtB,2EAA2E;IAC3E,QAAQ,CAAC,GAAG,EAAE,MAAM,MAAM,CAAA;IAC1B,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,IAAI,CAAA;IACzB,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;IACzC,8DAA8D;IAC9D,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAC7B;AAuJD;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAM3E;AAED,uFAAuF;AACvF,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,iBAAiB,CAiBhE;AAuBD;;;;;GAKG;AACH,wBAAgB,WAAW,CACzB,OAAO,EAAE,SAAS,WAAW,EAAE,EAC/B,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,MAAM,EACX,UAAU,EAAE,MAAM,GACjB,WAAW,EAAE,CAMf;AAMD,mEAAmE;AACnE,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,oBAAoB,GAAG,aAAa,CAsGhF"}