@itookit/dsht 0.2.0 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.i18n.yaml CHANGED
@@ -1,3 +1,3 @@
1
1
  # Git blob hashes of the reviewed bilingual pair.
2
- README.md: 8f9296caa5de076cfc7541ff372a5ce0712e9181
3
- README.zh.md: c78f46e20d2cca7fbda700e810234872016e69a7
2
+ README.md: 0ea1647c541917bb3f9d9b1d27cf78af05cae6f6
3
+ README.zh.md: 8e2f6b98050f334c55372d794b882a4e73e68375
package/README.md CHANGED
@@ -42,7 +42,7 @@ Main features:
42
42
 
43
43
  - **Remote-first**: built for SSH, nested SSH, bastion hosts, ProxyJump, and tmux.
44
44
  - **Phone-friendly**: one working mobile SSH client is enough to keep controlling a remote Harness away from your desk.
45
- - Workspace and session pickers, direct switching with `/ws` and `/s`, and explicit session creation.
45
+ - Workspace and session pickers, direct switching with `/ws` and `/resume`, and explicit session creation.
46
46
  - Streaming replies, reasoning, compact tool names, success/failure status, and paged conversation history.
47
47
  - Queued prompts, steering, turn cancellation, approvals, and free-text question answers.
48
48
  - Cookie persistence per host, automatic reconnect, and snapshot replacement, so control survives a dropped connection.
@@ -118,7 +118,7 @@ today
118
118
  today + the previous two calendar days
119
119
  ```
120
120
 
121
- The status bar also keeps showing session cost `S:` and today's cost `D:`, so cost changes surface while a task runs instead of only after the invoice arrives.
121
+ The status bar also keeps showing session / today cost (`~¥1.23/~¥5.00`), so cost changes surface while a task runs instead of only after the invoice arrives.
122
122
 
123
123
  That gives `dsht` two roles at once:
124
124
 
@@ -287,16 +287,27 @@ JSON output is `{ "items": [...] }`; omit `--json` for tab-separated output. Wor
287
287
 
288
288
  ## Conversation controls
289
289
 
290
- Enter submits a prompt. Ctrl+C 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 local history/search/cost loads 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.
290
+ Enter submits a prompt. 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 local history/search/cost loads 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
291
 
292
- The mouse wheel and Page Up/Down scroll conversation history; scrolling to the top automatically requests an older page. New output preserves a scrolled reading position. `/jump last` resumes following the newest output. Mouse reporting is enabled while the TUI is mounted and disabled on exit; the terminal must support SGR mouse reports. Esc or Ctrl+C cancels a history load or search before interrupting the remote agent.
292
+ `/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 automatically freeze background title, conversation, and status updates, and release mouse capture while remaining interactive. 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
293
 
294
- `/search` matches literal text case-insensitively in displayed messages, including older pages; hidden tool bodies are excluded. `/history` only lists loaded records. 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.
294
+ The mouse wheel and Page Up/Down scroll conversation history; scrolling to the top automatically requests an older page. New output preserves a scrolled reading position; scrolling back down resumes following the newest output. Mouse reporting is enabled while the TUI is mounted and disabled on exit; the terminal must support SGR mouse reports. Esc or Ctrl+C cancels a history load or search before interrupting the remote agent.
295
295
 
296
- The single-line composer supports Readline-style editing. Words are whitespace-delimited; cursor movement and character deletion preserve composed Unicode characters. Multiline pasted text becomes one line with spaces. Ctrl+D on empty input does not exit; Ctrl+C keeps its stop/exit behavior. 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.
296
+ In `/ws` and `/resume` pickers, select a workspace or session and press `d` or Delete with an empty composer to review removal. With a draft, `d` remains normal input; Backspace never opens removal. `/ws --delete <name or ID>` and `/resume --delete <title or ID>` open the same confirmation; `/resume --archive <title or ID>` is an alias for session archival. Before session removal the client refreshes `session/list`. A session explicitly marked `blank: true` and idle, with no known queued jobs or pending local prompt admission, is archived immediately without confirmation. This uses the host blank flag rather than the loaded history window or title. Other sessions still require confirmation: Cancel is selected by default, and Escape closes the dialog. Workspace removal calls `workspace/delete` and removes only its registration: directories and sessions remain. Session removal calls `workspace/archiveSession`, hiding the session from workspace lists and `/resume all` while retaining history; `/resume ID` can reopen it. This host API exposes archival rather than permanent session deletion. Running tasks continue. Archiving the selected session releases its transcript and layout caches; rejected operations retain the list and confirmation for retry.
297
+
298
+ `/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
+
300
+ ↑/↓ or Ctrl+P/N recalls previously submitted prompts and slash commands without sending them; Enter submits the recalled text. Moving past the newest entry restores the unsent draft. Editing recalled text starts a new draft. Recall keeps up to 200 entries and approximately 256 KiB of text for the selected session. Opening or restoring a session seeds recall from its already-loaded User messages; switching sessions releases the previous recall buffer. It does not fetch older pages or write a separate history file. 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.
301
+
302
+ 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
+
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. Mouse capture is already disabled in dialogs, where Ctrl+S freezes the entire dialog if needed.
305
+
306
+ Tab completes the leading slash command, extending an ambiguous draft to the shared prefix. The single-line composer supports Readline-style editing. Words are whitespace-delimited; cursor movement and character deletion preserve composed Unicode characters. Multiline pasted text becomes one line with spaces. 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.
297
307
 
298
308
  | Key | Edit |
299
309
  | --- | --- |
310
+ | ↑ / ↓, Ctrl+P / Ctrl+N | Recall older / newer submitted input |
300
311
  | Ctrl+A / Ctrl+E, Home / End | Move to start / end |
301
312
  | Ctrl+B / Ctrl+F, ← / → | Move one character |
302
313
  | Alt+B / Alt+F, Ctrl+← / Ctrl+→ | Move one word |
@@ -310,42 +321,52 @@ The single-line composer supports Readline-style editing. Words are whitespace-d
310
321
  | --- | --- |
311
322
  | `/ws` | Show all workspaces; choosing one opens its session list |
312
323
  | `/ws TARGET` | Select a workspace by ID, exact name/path, or unique ID prefix |
313
- | `/s` | Show sessions in the current workspace; choose a workspace first if none is selected |
314
- | `/s TARGET` | Open a session by ID, exact title, or unique ID prefix across workspaces |
315
- | `/s all` | Show sessions from every workspace |
324
+ | `/resume` | Show sessions in the current workspace; choose a workspace first if none is selected |
325
+ | `/resume TARGET` | Open a session by ID, exact title, or unique ID prefix across workspaces |
326
+ | `/resume all` | Show sessions from every workspace |
327
+ | `/model [provider model [effort]]` | Choose a model and its reasoning effort, or submit exact route IDs |
316
328
  | `/new` | Create a session in the selected workspace |
317
329
  | `/cancel` | Cancel the active turn; leave pending queue items intact |
318
330
  | `/steer TEXT` | Submit steering input |
319
331
  | `/older` | Load older history |
320
- | `/history [text]` | List loaded records, optionally filtered; Enter jumps to the selected record |
321
- | `/jump <seq\|first\|last>` | Jump to a visible record sequence, oldest history, or latest output |
322
- | `/search <text>` | Load and search the current session history; choose a matching message to jump |
332
+ | `/history [text]` | List your own prompts, optionally filtered; Enter jumps to the selected record |
333
+ | `/search <text>` | Search history page by page; choose a match to open its location |
334
+ | `/copy` | Freeze for terminal selection; Esc resumes |
335
+ | `/latest` | Return to live output and release the separate historical window |
323
336
  | `/ssearch <text>` | Search host results within the selected workspace |
324
337
  | `/wsearch <text>` | Search sessions across all workspaces visible to the host |
325
338
  | `/allow`, `/deny` | Answer the displayed approval; allow applies once |
326
339
  | `/status` | Expand or collapse full footer details |
327
340
  | `/cost` | Toggle session/today/three-day estimates and refresh usage |
328
- | `/help`, `/quit` | Show command hints or exit |
341
+ | `/think` | List reasoning with user prompt summaries; ↑/↓ and Enter jump to and expand a thought |
342
+ | `/think SEQ` | Toggle one loaded thought; `live` toggles the active attempt |
343
+ | `/help`, `/quit` | List every command with its description, or exit |
329
344
 
330
- Slash commands work in both pickers and the conversation composer. Typing `/` displays matching commands. The `/help`, `/cost`, and `/status` panels are temporary: the next command, or ten seconds, closes whichever one is open. The long forms `/workspace`, `/workspaces`, `/session`, and `/sessions` remain aliases. Names may contain spaces; quotes around the complete target are optional. The unquoted target `all` is reserved for `/s all`; use `/s "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.
345
+ Slash commands work in both pickers and the conversation composer. Typing `/` displays matching commands, and `/help` lists each command with its one-line description. The `/help`, `/cost`, and `/status` panels remain open until the next command or Esc; 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.
331
346
 
332
347
  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.
333
348
 
334
349
  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.
335
350
 
336
- User questions accept typed free-text answers one question at a time. Other sessions' interactions and unrecognized waterfalls delegate with `next`. Failed submissions retain their input; an interrupted HTTP response can leave delivery uncertain, so check the transcript before manually resending. The client never retries a mutation automatically.
351
+ User questions show progress, numbered options, and descriptions. Pending questions and approvals use the conversation area exclusively, so frozen history cannot compress their rows. The visible option window adapts to terminal height and follows the highlighted choice. With an empty composer, ↑/↓ or 1–9 selects an option and Enter confirms; numbers only select and do not submit. For multi-select questions, Space or 1–9 toggles checkboxes, and Enter confirms the selection. Options beyond nine remain reachable with arrows. Choose Other answer to type numeric free text; ordinary text answers remain supported. Existing drafts keep normal typing, and Escape returns from Other to the options without cancelling the question. All questions are submitted together as structured selected labels and optional custom text; a failed submission preserves the answers for retry. Recognized question and approval events are retained by event ID for the connection, including replay before session selection; only the selected session displays them. Switching pickers does not decline those requests. Unrecognized waterfalls still delegate with `next`. The live host replays pending events after client reconnection; client restart does not preserve unsubmitted answer drafts. Normal TUI shutdown cancels a running turn. A cancelled/failed tool call or a host restart cannot restore the original wait from local UI state; send a new prompt requesting the questions again. Failed submissions retain their input; an interrupted HTTP response can leave delivery uncertain, so check the transcript before manually resending. The client never retries a mutation automatically.
337
352
 
338
- The conversation header shows the latest session title, falling back to the ID; `/status` retains the complete session ID. The cancellation acknowledgement stays visible through incoming history until the host reports idle; acceptance does not mean a tool process has already exited.
353
+ The header stays above the scrollable conversation while the composer and status stay below it. The always-visible keyboard legend is removed; `/help` contains the full shortcuts and pickers retain their local navigation hints. The single-line header shows `dsht · <host>`, the workspace name followed by the latest session title (falling back to the ID), and a right-aligned connection indicator above a divider; `/status` retains the complete session ID. The cancellation acknowledgement stays visible through incoming history until the host reports idle; acceptance does not mean a tool process has already exited.
339
354
 
340
355
  ## Live status
341
356
 
342
- The footer defaults to one borderless line showing activity, model, workspace, context occupancy, and total tokens; wider terminals also show input/output, cache, queue, and job counts. Long names shorten by terminal display width, and narrow terminals omit lower-priority fields first. `/status` toggles full multiline details with the complete path, provider/model, reasoning effort, and usage buckets. `!` flags a metrics or model catalog error, or incomplete cost coverage; the reason appears in the details. During a run it distinguishes the last-used model from a different next-request selection; a fresh session uses the host catalog default. Model catalog changes refresh on host settings, credential, and adapter notifications.
357
+ The footer groups `◐ Working · 8s · Ctrl+C Stop` or `● Ready`, model and reasoning effort, session / today cost, a ten-cell context bar and percentage, and session turns / total tokens. Wide terminals reserve the activity column so model and metrics stay aligned when a run completes. Narrow terminals reclaim padding, remove the bar, shorten the model, and then omit lower-priority metrics while retaining the stop hint. `/status` shows the host URL, operation status, workspace path, full provider/model, pending model, usage buckets, turns, queues, jobs, and four-decimal costs. `!` flags a metrics or model catalog error, or incomplete cost coverage; details explain the cause. Running sessions use the last-used model; ready sessions use the next selection, with the host catalog default for new sessions. Model catalog changes refresh on host settings, credential, and adapter notifications.
358
+
359
+ Working time uses the retained `turn/start` timestamp. If that timestamp is unavailable, `~` after the compact elapsed time (`(observed)` in details) means time since this client observed the run; reconnecting can reset this fallback. The clock stops when the host reports idle. The status includes model generation, tool execution, and approval waits, not just streamed text. Offline status is explicitly marked as last known.
360
+
361
+ Turns come from the complete session's `sessionStats.turns` projection. Context occupancy is marked `~`: Harness combines provider usage with estimated surface changes and the latest route capacity. Token totals come from the complete session's `tokenUsage` projection, with separate uncached input, output, cache-read, and cache-write buckets; reasoning is already included in output. Totals update when the host publishes usage, not on every streamed character. Missing measurements display `unknown` or `?`. Control-stream baselines replace state on reconnect, and per-key watermarks prevent an older follow snapshot from overwriting newer metrics.
362
+
363
+ The default [Catppuccin Mocha](https://catppuccin.com/palette/) theme distinguishes `❯ User` (blue), `✦ Assistant` (green), reasoning (mauve), tools (sky), success (green), and errors (red). The compact status bar uses green for Ready, yellow for Working, red for offline, mauve for model/effort, sky for costs, and subdued gray for usage. Context occupancy changes from green to yellow at 80% and red at 95%; these are visual thresholds, not host compaction triggers. Groups are fitted before ANSI styling, preserving alignment and plain-terminal output. Semantic colors live in `src/theme.ts`; the application accepts a theme independently of stored messages. Ink adapts ANSI output to terminal capabilities, and plain terminals retain the role markers. Tool calls show their name and description plus a `$` preview of the command's first line when it differs from the description. Without a description, the first command line, path, or query supplies the summary. Each line truncates by terminal display width; completion updates the original call from ⚙ to ✓ or ✗ by call ID, without a second result entry. Command previews retain two-space indentation. Results whose calls are outside the loaded window remain visible until their call page is loaded; nested result bodies stay hidden.
343
364
 
344
- Working time uses the retained `turn/start` timestamp. If that timestamp is unavailable, `(observed)` means time since this client observed the run; reconnecting can reset this fallback. The clock stops when the host reports idle. The status includes model generation, tool execution, and approval waits, not just streamed text. Offline status is explicitly marked as last known.
365
+ Reasoning streams in full while being generated, then folds when its block closes or answer/tool output begins. `/think` opens a newest-first list of reasoning summaries with the preceding loaded user prompt and an entry for the active attempt. Select with ↑/↓ and Enter to jump to the original message and expand it; `/think SEQ` folds or expands that message, and `/think live` controls the active attempt. The list stays open until selection, Esc, or another command. It initially uses loaded history; `Load older reasoning` fetches one earlier page on demand. If a prompt precedes the loaded window, the list says so until that page is loaded. Full reasoning remains available for search.
345
366
 
346
- Context occupancy is marked `~`: Harness combines provider usage with estimated surface changes and the latest route capacity. Token totals come from the complete session's `tokenUsage` projection, with separate uncached input, output, cache-read, and cache-write buckets; reasoning is already included in output. Totals update when the host publishes usage, not on every streamed character. Missing measurements display `unknown` or `?`. Control-stream baselines replace state on reconnect, and per-key watermarks prevent an older follow snapshot from overwriting newer metrics.
367
+ `/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` instead of competing with the connection indicator.
347
368
 
348
- Tool-only rows omit the separate role heading: `⚙` identifies a call, `✓` a successful result, and `✗` a failed result. Once complete arguments are available, each row shows the tool name and operation description, falling back to its command, path, or query. Results reuse the matching call summary when retained history contains it. Each operation occupies at most one terminal row, with whitespace flattened and long text ellipsized by display width. Other arguments, nested results, and tool output remain hidden. Assistant prose and explicit approval requests remain visible so the user can understand the response and decide whether to approve an action.
369
+ 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. 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.
349
370
 
350
371
  ## Cost estimates
351
372
 
@@ -353,13 +374,13 @@ Tool-only rows omit the separate role heading: `⚙` identifies a call, `✓` a
353
374
 
354
375
  > These figures are a high-precision estimate from Harness-visible usage and local price configuration, for cost monitoring and control. They are not a provider account bill, and the provider invoice remains authoritative.
355
376
 
356
- `/cost` shows the selected session, today, and today plus the preceding two calendar days. Dates use Asia/Shanghai; the three-day view is not a rolling 72-hour window. The status bar reserves `S:` for session cost and `D:` for today. `~` marks an estimate; `*` marks a subtotal that is not exact, because a request carries no timestamp, no price covers it, or a calendar range cannot place it. Incomplete coverage is reported separately: charges cached by an earlier run count as complete, while an empty or failed scan raises the bar's `!` prefix and a reason in `/status`. Each host origin has a separate ledger. Totals cover HTTP-visible sessions and previously cached sessions; they are not account-wide provider bills.
377
+ `/cost` shows the selected session, today, and today plus the preceding two calendar days. Dates use Asia/Shanghai; the three-day view is not a rolling 72-hour window. The status bar shows session / today cost rounded to two decimals; the slash does not denote a budget. `~` marks an estimate; `*` marks a subtotal that is not exact, because a request carries no timestamp, no price covers it, or a calendar range cannot place it. Incomplete coverage is reported separately: charges cached by an earlier run count as complete, while an empty or failed scan raises the bar's `!` prefix and a reason in `/status`. Each host origin has a separate ledger. Totals cover HTTP-visible sessions and previously cached sessions; they are not account-wide provider bills.
357
378
 
358
- The client reads complete histories in the background on connection, every 60 seconds, at turn completion, and when opening `/cost`. Idle sessions with unchanged host update timestamps are skipped. No model requests are made by billing. Esc or Ctrl+C cancels an explicit refresh. The ledger counts disjoint uncached input, cache read/write and output buckets; reasoning is already part of output. Retries count separately, replacement samples update their attempt, and fork-inherited history is excluded. A request without a settlement timestamp still contributes a floor amount, priced at the cheapest rate of its model family and reported as estimated. Inconsistent usage, and prices that no model or provider entry covers, remain unpriced; the model-name family decides Pro against Flash, while an unlisted provider is never billed from the official table. Failed scans retain labelled partial cached totals.
379
+ The client reads complete histories in the background on connection, every 60 seconds, at turn completion, and when opening `/cost`. Idle sessions with unchanged host update timestamps are skipped. No model requests are made by billing. Esc or Ctrl+C cancels an explicit refresh. The ledger counts disjoint uncached input, cache read/write and output buckets; reasoning is already part of output. Retries count separately, replacement samples update their attempt, and fork-inherited history is excluded. A request without a settlement timestamp still contributes a floor amount, priced at the cheapest rate of its model family and reported as estimated. Inconsistent usage, and prices that no model or provider entry covers, remain unpriced; the model-name family decides Pro against Flash, while an unlisted provider is never billed from the official table. Every listed session is read independently, so one unreachable or rejected session is reported as a failure count instead of stopping the scan, and a subagent child is read under its durable parent address. Failed scans retain labelled partial cached totals.
359
380
 
360
381
  The bundled CNY rates were checked against the [official pricing page](https://api-docs.deepseek.com/zh-cn/quick_start/pricing/) on 2026-09-10. Beijing weekday peak windows are 09:00–12:00 and 14:00–18:00; other times use half-price rates. Flash peak cache-miss-input/cache-hit-input/output rates are ¥2/¥0.04/¥8 per million tokens and Pro rates are ¥9/¥0.30/¥27; the current model name is `deepseek-flash`, and older Flash names keep those same rates. The provider has announced that from 2026-09-14T12:00+08:00 it serves `deepseek-v4-pro` from Flash and bills it at Flash rates, which the bundled entry records so that date does not overstate Pro usage. Separate cache writes use the uncached-input rate. An exact configured model price takes priority; otherwise `deepseek-official` names containing `pro` (case-insensitive) use Pro and all other names use Flash, including temporary model aliases. Other providers require explicit entries.
361
382
 
362
- The default price validity starts at Beijing midnight on the verification date; this is a local estimate policy, not a claim about the official effective date. Earlier usage needs historical price entries. The recorded assistant settlement timestamp selects the rate; requests spanning a tariff boundary may differ from the invoice because the official page does not specify their attribution. Images use provider-reported tokens. Cached priced requests retain their price version when configuration changes; previously unpriced requests can be priced on a later scan.
383
+ The default price validity starts at Beijing midnight on the verification date; this is a local estimate policy, not a claim about the official effective date. Earlier usage needs historical price entries. The recorded assistant settlement timestamp selects the rate; requests spanning a tariff boundary may differ from the invoice because the official page does not specify their attribution. Images use provider-reported tokens. Every scan reprices stored requests from the current configuration, so correcting a price version also corrects earlier totals; a request that no entry covered is priced once an entry covers its settlement date.
363
384
 
364
385
  On first interactive launch, the client creates `~/.config/dsht/prices.json` (or `$XDG_CONFIG_HOME/dsht/prices.json`). `DSHT_CONFIG_DIR` overrides that directory. The JSON array contains price versions with `id`, `provider`, `model`, `currency: "CNY"`, `source`, inclusive `from`, optional exclusive `until`, `timezone`, weekday numbers (`0` Sunday), minute-of-day `windows`, and `peak`/`offPeak` rates named `input`, `cacheRead`, `cacheWrite`, `output`, per million tokens. To update prices, close the old interval with `until` and append a new version with a unique ID and matching `from`; overlapping intervals are rejected. Restart to load configuration changes. Price discovery is manual; the TUI does not scrape prices during startup.
365
386
 
@@ -377,7 +398,7 @@ This repository publishes one public package, `@itookit/dsht`, from the `mushuan
377
398
 
378
399
  | Field | Value |
379
400
  | --- | --- |
380
- | Name and version | `@itookit/dsht` `0.2.0` |
401
+ | Name and version | `@itookit/dsht` `0.2.4` |
381
402
  | Executable | `dsht`, or `npx @itookit/dsht` without installing |
382
403
  | Library entries | `@itookit/dsht` and `@itookit/dsht/auth` |
383
404
  | Author | lizlok@gmail.com |
@@ -399,7 +420,7 @@ npm publish --access public
399
420
 
400
421
  `publishConfig.access` is `public`, which a scoped package needs to be installable without a paid plan; the flag is therefore part of the package rather than of the publish command. An account with two-factor authentication publishes with a live code, `npm publish --otp=<code>`; the code is checked at the final request, after the typecheck, suite, and build have already run.
401
422
 
402
- Later releases run in `.github/workflows/publish.yml`, which publishes from a version tag with [trusted publishing](https://docs.npmjs.com/trusted-publishers) (OIDC) and provenance, so no publish token is stored. Configure it once at `npmjs.com` → `@itookit/dsht` → Settings → Trusted Publisher → GitHub Actions with organization or user `mushuanli`, repository `dsht`, workflow filename `publish.yml`, and allowed action `npm publish`. Trusted publishing cannot create a package, so the first version is published by hand; after that, `npm version 0.2.1 && git push --follow-tags` releases.
423
+ Later releases run in `.github/workflows/publish.yml`, which publishes from a version tag with [trusted publishing](https://docs.npmjs.com/trusted-publishers) (OIDC) and provenance, so no publish token is stored. Configure it once at `npmjs.com` → `@itookit/dsht` → Settings → Trusted Publisher → GitHub Actions with organization or user `mushuanli`, repository `dsht`, workflow filename `publish.yml`, and allowed action `npm publish`. Trusted publishing cannot create a package, so the earliest versions were published by hand; a later release pushes the matching tag, for example `npm version 0.2.3 && git push --follow-tags`.
403
424
 
404
425
  The workflow packs without publishing when started manually, and refuses a tag that disagrees with `package.json`. See the official [scoped publishing guide](https://docs.npmjs.com/creating-and-publishing-scoped-public-packages/) and [npx documentation](https://docs.npmjs.com/cli/npm-exec/). Registry publication is not part of the local validation performed for this repository.
405
426
 
@@ -415,8 +436,8 @@ node dist/cli.js --help
415
436
 
416
437
  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.
417
438
 
418
- `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.
439
+ `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.
419
440
 
420
441
  Typing reuses history projection and wrapping until the transcript revision or terminal width changes; host updates and older pages invalidate that reuse. `bench:input` measures local input-to-render work with 20 and 500 synthetic messages, 30 measured keystrokes after warmup, and history projection read counts. It excludes network/model time and is a diagnostic, not a machine-independent latency threshold.
421
442
 
422
- The interface presents plain text, reasoning, tool calls, and tool results. Rich plugin cards, file upload, subagent navigation, model selection, and queue editing are not implemented. Reconnect uses bounded exponential backoff with jitter and replaces snapshots; list commands fail directly instead of retrying. Updating pre-stable host APIs requires updating the local wire adapter and tests.
443
+ The interface presents plain text, reasoning, tool calls, and tool results. Rich plugin cards, file upload, subagent navigation, and queue editing are not implemented. Reconnect uses bounded exponential backoff with jitter and replaces snapshots; list commands fail directly instead of retrying. Updating pre-stable host APIs requires updating the local wire adapter and tests.
package/README.zh.md CHANGED
@@ -42,7 +42,7 @@
42
42
 
43
43
  - **远程优先**:适合 SSH、嵌套 SSH、跳板机、ProxyJump、tmux 等远程开发环境。
44
44
  - **手机友好**:只需要一个可用的移动端 SSH 客户端,就能在离开电脑后继续控制远程 Harness。
45
- - 工作区和会话选择器,通过 `/ws`、`/s` 直接切换,并显式创建会话。
45
+ - 工作区和会话选择器,通过 `/ws`、`/resume` 直接切换,并显式创建会话。
46
46
  - 流式回复、思考内容、精简工具名称、成功/失败状态以及分页对话历史。
47
47
  - 排队消息、转向输入、轮次取消、审批和自由文本问题回答。
48
48
  - 按服务端保存 cookie、自动重连和快照替换,方便断线后恢复控制。
@@ -118,7 +118,7 @@ DeepSeek Harness 往往运行在性能更强、环境更完整的开发工作站
118
118
  今日 + 前两个自然日
119
119
  ```
120
120
 
121
- 状态栏还可以持续显示会话费用 `S:` 和今日费用 `D:`,便于在任务执行过程中及时发现成本变化,而不是等到账单出现后才知道消耗了多少。
121
+ 状态栏还可以持续显示会话/今日费用(`~¥1.23/~¥5.00`),便于在任务执行过程中及时发现成本变化,而不是等到账单出现后才知道消耗了多少。
122
122
 
123
123
  这让 `dsht` 同时承担两个角色:
124
124
 
@@ -287,16 +287,27 @@ JSON 输出格式为 `{ "items": [...] }`;省略 `--json` 则输出制表符
287
287
 
288
288
  ## 对话操作
289
289
 
290
- Enter 提交消息。所选会话运行中时,Ctrl+C 请求取消;只有空闲时才退出,连续按键会复用尚未完成的取消请求。取消会等待正在提交的消息完成接收,失败时保留客户端。聊天界面中 Esc 会发送取消请求,不受本地空闲状态判断限制。任务运行中时,Esc 关闭文件或历史/搜索菜单的同时请求取消;空闲菜单仅关闭。正在执行的本地历史/搜索/费用加载优先被取消。Page Up/Down 滚动当前对话;`/older` 加载更早记录。所有退出路径(包括 `/quit` 和 SIGTERM)都会在关闭连接前停止所选任务,因此退出不会留下仍在运行的代理;会话空闲时不发送取消。取消当前任务会保留排队消息。
290
+ Enter 提交消息。输入框非空时,Ctrl+C 先清空输入;否则所选会话运行中时请求取消,只有空闲时才退出,连续按键会复用尚未完成的取消请求。取消会等待正在提交的消息完成接收,失败时保留客户端。聊天界面中 Esc 会发送取消请求,不受本地空闲状态判断限制。任务运行中时,Esc 关闭文件或历史/搜索菜单的同时请求取消;空闲菜单仅关闭。正在执行的本地历史/搜索/费用加载优先被取消。Page Up/Down 滚动当前对话;`/older` 加载更早记录。所有退出路径(包括 `/quit` 和 SIGTERM)都会在关闭连接前停止所选任务,因此退出不会留下仍在运行的代理;会话空闲时不发送取消。取消当前任务会保留排队消息。
291
291
 
292
- 鼠标滚轮和 Page Up/Down 滚动对话;滚到顶部自动加载更早的一页。查看旧记录时,新输出保留阅读位置;`/jump last` 恢复跟随最新输出。TUI 挂载时启用鼠标报告,退出时关闭,需要终端支持 SGR 鼠标报告。加载历史或搜索期间,Esc 或 Ctrl+C 优先取消本地操作,不中断远程任务。
292
+ `/copy`、Ctrl+S 或普通聊天界面的鼠标左键单击冻结画面并关闭鼠标事件捕获,便于使用终端原生选择复制。Esc、Ctrl+S 或 Ctrl+C 退出复制模式并显示最新输出,退出复制模式不会取消代理。对话框和选择器自动冻结背景标题、对话和状态更新,并释放鼠标捕获,界面操作仍可使用。回看旧历史时 Working 计时显示也暂停。帮助/状态/费用面板不再定时消失。后台接收与内存回收继续运行,调整窗口大小仍可能重绘。
293
293
 
294
- `/search` 对显示的消息进行不区分大小写的字面文本匹配,包含旧页,不搜索隐藏的工具正文。`/history` 仅列出已加载记录,选择器显示的数字就是记录序号。`/ssearch` 与 `/wsearch` 调用 `session/search`,服务端搜索当前用户/助手消息内容,最多返回 20 个会话、摘要和截断标记,没有结果分页游标或命中记录序号。工作区筛选在全局数量限制之后进行,因此截断时可能漏掉工作区内的匹配会话;界面会提示结果不完整,可缩小查询范围。选择会话后加载其历史,再选择匹配消息跳转。所有操作均通过 HTTP 完成,不扫描服务端配置目录。
294
+ 鼠标滚轮和 Page Up/Down 滚动对话;滚到顶部自动加载更早的一页。查看旧记录时,新输出保留阅读位置;向下滚动即可恢复跟随最新输出。TUI 挂载时启用鼠标报告,退出时关闭,需要终端支持 SGR 鼠标报告。加载历史或搜索期间,Esc 或 Ctrl+C 优先取消本地操作,不中断远程任务。
295
295
 
296
- 单行输入框支持 Readline 风格编辑。单词以空白分隔;光标移动和逐字符删除保持完整的 Unicode 组合字符。粘贴的多行文本会以空格连接成一行。空输入时 Ctrl+D 不退出;Ctrl+C 保持停止/退出行为。未处理的修饰键快捷键不会将控制字符插入消息。终端退格键的 BS 和 DEL 编码均向后删除;独立 Delete 键(CSI 3~)向前删除。
296
+ 在 `/ws` 和 `/resume` 列表选中工作区或会话后,输入框为空时按 `d` 或 Delete 查看移除确认页。已有草稿时 `d` 仍正常输入,Backspace 不会打开移除页。`/ws --delete <名称或ID>` 和 `/resume --delete <标题或ID>` 打开相同确认页,`/resume --archive <标题或ID>` 也可归档会话。移除会话前重新读取 `session/list`;服务端明确标记 `blank: true`、未运行,且没有已知排队任务或本地正在提交的提示词时,直接归档,不再确认。判定使用服务端空会话标记,不依赖当前已加载历史或标题。其他会话仍需确认,默认选中取消,Esc 关闭确认页。工作区移除调用 `workspace/delete`,只移除注册,保留目录和会话。会话移除调用 `workspace/archiveSession`,从工作区会话列表和 `/resume all` 隐藏,但保留历史,可通过 `/resume ID` 重开。当前服务端 API 提供归档,没有永久删除会话接口。运行中的任务继续执行。归档当前会话会释放其 transcript 和排版缓存;操作被拒绝时保留列表及确认页,便于重试。
297
+
298
+ `/search` 对对话消息进行不区分大小写的字面文本匹配,包含旧页,排除纯工具行。搜索每次请求最多 80 条消息,扫描后释放临时页,只保留最多 200 条简短命中摘要,包含折叠的思考;结果截断时提示缩小查询范围。选择命中项只加载其序号附近的独立页面,`/latest` 释放该窗口。Esc 或 Ctrl+C 可取消搜索。稀有词或无匹配查询仍需通过 HTTP 扫描全部历史,此命令尚无服务端全文索引。`/history` 只列出已加载页面中自己的提示词,选择器显示的数字就是记录序号。`/ssearch` 与 `/wsearch` 调用 `session/search`,服务端搜索当前用户/助手消息内容,最多返回 20 个会话、摘要和截断标记,没有结果分页游标或命中记录序号。工作区筛选在全局数量限制之后进行,因此截断时可能漏掉工作区内的匹配会话;界面会提示结果不完整,可缩小查询范围。选择会话后加载其历史,再选择匹配消息跳转。所有操作均通过 HTTP 完成,不扫描服务端配置目录。
299
+
300
+ ↑/↓ 或 Ctrl+P/N 回填之前提交的提示词和 slash 命令,按 Enter 才提交。向下越过最新记录时恢复未发送草稿;编辑回填内容后开始新的草稿。历史按当前会话保留,最多 200 条、约 256 KiB 文本。打开或恢复会话时,从已加载的 User 消息初始化回填;切换会话释放旧缓存。不额外拉取历史页,也不写入独立历史文件。连续重复输入合并,超大输入跳过,提问和审批回答不记入历史。提问选项与补全菜单优先使用箭头;工作区/会话列表在输入框为空时使用箭头选择,可用 Ctrl+P/N 调出输入历史。
301
+
302
+ 每条 User 消息之后,只在第一段助手正文或思考前显示 Assistant 标题;后续消息及流式输出沿用分组,工具结果和 Context 消息不重置分组。当前加载的历史窗口从自身起点建立可见分组,消息序号、工具状态、搜索和思考展开仍各自保留。
303
+
304
+ 鼠标复制时,先单击进入复制模式,待画面冻结后再拖动选择。松开鼠标不会恢复刷新,需按 Esc、Ctrl+S 或 Ctrl+C。终端原生 Shift+拖选可能不向应用发送鼠标事件,此时请先按 Ctrl+S。对话框中已关闭鼠标捕获,如需冻结整个对话框可按 Ctrl+S。
305
+
306
+ Tab 补全开头的 slash 命令,多个候选时补到公共前缀。单行输入框支持 Readline 风格编辑。单词以空白分隔;光标移动和逐字符删除保持完整的 Unicode 组合字符。粘贴的多行文本会以空格连接成一行。空输入时 Ctrl+D 不退出;输入非空时 Ctrl+C 先清空输入,然后才停止或退出。未处理的修饰键快捷键不会将控制字符插入消息。终端退格键的 BS 和 DEL 编码均向后删除;独立 Delete 键(CSI 3~)向前删除。
297
307
 
298
308
  | 按键 | 编辑操作 |
299
309
  | --- | --- |
310
+ | ↑ / ↓、Ctrl+P / Ctrl+N | 回填更早/较新的提交内容 |
300
311
  | Ctrl+A / Ctrl+E、Home / End | 移到开头/末尾 |
301
312
  | Ctrl+B / Ctrl+F、← / → | 移动一个字符 |
302
313
  | Alt+B / Alt+F、Ctrl+← / Ctrl+→ | 移动一个词 |
@@ -310,42 +321,52 @@ Enter 提交消息。所选会话运行中时,Ctrl+C 请求取消;只有空
310
321
  | --- | --- |
311
322
  | `/ws` | 显示所有工作区,选中后打开其会话列表 |
312
323
  | `/ws TARGET` | 按 ID、完整名称/路径或唯一 ID 前缀选择工作区 |
313
- | `/s` | 显示当前工作区的会话;未选择工作区时先引导选择 |
314
- | `/s TARGET` | 按 ID、完整标题或唯一 ID 前缀跨工作区打开会话 |
315
- | `/s all` | 显示所有工作区的会话 |
324
+ | `/resume` | 显示当前工作区的会话;未选择工作区时先引导选择 |
325
+ | `/resume TARGET` | 按 ID、完整标题或唯一 ID 前缀跨工作区打开会话 |
326
+ | `/resume all` | 显示所有工作区的会话 |
327
+ | `/model [provider model [effort]]` | 选择模型及其思考强度,也可直接提交精确路由 ID |
316
328
  | `/new` | 在所选工作区创建会话 |
317
329
  | `/cancel` | 取消当前轮次,保留待处理队列 |
318
330
  | `/steer TEXT` | 提交转向输入 |
319
331
  | `/older` | 加载更早的历史 |
320
- | `/history [text]` | 列出并可选筛选已加载记录;Enter 跳到所选记录 |
321
- | `/jump <seq\|first\|last>` | 跳到可见记录序号、最早历史或最新输出 |
322
- | `/search <text>` | 补齐并搜索当前会话历史,选择匹配消息后跳转 |
332
+ | `/history [text]` | 列出自己的提示词并可选筛选;Enter 跳到所选记录 |
333
+ | `/search <text>` | 逐页搜索历史,选择命中项后打开其位置 |
334
+ | `/copy` | 冻结画面便于终端选择,Esc 恢复 |
335
+ | `/latest` | 返回实时输出并释放独立历史窗口 |
323
336
  | `/ssearch <text>` | 在服务端搜索结果中筛选当前工作区的会话 |
324
337
  | `/wsearch <text>` | 搜索服务端可见的所有工作区会话 |
325
338
  | `/allow`, `/deny` | 回复当前审批;批准仅限一次 |
326
339
  | `/status` | 展开或收起底部完整状态信息 |
327
340
  | `/cost` | 展开/收起会话、今日、三日费用,并刷新用量 |
328
- | `/help`, `/quit` | 显示命令提示或退出 |
341
+ | `/think` | 显示思考及用户 prompt 摘要列表;↑/↓ 选择、Enter 跳转并展开 |
342
+ | `/think SEQ` | 切换一条已加载思考的展开状态;`live` 表示当前尝试 |
343
+ | `/help`, `/quit` | 列出每条命令及其说明,或退出 |
329
344
 
330
- Slash 命令在选择器和对话输入框中均可使用。输入 `/` 会显示匹配命令。`/help`、`/cost`、`/status` 三个面板是临时的:执行下一条命令、或十秒后,当前打开的面板会自动关闭。长命令 `/workspace`、`/workspaces`、`/session`、`/sessions` 保留为别名。名称可以包含空格,完整目标两侧的引号可选。不带引号的目标 `all` 保留给 `/s all`;打开标题为 `all` 的会话时,使用 `/s "all"` 或其 ID。目标有歧义时必须提供完整 ID。切换工作区会打开其会话列表并解除旧对话订阅;切换会话会同步工作区标签。两种操作均不会取消远程代理。
345
+ Slash 命令在选择器和对话输入框中均可使用。输入 `/` 会显示匹配命令,`/help` 会逐条列出命令及其单行说明。`/help`、`/cost`、`/status` 面板保持打开,直到下一条命令或 Esc;Esc 保留输入内容。`/workspace`、`/workspaces` 是 `/ws` 的别名;`/session`、`/sessions` 是 `/resume` 的别名。名称可以包含空格,完整目标两侧的引号可选。不带引号的目标 `all` 保留给 `/resume all`;打开标题为 `all` 的会话时,使用 `/resume "all"` 或其 ID。目标有歧义时必须提供完整 ID。切换工作区会打开其会话列表并解除旧对话订阅;切换会话会同步工作区标签。两种操作均不会取消远程代理。
331
346
 
332
347
  在输入末尾键入 `@`,可搜索所选会话**在服务端**工作目录中的文件和目录。使用 ↑/↓ 选择,Tab 或 Enter 插入;选择目录后继续补全其内部路径。带空格的路径使用 `@"path with spaces"`。Esc 关闭菜单,任务运行中时同时请求取消;关闭后 Enter 发送原样输入,包括未匹配到的路径。搜索失败时显示错误,不提交输入。补全针对输入末尾的引用,不跟踪已有文本内部的光标位置。
333
348
 
334
349
  文件引用仅在文本块中发送 `@path`。Harness 提示模型按需读取文件或列出目录;TUI 不读取本地文件、不上传字节,也不将文件内容展开进提示词。引用图片路径不会附带图片数据。尚未实现本地附件、图片上传/预览及 `@` 会话引用。
335
350
 
336
- 用户问题逐题接收自由文本回答。其他会话的交互以及不识别的 waterfall 通过 `next` 委托后续处理。提交失败时保留输入;HTTP 响应中断可能导致投递状态不确定,手动重发前应检查会话记录。客户端不会自动重试修改请求。
351
+ 用户问题显示题目进度、编号选项和说明。待答问题和审批独占对话区域,避免冻结的历史挤压选项行;可见选项数量按终端高度调整,并跟随当前高亮项滚动。输入框为空时,↑/↓ 或 1–9 定位选项,Enter 确认;数字键只选择、不提交。多选题用空格或 1–9 勾选/取消勾选,Enter 确认,超过九个选项仍可通过方向键访问。选择 Other answer 后可输入纯数字自由文本,也保留普通文本回答。已有草稿时按正常文字输入处理,Esc 从 Other 返回选项而不取消提问。全部题目回答完成后,一次提交结构化选项标签及可选自定义文本;失败时保留答案以便重试。已识别的提问和审批事件在本次连接中按事件 ID 保留,包括早于会话选择到达的重放事件;只展示当前会话对应的请求。切换列表不会退回这些请求,不识别的 waterfall 仍通过 `next` 委托后续处理。存活服务端在客户端重连后重发待答事件;客户端重启不保留尚未提交的回答草稿。正常退出 TUI 会取消正在运行的轮次。调用已取消/失败或服务端已重启时,无法靠本地 UI 状态恢复原等待,需要发送新提示词要求重新提问。提交失败时保留输入;HTTP 响应中断可能导致投递状态不确定,手动重发前应检查会话记录。客户端不会自动重试修改请求。
337
352
 
338
- 对话顶部显示最新会话标题,无标题时回退到 ID;`/status` 保留完整会话 ID。取消回执在后续历史消息到达时保持可见,直到服务端报告空闲;接受取消不表示工具进程已经退出。
353
+ 标题固定在可滚动对话区域上方,输入框和状态栏保留在下方。底部不再常驻快捷键说明,完整快捷键放在 `/help`,选择器只显示当前需要的导航提示。顶部单行显示 `dsht · <主机名>`、工作区名称和最新会话标题(无标题时回退到 ID)及右对齐的连接状态,下方为分隔线;`/status` 保留完整会话 ID。取消回执在后续历史消息到达时保持可见,直到服务端报告空闲;接受取消不表示工具进程已经退出。
339
354
 
340
355
  ## 实时状态
341
356
 
342
- 底栏默认无边框单行显示运行状态、模型、工作区、上下文占用和 token 总量;宽度足够时补充输入/输出、缓存、队列和后台任务数。长名称按终端显示宽度缩短,窄终端优先省略次要信息。`/status` 切换完整多行详情,显示完整路径、供应商/模型、思考强度及各项用量。`!` 表示有指标或模型目录错误,或计费覆盖不完整;详情中显示原因。运行中时区分最近实际使用的模型和不同的下次请求模型;新会话使用服务端模型目录的默认值。服务端设置、凭据和适配器变更通知会刷新模型目录。
357
+ 底栏分组显示 `◐ Working · 8s · Ctrl+C Stop` 或 `● Ready`、模型与思考强度、会话/今日费用、十格上下文进度条及百分比、会话轮次与累计 token。宽终端为运行状态预留固定宽度,完成后模型和指标保持对齐。窄终端依次回收留白、隐藏进度条、缩短模型名、省略次要指标,优先保留停止提示。`/status` 显示主机 URL、操作状态、工作区完整路径、完整供应商/模型、下次模型、各项用量、轮次、队列、后台任务和四位小数费用。`!` 表示有指标或模型目录错误,或计费覆盖不完整;详情中显示原因。运行中使用最近实际使用的模型,空闲时使用下次模型,新会话使用服务端模型目录默认值。服务端设置、凭据和适配器变更通知会刷新模型目录。
358
+
359
+ 工作计时使用已加载日志的 `turn/start` 时间戳。缺少该时间戳时,紧凑计时后的 `~`(详情中的 `(observed)`)表示从客户端观察到运行开始计时;重连可能重置此备用计时。服务端报告空闲后停止计时。运行状态涵盖模型生成、工具执行及审批等待,不仅是文本输出。断线时明确标注为最后已知状态。
360
+
361
+ 轮次数来自完整会话的 `sessionStats.turns` 投影。上下文占用标为 `~`:Harness 将供应商用量与对话变化估算值、最新模型容量结合。Token 总量来自完整会话的 `tokenUsage` 投影,分别显示非缓存输入、输出、缓存读取和缓存写入;思考 token 已包含在输出中。总量随服务端用量投影更新,不按流式字符计数。缺失数据显示 `unknown` 或 `?`。重连时控制流基线整体替换状态,每个投影键的序号防止旧 follow 快照覆盖较新的指标。
362
+
363
+ 默认使用 [Catppuccin Mocha](https://catppuccin.com/palette/) 主题:`❯ User` 为蓝色,`✦ Assistant` 为绿色,思考为淡紫色,工具为天蓝色,成功为绿色,错误为红色。紧凑状态栏中 Ready 为绿色、Working 为黄色、离线为红色,模型/强度为淡紫色、费用为天蓝色、用量为柔和灰色。上下文占用达到 80% 时从绿色变黄,95% 时变红;这只是视觉阈值,不代表服务端压缩触发条件。各组先按宽度裁剪,再添加 ANSI 样式,保持对齐及无色终端输出。语义配色独立放在 `src/theme.ts`,应用可单独接收主题,消息不保存 ANSI 样式。Ink 根据终端能力输出颜色,无色终端仍保留角色标记。工具调用显示名称和描述;命令与描述不同时,下一行以 `$` 显示命令第一行。没有描述时使用命令第一行、路径或查询作为摘要。各行按终端显示宽度截断;结果按调用 ID 在原条目上将 ⚙ 更新为 ✓ 或 ✗,不再重复新增结果条目,命令预览保留两格缩进。调用尚未加载时单独显示结果摘要,加载调用页后合并;嵌套结果正文保持隐藏。
343
364
 
344
- 工作计时使用已加载日志的 `turn/start` 时间戳。缺少该时间戳时,`(observed)` 表示从客户端观察到运行开始计时;重连可能重置此备用计时。服务端报告空闲后停止计时。运行状态涵盖模型生成、工具执行及审批等待,不仅是文本输出。断线时明确标注为最后已知状态。
365
+ 思考生成时完整流式显示,思考块结束或正文/工具输出开始后自动折叠。`/think` 按从新到旧列出思考摘要、前一条已加载的用户 prompt,并包含当前尝试。↑/↓ 选择、Enter 跳到原消息并展开;`/think SEQ` 可切换该消息的折叠状态,`/think live` 控制当前尝试。列表在选择、Esc 或其他命令时关闭,不自动超时。打开列表只使用已加载记录,选择 `Load older reasoning` 才读取一页更早历史;prompt 在已加载窗口之前时明确提示,加载对应页面后补全。完整思考仍可被搜索。
345
366
 
346
- 上下文占用标为 `~`:Harness 将供应商用量与对话变化估算值、最新模型容量结合。Token 总量来自完整会话的 `tokenUsage` 投影,分别显示非缓存输入、输出、缓存读取和缓存写入;思考 token 已包含在输出中。总量随服务端用量投影更新,不按流式字符计数。缺失数据显示 `unknown` 或 `?`。重连时控制流基线整体替换状态,每个投影键的序号防止旧 follow 快照覆盖较新的指标。
367
+ `/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` 查看,为连接指示留出空间。
347
368
 
348
- 纯工具行省略独立角色标题:`⚙` 表示调用,`✓` 表示成功结果,`✗` 表示失败结果。完整参数到达后,每行显示工具名和操作描述,缺少描述时使用命令、路径或查询摘要。已加载历史包含对应调用时,结果复用该调用摘要。每个操作最多占一行,合并空白,并按终端显示宽度用省略号截断。其他参数、嵌套结果和工具正文仍然隐藏。助手正文与显式审批请求保持可见,以便用户理解回答并判断是否批准操作。
369
+ 历史分为语义消息块、prompt/思考摘要、独立折叠状态和行数索引。流式更新复用已提交历史的位置索引,只生成当前可见区域。每个会话的 LRU 最多保留 2,048 行已提交终端内容,移出缓存的行在回看时重建。已结束的旧版流式分片和不用展示的工具结果正文会释放,原始日志由服务端保存。服务端日志作为持久层,客户端作为可重载的内存层。实时历史默认以 2,000 条记录或 16 MiB 语义数据估算量为软限制(`--history-records`、`--history-mb`),触发回收后以限制的 75% 为目标,释放旧正文、摘要和排版缓存。切换会话会释放上一会话的 transcript。回看和思考导航期间保护已加载窗口;`/latest` 返回实时输出并恢复回收。离线历史、未结束流和最小近期尾部受保护,因此这些参数不是进程 RSS 硬上限。首次排版、改变终端宽度及展开特别大的单个内容块,仍需要处理对应全文。`npm run bench:history` 测量 500、2,000、10,000 条消息下的本地流式排版耗时,不含网络和模型时间。
349
370
 
350
371
  ## 费用估算
351
372
 
@@ -353,13 +374,13 @@ Slash 命令在选择器和对话输入框中均可使用。输入 `/` 会显示
353
374
 
354
375
  > 这里的费用是基于 Harness 可见用量和本地价格配置得到的高精度估算,用于成本监控和控制;它不是供应商账户级账单,最终费用仍以供应商账单为准。
355
376
 
356
- `/cost` 显示当前会话、今日及今日加前两个自然日的费用。日期使用 Asia/Shanghai,三日统计不是滚动 72 小时。状态栏中 `S:` 表示会话费用,`D:` 表示今日费用。`~` 表示估算;`*` 表示该小计并不精确:请求缺少时间戳、没有价格覆盖,或无法归入所选自然日区间。覆盖不完整另行通报:之前运行缓存的费用视为完整,而账本为空或扫描失败时状态栏出现 `!` 前缀,并在 `/status` 中说明原因。每个服务端 origin 使用独立账本;总额覆盖 HTTP 可见会话及之前缓存的会话,不是供应商账户级账单。
377
+ `/cost` 显示当前会话、今日及今日加前两个自然日的费用。日期使用 Asia/Shanghai,三日统计不是滚动 72 小时。状态栏以两位小数显示会话/今日费用,斜杠不表示预算。`~` 表示估算;`*` 表示该小计并不精确:请求缺少时间戳、没有价格覆盖,或无法归入所选自然日区间。覆盖不完整另行通报:之前运行缓存的费用视为完整,而账本为空或扫描失败时状态栏出现 `!` 前缀,并在 `/status` 中说明原因。每个服务端 origin 使用独立账本;总额覆盖 HTTP 可见会话及之前缓存的会话,不是供应商账户级账单。
357
378
 
358
- 客户端连接后、每 60 秒、任务结束及打开 `/cost` 时在后台通过 HTTP 读取完整历史;服务端更新时间未变的空闲会话跳过扫描。计费不会发起模型请求。显式刷新时可按 Esc 或 Ctrl+C 取消。账本分别统计未缓存输入、缓存读/写和输出,思考 token 已包含在输出中。重试单独计费,同一次尝试的替换用量更新原记录,fork 继承历史不重复计费。缺少结算时间戳的请求仍按该模型族的最低费率给出下限金额,并标记为估算。用量矛盾,以及没有任何模型或供应商条目覆盖的价格,仍标为未计价;模型名是否包含 `pro` 决定按 Pro 还是 Flash 计价,而未列出的供应商不会套用官方价目。扫描失败保留并标明部分缓存结果。
379
+ 客户端连接后、每 60 秒、任务结束及打开 `/cost` 时在后台通过 HTTP 读取完整历史;服务端更新时间未变的空闲会话跳过扫描。计费不会发起模型请求。显式刷新时可按 Esc 或 Ctrl+C 取消。账本分别统计未缓存输入、缓存读/写和输出,思考 token 已包含在输出中。重试单独计费,同一次尝试的替换用量更新原记录,fork 继承历史不重复计费。缺少结算时间戳的请求仍按该模型族的最低费率给出下限金额,并标记为估算。用量矛盾,以及没有任何模型或供应商条目覆盖的价格,仍标为未计价;模型名是否包含 `pro` 决定按 Pro 还是 Flash 计价,而未列出的供应商不会套用官方价目。每个会话独立读取:某个会话不可达或被拒绝时只计为失败数量并继续扫描,不会中止整轮;子代理会话按其持久父级地址读取。扫描失败保留并标明部分缓存结果。
359
380
 
360
381
  内置人民币价格于 2026-09-10 根据[官方价格页](https://api-docs.deepseek.com/zh-cn/quick_start/pricing/)核对。北京时间工作日 09:00–12:00、14:00–18:00 为高峰,其余时段半价。Flash 高峰未命中输入/缓存命中输入/输出为每百万 token ¥2/¥0.04/¥8,Pro 为 ¥9/¥0.30/¥27;当前模型名为 `deepseek-flash`,旧 Flash 名称沿用同一费率。供应方已公告自北京时间 2026-09-14 12:00 起将 `deepseek-v4-pro` 交由 Flash 服务并按 Flash 价格计费,内置条目已记录该变更,避免此后高估 Pro 用量。单列的缓存写入按未命中输入价计算。配置中的精确模型价格优先;否则 `deepseek-official` 模型名包含 `pro`(不区分大小写)时按 Pro 计价,其余名称包括临时别名均按 Flash 计价。其他供应商需要显式配置。
361
382
 
362
- 默认价格有效期从核对日期的北京时间零点开始,这是本地估算规则,不代表官方价格生效日期。更早用量需要补充历史价格版本。程序按助手请求结算记录的时间选择单价;官方未说明跨时段请求的归属,因此边界附近的估算可能与账单不同。图片使用供应商报告的 token 数。已计价请求保留原价格版本,不随配置修改重新套价;未计价请求可以在后续扫描时补算。
383
+ 默认价格有效期从核对日期的北京时间零点开始,这是本地估算规则,不代表官方价格生效日期。更早用量需要补充历史价格版本。程序按助手请求结算记录的时间选择单价;官方未说明跨时段请求的归属,因此边界附近的估算可能与账单不同。图片使用供应商报告的 token 数。每次扫描都按当前配置重新计算已存请求,因此修正价格版本会一并修正此前的小计;此前没有任何条目覆盖的请求,会在条目覆盖其结算日期后被计价。
363
384
 
364
385
  首次交互启动会创建 `~/.config/dsht/prices.json`(或 `$XDG_CONFIG_HOME/dsht/prices.json`),可用 `DSHT_CONFIG_DIR` 覆盖目录。JSON 数组中的价格版本包含 `id`、`provider`、`model`、`currency: "CNY"`、`source`、包含起点的 `from`、可选且不含终点的 `until`、`timezone`、星期数字 `weekdays`(`0` 为周日)、日内分钟区间 `windows`,以及 `peak`/`offPeak` 下每百万 token 的 `input`、`cacheRead`、`cacheWrite`、`output` 单价。调价时用 `until` 结束旧区间,再添加唯一 ID 且 `from` 衔接的新版本;程序拒绝重叠区间。重启后读取配置修改;价格由用户维护,启动时不抓取网页价格。
365
386
 
@@ -377,7 +398,7 @@ Slash 命令在选择器和对话输入框中均可使用。输入 `/` 会显示
377
398
 
378
399
  | 字段 | 值 |
379
400
  | --- | --- |
380
- | 名称与版本 | `@itookit/dsht` `0.2.0` |
401
+ | 名称与版本 | `@itookit/dsht` `0.2.4` |
381
402
  | 可执行命令 | `dsht`,不安装时用 `npx @itookit/dsht` |
382
403
  | 库入口 | `@itookit/dsht` 和 `@itookit/dsht/auth` |
383
404
  | 作者 | lizlok\@gmail.com |
@@ -399,7 +420,7 @@ npm publish --access public
399
420
 
400
421
  `publishConfig.access` 为 `public`;scoped 包需要它才能被公开安装,因此该设置放在包里而不是每次发布命令上。启用两步验证的账号需用即时验证码发布:`npm publish --otp=<验证码>`;验证码在最后一次请求时校验,此时类型检查、测试和构建均已执行完毕。
401
422
 
402
- 后续版本由 `.github/workflows/publish.yml` 发布:它以版本 tag 触发,使用 [trusted publishing](https://docs.npmjs.com/trusted-publishers)(OIDC)并生成 provenance,不保存任何发布 token。需在 `npmjs.com` → `@itookit/dsht` → Settings → Trusted Publisher → GitHub Actions 一次性配置:组织或用户 `mushuanli`、仓库 `dsht`、工作流文件名 `publish.yml`、允许动作 `npm publish`。Trusted publishing 无法创建包,因此首个版本需手工发布;之后执行 `npm version 0.2.1 && git push --follow-tags` 即可发布。
423
+ 后续版本由 `.github/workflows/publish.yml` 发布:它以版本 tag 触发,使用 [trusted publishing](https://docs.npmjs.com/trusted-publishers)(OIDC)并生成 provenance,不保存任何发布 token。需在 `npmjs.com` → `@itookit/dsht` → Settings → Trusted Publisher → GitHub Actions 一次性配置:组织或用户 `mushuanli`、仓库 `dsht`、工作流文件名 `publish.yml`、允许动作 `npm publish`。Trusted publishing 无法创建包,因此最早的版本需手工发布;之后的版本推送对应 tag 即可发布,例如 `npm version 0.2.3 && git push --follow-tags`。
403
424
 
404
425
  手动触发时该工作流只打包不发布,并拒绝与 `package.json` 不一致的 tag。参见官方 [scoped 发布指南](https://docs.npmjs.com/creating-and-publishing-scoped-public-packages/)和 [npx 文档](https://docs.npmjs.com/cli/npm-exec/)。Registry 发布不属于本仓库已执行的本地验证。
405
426
 
@@ -415,8 +436,8 @@ node dist/cli.js --help
415
436
 
416
437
  测试使用隔离的 HTTP/WebSocket 服务,驱动实际 Ink 选择器和输入框,在子进程中运行 CLI,并投影复制的 Harness v2 工作区编辑记录和 v0 压缩 chunk 记录。这些检查不需要模型凭据。记录和预期对话输出位于 `tests/`,不依赖父仓库。测试不覆盖真实模型供应商行为。
417
438
 
418
- `npm test` 渲染不带样式的帧,因为断言和 `tests/expected/` 中的预期输出描述的是文本。从终端启动的测试运行器会向每个测试文件导出 `FORCE_COLOR=1`,使 Ink 在提示符与文本之间插入 SGR 转义序列;`npm run test:terminal` 在任何主机上复现该环境,`prepublishOnly` 也会运行它,因此从终端发布时验证的就是终端实际渲染的结果。
439
+ `npm test` 渲染不带样式的帧,因为断言和 `tests/expected/` 中的预期输出描述的是文本。从终端启动的测试运行器会向每个测试文件导出 `FORCE_COLOR=1`,使 Ink 在提示符与文本之间插入 SGR 转义序列;`npm run test:terminal` 在任何主机上复现该环境,`prepublishOnly` 也会运行它,因此从终端发布时验证的就是终端实际渲染的结果。 主题测试在独立子进程中分别渲染真彩色和纯文本,并隔离父进程中影响终端和 CI 颜色检测的环境设置。
419
440
 
420
441
  输入期间复用历史投影和换行结果,直到对话版本或终端宽度变化;服务端更新和历史翻页会使缓存失效。`bench:input` 使用 20 条和 500 条合成消息,在预热后测量 30 次按键的本地输入至渲染耗时及历史投影读取次数。它排除网络/模型耗时,仅供诊断,不作为跨机器的延迟阈值。
421
442
 
422
- 界面显示纯文本、思考内容、工具调用和工具结果。尚未实现富插件卡片、文件上传、子代理导航、模型选择和队列编辑。重连采用有上限的指数退避及抖动,并替换快照;列表命令直接报告失败而不重试。服务端的非稳定 API 更新后,需要同步本地报文适配和测试。
443
+ 界面显示纯文本、思考内容、工具调用和工具结果。尚未实现富插件卡片、文件上传、子代理导航和队列编辑。重连采用有上限的指数退避及抖动,并替换快照;列表命令直接报告失败而不重试。服务端的非稳定 API 更新后,需要同步本地报文适配和测试。
package/dist/app.d.ts CHANGED
@@ -1,6 +1,21 @@
1
+ import { type Theme } from './theme.ts';
1
2
  import { Controller } from './controller.ts';
3
+ /** One slash command advertised by completion and `/help`. */
4
+ export interface CommandHint {
5
+ /** Slash command as typed without arguments. */
6
+ command: string;
7
+ /** Argument hint shown after the command; absent when it takes none. */
8
+ usage?: string;
9
+ /** One-line action description shown by `/help`. */
10
+ description: string;
11
+ }
12
+ /** Command discovery catalog shared by Tab completion and the `/help` panel. */
13
+ export declare const COMMAND_HINTS: readonly CommandHint[];
14
+ /** Longest common prefix of the candidate commands, so Tab can extend an ambiguous draft. */
15
+ export declare function commonPrefix(values: string[]): string;
2
16
  /** The caller owns starting and stopping the controller around the Ink render lifetime. */
3
- export declare function App({ controller, panelLifetimeMs }: {
17
+ export declare function App({ controller, panelLifetimeMs, theme }: {
4
18
  controller: Controller;
5
19
  panelLifetimeMs?: number;
20
+ theme?: Theme;
6
21
  }): import("react").JSX.Element;