@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.
- package/README.md +158 -6
- package/cordis.patch.yml +14 -0
- package/lib/agent/projections.d.ts +22 -6
- package/lib/agent/projections.d.ts.map +1 -1
- package/lib/agent/projections.js +21 -10
- package/lib/agent/projections.js.map +1 -1
- package/lib/agent/prompt-history.d.ts +89 -0
- package/lib/agent/prompt-history.d.ts.map +1 -0
- package/lib/agent/prompt-history.js +380 -0
- package/lib/agent/prompt-history.js.map +1 -0
- package/lib/agent/status.d.ts +6 -0
- package/lib/agent/status.d.ts.map +1 -1
- package/lib/agent/status.js +1 -0
- package/lib/agent/status.js.map +1 -1
- package/lib/herdr/client.d.ts +57 -0
- package/lib/herdr/client.d.ts.map +1 -0
- package/lib/herdr/client.js +218 -0
- package/lib/herdr/client.js.map +1 -0
- package/lib/herdr/constants.d.ts +93 -0
- package/lib/herdr/constants.d.ts.map +1 -0
- package/lib/herdr/constants.js +79 -0
- package/lib/herdr/constants.js.map +1 -0
- package/lib/herdr/reporter.d.ts +60 -0
- package/lib/herdr/reporter.d.ts.map +1 -0
- package/lib/herdr/reporter.js +217 -0
- package/lib/herdr/reporter.js.map +1 -0
- package/lib/herdr/state.d.ts +57 -0
- package/lib/herdr/state.d.ts.map +1 -0
- package/lib/herdr/state.js +62 -0
- package/lib/herdr/state.js.map +1 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +252 -20
- package/lib/index.js.map +1 -1
- package/lib/input/actions.d.ts +1 -1
- package/lib/input/actions.d.ts.map +1 -1
- package/lib/input/actions.js +3 -0
- package/lib/input/actions.js.map +1 -1
- package/lib/input/ghost.d.ts +46 -0
- package/lib/input/ghost.d.ts.map +1 -0
- package/lib/input/ghost.js +58 -0
- package/lib/input/ghost.js.map +1 -0
- package/lib/input/keymap.d.ts.map +1 -1
- package/lib/input/keymap.js +2 -0
- package/lib/input/keymap.js.map +1 -1
- package/lib/input/submission.d.ts +24 -1
- package/lib/input/submission.d.ts.map +1 -1
- package/lib/input/submission.js +28 -1
- package/lib/input/submission.js.map +1 -1
- package/lib/stash/lock.d.ts +72 -0
- package/lib/stash/lock.d.ts.map +1 -0
- package/lib/stash/lock.js +374 -0
- package/lib/stash/lock.js.map +1 -0
- package/lib/stash/paths.d.ts +22 -0
- package/lib/stash/paths.d.ts.map +1 -0
- package/lib/stash/paths.js +83 -0
- package/lib/stash/paths.js.map +1 -0
- package/lib/stash/private-fs.d.ts +61 -0
- package/lib/stash/private-fs.d.ts.map +1 -0
- package/lib/stash/private-fs.js +379 -0
- package/lib/stash/private-fs.js.map +1 -0
- package/lib/stash/schema.d.ts +57 -0
- package/lib/stash/schema.d.ts.map +1 -0
- package/lib/stash/schema.js +127 -0
- package/lib/stash/schema.js.map +1 -0
- package/lib/stash/store.d.ts +92 -0
- package/lib/stash/store.d.ts.map +1 -0
- package/lib/stash/store.js +262 -0
- package/lib/stash/store.js.map +1 -0
- package/lib/stash.d.ts +91 -0
- package/lib/stash.d.ts.map +1 -0
- package/lib/stash.js +296 -0
- package/lib/stash.js.map +1 -0
- package/lib/theme-settings.d.ts +37 -8
- package/lib/theme-settings.d.ts.map +1 -1
- package/lib/theme-settings.js +60 -3
- package/lib/theme-settings.js.map +1 -1
- package/lib/theme-tokens.d.ts +1 -1
- package/lib/theme-tokens.d.ts.map +1 -1
- package/lib/theme-tokens.js +8 -0
- package/lib/theme-tokens.js.map +1 -1
- package/lib/todo-guard.d.ts +116 -0
- package/lib/todo-guard.d.ts.map +1 -0
- package/lib/todo-guard.js +259 -0
- package/lib/todo-guard.js.map +1 -0
- package/lib/ui/editor.d.ts +41 -1
- package/lib/ui/editor.d.ts.map +1 -1
- package/lib/ui/editor.js +98 -4
- package/lib/ui/editor.js.map +1 -1
- package/lib/ui/history-picker.d.ts +15 -0
- package/lib/ui/history-picker.d.ts.map +1 -0
- package/lib/ui/history-picker.js +26 -0
- package/lib/ui/history-picker.js.map +1 -0
- package/lib/ui/picker.d.ts +11 -1
- package/lib/ui/picker.d.ts.map +1 -1
- package/lib/ui/picker.js +17 -2
- package/lib/ui/picker.js.map +1 -1
- package/lib/ui/prompt.d.ts +2 -0
- package/lib/ui/prompt.d.ts.map +1 -1
- package/lib/ui/prompt.js +4 -0
- package/lib/ui/prompt.js.map +1 -1
- package/lib/ui/stash-picker.d.ts +43 -0
- package/lib/ui/stash-picker.d.ts.map +1 -0
- package/lib/ui/stash-picker.js +78 -0
- package/lib/ui/stash-picker.js.map +1 -0
- package/lib/ui/status.d.ts +2 -0
- package/lib/ui/status.d.ts.map +1 -1
- package/lib/ui/status.js +7 -0
- package/lib/ui/status.js.map +1 -1
- package/lib/work.d.ts +3 -1
- package/lib/work.d.ts.map +1 -1
- package/lib/work.js +9 -1
- package/lib/work.js.map +1 -1
- 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,
|
|
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 `
|
|
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
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
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,
|
|
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"}
|
package/lib/agent/projections.js
CHANGED
|
@@ -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
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
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
|
|
10
|
+
export function projectionRead(ctx, session, key) {
|
|
11
11
|
const projections = ctx.get('sessionProjections');
|
|
12
12
|
if (projections?.stateOf === undefined)
|
|
13
|
-
return
|
|
13
|
+
return { kind: 'unavailable' };
|
|
14
14
|
try {
|
|
15
|
-
|
|
15
|
+
const state = projections.stateOf(session, key);
|
|
16
|
+
return state === undefined ? { kind: 'unregistered' } : { kind: 'state', state };
|
|
16
17
|
}
|
|
17
18
|
catch {
|
|
18
|
-
return
|
|
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":"
|
|
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"}
|