dsh-ssh-tui 0.6.2 → 0.6.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.
Files changed (44) hide show
  1. package/README.en.md +67 -2
  2. package/README.md +56 -3
  3. package/lib/context-window.js +139 -0
  4. package/lib/context-window.js.map +1 -0
  5. package/lib/dialogs.js +13 -7
  6. package/lib/dialogs.js.map +1 -1
  7. package/lib/i18n/en.js +69 -3
  8. package/lib/i18n/en.js.map +1 -1
  9. package/lib/i18n/zh.js +69 -3
  10. package/lib/i18n/zh.js.map +1 -1
  11. package/lib/index.js +10 -4
  12. package/lib/index.js.map +1 -1
  13. package/lib/job-label.js +45 -0
  14. package/lib/job-label.js.map +1 -0
  15. package/lib/paint.js +3 -3
  16. package/lib/paint.js.map +1 -1
  17. package/lib/preset-label.js +73 -0
  18. package/lib/preset-label.js.map +1 -0
  19. package/lib/preset-rows.js +96 -0
  20. package/lib/preset-rows.js.map +1 -0
  21. package/lib/provider-catalog.js +12 -2
  22. package/lib/provider-catalog.js.map +1 -1
  23. package/lib/quota.js +149 -1
  24. package/lib/quota.js.map +1 -1
  25. package/lib/reasoning.js +9 -4
  26. package/lib/reasoning.js.map +1 -1
  27. package/lib/term-text.js +82 -11
  28. package/lib/term-text.js.map +1 -1
  29. package/lib/tool-present.js +14 -1
  30. package/lib/tool-present.js.map +1 -1
  31. package/lib/tui.js +363 -25
  32. package/lib/tui.js.map +1 -1
  33. package/lib/types/context-window.d.ts +46 -0
  34. package/lib/types/dialogs.d.ts +5 -4
  35. package/lib/types/job-label.d.ts +20 -0
  36. package/lib/types/preset-label.d.ts +38 -0
  37. package/lib/types/preset-rows.d.ts +47 -0
  38. package/lib/types/provider-catalog.d.ts +2 -0
  39. package/lib/types/quota.d.ts +49 -0
  40. package/lib/types/reasoning.d.ts +9 -4
  41. package/lib/types/term-text.d.ts +33 -4
  42. package/lib/types/tool-present.d.ts +1 -0
  43. package/lib/types/tui.d.ts +76 -1
  44. package/package.json +1 -1
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Route context-window sizing for hand-declared gateways.
3
+ *
4
+ * A gateway whose `GET /models` discloses no capacity leaves every model on the
5
+ * route at the harness default (262,144), so a long session compacts far
6
+ * earlier than the endpoint requires. `/setup` closes that gap from two
7
+ * sources, in order: the listing's own `context_length`, then the installed
8
+ * pi-ai catalog, which sizes 1,200-odd models under its own provider ids.
9
+ *
10
+ * A gateway id is rarely a catalog id verbatim — `gemini-claude-sonnet-4-6`
11
+ * proxies `claude-sonnet-4-6`, `deepseek/deepseek-v4.1-flash` is namespaced,
12
+ * `gemini-3.6-flash-high` names its thinking level — so the lookup normalizes
13
+ * before it matches, and reports nothing rather than guessing a window it
14
+ * never found.
15
+ *
16
+ * @module dsh-ssh-tui/context-window
17
+ */
18
+ import type { CatalogPreset } from './provider-catalog.js';
19
+ /**
20
+ * Catalog ids to try for one gateway model id, most specific first.
21
+ * @param raw - the id the endpoint advertises.
22
+ * @returns lowercased candidates, deduplicated and order-preserving.
23
+ */
24
+ export declare function catalogIdCandidates(raw: string): string[];
25
+ /**
26
+ * Every catalog window, keyed by lowercased model id. The first provider to
27
+ * size an id wins, so a shared id keeps one stable answer.
28
+ * @param presets - the read catalog presets.
29
+ * @returns the lookup index.
30
+ */
31
+ export declare function catalogWindowIndex(presets: readonly CatalogPreset[]): Map<string, number>;
32
+ /**
33
+ * The catalog window for one gateway model id.
34
+ * @param id - the id the endpoint advertises.
35
+ * @param index - {@link catalogWindowIndex} for the loaded catalog.
36
+ * @returns the window, or `undefined` when no candidate names a sized model.
37
+ */
38
+ export declare function catalogContextWindow(id: string, index: ReadonlyMap<string, number>): number | undefined;
39
+ /**
40
+ * The route-level default to persist: the smallest window discovered for the
41
+ * route's selected models. A model neither the listing nor the catalog sizes
42
+ * then inherits a value the route already proved it serves, never more.
43
+ * @param windows - the per-model windows discovered so far.
44
+ * @returns the window, or `undefined` when nothing was discovered.
45
+ */
46
+ export declare function suggestedRouteContextWindow(windows: Iterable<number>): number | undefined;
@@ -67,10 +67,11 @@ export type QuestionSubmit = {
67
67
  kind: 'none';
68
68
  };
69
69
  /**
70
- * What Enter means for a question dialog. A single-select list with nothing
71
- * highlighted cancels (the user must not accidentally answer with the first
72
- * option); a multi-select list may answer with an empty selection. With no
73
- * options at all, the typed text is the answer.
70
+ * What Enter means for a question dialog. A single-select list answers with the
71
+ * option its highlight sits on: the list opens on the first option, so Enter
72
+ * without arrowing answers that default instead of cancelling, and Esc stays the
73
+ * explicit cancel. A multi-select list may answer with an empty selection. With
74
+ * no options at all, the typed text is the answer.
74
75
  */
75
76
  export declare function questionSubmit(dialog: QuestionDialog, input: string): QuestionSubmit;
76
77
  /** y/n/ctrl-c/esc answers for a confirm prompt. */
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Friendly display names for background jobs.
3
+ *
4
+ * `job_*` cards otherwise read as the raw tool vocabulary (`job_output`,
5
+ * `job_id: bash-1`), which is both untranslated and hard to talk about out
6
+ * loud. Each job id instead gets a two-word alias — "蔚蓝水獭", "azure otter" —
7
+ * derived from the id itself rather than from a random draw, so the same job
8
+ * keeps one name across its call card, its result card, and every later
9
+ * `job_output` / `job_kill` mention. The model-facing id stays authoritative;
10
+ * the alias is presentation only.
11
+ *
12
+ * @module dsh-ssh-tui/job-label
13
+ */
14
+ /**
15
+ * One stable alias for a background job, or `undefined` when the id is empty
16
+ * or the locale carries no vocabulary.
17
+ * @param jobId - the native job id (`bash-1`, `pwsh-2`, …).
18
+ * @returns the locale-formatted alias, e.g. `蔚蓝水獭` / `azure otter`.
19
+ */
20
+ export declare function jobAlias(jobId: string): string | undefined;
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Agent-preset display names, and the profile row the terminal `/mode` needs.
3
+ *
4
+ * A shipped preset's name resolves through the id-keyed dictionary this module
5
+ * owns — the same rule `@deepseek-ai/dsh-agent-presets/display` applies on the
6
+ * browser surface — so `/language` reaches the picker, the banner, and
7
+ * `/status`. A user-authored preset keeps the name its own `preset.yml`
8
+ * published: that metadata is never translated.
9
+ *
10
+ * A terminal profile built on `dsh-base` composes no preset roster, and this
11
+ * plugin's own bundle patch may not mount one (DSH STORE accepts additive
12
+ * plugin-owned rows only), so the profile's user layer owns the row. When it is
13
+ * absent `/mode` reports the exact repair instead of a dead end.
14
+ * @module dsh-ssh-tui/preset-label
15
+ */
16
+ /** Whether this id belongs to the shipped, translatable roster. */
17
+ export declare function isShippedPreset(id: string): boolean;
18
+ /**
19
+ * Resolve the name a picker, banner, or report renders for one preset.
20
+ *
21
+ * A published name equal to the id is the roster's own "no metadata" answer,
22
+ * not a name, so it falls through to the dictionary or back to the id.
23
+ * @param id - the preset id, also the last-resort label.
24
+ * @param name - display name the preset published, when it published one.
25
+ * @param trust - `user` keeps published metadata; anything else (including an
26
+ * unknown trust) resolves a shipped id through the dictionary.
27
+ * @returns the localized or published name, never empty.
28
+ */
29
+ export declare function presetLabel(id: string, name?: string, trust?: string): string;
30
+ /**
31
+ * The profile this process booted, from `--profile <name>` / `--profile=<name>`.
32
+ *
33
+ * The launcher passes the same arguments to the front end and to the Host it
34
+ * spawns (only `--resume`/`--new` are rewritten), so both report one profile.
35
+ * @param argv - the argument list to read.
36
+ * @returns the profile name, or the TUI's own default when none was named.
37
+ */
38
+ export declare function profileFromArgv(argv?: readonly string[]): string;
@@ -0,0 +1,47 @@
1
+ /**
2
+ * The profile patch that mounts the agent-preset roster `/mode` needs.
3
+ *
4
+ * A terminal profile built on `dsh-base` composes no preset roster (only the
5
+ * Web bundle does), and this plugin's own bundle patch may not mount one: DSH
6
+ * STORE accepts additive rows with plugin-owned ids and no `@deepseek-ai/*`
7
+ * module names. The profile's user layer is the supported home for the row, so
8
+ * both the install script and the running TUI write the same block here — the
9
+ * TUI needs it because `dsh plugin add dsh-ssh-tui@latest` (the in-app update
10
+ * path) never runs `scripts/`, which npm installs do not ship.
11
+ *
12
+ * The roster is not cosmetic: without it `/mode` cannot switch, and the tools
13
+ * the shipped presets own (`ask_user_question`, `present`, PTC's presentation)
14
+ * are absent from the agent's catalog.
15
+ * @module dsh-ssh-tui/preset-rows
16
+ */
17
+ /**
18
+ * The exact profile patch block that mounts the roster and the two host
19
+ * services the shipped presets need. `scripts/ensure-profile-rows.sh` carries
20
+ * the same text; a test compares the two so they cannot drift.
21
+ */
22
+ export declare const ROSTER_PATCH_BLOCK = "# dsh-ssh-tui /mode: the agent-preset roster (standard / minimal / PTC /\n# cordis, plus every preset under $DSH_HOME/.agent-presets) and the two host\n# services the shipped presets need. dsh-base composes no roster in a terminal\n# profile, and a third-party bundle patch may not mount an @deepseek-ai row, so\n# the profile's user layer owns them.\n- insert:\n - id: agent-presets\n name: '@deepseek-ai/dsh-agent-presets'\n config:\n default: standard\n\n - id: code-runtime\n name: '@deepseek-ai/dsh-code-runtime-worker-thread'\n\n - id: subagent-model-selection-settings\n name: '@deepseek-ai/dsh-tool-subagent/model-selection-settings'\n";
23
+ /** The profile patch file the roster block belongs in. */
24
+ export declare function rosterPatchPath(home: string, profile: string): string;
25
+ /**
26
+ * The patch text with the roster block appended, or `undefined` when the file
27
+ * already names the roster row.
28
+ *
29
+ * A file that is exactly `[]` (the profile template) is replaced, so the result
30
+ * stays a valid top-level patch list; anything else keeps its content and the
31
+ * block is appended after a blank line.
32
+ * @param existing - the patch file's current text.
33
+ * @returns the new text, or `undefined` when nothing has to change.
34
+ */
35
+ export declare function rosterPatchText(existing: string): string | undefined;
36
+ /**
37
+ * Mount the roster in one profile, unless it is already composed.
38
+ *
39
+ * Idempotent: a profile whose patch already names the roster row (one bundling
40
+ * `@deepseek-ai/dsh-web-app`, for example) is left untouched. The write is a
41
+ * plain whole-file replace because the file is small and read once at boot; a
42
+ * half-written patch would only be seen by the next launch.
43
+ * @param home - the harness home carrying `profiles/`.
44
+ * @param profile - the profile to patch.
45
+ * @returns `present` when the row already exists, else `written`.
46
+ */
47
+ export declare function ensureRosterRows(home: string, profile: string): Promise<'present' | 'written'>;
@@ -3,6 +3,8 @@ export interface CatalogPreset {
3
3
  name: string;
4
4
  baseUrl: string;
5
5
  modelIds: string[];
6
+ /** Context capacity the catalog records, by lowercased model id; absent when it sizes none. */
7
+ capacities?: Record<string, number>;
6
8
  }
7
9
  /** Filter presets by a case-insensitive substring match on id or name. */
8
10
  export declare function filterCatalogPresets(presets: readonly CatalogPreset[], query: string): CatalogPreset[];
@@ -27,6 +27,15 @@ export declare const OPENCODE_GO_USAGE_URL = "https://opencode.ai/zen/go/v1/usag
27
27
  export declare const OPENCODE_ZEN_BASE_URL = "https://opencode.ai/zen/v1";
28
28
  export declare const SUPERGROK_BILLING_URL = "https://cli-chat-proxy.grok.com/v1/billing?format=credits";
29
29
  export declare const DEEPSEEK_PUBLIC_BASE_URL = "https://api.deepseek.com";
30
+ /**
31
+ * Command Code's billing surface sits on the API root, not under the
32
+ * `/provider/v1` route its chat requests use, so these are fixed: a configured
33
+ * base URL only decides whether the canonical host is the right one to ask.
34
+ */
35
+ export declare const COMMAND_CODE_BASE_URL = "https://api.commandcode.ai";
36
+ export declare const COMMAND_CODE_CREDITS_URL = "https://api.commandcode.ai/alpha/billing/credits";
37
+ export declare const COMMAND_CODE_SUBSCRIPTIONS_URL = "https://api.commandcode.ai/alpha/billing/subscriptions";
38
+ export declare const COMMAND_CODE_USAGE_URL = "https://api.commandcode.ai/alpha/usage/summary";
30
39
  /** OpenAI-completions gateways: probe these relative to the configured base URL. */
31
40
  export declare const OPENAI_COMPAT_BALANCE_PATHS: readonly ["/user/balance", "/dashboard/billing/credit_grants", "/v1/dashboard/billing/credit_grants", "/v1/dashboard/billing/subscription"];
32
41
  /**
@@ -35,6 +44,23 @@ export declare const OPENAI_COMPAT_BALANCE_PATHS: readonly ["/user/balance", "/d
35
44
  * routes are recognized by their `opencode.ai` base URL.
36
45
  */
37
46
  export declare function openCodeSourceFor(provider: string, llmPiAiSection: unknown): OpenCodeSource | null;
47
+ /** A recognized Command Code route, used by the quota poller. */
48
+ export interface CommandCodeSource {
49
+ provider: string;
50
+ apiKeyEnv: string;
51
+ label: string;
52
+ }
53
+ /**
54
+ * Classify one route as Command Code's quota surface.
55
+ *
56
+ * Recognized by provider id or by a canonical base URL. The billing requests
57
+ * always target the canonical host, so a lookalike base URL never receives the
58
+ * credential even when the route keeps Command Code's provider id.
59
+ * @param provider - the selected provider route.
60
+ * @param llmPiAiSection - the `llm-pi-ai` settings section, if readable.
61
+ * @returns the credential reference to use, or `null` for another provider.
62
+ */
63
+ export declare function commandCodeSourceFor(provider: string, llmPiAiSection: unknown): CommandCodeSource | null;
38
64
  export type QuotaPeriod = 'hourly' | 'weekly' | 'monthly' | 'unknown';
39
65
  export interface QuotaWindow {
40
66
  label: string;
@@ -42,6 +68,8 @@ export interface QuotaWindow {
42
68
  /** Remaining percent of the window (100 = unused). */
43
69
  remainingPercent: number;
44
70
  resetsAt?: string;
71
+ /** Extra human-readable fact beside the percent, e.g. remaining USD credits. */
72
+ detail?: string;
45
73
  }
46
74
  export interface QuotaSnapshot {
47
75
  provider: string;
@@ -64,6 +92,27 @@ export declare function quotaRefreshEverySteps(window: QuotaWindow | undefined):
64
92
  export declare const quotaRefreshEveryTurns: typeof quotaRefreshEverySteps;
65
93
  export declare function parseSuperGrokBilling(payload: unknown): QuotaSnapshot;
66
94
  export declare function parseOpenCodeGoQuota(payload: unknown, provider: string): QuotaSnapshot;
95
+ /** The billing-period start used to scope the spend query, when reported. */
96
+ export declare function commandCodePeriodStart(subscription: unknown): string | undefined;
97
+ export interface CommandCodeQuotaPayload {
98
+ /** Required `/alpha/billing/credits` reply. */
99
+ credits: unknown;
100
+ /** Optional `/alpha/billing/subscriptions` reply, for the plan and period. */
101
+ subscription?: unknown;
102
+ /** Optional `/alpha/usage/summary` reply, for period spend. */
103
+ summary?: unknown;
104
+ }
105
+ /**
106
+ * Parse Command Code's credits reply into the shared quota snapshot.
107
+ *
108
+ * The rolling five-hour and weekly windows report used/cap; the credit pool
109
+ * reports a remaining USD balance, which becomes a third window whose percent
110
+ * divides period spend by spend plus remaining balance.
111
+ * @param payload - the billing replies already fetched.
112
+ * @param provider - the route the snapshot is labeled with.
113
+ * @throws Error when no window could be read.
114
+ */
115
+ export declare function parseCommandCodeQuota(payload: CommandCodeQuotaPayload, provider: string): QuotaSnapshot;
67
116
  export declare function formatQuotaSnapshot(snapshot: QuotaSnapshot): string;
68
117
  /** Compact `/status` quota line: tightest window first, then the rest. */
69
118
  export declare function formatQuotaStatusLine(snapshot: QuotaSnapshot | undefined): string;
@@ -2,11 +2,16 @@
2
2
  * Shared reasoning-effort defaults for the SSH TUI.
3
3
  *
4
4
  * OpenCode / third-party (llm-pi-ai) routes carry no adapter-level reasoning
5
- * default (unlike `llm-deepseek`, whose row declares `reasoningEffort: max`).
6
- * Without an explicit effort the model streams its thinking as plain `text`
7
- * chunks instead of `reasoning` blocks, so the TUI never receives the data
8
- * needed to render the collapsible `思考中` block. This helper picks a
5
+ * default. Without an explicit effort the model streams its thinking as plain
6
+ * `text` chunks instead of `reasoning` blocks, so the TUI never receives the
7
+ * data needed to render the collapsible `思考中` block. This helper picks a
9
8
  * supported default so the fold has data to show.
9
+ *
10
+ * `llm-deepseek` is not covered here: its adapter always reports a
11
+ * `defaultEffort`, which the LLM runtime materializes into the request on its
12
+ * own. Since the 0.5.1 STORE compliance trim dropped the bundle patch's
13
+ * `reasoningEffort: max` row override, that adapter default is `high` unless a
14
+ * deployment sets it (or `/effort` picks one, remembered per route).
10
15
  */
11
16
  import { type LlmRuntime, type ReasoningEffortId } from '@deepseek-ai/dsh-llm';
12
17
  /**
@@ -46,15 +46,44 @@ export declare function wrapWaitDetails(detail: string, width: number, maxLines?
46
46
  * those ambiguous glyphs as two made `repeatToWidth('─', cols)` paint a
47
47
  * half-width rule and parked the input cursor half a cell past the text.
48
48
  *
49
- * Emoji-bearing symbols are the exception: an emoji font draws them two cells
50
- * wide even where wcwidth says one, and / from `npm test` spilled a row
51
- * into the next card. Variation selectors are zero-width, so ✔️ counts once
52
- * for the base plus nothing for VS16.
49
+ * Emoji-bearing symbols are the exception: a terminal with an emoji font draws
50
+ * a symbol the monospace font lacks with a colour glyph that is wider than the
51
+ * cell it advances, so they are budgeted two cells. {@link pinEmojiCells} makes
52
+ * the terminal actually spend both: VS15 asks for the narrow text form, and a
53
+ * reserving space clears the second cell when the run does not already end in
54
+ * one. Budgeting two cells without clearing the second is what left `✖`
55
+ * overlapping the `|` beside it while every `▶` row came up a cell short.
53
56
  *
54
57
  * Overflow into the input box is handled by clipping/padding painted rows to
55
58
  * the measured column count, not by inflating glyph width.
56
59
  */
57
60
  export declare function displayWidth(text: string): number;
61
+ /**
62
+ * Make a painted row spend the two cells {@link displayWidth} budgets for every
63
+ * BMP emoji symbol.
64
+ *
65
+ * The monospace font of the terminal this was measured on covers `▶` but not
66
+ * `✖` or `ℹ`, so the two fall back to a colour emoji glyph about 1.6 cells wide
67
+ * while still advancing one cell: the `|` and `^` beside them were drawn under
68
+ * the glyph, and the row ended short because the second budgeted cell was never
69
+ * spent. The fix has two halves:
70
+ *
71
+ * - VS15 asks for the narrow text form (`emoji-variation-sequences.txt`,
72
+ * Unicode 17.0, lists `2139 FE0E` and `2716 FE0E` among 371 such sequences).
73
+ * Terminals that ignore it fall back to the colour glyph, which the next
74
+ * bullet still contains.
75
+ * - A reserving space supplies the second cell, always emitted even when the run
76
+ * already has one. That keeps the budget and the terminal in step for every
77
+ * shape: the table counts the symbol as two cells plus every space the text
78
+ * carries, and the terminal spends one cell of glyph advance, one for the
79
+ * reserving space, and one for each of those spaces.
80
+ *
81
+ * A symbol whose Unicode default presentation is emoji already advances two
82
+ * cells, so it is left untouched.
83
+ * @param text - one already-padded, already-clipped row.
84
+ * @returns the row with the text request and the second cell filled in.
85
+ */
86
+ export declare function pinEmojiCells(text: string): string;
58
87
  /** Pad or clip one already-sanitized line so it occupies exactly `width` cells. */
59
88
  export declare function padToWidth(text: string, width: number): string;
60
89
  /**
@@ -6,6 +6,7 @@ import { type TextSegment } from './term-text.js';
6
6
  import type { DisplayKind, Row, ToolDiffHunk } from './transcript-types.js';
7
7
  export declare const SHELL_TOOL_NAMES: Set<string>;
8
8
  export declare const DIFF_TOOL_NAMES: Set<string>;
9
+ export declare const JOB_TOOL_NAMES: Set<string>;
9
10
  /** Format a model list compactly: show the first few entries and an ellipsis. */
10
11
  export declare function formatModelList(models: readonly string[], max?: number): string;
11
12
  /** Prefer the fields a human scans for; fall back to the first scalar pairs. */
@@ -29,7 +29,7 @@ export type { CollapsibleBlock, DisplayKind, DisconnectPolicyName, PlanTodoItem,
29
29
  export { clipAnsiToWidth, cursorVisualPosition, displayWidth, foldInputView, hrefAtColumn, osc52Clipboard, osc8Enabled, paintedLinkHits, fmtElapsedCompact, padAnsiToWidth, padToWidth, renderMarkdownLines, repeatToWidth, shimmerText, truncateToWidth, visibleWidth, waitCardCopy, waitSummaryFromReasoning, wrapWaitDetails, } from './term-text.js';
30
30
  export { captureHangupSignals, composePaintOutput, detectSshSession, formatLinkQualityChip, ignoreFurtherHangupSignals, isEscapePrefix, findCursorPositionReply, isHangupErrno, linkQualityOf, linkSignalPips, paintIntervalForRtt, paintLinkLabel, parseCursorPositionReply, pickerWindowStart, probeTerminalRttMs, releaseHangupSignals, resolvePaintIntervalMs, waitUntilIdleOrTimeout, writeBootSplash, type LinkQuality, type PaintLinkKind, } from './paint.js';
31
31
  export { CONTEXT_IDLE_COMPACT_RATIO, CONTEXT_PRESSURE_DANGER_RATIO, CONTEXT_PRESSURE_WARN_RATIO, CONTEXT_RING_EMPTY, CONTEXT_RING_SEGMENTS, contextPressureAlertText, contextPressureRingColor, contextPressureUsedTokens, contextPressureView, describeProviderRoute, dropFooterQuotaPlanName, fitFooterStatsLine, fitFooterStatusLine, footerActivity, footerIdentityParts, footerStatsGroups, formatContextPressureChip, formatContextPressureRing, formatContextPressureStatusLine, formatDuration, formatFooterQuota, formatQuotaBar, formatStatusReport, formatTokens, formatTokensPerSecond, parseContextPressure, promptPressureTokens, providerShortCode, providerUsesLocalOAuth, shouldIdleAutoCompact, subagentRouteLabel, type ContextPressureSample, type ContextPressureView, type FooterActivityKind, type FooterStatsInput, type FooterStatusInput, type StatusReportInput, } from './footer.js';
32
- export { crossedQuotaThresholds, formatAccountBalance, formatFooterBalance, formatOpenCodeGoUsage, formatQuotaSnapshot, formatQuotaStatusLine, joinUrl, openCodeSourceFor, parseDeepSeekBalance, parseOpenAiCompatibleBalance, parseOpenCodeGoQuota, parseSuperGrokBilling, quotaAlertText, quotaRefreshEverySteps, quotaRefreshEveryTurns, remainingPercentFromUsed, tightestQuotaWindow, type AccountBalanceLine, type AccountBalanceSnapshot, type OpenCodeFlavor, type OpenCodeSource, type QuotaPeriod, type QuotaSnapshot, type QuotaWindow, } from './quota.js';
32
+ export { commandCodePeriodStart, commandCodeSourceFor, crossedQuotaThresholds, formatAccountBalance, formatFooterBalance, formatOpenCodeGoUsage, formatQuotaSnapshot, formatQuotaStatusLine, joinUrl, openCodeSourceFor, parseCommandCodeQuota, parseDeepSeekBalance, parseOpenAiCompatibleBalance, parseOpenCodeGoQuota, parseSuperGrokBilling, quotaAlertText, quotaRefreshEverySteps, quotaRefreshEveryTurns, remainingPercentFromUsed, tightestQuotaWindow, type AccountBalanceLine, type AccountBalanceSnapshot, type CommandCodeSource, type OpenCodeFlavor, type OpenCodeSource, type QuotaPeriod, type QuotaSnapshot, type QuotaWindow, } from './quota.js';
33
33
  export { commandAcceptsAttachments, forEachSessionEvent, isAssistantStreamEvent, isTokenDeltaChunk, listPersistenceHeaders, inspectPersistenceSession, sessionEventType, sessionEvents, settingsNamespace, streamChunkOf, streamFirstTokenTime, streamFrameAttemptId, streamFrameOwner, } from './dsh-compat.js';
34
34
  export { applyTurnEndToPlan, askSummary, cardCategoryOf, compactionHeaderText, formatCompactCommandError, isPromptInjectionMessage, matchTranscriptRows, parseFindQuery, parsePlanTodos, planCloseNudgeText, planDockNote, planIsLive, planTitleFromMarkdown, planTurnLeftOpen, promptInjectionSources, promptInjectionTitle, subagentHeaderText, todoProgressLabel, todoSummary, type CardCategory, } from './plan.js';
35
35
  export { buildToolHeader, canMergeToolCall, compactEditPath, compactToolBursts, compactToolGroups, countDiffAddDel, countDiffLines, countOutputLines, diffMetaDiffs, diffStatToken, friendlyJsonLines, parseExitStatus, presentToolCall, READ_TOOL_NAMES, renderToolDiff, toolBodyFitsWorkspace, toolBodyLines, toolStateColor, toolStateLabel, wrappedToolBodyLineCount, } from './tool-present.js';
@@ -63,6 +63,12 @@ export interface TuiConfig {
63
63
  presetId?: string;
64
64
  /** Display name of the active preset. */
65
65
  presetName?: string;
66
+ /**
67
+ * Trust of the root the active preset came from (`system`/`user`). A `user`
68
+ * preset keeps its own published name; a shipped one resolves through the
69
+ * locale dictionary.
70
+ */
71
+ presetTrust?: string;
66
72
  /** Notify the launcher of an explicit in-process selection change. */
67
73
  onSelectionChanged?: (selection: ModelSelection) => void;
68
74
  /**
@@ -88,6 +94,31 @@ export interface TuiController {
88
94
  }
89
95
  export type WorkspaceView = 'detailed' | 'compact';
90
96
  export declare function parseDisconnectPolicy(raw: string): DisconnectPolicyName | undefined;
97
+ /**
98
+ * The `reasoningEfforts` map `/setup` writes for a hand-declared
99
+ * OpenAI-compatible route.
100
+ *
101
+ * Such a route is absent from pi-ai's catalog, so `defaultReasoningEffort`
102
+ * resolves nothing before the profile is saved and the model would land as a
103
+ * bare `{ id }` entry. The Harness then reports that model as non-reasoning
104
+ * (`levels [off]`) and refuses every effort the TUI itself offers for an
105
+ * undeclared model. Declaring the offered vocabulary up front keeps the model
106
+ * dispatchable; no parameter is sent until an effort is selected.
107
+ * @returns the level-to-wire map, with `off` meaning "send no parameter".
108
+ */
109
+ export declare function handDeclaredReasoningEfforts(): Record<string, string | null>;
110
+ /**
111
+ * The `reasoningEfforts` map `/setup` persists for one model entry.
112
+ *
113
+ * A hand-declared OpenAI-compatible completions route always receives the
114
+ * offered vocabulary; a catalog-backed route keeps whatever live model info
115
+ * resolved, preserving the existing behavior of redeclaring only the chosen
116
+ * default.
117
+ * @param providerType - the wizard template that produced the route.
118
+ * @param defaultEffort - the level resolved from live model info, if any.
119
+ * @returns the level-to-wire map, or `undefined` to declare nothing.
120
+ */
121
+ export declare function onboardingReasoningEfforts(providerType: string, defaultEffort: string | undefined): Record<string, string | null> | undefined;
91
122
  /** Parse `/effort high` / `/subeffort default`. Empty or unknown → undefined. */
92
123
  export declare function parseEffortArg(raw: string): {
93
124
  kind: 'default';
@@ -113,6 +144,8 @@ export declare class SshTui {
113
144
  /** Web-aligned provider presets from the host's pi-ai catalog (undefined until loaded / when unreachable). */
114
145
  private catalogPresets;
115
146
  private catalogLoad;
147
+ /** Memoized id→window index over {@link catalogPresets}. */
148
+ private catalogWindows;
116
149
  private input;
117
150
  private cursor;
118
151
  private inputFolded;
@@ -557,6 +590,20 @@ export declare class SshTui {
557
590
  private runSubmodelCommand;
558
591
  /** /effort: pick or set the reasoning effort for the current model. */
559
592
  private runEffortCommand;
593
+ /**
594
+ * Persist one explicitly chosen level as a model's `reasoningEfforts`.
595
+ *
596
+ * `/effort` offers `low`…`max` for a model whose route declares nothing, but
597
+ * the selection alone cannot make the request legal: the Harness still
598
+ * resolves the model as non-reasoning and refuses it. Writing the chosen
599
+ * level — merged with whatever the entry already declares — keeps the
600
+ * selection and the declaration consistent. Only a route that explicitly
601
+ * speaks OpenAI Completions is amended: its dialect omits the parameter for
602
+ * `off` and was verified against a gateway, while another protocol may
603
+ * materialize an unset level as a value its endpoint refuses.
604
+ * @returns whether the entry now declares the level.
605
+ */
606
+ private declareReasoningEffort;
560
607
  private setReasoningEffort;
561
608
  /** /subeffort: pick the reasoning effort subagent children use. */
562
609
  private runSubeffortCommand;
@@ -568,12 +615,30 @@ export declare class SshTui {
568
615
  private runDisconnectCommand;
569
616
  /** /mode: pick an agent preset (standard / minimal / ptc / cordis / routing-suite / ...). */
570
617
  private runModeCommand;
618
+ /**
619
+ * `/mode fix`: write the roster row into this profile's user patch layer.
620
+ *
621
+ * The write only takes effect on the next launch — the loader composes the
622
+ * patch tree once at boot — so the report says so instead of pretending the
623
+ * running session gained a roster.
624
+ */
625
+ private repairRoster;
571
626
  /** Current provider route selected for the running agent. */
572
627
  private currentProvider;
573
628
  /** Resolve one credential reference without exposing its value. */
574
629
  private resolveCredential;
575
630
  /** Query the OpenCode Go quota endpoint. */
576
631
  private fetchOpenCodeGoUsage;
632
+ /**
633
+ * Command Code's quota: the rolling 5h/weekly windows and credit pool off
634
+ * `/alpha/billing/credits`, enriched by the subscription period and spend.
635
+ * Only the credits read is required; the two enrichment reads are
636
+ * best-effort so one failing endpoint cannot hide the windows.
637
+ * @param source - the recognized Command Code route.
638
+ */
639
+ private fetchCommandCodeQuota;
640
+ /** Best-effort JSON read for quota enrichment; a failure leaves the field out. */
641
+ private tryFetchJson;
577
642
  /** Explain Zen metered billing instead of pretending it has a quota. */
578
643
  private zenUsageText;
579
644
  /** /usage and /balance: remaining quota or prepaid balance for the current provider. */
@@ -600,6 +665,16 @@ export declare class SshTui {
600
665
  */
601
666
  private mergedProviderEntries;
602
667
  private moveProviderCursor;
668
+ /** The installed catalog's id→window index, built once per catalog load. */
669
+ private catalogWindowIndexSource;
670
+ /**
671
+ * Size the wizard's picked models from the installed catalog, filling in the
672
+ * capacities the endpoint's listing did not disclose and recording them on
673
+ * the wizard state so the saved entries carry them.
674
+ * @param state - the wizard state whose `models` are being sized.
675
+ * @returns the windows discovered for this pick, by model id.
676
+ */
677
+ private sizeOnboardingModels;
603
678
  private handleOnboardingChar;
604
679
  private advanceOnboarding;
605
680
  /** Fetch the endpoint's model list into the onboarding wizard's models step. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-ssh-tui",
3
- "version": "0.6.2",
3
+ "version": "0.6.4",
4
4
  "description": "SSH-friendly interactive terminal TUI plugin for DeepSeek Harness",
5
5
  "keywords": [
6
6
  "deepseek-harness",