@itookit/dsht 0.3.7 → 0.5.1

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 (132) hide show
  1. package/README.i18n.yaml +2 -2
  2. package/README.md +31 -11
  3. package/README.zh.md +31 -11
  4. package/dist/cli/dsht.js +207 -19
  5. package/dist/cli/startup.d.ts +40 -0
  6. package/dist/cli/startup.js +295 -0
  7. package/dist/cli/trace-summary.d.ts +78 -0
  8. package/dist/cli/trace-summary.js +241 -0
  9. package/dist/cli/verifier.d.ts +60 -0
  10. package/dist/cli/verifier.js +242 -0
  11. package/dist/contracts.d.ts +344 -0
  12. package/dist/contracts.js +1 -0
  13. package/dist/controller/commands.d.ts +47 -0
  14. package/dist/controller/commands.js +322 -0
  15. package/dist/controller/connection.d.ts +11 -29
  16. package/dist/controller/connection.js +26 -60
  17. package/dist/controller/controller.d.ts +619 -164
  18. package/dist/controller/controller.js +1420 -141
  19. package/dist/controller/index.d.ts +8 -1
  20. package/dist/controller/index.js +5 -0
  21. package/dist/controller/loop-contract.d.ts +136 -0
  22. package/dist/controller/loop-contract.js +308 -0
  23. package/dist/controller/loop-prompts-schema.d.ts +56 -0
  24. package/dist/controller/loop-prompts-schema.js +144 -0
  25. package/dist/controller/loop-prompts.d.ts +55 -0
  26. package/dist/controller/loop-prompts.generated.d.ts +104 -0
  27. package/dist/controller/loop-prompts.generated.js +185 -0
  28. package/dist/controller/loop-prompts.js +104 -0
  29. package/dist/controller/loop-protocols.d.ts +39 -0
  30. package/dist/controller/loop-protocols.js +115 -0
  31. package/dist/controller/loop.d.ts +275 -0
  32. package/dist/controller/loop.js +378 -0
  33. package/dist/controller/prompts.d.ts +54 -0
  34. package/dist/controller/prompts.js +162 -0
  35. package/dist/controller/trace-log.d.ts +45 -0
  36. package/dist/controller/trace-log.js +144 -0
  37. package/dist/controller/verifier.d.ts +126 -0
  38. package/dist/controller/verifier.js +75 -0
  39. package/dist/cost/index.d.ts +1 -1
  40. package/dist/cost/index.js +1 -1
  41. package/dist/cost/ledger.d.ts +0 -1
  42. package/dist/cost/ledger.js +0 -1
  43. package/dist/json.d.ts +18 -0
  44. package/dist/json.js +19 -0
  45. package/dist/references.d.ts +25 -0
  46. package/dist/references.js +26 -0
  47. package/dist/session/connection-view.d.ts +2 -11
  48. package/dist/session/controller.d.ts +82 -72
  49. package/dist/session/controller.js +211 -209
  50. package/dist/session/history.d.ts +9 -1
  51. package/dist/session/history.js +1 -9
  52. package/dist/session/index.d.ts +9 -4
  53. package/dist/session/index.js +7 -3
  54. package/dist/session/info.d.ts +25 -52
  55. package/dist/session/info.js +39 -25
  56. package/dist/session/markdown.js +1 -1
  57. package/dist/session/math.js +1 -1
  58. package/dist/session/mutation-gate.d.ts +51 -0
  59. package/dist/session/mutation-gate.js +73 -0
  60. package/dist/session/navigation.d.ts +2 -89
  61. package/dist/session/navigation.js +2 -129
  62. package/dist/session/peek.d.ts +38 -0
  63. package/dist/session/peek.js +103 -0
  64. package/dist/session/references.d.ts +2 -20
  65. package/dist/session/references.js +1 -26
  66. package/dist/session/runtime.d.ts +26 -0
  67. package/dist/session/runtime.js +28 -0
  68. package/dist/session/telemetry.d.ts +12 -13
  69. package/dist/session/telemetry.js +27 -58
  70. package/dist/session/transcript.d.ts +0 -6
  71. package/dist/session/transcript.js +2 -15
  72. package/dist/session/types.d.ts +25 -0
  73. package/dist/session/types.js +0 -1
  74. package/dist/session-title.d.ts +9 -0
  75. package/dist/session-title.js +21 -0
  76. package/dist/shell/controller.d.ts +97 -0
  77. package/dist/shell/controller.js +158 -0
  78. package/dist/shell/index.d.ts +5 -0
  79. package/dist/shell/index.js +3 -0
  80. package/dist/shell/runner.d.ts +38 -0
  81. package/dist/shell/runner.js +147 -0
  82. package/dist/slash/index.d.ts +10 -0
  83. package/dist/slash/index.js +7 -0
  84. package/dist/slash/parse.d.ts +166 -0
  85. package/dist/slash/parse.js +259 -0
  86. package/dist/slash/pipeline.d.ts +140 -0
  87. package/dist/slash/pipeline.js +115 -0
  88. package/dist/slash/registry.d.ts +88 -0
  89. package/dist/slash/registry.js +177 -0
  90. package/dist/state.d.ts +14 -4
  91. package/dist/state.js +3 -2
  92. package/dist/text.d.ts +28 -0
  93. package/dist/text.js +55 -0
  94. package/dist/transport/events.d.ts +104 -0
  95. package/dist/transport/events.js +149 -0
  96. package/dist/transport/wire.d.ts +9 -17
  97. package/dist/transport/wire.js +2 -27
  98. package/dist/ui/app.js +865 -431
  99. package/dist/ui/chat/header.js +1 -1
  100. package/dist/ui/chat/history-view.d.ts +1 -1
  101. package/dist/ui/chat/history-view.js +1 -1
  102. package/dist/ui/chat/loop-status.d.ts +11 -0
  103. package/dist/ui/chat/loop-status.js +28 -0
  104. package/dist/ui/chat/navigation-model.d.ts +86 -0
  105. package/dist/ui/chat/navigation-model.js +107 -0
  106. package/dist/ui/chat/shell-view.d.ts +47 -0
  107. package/dist/ui/chat/shell-view.js +145 -0
  108. package/dist/ui/chat/status.d.ts +47 -3
  109. package/dist/ui/chat/status.js +65 -50
  110. package/dist/ui/chat/viewport.d.ts +1 -1
  111. package/dist/ui/dialogs/cost.d.ts +21 -4
  112. package/dist/ui/dialogs/cost.js +7 -12
  113. package/dist/ui/dialogs/index.d.ts +22 -5
  114. package/dist/ui/dialogs/index.js +19 -3
  115. package/dist/ui/dialogs/loop.d.ts +43 -0
  116. package/dist/ui/dialogs/loop.js +224 -0
  117. package/dist/ui/dialogs/peek.d.ts +25 -0
  118. package/dist/ui/dialogs/peek.js +35 -0
  119. package/dist/ui/dialogs/picker.d.ts +2 -0
  120. package/dist/ui/dialogs/picker.js +4 -2
  121. package/dist/ui/input/mouse.d.ts +12 -2
  122. package/dist/ui/input/mouse.js +20 -7
  123. package/dist/ui/input/references.d.ts +1 -1
  124. package/dist/ui/status/model.d.ts +7 -0
  125. package/dist/ui/status/model.js +5 -0
  126. package/dist/ui/theme/index.d.ts +6 -1
  127. package/dist/ui/theme/index.js +2 -1
  128. package/package.json +6 -4
  129. package/dist/ui/commands/parse.d.ts +0 -99
  130. package/dist/ui/commands/parse.js +0 -126
  131. package/dist/ui/commands/registry.d.ts +0 -33
  132. package/dist/ui/commands/registry.js +0 -73
package/README.i18n.yaml CHANGED
@@ -1,3 +1,3 @@
1
1
  # Git blob hashes of the reviewed bilingual pair.
2
- README.md: 92c2be7222eebf3a4377d320d6107415e483a338
3
- README.zh.md: 810b6f5f4cc3a417aa99d0c61aa61700d6df3a37
2
+ README.md: 4909b6c278281d0f17a4140287c3c09372c1644e
3
+ README.zh.md: f515831f168100fd7c85d8441dfd471c8cefb075
package/README.md CHANGED
@@ -166,7 +166,7 @@ npm start
166
166
 
167
167
  Both paths read the same `DSH_URL` and `DSH_TOKEN` variables.
168
168
 
169
- Select a workspace with ↑/↓ and Enter, then select a session or **New session**. **All sessions** also exposes sessions outside registered workspaces. **Add workspace (this directory)** registers the directory `dsht` itself runs in, and appears only while the host does not already have it; **Add workspace (host directory)** takes an existing absolute directory on the host, which may differ from your local filesystem, and Esc leaves that prompt for the picker again. Creating a session requires a selected workspace.
169
+ Select a workspace with ↑/↓ and Enter, then select a session or **New session**. **All sessions** also exposes sessions outside registered workspaces. **Add workspace (this directory)** registers the directory `dsht` itself runs in, and appears only while the host does not already have it; **Add workspace (host directory)** takes an existing absolute directory on the host, which may differ from your local filesystem, and Esc leaves that prompt for the picker again. Starting inside a registered workspace directory selects that workspace instead of showing the picker, with `←` in the session list switching to another; a session id passed on the command line still opens directly. Creating a session requires a selected workspace.
170
170
 
171
171
  On first login, authentication exchanges the token at `GET /` and saves the cookie per HTTP origin. Later starts, including list commands, reuse that cookie without a token. The store uses `$XDG_STATE_HOME/dsht/auth`, or `~/.local/state/dsht/auth` when unset; `--auth-dir` or `DSHT_AUTH_DIR` overrides it. POSIX directories use 0700 and cookie files use 0600; Windows uses the account directory's inherited access controls. Launch tokens are never saved.
172
172
 
@@ -285,9 +285,19 @@ node --import tsx src/cli/index.ts list sessions --json
285
285
 
286
286
  JSON output is `{ "items": [...] }`; omit `--json` for tab-separated output. Workspace filtering uses the host's `sessionIds` membership. The workspace list consumes and cancels the first `workspace/follow` baseline; it does not call a nonexistent `workspace/list` endpoint.
287
287
 
288
+ ## Read the transition trace
289
+
290
+ ```sh
291
+ npx @itookit/dsht trace
292
+ npx @itookit/dsht trace --json
293
+ npx @itookit/dsht trace --trace /path/to/trace.log
294
+ ```
295
+
296
+ `dsht trace` reads `<state>/trace.log` back and prints a few lines of facts: how many commands ran and how they ended (with their kinds), how many session writes each lane and session saw, how many foreground operations ran with how many were cancelled and which took longest, what each loop run sent and how it ended, how verifications concluded (including the class of an unavailable verifier), and every span whose `begin` has no matching `end` — the anomalies a JSONL file hides at 1,443 lines. `--json` prints the same summary as structured data, and `--trace` reads a different file instead of appending to the default one.
297
+
288
298
  ## Conversation controls
289
299
 
290
- Enter submits a prompt: while the agent is Working it becomes steering for the next step; while idle it starts a new turn. Steering waits for the current step, including its tools, to finish and does not interrupt a running tool. Ctrl+C clears a non-empty draft first; otherwise it requests cancellation while the selected session is running and exits only when it is idle; repeated keys share an in-flight cancellation. Cancellation waits for a pending prompt admission, and failures keep the client open. Esc sends an explicit cancellation from the conversation even when the cached running flag is idle; open menus also cancel a known running agent while closing. Active history/search/cost loads, host commands, and exports are cancelled first. Page Up/Down scroll the retained transcript; `/older` loads an earlier page. Every exit path, including `/quit` and SIGTERM, stops the selected turn before the connection closes, so quitting does not leave the agent running; an idle session is left untouched. Cancellation leaves pending queue items intact.
300
+ Enter submits a prompt: while the agent is Working it becomes steering for the next step; while idle it starts a new turn. Steering waits for the current step, including its tools, to finish and does not interrupt a running tool. Ctrl+C clears a non-empty draft first; otherwise it requests cancellation while the selected session is running and exits only when it is idle; repeated keys share an in-flight cancellation, and a press that could not exit arms the next one: a second Ctrl+C within five seconds leaves even though the host has not confirmed the stop, so a turn the host never reports idle cannot trap the client; leaving still asks the host to stop it. Cancellation waits for a pending prompt admission, and failures keep the client open. Esc sends an explicit cancellation from the conversation even when the cached running flag is idle; open menus also cancel a known running agent while closing. Active history/search/cost loads, host commands, and exports are cancelled first. Page Up/Down scroll the retained transcript; `/older` loads an earlier page. Every exit path, including `/quit` and SIGTERM, stops the selected turn before the connection closes, so quitting does not leave the agent running; an idle session is left untouched. Cancellation leaves pending queue items intact.
291
301
 
292
302
  `/copy`, Ctrl+S, or an unmodified left click in the normal chat view freezes the display and disables mouse reporting for native terminal selection. Esc, Ctrl+S, or Ctrl+C leaves copy mode and catches up with the latest output; leaving copy mode does not cancel the agent. Dialogs and pickers pause automatic background title and conversation updates; chat dialogs also pause status updates. Workspace selection, session selection, and host-path entry keep connection notices and the status bar live unless copy mode is active. The conversation remains above the composer; mouse wheel and PgUp/PgDn scroll its history without moving the selected option. In the help panel PgUp/PgDn changes help pages. Dialog clicks do not enter copy mode; Ctrl+S freezes the whole display and releases mouse capture for native selection. The Working clock also pauses while reading older history. Help/status/cost panels no longer expire on a timer. Background reception and memory reclamation continue; window resizing can redraw the screen.
293
303
 
@@ -297,13 +307,14 @@ In `/ws` and `/resume` pickers, every row reports one of three user-visible stat
297
307
 
298
308
  `/search` matches literal text case-insensitively in conversation messages, including older pages; tool-only rows are excluded. Search scans up to 80 messages per request, discards each temporary page, and retains at most 200 short matches, including folded reasoning. A truncated result asks you to refine the query. Opening a match loads a separate page around its sequence; `/latest` releases that window. Esc or Ctrl+C cancels the search. A rare or absent term still requires scanning the full history over HTTP; there is no server-side full-text index for this command. `/history` lists your own prompts from the loaded pages. Record sequences are the numbers shown by these pickers. `/ssearch` and `/wsearch` call `session/search`, which searches current user/assistant message content and returns at most 20 sessions, snippets, and a truncation flag; it exposes neither a result cursor nor matching record sequences. Workspace filtering happens after that global limit, so a truncated workspace result can omit matches. The UI warns when results are incomplete; refine the query. Selecting a session loads its history and offers matching messages for the jump. These operations use HTTP and never scan the host configuration directory.
299
309
 
300
- ↑/↓ or Ctrl+P/N recalls previously submitted prompts and slash commands without sending them; Enter submits the recalled text. A reading panel that fits the screen leaves the arrows with this history, a panel that has to scroll takes them for itself, and Ctrl+P/N reach the history from any panel. Moving past the newest entry restores the unsent draft. Editing recalled text starts a new draft, and switching sessions clears an unsent one. Recall keeps the selected session's user prompts — up to 2,000 entries and approximately 512 KiB of text — and never writes a separate history file; switching sessions releases them. It folds those prompts from the session records as they arrive, so it covers prompts from before this client connected rather than only the window that happened to load. Opening a session also walks the earlier history in the background, keeping only prompts rather than loading those pages into the conversation, so the whole session's prompt list is available from the start. Reaching its oldest retained prompt first refills from the already-loaded conversation at no request cost, and only then fetches the page before the window, inside one bounded loop that skips tool-only pages so the key cannot stall; the fetched page also stays in the conversation above the composer. Eviction therefore bounds memory without deciding reachability: an evicted prompt is either still loaded or still on the host. Consecutive duplicates are merged, oversized entries are skipped, and question or approval answers are excluded. Question options and completion menus keep arrow navigation; workspace/session lists use arrows when the composer is empty, with Ctrl+P/N available for recall.
310
+ ↑/↓ or Ctrl+P/N recalls previously submitted prompts and slash commands without sending them; Enter submits the recalled text. A reading panel that fits the screen leaves the arrows with this history, a panel that has to scroll takes them for itself, and Ctrl+P/N reach the history from any panel. Moving past the newest entry restores the unsent draft. Editing recalled text starts a new draft, and switching sessions clears an unsent one. Recall keeps the selected session's user prompts — up to 2,000 entries and approximately 512 KiB of text — and never writes a separate history file; switching sessions releases them. It folds those prompts from the session records as they arrive, so it covers prompts from before this client connected rather than only the window that happened to load. Opening a session also walks the earlier history in the background, keeping only prompts rather than loading those pages into the conversation, so the whole session's prompt list is available from the start. Reaching its oldest retained prompt first refills from the already-loaded conversation at no request cost, and only then fetches the page before the window, inside one bounded loop that skips tool-only pages so the key cannot stall; the fetched page also stays in the conversation above the composer. Eviction therefore bounds memory without deciding reachability: an evicted prompt is either still loaded or still on the host. Consecutive duplicates are merged, oversized entries are skipped, and question or approval answers are excluded. Prompts the client assembled itself — the `/loop` brief and follow-ups, and the `/handoff` request — are sent as internal turns and never enter this history, so the arrows keep showing what you actually typed. Question options and completion menus keep arrow navigation; workspace/session lists use arrows when the composer is empty, with Ctrl+P/N available for recall.
311
+ `!command` runs that command on the machine this client is on — not on the host the agent works in — and prints the command and its output inline in the conversation, where it stays in place and scrolls away with the history; nothing is sent to the model and nothing is written to disk. Output is capped at 200 lines and 64 KiB per command, only the last 20 commands are kept, and the number of dropped lines is stated in the block. One command runs at a time; Esc, or Ctrl+C on an empty prompt, stops it by killing its whole process group, so pipelines and background children die with it; a command that needs a terminal of its own, such as `vim`, cannot work. `--no-shell` or `DSHT_NO_SHELL=1` disables the prefix. Every such run is also a read-only *source*: `Ctrl+O` opens the newest source — a `!` run, a forked verifier's own session, or a host subagent child — in a full-screen view that follows it live without selecting it, so reading one never becomes writing to it. The header says what the source is and which session it belongs to, ↑/↓, PgUp/PgDn and the wheel scroll it, and Esc closes it and gives the composer back. A verifier's session is created and titled `[dsht-verify] <record> · <step>/<attempt>`; it stays listed after the run stops, and the source list itself lives in this client run, so a later start finds it again only as an ordinary session in the host's session list.
301
312
 
302
313
  Within each User group, only the first assistant prose or reasoning message shows an Assistant heading. Later messages and live output reuse that heading across tool results and Context messages. A newly loaded history window starts its own visible group; message sequences, tool status, search, and reasoning expansion remain independent.
303
314
 
304
- For mouse copying, click once to enter copy mode, then drag to select after the display freezes. Releasing the mouse keeps the display frozen until Esc, Ctrl+S, or Ctrl+C resumes it. Terminal-native Shift-drag may bypass application mouse reports; press Ctrl+S first in that case. In dialogs, press Ctrl+S to freeze the entire display and release mouse capture before native selection.
315
+ A click on a local run opens that run's read-only view instead: the `view` row of the `/loop` line a run leaves behind (or that line's bar), or the bar of a `!` block; anywhere else, click once to enter copy mode, then drag to select after the display freezes. Releasing the mouse keeps the display frozen until Esc, Ctrl+S, or Ctrl+C resumes it. Terminal-native Shift-drag may bypass application mouse reports; press Ctrl+S first in that case. In dialogs, press Ctrl+S to freeze the entire display and release mouse capture before native selection.
305
316
 
306
- Tab completes the leading slash command, extending an ambiguous draft to the shared prefix. The composer supports Readline-style editing and keeps the line breaks and tabs of pasted text; a tab is displayed at its tab stop and sent unchanged. Words are whitespace-delimited; cursor movement and character deletion preserve composed Unicode characters. The composer shows only a few content rows and scrolls to keep the cursor visible, so a long draft never squeezes the conversation away; its window is sized from the terminal height alone, so a wider terminal only wraps less. A multiline draft taller than that window folds its interior lines behind `[N lines · X KB]` while the first and last lines stay visible; ←/→ cross the block in one step, Backspace or Delete at its edge removes the whole block, and Enter sends the full text. Ctrl+D on empty input does not exit; Ctrl+C clears a non-empty draft before it stops or exits. Other unhandled modifier shortcuts do not insert their control characters. Both BS and DEL terminal backspace encodings delete backward; the dedicated Delete key (CSI 3~) deletes forward.
317
+ Tab completes the leading slash command, extending an ambiguous draft to the shared prefix. Enter also runs a draft whose first word names exactly one command, so `/pro Save tests` runs `/prompt Save tests` without typing the whole name; a token that names several commands is refused with the candidates listed, and `/quit`, `/allow` and `/deny` must be typed in full. The composer supports Readline-style editing and keeps the line breaks and tabs of pasted text; a tab is displayed at its tab stop and sent unchanged. Words are whitespace-delimited; cursor movement and character deletion preserve composed Unicode characters. The composer shows only a few content rows and scrolls to keep the cursor visible, so a long draft never squeezes the conversation away; its window is sized from the terminal height alone, so a wider terminal only wraps less. A multiline draft taller than that window folds its interior lines behind `[N lines · X KB]` while the first and last lines stay visible; ←/→ cross the block in one step, Backspace or Delete at its edge removes the whole block, and Enter sends the full text. Ctrl+D on empty input does not exit; Ctrl+C clears a non-empty draft, cancels a long operation that still owns the client, and only then stops the agent or exits. While a long operation such as an export or a compaction owns the client, the composer stays editable — the next line can be written — but Enter refuses to send it until the operation finishes, and finishing never submits the draft on its own. Other unhandled modifier shortcuts do not insert their control characters. Both BS and DEL terminal backspace encodings delete backward; the dedicated Delete key (CSI 3~) deletes forward.
307
318
 
308
319
  | Key | Edit |
309
320
  | --- | --- |
@@ -333,11 +344,14 @@ Tab completes the leading slash command, extending an ambiguous draft to the sha
333
344
  | `/goal [objective|clear|edit text|pause|resume]` | View, set, edit, pause, resume, or clear the task goal |
334
345
  | `/permission [preset]` | View or switch the host sandbox/approval preset |
335
346
  | `/feedback TEXT` | Record feedback about the current session |
347
+ | `/handoff` | Delete the local HANDOFF.md, then have the agent write a fresh session handoff |
348
+ | `/loop [name\|stop\|answer] [score] [tries]` | Pick a `loop.yaml` record from a list, edit the inputs and limits it starts from, then run the scored loop; `stop`/`abort` ends the run and `answer TEXT` supplies what a paused verifier asked for |
336
349
  | `/export [local.zip]` | Download the session log ZIP to a new local file |
337
350
  | `/export-html [local.html]` | Save the loaded conversation as offline HTML with diagrams and math |
338
351
  | `/coredump [tag]` | Write a V8 heap snapshot to the working directory for memory diagnosis |
339
352
  | `/older` | Load older history |
340
353
  | `/history [text]` | List your own prompts, optionally filtered; Enter jumps to the selected record |
354
+ | `/prompt [text]` | List saved shortcut prompts — Enter uses, `e` edits, `d` deletes — or save TEXT as a new one |
341
355
  | `/search <text>` | Search history page by page; choose a match to open its location |
342
356
  | `/copy` | Freeze for terminal selection; Esc resumes |
343
357
  | `/latest` | Return to live output and release the separate historical window |
@@ -350,9 +364,15 @@ Tab completes the leading slash command, extending an ambiguous draft to the sha
350
364
  | `/think SEQ` | Toggle one loaded thought; `live` toggles the active attempt |
351
365
  | `/help`, `/quit` | List every command with its description, or exit |
352
366
 
353
- Slash commands work in both pickers and the conversation composer. Typing `/` displays matching commands, and `/help` lists commands with their one-line descriptions; PgUp/PgDn changes pages. The `/help`, `/cost`, and `/status` panels remain open until the next command or Esc; `/history` also expires after ten seconds, so a forgotten lookup releases the composer. Esc leaves the draft in place. `/workspace` and `/workspaces` alias `/ws`; `/session` and `/sessions` alias `/resume`. Names may contain spaces; quotes around the complete target are optional. The unquoted target `all` is reserved for `/resume all`; use `/resume "all"` or an ID to open a session titled `all`. Ambiguous targets require a full ID. Switching a workspace opens its sessions and detaches the old transcript; switching sessions updates the workspace label. Neither operation cancels a remote agent.
367
+ Slash commands work in both pickers and the conversation composer. Typing `/` displays matching commands, and once a name is settled and a space follows, the composer shows that command's usage and one-line description under the draft, so an argument never has to be remembered; `/help` lists every command with its description, and PgUp/PgDn changes pages. `/loop` additionally lists its `loop.yaml` records under the composer as soon as the draft names a record, so a run never has to be spelled out flag by flag. The `/help`, `/cost`, and `/status` panels remain open until the next command or Esc; `/history` also expires after ten seconds, so a forgotten lookup releases the composer. Esc leaves the draft in place; while a long operation owns the client — a search, an export, a history page load — it cancels that operation first, and otherwise it cancels whatever a command opened: a picker returns to the conversation it was opened over — without reloading it — and at startup, with no conversation yet, the session list steps back to the workspace list. `/workspace` and `/workspaces` alias `/ws`; `/session` and `/sessions` alias `/resume`. Names may contain spaces; quotes around the complete target are optional. The unquoted target `all` is reserved for `/resume all`; use `/resume "all"` or an ID to open a session titled `all`. Ambiguous targets require a full ID. Switching a workspace opens its sessions and detaches the old transcript; switching sessions updates the workspace label. Neither operation cancels a remote agent.
368
+
369
+ `/prompt` opens the shortcut prompts you saved, and `/prompt TEXT` saves TEXT as one; the list takes ↑/↓, Enter uses the selected prompt, `e` edits it, and `d` or the dedicated Delete key removes it. Choosing a prompt puts its text into the composer as an ordinary draft instead of sending it, so it can still be changed before Enter sends it. An edit started with `e` is the opposite: Enter writes the text back to the list and never sends it, and Esc abandons the edit and restores the draft it replaced. The prompts live in one private file (`<state>/prompts.json`, mode 0600) beside the memory log, and that one list is shared by every workspace and every host this client talks to, so a shortcut is written once and used everywhere; an identical text is not saved twice, a prompt is limited to 8 KiB, and the list holds at most 500 entries. A host is not needed to save or list them, and a file this build cannot read leaves the list empty and says so in the panel instead of stopping the client.
370
+
371
+ `/handoff` prepares the conversation for whoever picks it up next. It first deletes this client's own `HANDOFF.md`, in the directory `dsht` runs in, so a stale file can never be mistaken for the new one; then it sends the agent an ordinary turn asking it to write a fresh `HANDOFF.md` in the workspace root. That document is asked to cover why the session exists, the goal, and the state of every task — completed, still open, or impossible and why — followed by the decisions made, the files changed, how to verify the current state, and the exact next steps. The command takes no arguments, needs a selected conversation, and waits for a pending approval or question to be settled first. The local deletion happens on this machine and does not by itself write anything on the host.
372
+
373
+ A command that would write to the conversation while the agent is working — `/compact`, `/handoff`, `/loop <name>` — is accepted and held rather than refused: it runs as soon as the turn (or the loop) ends, in the order it was submitted, and `/clear`-style edits to the draft are never involved because the line was already submitted. Everything else keeps working while the agent works, and `/cancel`, `/allow`, `/deny` and `/loop stop` are never held.
354
374
 
355
- Type `@` at the end of the draft to search files and directories in the selected session's working directory **on the host**. Use ↑/↓ to select and Tab or Enter to insert; selecting a directory continues completion inside it. Paths with spaces use `@"path with spaces"`. Escape closes the menu and requests cancellation when the agent is running; after closing it, Enter sends the literal draft, including an unmatched path. Lookup failures remain visible and do not submit the draft. Completion operates on the trailing reference, not the cursor position inside existing text.
375
+ `/loop [name|stop|answer] [score] [tries]` runs one record from `loop.yaml` as a **client-driven** loop, and it is meant to be chosen rather than typed. Typing `/loop` lists every record under the composer with its rounds, artifact, declared defaults and any input it is pointed at; ↑/↓ selects, Enter confirms the highlighted record, and Tab inserts its name for anyone who wants to add flags. Confirming opens a parameter list pre-filled with that record's own inputs first — for `designdoc-review`, the `path` of the document under review — followed by the shared limits `From`, `To`, `Pass`, `Tries`; a value is replaced by typing over it, the inputs as free text and the limits as numbers checked exactly as the flags are, leaving a row with the arrows or Enter commits what was typed so no box needs its own confirmation, `Start run` launches, and `← Choose another record` goes back; nothing runs until Start is pressed, so a default is always visible before it is spent. Editing a record's input retargets that one run and leaves `loop.yaml` alone, so `designdoc-review` reviews any document and `design-review` is unaffected. Passing any flag on the line (`/loop design-review 9 3`, `--from`, `--to`, `--score`, `--tries`) skips the form and runs exactly what was typed, which keeps scripted and headless use unchanged, and a name that does not exist lists the records that do. The record itself owns the rounds, their checklists, any extra standard, its fixed inputs and the defaults: this client sends the round's brief, an independent `dsht` process verifies the work in a session of its own and writes a verdict file back, and the client decides what comes next — a score at or above the passing mark advances to the next round, a lower score costs one attempt, and a round that exhausts its budget stops the run. The shipped records are `design-review` (ten rounds over the module design) and `designdoc-review` (the same loop over the document named by the record's `vars.path`, keeping one review file per document next to it — `tui-design.md.review.md`, `loop.md.review.md` — so retargeting a run never reads or overwrites another document's rounds; that file is a template in the record, `{{path}}.review.md`, and it is gitignored). `blocked` stops the run at once instead of spending the attempt budget, a verifier that cannot judge asks for a person instead and stops it the same way (headless runs exit 3), a round is only accepted once the client itself has checked that the round's own section reached the artifact file, and `--deadline <minutes>` bounds the whole run. A `passed` run only ever claims the rounds it covered and says which ones (`rounds 1–3/10 · selected range`); a run over the whole record ends on the consolidation round, whose verifier is handed every earlier round's checklist to re-check, so a later round that broke an earlier requirement cannot pass unnoticed. A round that starts by verifying runs inside the forked verifier's own session, so the selected session's conversation stays empty until a round fails and asks the agent to work; the progress line says `verifying step N · attempt M` while that check runs, the status bar reports the same work (`◐` with the loop's step) instead of claiming Ready, and a verifier that cannot produce a verdict reports a structured reason — its exit code plus what the child itself reported (no JSON verdict in the reply, or no reply committed after the turn) rather than the class of its error output alone — instead of waiting in silence, and the verifier is handed the run's own inputs, so it cannot judge a document the artifact only mentions from an earlier run, and `--trace-verbose` adds the sanitized last line. A progress line above the composer shows the round, attempt and best score; sending an ordinary message, `/loop stop`, `/cancel`, Esc, Ctrl+C, switching sessions or losing the connection all stop the loop, which is never persisted. `/loop stop` is a control command, so it is admitted while the run is in flight; with nothing running it says so instead of failing, and the finished run's progress line stays readable — the line that ended it still shows the result, and the next line you run clears it. A verifier that cannot judge *pauses* the run instead of ending it: the progress line and the status bar say `needs you`, `/loop answer TEXT` adds the missing condition and re-judges the current artifact under a new verification identity without spending an attempt or sending the agent anything, and `/loop abort` (the paused spelling of `/loop stop`) ends the run.
356
376
 
357
377
  A file reference sends only `@path` in a text block. Harness instructs the model to read the referenced file or list the directory when needed; the TUI does not read local files, upload bytes, or expand contents into the prompt. Referencing an image path does not attach image data. Local attachments, image uploads/previews, and `@` session references are not implemented.
358
378
 
@@ -394,7 +414,7 @@ Reasoning streams in full while being generated, then folds when its block close
394
414
 
395
415
  `/model` reads `session/modelCatalog` and offers the provider/model routes and reasoning efforts advertised by the host. Selection calls `session/selectModel` with `{ request: { sessionId, provider, model, reasoningEffort? } }`; omitting effort uses the adapter default. The host applies it to subsequent requests, logs the selection, and also attempts to save it as the deployment default. It does not replace an in-flight request. `modelSelection.next` and `lastUsed` remain the authority for the displayed model; failures retain the previous selection. Provider catalog failures are shown without hiding healthy providers. The header follows the web agent-preset label: `agentPreset` supplies the current ID and `agentPresets/list` supplies names and trust metadata. Built-in system presets display Standard mode, PTC mode, Minimal mode, or Creator mode. Custom presets retain their names; missing roster entries fall back to the ID. The optional roster loads only when needed and is reused for the connection. Plan is a separate feature and does not determine this mode label. Below 62 terminal columns, mode remains available in `/status` to leave room for the session title.
396
416
 
397
- History separates semantic message blocks, prompt/reasoning summaries, view-only fold state, and a row index. Stream frames reuse committed offsets and materialize only the viewport. A per-session LRU holds at most 2,048 committed terminal rows; evicted rows are recreated when revisited. Finished legacy chunks and unused tool-result bodies are released, while the host retains the original log. The host log is the durable tier; the client is a reloadable memory tier. The live tail defaults to soft budgets of 2,000 records or 16 MiB of estimated semantic payload (`--history-records`, `--history-mb`). Eviction targets 75% of the budgets and releases old text, summaries, and layout caches. Switching sessions disposes the previous transcript. Scrolled reading and reasoning navigation protect the loaded window; `/latest` returns to live output and resumes reclamation. Offline history, unfinished streams, and a minimum recent tail are protected, so these limits are not a process RSS cap. A bounded runtime memory log is enabled by default at `<state>/memory.log`: one JSON line every 30 seconds with the process counters, the retained record and byte counts, the pin state, the ledger size, the layout row cache, the bounded math and diagram cache, the React render-measurement count, and the sessions, pages and events the last cost scan re-read, so growth can be told apart from V8's high-water mark. On a runtime that exposes a collection the sample also records the heap after a forced one; `npm run start:profile` supplies that runtime together with a heap-snapshot signal, where `kill -USR2 <pid>` writes a snapshot. `/coredump [tag]` writes the same `.heapsnapshot` artifact into the client's working directory without a signal or a profiling runtime; the tag labels the file (default `snapshot`) and V8 serializes the heap synchronously, so the client pauses until the file exists. Open the snapshot in Chrome DevTools rather than the terminal. `--memory-log <path>` or `DSHT_MEMORY_LOG` changes the path, and `--no-memory-log` or `DSHT_MEMORY_LOG=off` disables it. The executable selects React's production build, because the development build writes one performance-timeline entry per rendered component that Node retains for the life of the process; set `DSHT_REACT_DEV=1` to keep the development build for React warnings and DevTools performance tracks. First layout, width changes, and an expanded very large block still require wrapping that content. `npm run bench:history` measures local stream/layout cost at 500, 2,000, and 10,000 messages without model or network time.
417
+ History separates semantic message blocks, prompt/reasoning summaries, view-only fold state, and a row index. Stream frames reuse committed offsets and materialize only the viewport. A per-session LRU holds at most 2,048 committed terminal rows; evicted rows are recreated when revisited. Finished legacy chunks and unused tool-result bodies are released, while the host retains the original log. The host log is the durable tier; the client is a reloadable memory tier. The live tail defaults to soft budgets of 2,000 records or 16 MiB of estimated semantic payload (`--history-records`, `--history-mb`). Eviction targets 75% of the budgets and releases old text, summaries, and layout caches. Switching sessions disposes the previous transcript. Scrolled reading and reasoning navigation protect the loaded window; `/latest` returns to live output and resumes reclamation. Offline history, unfinished streams, and a minimum recent tail are protected, so these limits are not a process RSS cap. A bounded runtime memory log is enabled by default at `<state>/memory.log`: one JSON line every 30 seconds with the process counters, the retained record and byte counts, the pin state, the ledger size, the layout row cache, the bounded math and diagram cache, the React render-measurement count, and the sessions, pages and events the last cost scan re-read, so growth can be told apart from V8's high-water mark. On a runtime that exposes a collection the sample also records the heap after a forced one; `npm run start:profile` supplies that runtime together with a heap-snapshot signal, where `kill -USR2 <pid>` writes a snapshot. `/coredump [tag]` writes the same `.heapsnapshot` artifact into the client's working directory without a signal or a profiling runtime; the tag labels the file (default `snapshot`) and V8 serializes the heap synchronously, so the client pauses until the file exists. Open the snapshot in Chrome DevTools rather than the terminal. `--memory-log <path>` or `DSHT_MEMORY_LOG` changes the path, and `--no-memory-log` or `DSHT_MEMORY_LOG=off` disables it. A bounded transition trace is enabled by default at `<state>/trace.log`: one JSON event per line for every connection generation, picker request, local-workspace adoption, session resolution, screen or selection change, every executed command as a `begin`/`end` span sharing one `commandId` (so a crash mid-command is visible, and the end carries the same `outcome` and `disposition` the reader is shown, plus the failure text), every session write's admission order with its lane (`normal` or `control`) and whether it had to wait — the line order is the dispatch order — every long operation the client ran (`foreground` begin/end with its kind and whether it was cancelled), and every `/loop` decision — which record the list chose, whether the form opened, whether the run began and what it sent, why it was refused, and the whole lifecycle of each forked verification (`begin`, `verified`, `retry`, `unavailable`, …, with a structured reason — exit code and error class — when it could not judge) — so a screen that moves on its own, a run that never starts, or a round whose verdict never arrives can be read back with the values it moved between instead of guessed at. It never records prompt, tool or session text. `--trace <path>` or `DSHT_TRACE` changes the path, and `--no-trace` or `DSHT_TRACE=off` disables it; `--trace-verbose` (or `DSHT_TRACE_VERBOSE=1`) quotes the verifier child's own last line in a failure reason, after stripping paths and credentials. The executable selects React's production build, because the development build writes one performance-timeline entry per rendered component that Node retains for the life of the process; set `DSHT_REACT_DEV=1` to keep the development build for React warnings and DevTools performance tracks. First layout, width changes, and an expanded very large block still require wrapping that content. `npm run bench:history` measures local stream/layout cost at 500, 2,000, and 10,000 messages without model or network time.
398
418
 
399
419
  ## Cost estimates
400
420
 
@@ -426,8 +446,8 @@ This repository publishes one public package, `@itookit/dsht`, from the `mushuan
426
446
 
427
447
  | Field | Value |
428
448
  | --- | --- |
429
- | Name and version | `@itookit/dsht` `0.3.7` |
430
- | Executable | `dsht`, or `npx @itookit/dsht` without installing |
449
+ | Name | `@itookit/dsht` |
450
+ | Executable | `dsht`, or `npx @itookit/dsht` without installing; `dsht --version` prints the published version |
431
451
  | Library entries | `@itookit/dsht` and `@itookit/dsht/auth` |
432
452
  | Author | lizlok@gmail.com |
433
453
  | License | MIT, with the license text in `LICENSE` |
@@ -464,7 +484,7 @@ node dist/cli/index.js --help
464
484
 
465
485
  Tests use isolated HTTP/WebSocket hosts, drive the real Ink picker and composer, run the CLI in subprocesses, and project copied Harness v2 workspace-edit and v0 packed-chunk recordings. The repository needs no model credentials for these checks. The recording and expected transcript live under `tests/`; they do not depend on a parent checkout. Live model-provider behavior is not covered by these tests.
466
486
 
467
- Source is organised by business domain under `src/`: `transport/` owns the host wire protocol and authentication, `session/` the transcript, history and interactions, `cost/` the folded billing ledger, `catalog/` models and presets, `controller/` the application facade, `ui/` everything React and Ink, `storage/` every filesystem operation, and `cli/` the composition root. Cross-domain imports go through each module's `index.ts`; `tests/architecture/dependencies.test.ts` rejects a forbidden direction.
487
+ Source is organised by business domain under `src/`: `transport/` owns the host wire protocol and normalizes host frames into semantic events, `session/` the transcript, history, interactions and the session runtime, `cost/` the billing ledger, `catalog/` models and presets, `shell/` local `!` commands, `controller/` the application facade and event routing, `slash/` the command syntax, `ui/` everything React and Ink, `storage/` every filesystem operation, `cli/` the composition root, and the root files (`state.ts`, `json.ts`, `text.ts`, `contracts.ts`, `session-title.ts`, `references.ts`) the shared contracts. Cross-domain imports go through each module's `index.ts` or a shared leaf, and `tests/architecture/dependencies.test.ts` enforces nine forbidden directions with no exemptions.
468
488
 
469
489
  `npm test` renders frames without styling, because the assertions and the recorded expectations in `tests/expected/` describe text. A test runner started from a terminal exports `FORCE_COLOR=1` to each test file, which makes Ink interleave SGR escapes between a prompt and its text; `npm run test:terminal` reproduces that environment on any host, and `prepublishOnly` runs it so a publish from a terminal validates what a terminal actually renders. Theme tests render separate truecolor and plain subprocesses with terminal and CI color detection isolated from the parent environment.
470
490
 
package/README.zh.md CHANGED
@@ -166,7 +166,7 @@ npm start
166
166
 
167
167
  两种方式读取相同的 `DSH_URL` 和 `DSH_TOKEN` 变量。
168
168
 
169
- 使用 ↑/↓ 和 Enter 选择工作区,然后选择已有会话或 **New session**。**All sessions** 同时显示未归属注册工作区的会话。**Add workspace (this directory)** 直接注册 `dsht` 自身所在的目录,只在服务端尚未注册它时出现;**Add workspace (host directory)** 接收服务端已有目录的绝对路径,该路径可能与本机文件系统不同,按 Esc 可以退回选择器。新建会话前必须选择工作区。
169
+ 使用 ↑/↓ 和 Enter 选择工作区,然后选择已有会话或 **New session**。**All sessions** 同时显示未归属注册工作区的会话。**Add workspace (this directory)** 直接注册 `dsht` 自身所在的目录,只在服务端尚未注册它时出现;**Add workspace (host directory)** 接收服务端已有目录的绝对路径,该路径可能与本机文件系统不同,按 Esc 可以退回选择器。在已注册的工作区目录里启动时,直接选中该工作区而不再显示选择器(会话列表里的 `←` 仍可切到别的工作区);命令行给出会话 ID 时依旧直接打开。新建会话前必须选择工作区。
170
170
 
171
171
  首次登录通过 `GET /` 兑换 token,并按 HTTP origin 保存 cookie。后续启动和列表命令自动复用 cookie,无需再次提供 token。默认目录为 `$XDG_STATE_HOME/dsht/auth`,未设置时使用 `~/.local/state/dsht/auth`;可通过 `--auth-dir` 或 `DSHT_AUTH_DIR` 覆盖。POSIX 下目录权限为 0700、cookie 文件为 0600;Windows 使用账户目录继承的访问控制。启动 token 永不保存。
172
172
 
@@ -285,9 +285,19 @@ node --import tsx src/cli/index.tsx list sessions --json
285
285
 
286
286
  JSON 输出格式为 `{ "items": [...] }`;省略 `--json` 则输出制表符分隔的列表。工作区筛选使用服务端 `sessionIds` 成员关系。工作区列表读取 `workspace/follow` 的首个 baseline 后取消订阅,不会调用不存在的 `workspace/list` 端点。
287
287
 
288
+ ## 回读状态迁移日志
289
+
290
+ ```sh
291
+ npx @itookit/dsht trace
292
+ npx @itookit/dsht trace --json
293
+ npx @itookit/dsht trace --trace /path/to/trace.log
294
+ ```
295
+
296
+ `dsht trace` 把 `<state>/trace.log` 读回并打印几行事实:执行了多少条命令、各自如何结束(含 kind);每个泳道与每个会话各有多少次写入;跑过多少次前台操作、其中多少被取消、哪一次最长;每个 loop run 发出了多少步、以什么结局(含 reason)结束;验证如何收尾(含 `unavailable` 的错误类别);以及所有 `begin` 没有配对 `end` 的 span——这些异常在 1443 行 JSONL 里是看不见的。`--json` 输出同一份汇总的结构化版本,`--trace` 读取指定文件而不是默认路径。
297
+
288
298
  ## 对话操作
289
299
 
290
- Enter 提交消息:代理处于 Working 时自动作为下一个 step 的转向输入;空闲时开启新一轮。转向输入等待当前步骤及其工具执行完成,不会中断正在运行的工具。输入框非空时,Ctrl+C 先清空输入;否则所选会话运行中时请求取消,只有空闲时才退出,连续按键会复用尚未完成的取消请求。取消会等待正在提交的消息完成接收,失败时保留客户端。聊天界面中 Esc 会发送取消请求,不受本地空闲状态判断限制。任务运行中时,Esc 关闭文件或历史/搜索菜单的同时请求取消;空闲菜单仅关闭。正在执行的历史/搜索/费用加载、服务端命令和导出请求优先被取消。Page Up/Down 滚动当前对话;`/older` 加载更早记录。所有退出路径(包括 `/quit` 和 SIGTERM)都会在关闭连接前停止所选任务,因此退出不会留下仍在运行的代理;会话空闲时不发送取消。取消当前任务会保留排队消息。
300
+ Enter 提交消息:代理处于 Working 时自动作为下一个 step 的转向输入;空闲时开启新一轮。转向输入等待当前步骤及其工具执行完成,不会中断正在运行的工具。输入框非空时,Ctrl+C 先清空输入;否则所选会话运行中时请求取消,只有空闲时才退出,连续按键会复用尚未完成的取消请求;一次没能退出的按键会为下一次“上膛”——五秒内再按一次 Ctrl+C 即可退出,即使 host 尚未确认停止,因此 host 永不报告空闲的 turn 也困不住客户端;退出前仍会请求 host 停止该 turn。取消会等待正在提交的消息完成接收,失败时保留客户端。聊天界面中 Esc 会发送取消请求,不受本地空闲状态判断限制。任务运行中时,Esc 关闭文件或历史/搜索菜单的同时请求取消;空闲菜单仅关闭。正在执行的历史/搜索/费用加载、服务端命令和导出请求优先被取消。Page Up/Down 滚动当前对话;`/older` 加载更早记录。所有退出路径(包括 `/quit` 和 SIGTERM)都会在关闭连接前停止所选任务,因此退出不会留下仍在运行的代理;会话空闲时不发送取消。取消当前任务会保留排队消息。
291
301
 
292
302
  `/copy`、Ctrl+S 或普通聊天界面的鼠标左键单击冻结画面并关闭鼠标事件捕获,便于使用终端原生选择复制。Esc、Ctrl+S 或 Ctrl+C 退出复制模式并显示最新输出,退出复制模式不会取消代理。对话框和选择器暂停背景标题和对话的自动更新,聊天对话框也暂停状态更新。工作区选择、会话选择和主机路径输入界面的连接提示及状态栏持续刷新,复制模式除外。对话历史保留在输入框上方,鼠标滚轮及 PgUp/PgDn 可滚动历史,不会移动当前选项;帮助面板中的 PgUp/PgDn 用于帮助翻页。对话框中的鼠标单击不会进入复制模式,Ctrl+S 可冻结整个画面并释放鼠标捕获以进行原生选择。回看旧历史时 Working 计时显示也暂停。帮助/状态/费用面板不再定时消失。后台接收与内存回收继续运行,调整窗口大小仍可能重绘。
293
303
 
@@ -297,13 +307,14 @@ Enter 提交消息:代理处于 Working 时自动作为下一个 step 的转
297
307
 
298
308
  `/search` 对对话消息进行不区分大小写的字面文本匹配,包含旧页,排除纯工具行。搜索每次请求最多 80 条消息,扫描后释放临时页,只保留最多 200 条简短命中摘要,包含折叠的思考;结果截断时提示缩小查询范围。选择命中项只加载其序号附近的独立页面,`/latest` 释放该窗口。Esc 或 Ctrl+C 可取消搜索。稀有词或无匹配查询仍需通过 HTTP 扫描全部历史,此命令尚无服务端全文索引。`/history` 只列出已加载页面中自己的提示词,选择器显示的数字就是记录序号。`/ssearch` 与 `/wsearch` 调用 `session/search`,服务端搜索当前用户/助手消息内容,最多返回 20 个会话、摘要和截断标记,没有结果分页游标或命中记录序号。工作区筛选在全局数量限制之后进行,因此截断时可能漏掉工作区内的匹配会话;界面会提示结果不完整,可缩小查询范围。选择会话后加载其历史,再选择匹配消息跳转。所有操作均通过 HTTP 完成,不扫描服务端配置目录。
299
309
 
300
- ↑/↓ 或 Ctrl+P/N 回填之前提交的提示词和 slash 命令,按 Enter 才提交。一屏放得下的阅读面板会把方向键留给该历史,需要滚动的面板才接管方向键,而 Ctrl+P/N 在任何面板打开时都能回填。向下越过最新记录时恢复未发送草稿;编辑回填内容后开始新的草稿,切换会话会清空未发送的草稿。回填按当前会话保留其 user prompt,最多 2,000 条、约 512 KiB 文本,且不写入独立历史文件;切换会话即释放。它在记录到达时增量折叠这些提示词,因此覆盖本客户端连接之前的提示词,而不只是碰巧加载的那个窗口。打开会话后它还会在后台把更早的历史翻一遍,只提取提示词、不把这些页读进上方对话,因此从会话开始就持有整个会话的提示词列表。走到索引里最旧一条时,先用已加载的对话补回被预算淘汰的提示词,这一步不发请求;只有窗口也用尽才取回窗口之前的一页,并在同一次有界循环里跳过整页没有 User 消息的页,因此按键不会被工具页卡住;取回的那页也会留在输入框上方的对话里。于是淘汰只约束内存、不决定可达性:被淘汰的提示词要么仍在窗口内,要么仍在宿主上。连续重复输入合并,超大输入跳过,提问和审批回答不记入历史。提问选项与补全菜单优先使用箭头;工作区/会话列表在输入框为空时使用箭头选择,可用 Ctrl+P/N 调出输入历史。
310
+ ↑/↓ 或 Ctrl+P/N 回填之前提交的提示词和 slash 命令,按 Enter 才提交。一屏放得下的阅读面板会把方向键留给该历史,需要滚动的面板才接管方向键,而 Ctrl+P/N 在任何面板打开时都能回填。向下越过最新记录时恢复未发送草稿;编辑回填内容后开始新的草稿,切换会话会清空未发送的草稿。回填按当前会话保留其 user prompt,最多 2,000 条、约 512 KiB 文本,且不写入独立历史文件;切换会话即释放。它在记录到达时增量折叠这些提示词,因此覆盖本客户端连接之前的提示词,而不只是碰巧加载的那个窗口。打开会话后它还会在后台把更早的历史翻一遍,只提取提示词、不把这些页读进上方对话,因此从会话开始就持有整个会话的提示词列表。走到索引里最旧一条时,先用已加载的对话补回被预算淘汰的提示词,这一步不发请求;只有窗口也用尽才取回窗口之前的一页,并在同一次有界循环里跳过整页没有 User 消息的页,因此按键不会被工具页卡住;取回的那页也会留在输入框上方的对话里。于是淘汰只约束内存、不决定可达性:被淘汰的提示词要么仍在窗口内,要么仍在宿主上。连续重复输入合并,超大输入跳过,提问和审批回答不记入历史。由客户端自己组装的提示词——`/loop` 的 Brief 与后续短跟进、以及 `/handoff` 的请求——以"内部回合"发送,不记入该历史,因此上下箭头只保留你真正输入过的内容。提问选项与补全菜单优先使用箭头;工作区/会话列表在输入框为空时使用箭头选择,可用 Ctrl+P/N 调出输入历史。
311
+ `!命令` 在这台运行客户端的机器上执行——不是 agent 所在的宿主——并把命令行与输出内联打印在对话里、留在它发生的位置随历史一起滚走;不会发给模型,也不写入磁盘。每条命令的输出上限为 200 行与 64 KiB,只保留最近 20 条命令,被丢弃的行数会在块内注明。同一时刻只运行一条命令;Esc(或输入框为空时 Ctrl+C)会终止其整个进程组来停止它,因此管道与后台子进程一起结束;需要独占终端的命令(如 `vim`)无法工作。`--no-shell` 或 `DSHT_NO_SHELL=1` 关闭该前缀。每一次这样的运行同时是一个只读**输出源**:`Ctrl+O` 会打开最新的源——`!` 运行、fork 出来的 verifier 自己的会话,或 host 侧 subagent 子会话——用整屏视图实时跟随它,全程不选中它,因此“看”永远不会变成“写”。视图头部写明这是什么源、属于哪个会话;↑/↓、PgUp/PgDn 与滚轮滚动,Esc 关闭并把输入框还回来。verifier 的会话由循环创建并命名为 `[dsht-verify] <记录名> · <轮>/<次>`,运行停止后仍留在列表里;源列表本身只属于本次客户端运行,所以下次启动只能在 host 的会话列表里把它当作普通会话找到。
301
312
 
302
313
  每条 User 消息之后,只在第一段助手正文或思考前显示 Assistant 标题;后续消息及流式输出沿用分组,工具结果和 Context 消息不重置分组。当前加载的历史窗口从自身起点建立可见分组,消息序号、工具状态、搜索和思考展开仍各自保留。
303
314
 
304
- 鼠标复制时,先单击进入复制模式,待画面冻结后再拖动选择。松开鼠标不会恢复刷新,需按 Esc、Ctrl+S 或 Ctrl+C。终端原生 Shift+拖选可能不向应用发送鼠标事件,此时请先按 Ctrl+S。对话框中可按 Ctrl+S 冻结整个画面并释放鼠标捕获,再进行原生选择。
315
+ 单击本地运行会打开它的整屏只读视图:`/loop` 运行留下的那一行里的 `view` 那一行(或该行的标题栏),或 `!` 块的标题栏;其他位置的单击仍是进入复制模式,待画面冻结后再拖动选择。松开鼠标不会恢复刷新,需按 Esc、Ctrl+S 或 Ctrl+C。终端原生 Shift+拖选可能不向应用发送鼠标事件,此时请先按 Ctrl+S。对话框中可按 Ctrl+S 冻结整个画面并释放鼠标捕获,再进行原生选择。
305
316
 
306
- Tab 补全开头的 slash 命令,多个候选时补到公共前缀。输入框支持 Readline 风格编辑,并保留粘贴内容中的换行与制表符;制表符按制表位显示、原样发送。单词以空白分隔;光标移动和逐字符删除保持完整的 Unicode 组合字符。输入框只显示少量内容行,超出后内部滚动并保持光标可见,因此长草稿不会把对话区挤没;窗口高度只由终端行数决定,终端更宽只会减少折行。行数超过该窗口的多行草稿会把中间行折叠为 `[N lines · X KB]`,首行与末行保持可见;←/→ 一次跨越整块,在其边界按 Backspace 或 Delete 删除整块,发送时仍为完整原文。空输入时 Ctrl+D 不退出;输入非空时 Ctrl+C 先清空输入,然后才停止或退出。未处理的修饰键快捷键不会将控制字符插入消息。终端退格键的 BS 和 DEL 编码均向后删除;独立 Delete 键(CSI 3~)向前删除。
317
+ Tab 补全开头的 slash 命令,多个候选时补到公共前缀;回车也会直接执行"首词唯一命名一条命令"的草稿,因此 `/pro Save tests` 等同于 `/prompt Save tests`,无需输入完整命令名。若首词匹配多条命令,则会拒绝执行并列出候选;`/quit`、`/allow`、`/deny` 必须输入完整命令名。输入框支持 Readline 风格编辑,并保留粘贴内容中的换行与制表符;制表符按制表位显示、原样发送。单词以空白分隔;光标移动和逐字符删除保持完整的 Unicode 组合字符。输入框只显示少量内容行,超出后内部滚动并保持光标可见,因此长草稿不会把对话区挤没;窗口高度只由终端行数决定,终端更宽只会减少折行。行数超过该窗口的多行草稿会把中间行折叠为 `[N lines · X KB]`,首行与末行保持可见;←/→ 一次跨越整块,在其边界按 Backspace 或 Delete 删除整块,发送时仍为完整原文。空输入时 Ctrl+D 不退出;Ctrl+C 先清空非空输入,再取消仍在占用客户端的长操作,最后才停止或退出。导出、压缩等长操作占用客户端期间,输入框仍保持可编辑——下一句可以先写好——但 Enter 在操作结束前不会把它发出去,操作结束也不会自动替你发送。未处理的修饰键快捷键不会将控制字符插入消息。终端退格键的 BS 和 DEL 编码均向后删除;独立 Delete 键(CSI 3~)向前删除。
307
318
 
308
319
  | 按键 | 编辑操作 |
309
320
  | --- | --- |
@@ -333,11 +344,14 @@ Tab 补全开头的 slash 命令,多个候选时补到公共前缀。输入框
333
344
  | `/goal [objective|clear|edit text|pause|resume]` | 查看、设置、编辑、暂停、恢复或清除目标 |
334
345
  | `/permission [preset]` | 查看或切换服务端沙箱与审批预设 |
335
346
  | `/feedback TEXT` | 记录当前会话反馈 |
347
+ | `/handoff` | 先删除本地 HANDOFF.md,再让 agent 写出新的会话交接文档 |
348
+ | `/loop [name\|stop\|answer] [score] [tries]` | 用列表选择 `loop.yaml` 记录,修改它启动时用的输入与限制后再运行评分循环;`stop`/`abort` 结束当前运行,`answer TEXT` 补上验证者要的判断条件 |
336
349
  | `/export [local.zip]` | 下载会话日志 ZIP 到新的本地文件 |
337
350
  | `/export-html [local.html]` | 将已加载会话保存为包含图表和公式的离线 HTML |
338
351
  | `/coredump [tag]` | 在当前工作目录写出 V8 堆快照,用于内存诊断 |
339
352
  | `/older` | 加载更早的历史 |
340
353
  | `/history [text]` | 列出自己的提示词并可选筛选;Enter 跳到所选记录 |
354
+ | `/prompt [text]` | 列出已保存的快捷提示词——Enter 使用、`e` 编辑、`d` 删除——或把 TEXT 保存为一条 |
341
355
  | `/search <text>` | 逐页搜索历史,选择命中项后打开其位置 |
342
356
  | `/copy` | 冻结画面便于终端选择,Esc 恢复 |
343
357
  | `/latest` | 返回实时输出并释放独立历史窗口 |
@@ -350,9 +364,15 @@ Tab 补全开头的 slash 命令,多个候选时补到公共前缀。输入框
350
364
  | `/think SEQ` | 切换一条已加载思考的展开状态;`live` 表示当前尝试 |
351
365
  | `/help`, `/quit` | 列出每条命令及其说明,或退出 |
352
366
 
353
- Slash 命令在选择器和对话输入框中均可使用。输入 `/` 会显示匹配命令,`/help` 分页列出命令及其单行说明,PgUp/PgDn 翻页。`/help`、`/cost`、`/status` 面板保持打开,直到下一条命令或 Esc;`/history` 还会在十秒后自动关闭,避免遗留列表一直占用输入框。Esc 保留输入内容。`/workspace`、`/workspaces` 是 `/ws` 的别名;`/session`、`/sessions` 是 `/resume` 的别名。名称可以包含空格,完整目标两侧的引号可选。不带引号的目标 `all` 保留给 `/resume all`;打开标题为 `all` 的会话时,使用 `/resume "all"` 或其 ID。目标有歧义时必须提供完整 ID。切换工作区会打开其会话列表并解除旧对话订阅;切换会话会同步工作区标签。两种操作均不会取消远程代理。
367
+ Slash 命令在选择器和对话输入框中均可使用。输入 `/` 会显示匹配命令;命令名确定并跟一个空格后,输入框下方会显示该命令的用法和单行说明,因此参数不必靠记忆;`/help` 分页列出命令及其单行说明,PgUp/PgDn 翻页。`/loop` 还会在草稿仍在写记录名时,把 `loop.yaml` 的记录列表显示在输入框下方,因此运行一次循环不必逐个敲出旗标。`/help`、`/cost`、`/status` 面板保持打开,直到下一条命令或 Esc;`/history` 还会在十秒后自动关闭,避免遗留列表一直占用输入框。Esc 保留输入内容;当有长操作占用客户端时(搜索、导出、加载历史页),它先取消那个操作,其余情况才去取消命令打开的面板:选择器会回到打开它的那个对话(不重新加载),启动阶段还没有对话时,会话列表则退回工作区列表。`/workspace`、`/workspaces` 是 `/ws` 的别名;`/session`、`/sessions` 是 `/resume` 的别名。名称可以包含空格,完整目标两侧的引号可选。不带引号的目标 `all` 保留给 `/resume all`;打开标题为 `all` 的会话时,使用 `/resume "all"` 或其 ID。目标有歧义时必须提供完整 ID。切换工作区会打开其会话列表并解除旧对话订阅;切换会话会同步工作区标签。两种操作均不会取消远程代理。
368
+
369
+ `/prompt` 打开你保存的快捷提示词列表,`/prompt TEXT` 则把 TEXT 保存为一条;列表支持 ↑/↓ 选择、Enter 使用、`e` 编辑、`d` 或独立 Delete 键删除。选择一条提示词只会把文本放进输入框作为普通草稿,不会立即发送,因此还能继续修改后再按 Enter 发送;用 `e` 进入的编辑相反:Enter 把文本写回列表而绝不发送,Esc 放弃编辑并恢复此前的草稿。提示词保存在与内存日志同级的单个私有文件(`<state>/prompts.json`,权限 0600)中,该列表在本客户端连接的所有工作区与主机间共用,因此写一次即可到处使用;相同文本不会重复保存,单条上限 8 KiB,列表最多 500 条。保存和列出都不需要连接服务端;本版本无法读取的文件只会让列表为空并在面板中说明,而不会阻止客户端启动。
370
+
371
+ `/handoff` 为接手这次会话的人准备交接。它会先删除本客户端运行目录下的 `HANDOFF.md`,避免旧文件被误当成新交接;随后向 agent 发送一个普通回合,要求它在工作区根目录写出新的 `HANDOFF.md`。该文档需要包含:本次会话为何存在、目标是什么、每个任务的状态——已完成、仍未完成、无法完成及原因——以及关键决策、改动的文件、如何验证当前状态、以及明确的下一步。该命令不带参数,需要先选中会话,并会等待待答的审批或提问处理完毕。删除发生在本机,本身不会在服务端写入任何内容。
372
+
373
+ 在代理工作期间会写同一个对话的命令——`/compact`、`/handoff`、`/loop <name>`——会被**接受并持有**而不是拒绝:turn(或 loop)一结束就按提交顺序运行;因为这一行已经提交,所以与"不替你发送草稿"无关。其余命令在代理工作期间照常可用,`/cancel`、`/allow`、`/deny`、`/loop stop` 永远不会被持有。
354
374
 
355
- 在输入末尾键入 `@`,可搜索所选会话**在服务端**工作目录中的文件和目录。使用 ↑/↓ 选择,Tab 或 Enter 插入;选择目录后继续补全其内部路径。带空格的路径使用 `@"path with spaces"`。Esc 关闭菜单,任务运行中时同时请求取消;关闭后 Enter 发送原样输入,包括未匹配到的路径。搜索失败时显示错误,不提交输入。补全针对输入末尾的引用,不跟踪已有文本内部的光标位置。
375
+ `/loop [name|stop|answer] [score] [tries]` 运行 `loop.yaml` 里的一条记录,是一条**客户端驱动**的循环,并且设计成"选"而不是"敲"。输入 `/loop` 会在输入框下方列出所有记录及其轮数、产出物、自带默认值和它指向的输入;↑/↓ 选择,Enter 确认高亮记录,Tab 则把记录名补进草稿以便继续加旗标。确认后会打开参数列表:**先是该记录自己的输入**(如 `designdoc-review` 的 `path`,即被审查的文档),**再是共享的四个值**(`From`、`To`、`Pass`、`Tries`);选中某行直接输入即可覆盖——输入按自由文本,四个值按数字、与旗标同一套校验;用方向键或 Enter 离开某行即提交该行的内容,因此不需要在每个框里各按一次回车;`Start run` 开始运行,`← Choose another record` 返回记录列表;只有按下 Start 才会真正启动,因此每个默认值都在被花掉之前可见。改一条记录的输入只作用于本次运行、不改 `loop.yaml`,所以 `designdoc-review` 可以审查任意文档,而 `design-review` 不受影响。若命令行里写了任一旗标(`/loop design-review 9 3`、`--from`、`--to`、`--score`、`--tries`),则跳过表单、按所写的值直接运行,脚本与 headless 用法因此保持不变;名字不存在时会列出可用记录。轮次、每轮检查要点、附加标准、固定输入与默认值都由记录自带:本客户端发出本轮 Brief,由 dsht fork 出的独立进程在自己的 session 里验证并把 verdict 文件写回,客户端据此决定下一步——达到及格线进入下一轮,低于及格线消耗一次尝试,某轮用尽预算则停止。内置记录有 `design-review`(对模块设计做十轮审查)与 `designdoc-review`(同一循环,审查记录 `vars.path` 指定的文档,并**一份文档一个审查文件、就放在它旁边**(`tui-design.md.review.md`、`loop.md.review.md`),所以换文档重跑既不会读到也不会覆盖另一份文档的轮次;这个文件名在记录里是模板 `{{path}}.review.md`,且已被 `.gitignore` 覆盖)。`status` 为 `blocked` 时立即停止、不再消耗尝试预算;验证者无法判断时会要求人工介入并以同样方式停止(headless 退出码 3);只有客户端自己核对过「本轮小节确实写进了产出物文件」的轮次才算通过;`--deadline <minutes>` 约束整个 run。`passed` 只声称本次 run 覆盖的轮次并写明是哪些(`rounds 1–3/10 · selected range`);跑完整份记录时最后一轮是收束轮,它的验证者会拿到前面每一轮的检查要点逐轮复核,因此后来某一轮改坏了前序要求不会被放过。以"先验证"开始的一轮(`starts: verify` 且有 forked verifier)跑在独立验证进程自己的 session 里,因此当前会话的 history 不会出现内容,直到某一轮未通过、才要求 agent 去工作;验证进行期间进度行显示 `verifying step N · attempt M`,状态栏也如实显示同一件事(`◐` 加本轮 `review N/M`)而不是 Ready;验证者拿不出 verdict 时会给出结构化原因——退出码 + 子进程自己报告的原因(回复里没有 JSON 判定、或回复在 turn 结束后仍未提交),而不是只给错误输出类别,更不是无声等待;同时验证者会拿到本次 run 自己的输入,因此不会去评审产出物里只属于更早那次 run 的文档,`--trace-verbose` 才会附上脱敏后的最后一行。输入框上方的进度行显示轮次、尝试与最高分;发送普通消息、`/loop stop`、`/cancel`、Esc、Ctrl+C、切换会话或断线都会停止循环,该状态不持久化:`/loop stop` 属于控制泳道,运行期间也允许提交;没有运行时它会直接说明而不是报错,且已结束运行的那行进度会保留可读——结束它的那一行仍然显示结果,而你运行下一行时它就被清掉。验证者无法判决时 run 会**暂停**而不是结束:进度行与状态栏显示 `needs you`,`/loop answer TEXT` 补上缺的判断条件、以新的 verification identity 重新判断当前产出物——不消耗尝试次数,也不给 agent 发任何东西;`/loop abort`(`/loop stop` 在暂停期的拼写)结束它。
356
376
 
357
377
  文件引用仅在文本块中发送 `@path`。Harness 提示模型按需读取文件或列出目录;TUI 不读取本地文件、不上传字节,也不将文件内容展开进提示词。引用图片路径不会附带图片数据。尚未实现本地附件、图片上传/预览及 `@` 会话引用。
358
378
 
@@ -394,7 +414,7 @@ Slash 命令在选择器和对话输入框中均可使用。输入 `/` 会显示
394
414
 
395
415
  `/model` 读取 `session/modelCatalog`,展示服务端公布的 provider/model 路由及思考强度。选择通过 `session/selectModel` 提交 `{ request: { sessionId, provider, model, reasoningEffort? } }`,省略强度时使用适配器默认值。服务端将选择用于后续请求、记录选择事件,并尝试保存为部署默认值;不会替换正在执行的请求。展示模型继续以 `modelSelection.next` 和 `lastUsed` 为准,调用失败保留原选择。部分 provider 目录失败会单独提示,不隐藏正常 provider。标题与网页的 Agent preset 标签一致:`agentPreset` 提供当前 ID,`agentPresets/list` 提供名称和信任来源。内置系统预设显示 Standard mode、PTC mode、Minimal mode、Creator mode;自定义预设保留其名称,目录缺失时回退显示 ID。可选目录按需读取并在本次连接中复用。Plan 是独立功能,不决定这里的模式名称。终端少于 62 列时,mode 可在 `/status` 查看,为会话标题留出空间。
396
416
 
397
- 历史分为语义消息块、prompt/思考摘要、独立折叠状态和行数索引。流式更新复用已提交历史的位置索引,只生成当前可见区域。每个会话的 LRU 最多保留 2,048 行已提交终端内容,移出缓存的行在回看时重建。已结束的旧版流式分片和不用展示的工具结果正文会释放,原始日志由服务端保存。服务端日志作为持久层,客户端作为可重载的内存层。实时历史默认以 2,000 条记录或 16 MiB 语义数据估算量为软限制(`--history-records`、`--history-mb`),触发回收后以限制的 75% 为目标,释放旧正文、摘要和排版缓存。切换会话会释放上一会话的 transcript。回看和思考导航期间保护已加载窗口;`/latest` 返回实时输出并恢复回收。离线历史、未结束流和最小近期尾部受保护,因此这些参数不是进程 RSS 硬上限。默认启用体积受限的运行时内存日志,位于 `<state>/memory.log`:每 30 秒一行 JSON,记录进程计数、保留记录数与字节数、是否处于回看保护状态、账本规模、布局行缓存、有界的数学与图表缓存,以及最近一次成本扫描重读的会话数、页数与事件数,用于区分真实增长与 V8 高水位。若运行时提供强制回收能力,样本还会记录回收后的堆;`npm run start:profile` 会以该能力加上堆快照信号启动,此时 `kill -USR2 <pid>` 即可写出快照。`/coredump [tag]` 无需信号或 profiling 运行时,即可把同样的 `.heapsnapshot` 产物写入客户端当前工作目录;`tag` 用于命名快照(默认 `snapshot`),V8 会同步序列化整个堆,因此客户端在文件写完前会暂停。快照请用 Chrome DevTools 打开分析,而不是终端。`--memory-log <路径>` 或 `DSHT_MEMORY_LOG` 可改路径,`--no-memory-log` 或 `DSHT_MEMORY_LOG=off` 可关闭。首次排版、改变终端宽度及展开特别大的单个内容块,仍需要处理对应全文。`npm run bench:history` 测量 500、2,000、10,000 条消息下的本地流式排版耗时,不含网络和模型时间。
417
+ 历史分为语义消息块、prompt/思考摘要、独立折叠状态和行数索引。流式更新复用已提交历史的位置索引,只生成当前可见区域。每个会话的 LRU 最多保留 2,048 行已提交终端内容,移出缓存的行在回看时重建。已结束的旧版流式分片和不用展示的工具结果正文会释放,原始日志由服务端保存。服务端日志作为持久层,客户端作为可重载的内存层。实时历史默认以 2,000 条记录或 16 MiB 语义数据估算量为软限制(`--history-records`、`--history-mb`),触发回收后以限制的 75% 为目标,释放旧正文、摘要和排版缓存。切换会话会释放上一会话的 transcript。回看和思考导航期间保护已加载窗口;`/latest` 返回实时输出并恢复回收。离线历史、未结束流和最小近期尾部受保护,因此这些参数不是进程 RSS 硬上限。默认启用体积受限的运行时内存日志,位于 `<state>/memory.log`:每 30 秒一行 JSON,记录进程计数、保留记录数与字节数、是否处于回看保护状态、账本规模、布局行缓存、有界的数学与图表缓存,以及最近一次成本扫描重读的会话数、页数与事件数,用于区分真实增长与 V8 高水位。若运行时提供强制回收能力,样本还会记录回收后的堆;`npm run start:profile` 会以该能力加上堆快照信号启动,此时 `kill -USR2 <pid>` 即可写出快照。`/coredump [tag]` 无需信号或 profiling 运行时,即可把同样的 `.heapsnapshot` 产物写入客户端当前工作目录;`tag` 用于命名快照(默认 `snapshot`),V8 会同步序列化整个堆,因此客户端在文件写完前会暂停。快照请用 Chrome DevTools 打开分析,而不是终端。`--memory-log <路径>` 或 `DSHT_MEMORY_LOG` 可改路径,`--no-memory-log` 或 `DSHT_MEMORY_LOG=off` 可关闭。默认还启用体积受限的状态迁移日志,位于 `<state>/trace.log`:每行一个 JSON 事件,记录每次连接代际、选择器请求、本地工作区采用、会话解析、屏幕/选中项变化,每条被执行的命令都写成共享同一 `commandId` 的 `begin`/`end` 一对(副作用中途崩溃因此可见,`end` 带与用户看到的同一个 `outcome`/`disposition` 以及失败原因),每次会话写入的准入顺序(`lane` 是 `normal` 还是 `control`、是否等待过——行序就是 dispatch 顺序),客户端跑过的每个长操作(`foreground` 的 begin/end、kind 与是否被取消),以及每次 `/loop` 决策——列表选中了哪条记录、表单是否打开、运行是否开始且发了什么、因何被拒,以及每次 forked 验证的完整生命周期(`begin`/`verified`/`retry`/`unavailable`…,无法判决时带结构化原因:退出码与错误类别)——因此"屏幕自己动了"(例如重连把读者带回会话列表)、"`/loop` 没有启动"或"某一轮迟迟没有 verdict"都可以连同变化前后的取值一起回读,而不是靠猜。日志只记录标识与屏幕名,绝不记录 prompt、工具或会话正文。`--trace <路径>` 或 `DSHT_TRACE` 可改路径,`--no-trace` 或 `DSHT_TRACE=off` 可关闭;`--trace-verbose`(或 `DSHT_TRACE_VERBOSE=1`)才会在失败原因里引用验证子进程自己的最后一行,且先去掉路径与凭据。首次排版、改变终端宽度及展开特别大的单个内容块,仍需要处理对应全文。`npm run bench:history` 测量 500、2,000、10,000 条消息下的本地流式排版耗时,不含网络和模型时间。
398
418
 
399
419
  ## 费用估算
400
420
 
@@ -426,8 +446,8 @@ Slash 命令在选择器和对话输入框中均可使用。输入 `/` 会显示
426
446
 
427
447
  | 字段 | 值 |
428
448
  | --- | --- |
429
- | 名称与版本 | `@itookit/dsht` `0.3.7` |
430
- | 可执行命令 | `dsht`,不安装时用 `npx @itookit/dsht` |
449
+ | 名称 | `@itookit/dsht` |
450
+ | 可执行命令 | `dsht`,不安装时用 `npx @itookit/dsht`;`dsht --version` 打印已发布版本 |
431
451
  | 库入口 | `@itookit/dsht` 和 `@itookit/dsht/auth` |
432
452
  | 作者 | lizlok\@gmail.com |
433
453
  | 许可证 | MIT,许可证正文位于 `LICENSE` |
@@ -464,7 +484,7 @@ node dist/cli/index.js --help
464
484
 
465
485
  测试使用隔离的 HTTP/WebSocket 服务,驱动实际 Ink 选择器和输入框,在子进程中运行 CLI,并投影复制的 Harness v2 工作区编辑记录和 v0 压缩 chunk 记录。这些检查不需要模型凭据。记录和预期对话输出位于 `tests/`,不依赖父仓库。测试不覆盖真实模型供应商行为。
466
486
 
467
- 源码在 `src/` 下按业务域组织:`transport/` 负责服务端 wire 协议与认证,`session/` 负责对话、历史与交互,`cost/` 负责折叠计费账本,`catalog/` 负责模型与 preset,`controller/` 是应用门面,`ui/` 承载全部 React 与 Ink,`storage/` 负责全部文件系统操作,`cli/` 是组装入口。跨模块导入统一走各模块的 `index.ts`;`tests/architecture/dependencies.test.ts` 会拒绝禁止的依赖方向。
487
+ 源码在 `src/` 下按业务域组织:`transport/` 负责服务端 wire 协议并把宿主帧归一化为语义事件,`session/` 负责对话、历史、交互与会话运行态,`cost/` 负责计费账本,`catalog/` 负责模型与 preset,`shell/` 负责本地 `!` 命令,`controller/` 是应用门面与事件路由,`slash/` 负责命令语法,`ui/` 承载全部 React 与 Ink,`storage/` 负责全部文件系统操作,`cli/` 是组装入口,根文件(`state.ts`、`json.ts`、`text.ts`、`contracts.ts`、`session-title.ts`、`references.ts`)是共享契约。跨模块导入统一走各模块的 `index.ts` 或共享叶子,`tests/architecture/dependencies.test.ts` 以九条无豁免的禁止方向强制这些边界。
468
488
 
469
489
  `npm test` 渲染不带样式的帧,因为断言和 `tests/expected/` 中的预期输出描述的是文本。从终端启动的测试运行器会向每个测试文件导出 `FORCE_COLOR=1`,使 Ink 在提示符与文本之间插入 SGR 转义序列;`npm run test:terminal` 在任何主机上复现该环境,`prepublishOnly` 也会运行它,因此从终端发布时验证的就是终端实际渲染的结果。 主题测试在独立子进程中分别渲染真彩色和纯文本,并隔离父进程中影响终端和 CI 颜色检测的环境设置。
470
490