dsh-ssh-tui 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/README.en.md +63 -38
  2. package/README.md +44 -20
  3. package/cordis.patch.yml +6 -0
  4. package/docs/screenshots/compare.png +0 -0
  5. package/docs/screenshots/headless.png +0 -0
  6. package/docs/screenshots/slow-link.gif +0 -0
  7. package/docs/screenshots/workspace.png +0 -0
  8. package/lib/approval-reviewer.js +62 -0
  9. package/lib/approval-reviewer.js.map +1 -0
  10. package/lib/auto-approval.js +124 -0
  11. package/lib/auto-approval.js.map +1 -0
  12. package/lib/display-sock.js +583 -0
  13. package/lib/display-sock.js.map +1 -0
  14. package/lib/dsh-compat.js +45 -0
  15. package/lib/dsh-compat.js.map +1 -0
  16. package/lib/i18n/en.js +108 -14
  17. package/lib/i18n/en.js.map +1 -1
  18. package/lib/i18n/index.js +2 -1
  19. package/lib/i18n/index.js.map +1 -1
  20. package/lib/i18n/zh.js +108 -14
  21. package/lib/i18n/zh.js.map +1 -1
  22. package/lib/index.js +110 -6
  23. package/lib/index.js.map +1 -1
  24. package/lib/picker.js +14 -1
  25. package/lib/picker.js.map +1 -1
  26. package/lib/provider-catalog.js +169 -0
  27. package/lib/provider-catalog.js.map +1 -0
  28. package/lib/route-memory.js +1 -1
  29. package/lib/route-memory.js.map +1 -1
  30. package/lib/session-list.js +116 -11
  31. package/lib/session-list.js.map +1 -1
  32. package/lib/session-lock.js +182 -6
  33. package/lib/session-lock.js.map +1 -1
  34. package/lib/startup.js +2 -0
  35. package/lib/startup.js.map +1 -1
  36. package/lib/subagent-model.js +1 -1
  37. package/lib/subagent-model.js.map +1 -1
  38. package/lib/tui.js +1776 -370
  39. package/lib/tui.js.map +1 -1
  40. package/lib/types/approval-reviewer.d.ts +20 -0
  41. package/lib/types/auto-approval.d.ts +42 -0
  42. package/lib/types/display-sock.d.ts +74 -0
  43. package/lib/types/dsh-compat.d.ts +30 -0
  44. package/lib/types/i18n/index.d.ts +2 -0
  45. package/lib/types/picker.d.ts +4 -0
  46. package/lib/types/provider-catalog.d.ts +35 -0
  47. package/lib/types/session-list.d.ts +8 -1
  48. package/lib/types/session-lock.d.ts +51 -0
  49. package/lib/types/tui.d.ts +208 -24
  50. package/package.json +46 -30
@@ -17,6 +17,7 @@ import { SessionId, type SessionEvent } from '@deepseek-ai/dsh-session';
17
17
  import type { SubagentRunEndInfo, SubagentRunInfo } from '@deepseek-ai/dsh-subagent';
18
18
  import { type SubagentSelectionRef } from './subagent-model.js';
19
19
  import { type AskUserQuestionAnswer, type AskUserQuestionRequest } from '@deepseek-ai/dsh-user-questions';
20
+ export type DisconnectPolicyName = 'pause' | 'continue';
20
21
  /** Presentation configuration for the terminal channel. */
21
22
  export interface TuiConfig {
22
23
  /** Exact shared agent/session identity driven by this terminal. */
@@ -59,6 +60,14 @@ export interface TuiConfig {
59
60
  * link. Defaults from `DSH_TUI_PAINT_MS` (160).
60
61
  */
61
62
  paintIntervalMs?: number;
63
+ /** Called after hangup when the Host is kept (busy) so the launcher can update the lock. */
64
+ onHangup?: () => void | Promise<void>;
65
+ /** Called when a Display relay attaches after hangup. */
66
+ onReattach?: () => void | Promise<void>;
67
+ /** Host process: no local TTY; paint only through the display socket. */
68
+ headlessDisplay?: boolean;
69
+ /** Hangup policy while busy: pause cancels the turn; continue lets it finish detached. Idle hangup always exits. */
70
+ disconnectPolicy?: DisconnectPolicyName;
62
71
  }
63
72
  type SubagentLogKind = 'user' | 'assistant' | 'tool' | 'result' | 'turn' | 'approval' | 'team' | 'system';
64
73
  /** One child-session event folded into a parent-side subagent card. */
@@ -101,6 +110,14 @@ type Row = {
101
110
  exitCode?: number;
102
111
  signal?: string;
103
112
  expanded: boolean;
113
+ /** Consecutive same-path reads/edits folded into this card. */
114
+ repeats?: number;
115
+ /** Sum of output characters across folded reads. */
116
+ totalChars?: number;
117
+ /** Sum of output lines across folded reads. */
118
+ totalLines?: number;
119
+ /** Flip-card animation until this timestamp (ms since epoch). */
120
+ flipUntil?: number;
104
121
  } | {
105
122
  kind: 'subagent';
106
123
  sessionId: string;
@@ -200,6 +217,8 @@ interface ToolDiffHunk {
200
217
  /** Lifecycle handle for a mounted interactive terminal channel. */
201
218
  export interface TuiController {
202
219
  dispose(): Promise<void>;
220
+ handleHangup(): Promise<void>;
221
+ disconnectPolicy(): DisconnectPolicyName;
203
222
  }
204
223
  export type PaintLinkKind = 'local' | 'ssh';
205
224
  /** Compact token count, matching the web stats line (517 / 12.2K / 1.2M). */
@@ -218,6 +237,21 @@ export declare function resolvePaintIntervalMs(configured?: number, env?: NodeJS
218
237
  }): number;
219
238
  /** True when this process is attached to an SSH session (jump host / proxy). */
220
239
  export declare function detectSshSession(env?: NodeJS.ProcessEnv): boolean;
240
+ /** Node errno on a write/close that means the TTY is gone (SSH drop, HUP). */
241
+ export declare function isHangupErrno(error: unknown): boolean;
242
+ /**
243
+ * Replace launcher SIGTERM/SIGINT/SIGHUP handlers with `handler`. SSH drop
244
+ * otherwise lets `dsh` dispose the whole tree before this plugin can detach.
245
+ */
246
+ export declare function captureHangupSignals(handler: () => void): void;
247
+ export declare function releaseHangupSignals(handler: () => void): void;
248
+ /** After detach, extra HUP/TERM from sshd must not kill the leftover Host. */
249
+ export declare function ignoreFurtherHangupSignals(): void;
250
+ /**
251
+ * Wait until `isIdle` is true or `timeoutMs` elapses. Used after cancel so a
252
+ * hangup can flush a settled session log instead of tearing a live write.
253
+ */
254
+ export declare function waitUntilIdleOrTimeout(isIdle: () => boolean, timeoutMs: number, now?: () => number, wait?: (ms: number) => Promise<void>): Promise<'idle' | 'timeout'>;
221
255
  /** Map a CSI-6n round-trip to a paint cadence. Unknown RTT uses the SSH default. */
222
256
  export declare function paintIntervalForRtt(rttMs: number | undefined): number;
223
257
  export declare function paintLinkLabel(kind: PaintLinkKind, intervalMs: number, probed: boolean): string;
@@ -272,6 +306,7 @@ export interface FooterStatusInput {
272
306
  subDiffers: boolean;
273
307
  quotaCode?: string;
274
308
  quotaPercent?: number;
309
+ balanceText?: string;
275
310
  search?: {
276
311
  index: number;
277
312
  total: number;
@@ -321,6 +356,7 @@ export interface StatusReportInput {
321
356
  activeSubagents: number;
322
357
  plan: 'off' | 'pending' | 'on';
323
358
  paint: string;
359
+ disconnect?: DisconnectPolicyName;
324
360
  waitingQuestions: number;
325
361
  quota?: QuotaSnapshot;
326
362
  parentModel?: string;
@@ -361,22 +397,27 @@ export declare function fmtElapsedCompact(elapsedSecs: number): string;
361
397
  */
362
398
  export declare function shimmerText(text: string, nowMs: number, color: boolean): string;
363
399
  /**
364
- * Codex-style short status from reasoning: first `**bold**` / heading, else
365
- * the first short clause. Keeps the wait card in sync with what the model is
366
- * doing instead of repeating the user's prompt.
400
+ * Codex `extract_first_bold`: the first **closed** `**bold**` in the thinking
401
+ * stream, else the first markdown heading. An unclosed `**` means the title
402
+ * has not arrived yet, so return undefined and keep the default header —
403
+ * never fall back to hard-truncated reasoning, reply, or prompt text.
367
404
  */
368
- export declare function waitSummaryFromReasoning(text: string, maxChars?: number): string | undefined;
405
+ export declare function waitSummaryFromReasoning(text: string): string | undefined;
369
406
  /** Wait-card header + optional detail. Header tracks model work when known. */
370
407
  export declare function waitCardCopy(input: {
371
408
  toolTitle?: string;
372
409
  toolSummary?: string;
373
410
  reasoning?: string;
374
- reply?: string;
375
- prompt?: string;
376
411
  }): {
377
412
  header: string;
378
413
  detail?: string;
379
414
  };
415
+ /**
416
+ * Codex `wrapped_details_lines`: word-wrap the wait-card detail under the
417
+ * ` └ ` prefix, continue wrapped rows at the prefix width, cap at 3 rows and
418
+ * end the last one with an ellipsis when the text does not fit.
419
+ */
420
+ export declare function wrapWaitDetails(detail: string, width: number, maxLines?: number): string[];
380
421
  export declare function displayWidth(text: string): number;
381
422
  /** Pad or clip one already-sanitized line so it occupies exactly `width` cells. */
382
423
  export declare function padToWidth(text: string, width: number): string;
@@ -460,12 +501,9 @@ export declare function remainingPercentFromUsed(usedPercent: number): number;
460
501
  export declare function crossedQuotaThresholds(previousRemaining: number | undefined, remaining: number): number[];
461
502
  export declare function quotaAlertText(snapshot: QuotaSnapshot, window: QuotaWindow): string;
462
503
  /**
463
- * How often to re-fetch quota, based on the tightest window.
464
- * Counted in model steps (not conversation turns): a turn with several
465
- * tool/LLM steps should refresh more often because it spends more quota.
466
- * Hourly/5h: every 10 steps, every 4 when near a threshold.
467
- * Weekly: every 50 steps, every 10 when near.
468
- * Monthly: every 80 steps, every 20 when near.
504
+ * How often to re-fetch quota or prepaid balance, counted in model steps.
505
+ * Default is every 10 steps. Near a remaining-percent threshold, hourly
506
+ * windows refresh every 4 steps.
469
507
  */
470
508
  export declare function quotaRefreshEverySteps(window: QuotaWindow | undefined): number;
471
509
  /** @deprecated Same cadence as {@link quotaRefreshEverySteps}; the name predates step accounting. */
@@ -495,6 +533,8 @@ export declare function joinUrl(base: string, path: string): string;
495
533
  export declare function parseDeepSeekBalance(payload: unknown, provider?: string): AccountBalanceSnapshot;
496
534
  /** Best-effort parse of OpenAI-compatible credit/balance JSON. */
497
535
  export declare function parseOpenAiCompatibleBalance(payload: unknown, provider: string, path: string): AccountBalanceSnapshot | undefined;
536
+ /** Compact footer chip: `余额 86.42 CNY`. Prefers remaining/available lines. */
537
+ export declare function formatFooterBalance(snapshot: AccountBalanceSnapshot): string | undefined;
498
538
  export declare function formatAccountBalance(snapshot: AccountBalanceSnapshot): string;
499
539
  /** Whether `text` could still grow into a recognized escape sequence. */
500
540
  export declare function isEscapePrefix(text: string): boolean;
@@ -510,8 +550,42 @@ export declare function parseCursorPositionReply(text: string): {
510
550
  */
511
551
  export declare function probeTerminalRttMs(stdin?: NodeJS.ReadStream, stdout?: NodeJS.WriteStream, timeoutMs?: number): Promise<number | undefined>;
512
552
  export type WorkspaceView = 'detailed' | 'compact';
553
+ /** Sliding window of `windowSize` items that keeps `cursor` visible. */
554
+ export declare function pickerWindowStart(cursor: number, total: number, windowSize?: number): number;
555
+ export declare function parseDisconnectPolicy(raw: string): DisconnectPolicyName | undefined;
556
+ /** Parse `/effort high` / `/subeffort default`. Empty or unknown → undefined. */
557
+ export declare function parseEffortArg(raw: string): {
558
+ kind: 'default';
559
+ } | {
560
+ kind: 'id';
561
+ id: string;
562
+ } | undefined;
513
563
  export declare function parseWorkspaceView(raw: string): WorkspaceView | undefined;
514
564
  export declare function countDiffLines(hunks: readonly ToolDiffHunk[] | undefined): number;
565
+ /** Added / removed line counts for a diff (`oldText: null` means a new file). */
566
+ export declare function countDiffAddDel(hunks: readonly ToolDiffHunk[] | undefined): {
567
+ add: number;
568
+ del: number;
569
+ };
570
+ /**
571
+ * Git diffstat token, deletions first like `-13 +24`. Zero parts drop out
572
+ * (a new file shows only `+24`); empty when the diff has no counted lines.
573
+ */
574
+ export declare function diffStatToken(add: number, del: number): string;
575
+ export declare function toolTargetPath(name: string, args: string, fallback?: string): string;
576
+ export declare function countOutputLines(text: string): number;
577
+ /**
578
+ * Consecutive same-path reads (or edits) collapse onto one card.
579
+ * A → B → C → A becomes four cards; A ×5 stays one card with repeats=5.
580
+ */
581
+ export declare function canMergeToolCall(previous: Extract<Row, {
582
+ kind: 'tool';
583
+ }> | undefined, next: {
584
+ name: string;
585
+ args: string;
586
+ }): previous is Extract<Row, {
587
+ kind: 'tool';
588
+ }>;
515
589
  export declare function compactToolGroups(tools: readonly Extract<Row, {
516
590
  kind: 'tool';
517
591
  }>[]): {
@@ -521,9 +595,18 @@ export declare function compactToolGroups(tools: readonly Extract<Row, {
521
595
  calls: Extract<Row, {
522
596
  kind: 'tool';
523
597
  }>[];
524
- editLines: number;
525
598
  failedCalls: number;
526
599
  };
600
+ /**
601
+ * Split compact-view tools into the bursts that belong with each assistant
602
+ * reply: tools after reply N sit with that reply, until the next reply.
603
+ */
604
+ export declare function compactToolBursts(rows: readonly Row[]): Array<{
605
+ after: Extract<Row, {
606
+ kind: 'assistant';
607
+ }> | undefined;
608
+ groups: ReturnType<typeof compactToolGroups>;
609
+ }>;
527
610
  /** True while a plan still belongs in the dock (latest incomplete work). */
528
611
  export declare function planIsLive(plan: {
529
612
  active: boolean;
@@ -607,7 +690,7 @@ interface DiffDisplayLine {
607
690
  /** Running / ok / error → ANSI color for the status dot and status word only. */
608
691
  export declare function toolStateColor(status: 'running' | 'ok' | 'error' | undefined): '33' | '32' | '31';
609
692
  export declare function toolStateLabel(status: 'running' | 'ok' | 'error' | undefined): string;
610
- /** Header + SGR spans: default title, dim operand, colored and [ok]/[error]. */
693
+ /** Header + SGR spans: default title, dim operand, colored ●. `[ok]` is omitted — the green dot is enough. */
611
694
  export declare function buildToolHeader(input: {
612
695
  focused: boolean;
613
696
  expanded: boolean;
@@ -618,6 +701,11 @@ export declare function buildToolHeader(input: {
618
701
  signal?: string;
619
702
  exitCode?: number;
620
703
  spinner?: string;
704
+ flipping?: boolean;
705
+ diffStat?: {
706
+ add: number;
707
+ del: number;
708
+ };
621
709
  }): {
622
710
  plain: string;
623
711
  segments: TextSegment[];
@@ -663,12 +751,25 @@ export declare class SshTui {
663
751
  private readonly agent;
664
752
  private readonly rows;
665
753
  private streaming;
754
+ private autoApprovalMode;
755
+ private autoAllowedCount;
756
+ private autoDeniedCount;
757
+ private aiReviewCount;
758
+ /** Host knobs folded from the session log: auto mode needs approval=ask to see requests. */
759
+ private hostSandboxMode;
760
+ private hostApprovalPolicy;
761
+ private approvalMismatchWarned;
762
+ /** Web-aligned provider presets from the host's pi-ai catalog (undefined until loaded / when unreachable). */
763
+ private catalogPresets;
764
+ private catalogLoad;
666
765
  private input;
667
766
  private cursor;
668
767
  private inputFolded;
669
768
  private inPaste;
670
769
  private history;
671
770
  private historyIndex;
771
+ /** Live input parked while browsing history with ↑. Restored by ↓ past the newest item. */
772
+ private historyDraft;
672
773
  private status;
673
774
  private dialog;
674
775
  private readonly dialogQueue;
@@ -676,6 +777,17 @@ export declare class SshTui {
676
777
  private dirty;
677
778
  private disposed;
678
779
  private exiting;
780
+ private hangingUp;
781
+ private readonly onDirectResize;
782
+ private readonly headlessDisplay;
783
+ private disconnectPolicy;
784
+ private detachedIdleTimer;
785
+ private displayDetached;
786
+ private displayHost;
787
+ private relayColumns;
788
+ private relayRows;
789
+ private readonly onHangup;
790
+ private readonly onReattach;
679
791
  private renderTimer;
680
792
  private readonly decoder;
681
793
  private readonly color;
@@ -706,6 +818,7 @@ export declare class SshTui {
706
818
  private stalledWarningShown;
707
819
  private lastPaintAt;
708
820
  private commandAbort;
821
+ private readonly seenCommandDoneIds;
709
822
  private activeSubagents;
710
823
  private subagentSessions;
711
824
  private openToolCalls;
@@ -721,7 +834,6 @@ export declare class SshTui {
721
834
  private escapeTimer;
722
835
  private thinkingStartedAt;
723
836
  private waitStartedAt;
724
- private waitPrompt;
725
837
  private completionSignaled;
726
838
  private replaying;
727
839
  private completedAt;
@@ -743,6 +855,7 @@ export declare class SshTui {
743
855
  private sessionTitle;
744
856
  private llmRetry;
745
857
  private quotaSnapshot;
858
+ private balanceSnapshot;
746
859
  private quotaAlerted;
747
860
  private quotaStepsSinceRefresh;
748
861
  private quotaRefreshInFlight;
@@ -754,14 +867,22 @@ export declare class SshTui {
754
867
  constructor(ctx: Context, agent: Agent, config: TuiConfig);
755
868
  /** Enter raw mode, switch to the alternate screen, and start listening. */
756
869
  start(): void;
870
+ private bindAgentEvents;
871
+ private bootBackgroundTasks;
757
872
  private notifyPluginUpdate;
758
873
  private readSkippedUpdate;
759
874
  private persistSkippedUpdate;
760
875
  private mergeUiSettings;
761
876
  private readWorkspaceView;
877
+ private readDisconnectPolicy;
878
+ private readAutoApprovalMode;
879
+ private detachedIdleMs;
880
+ private clearDetachedIdleTimer;
881
+ private armDetachedIdleTimer;
762
882
  private isCompactView;
763
883
  /** Test helper: switch the workspace view without going through /view. */
764
884
  setWorkspaceView(view: WorkspaceView): void;
885
+ private paintCompactBurst;
765
886
  private paintCompactSummary;
766
887
  private startRenderTimer;
767
888
  private calibratePaintInterval;
@@ -772,22 +893,59 @@ export declare class SshTui {
772
893
  /** Run the provider/API-key onboarding wizard. Resolves true when saved. */
773
894
  private runOnboarding;
774
895
  private cancelOnboarding;
775
- /** Restore the terminal, flush the session, and request process exit. */
896
+ /**
897
+ * Drop the TTY without disposing the agent. Safe to call when the fd is
898
+ * already dead: DECSET restore is best-effort and never throws.
899
+ */
900
+ detachDisplay(): void;
901
+ /** Restore the terminal and drop event wiring. Does not flush or exit. */
776
902
  dispose(): Promise<void>;
777
903
  /** Human-facing exit with goodbye and flush; called from key handling. */
778
904
  requestExit(code: number): Promise<void>;
905
+ /**
906
+ * True while the session is doing work the user would lose by killing the
907
+ * Host: a running turn (thinking / reply / tools), live subagents, in-flight
908
+ * compaction, or an LLM retry. Idle (including a waiting approval dialog
909
+ * after the turn has already settled) is not busy — hangup then exits
910
+ * instead of leaving a leftover process.
911
+ */
912
+ private isBusyForHangupKeepalive;
913
+ /**
914
+ * SSH / TTY hangup: drop the local display, flush, and either keep the Host
915
+ * (busy: thinking / reply / tools / subagents) or exit (idle).
916
+ * When keeping the Host, `pause` cancels the turn; `continue` lets it finish.
917
+ * Ctrl+C is not a hangup.
918
+ */
919
+ handleHangup(): Promise<void>;
920
+ private readonly handleHangupSignal;
921
+ private readonly handleHangupStream;
922
+ private readonly handleIoError;
923
+ private writeGoodbye;
924
+ private flushSession;
925
+ private exitProcess;
779
926
  /** Capture one painted frame. Used by README screenshot fixtures. */
780
927
  captureFrame(columns?: number, rows?: number): string[];
928
+ private screenColumns;
929
+ private screenRows;
781
930
  private write;
931
+ private ensureDisplayHost;
932
+ /** Re-open DECSET and start painting to an attached Display relay. */
933
+ attachRelayDisplay(): void;
934
+ currentDisconnectPolicy(): DisconnectPolicyName;
935
+ applyProbedRtt(rttMs: number | undefined): void;
782
936
  private markDirty;
937
+ private toolCardSummary;
938
+ private mergeIntoToolCard;
783
939
  /** Append one transcript row, bounding memory on long sessions. */
784
940
  private pushRow;
785
941
  /** The transcript rows that support per-row expand/collapse. */
786
942
  private collapsibleRows;
787
943
  private spinnerFrame;
788
944
  /**
789
- * Codex wait card: shown while the turn is running. Thinking/reply streams
790
- * feed the shimmer header; a live tool becomes the detail line.
945
+ * Codex wait card: shown while the turn is running. The live thinking
946
+ * stream feeds the shimmer header; a live tool becomes the detail rows.
947
+ * While the reply itself is streaming, the transcript paints those tokens
948
+ * and the card yields (Codex hides the status row once output commits).
791
949
  */
792
950
  private waitCardVisible;
793
951
  private beginWait;
@@ -865,6 +1023,7 @@ export declare class SshTui {
865
1023
  private findCompactionRow;
866
1024
  private handleCompactionEvent;
867
1025
  private handleCommandRun;
1026
+ private formatCommandText;
868
1027
  private handleCommandDone;
869
1028
  private handleGoalChange;
870
1029
  private pushPromptInjection;
@@ -873,6 +1032,21 @@ export declare class SshTui {
873
1032
  readonly handleSubagentSessionEvent: (sessionId: SessionId, event: SessionEvent) => void;
874
1033
  readonly handleSubagentStart: (info: SubagentRunInfo) => void;
875
1034
  readonly handleSubagentEnd: (info: SubagentRunEndInfo) => void;
1035
+ private hasLiveDisplay;
1036
+ private waitForLiveDisplay;
1037
+ /**
1038
+ * Auto mode rides the approval waterfall: when the host approval policy is
1039
+ * `never` no request is ever produced, so the classifier silently does
1040
+ * nothing. Say so instead of letting the user believe a guard is active.
1041
+ */
1042
+ private warnApprovalMismatch;
1043
+ /**
1044
+ * AI review for rule-table `ask` outcomes: one shot at the subagent
1045
+ * model route with a compact, injection-fenced context. Returns
1046
+ * 'allow' | 'deny', or undefined when the reviewer is unavailable or its
1047
+ * output was unusable (caller falls back to prompt/reject).
1048
+ */
1049
+ private reviewUnknownWithModel;
876
1050
  private readonly handleApproval;
877
1051
  readonly handleUserQuestions: (request: AskUserQuestionRequest) => Promise<AskUserQuestionAnswer>;
878
1052
  /** Queue one dialog behind an already-open one instead of overwriting it. */
@@ -900,13 +1074,9 @@ export declare class SshTui {
900
1074
  private discoverEndpointModels;
901
1075
  /** Add one endpoint-listed model to the stored provider profile when needed. */
902
1076
  private ensureProviderModelConfigured;
903
- /** How many endpoint-listed models fit on one picker page alongside navigation. */
904
- private readonly MODEL_PAGE_SIZE;
905
- private readonly MODEL_PAGE_PREV;
906
- private readonly MODEL_PAGE_NEXT;
907
1077
  /**
908
- * One pick across a possibly long model list, paging through the digit
909
- * dialog so an endpoint with dozens of models stays selectable.
1078
+ * One pick across a possibly long model list. The question dialog keeps a
1079
+ * 12-row sliding window so dozens of models stay selectable with ↑/↓.
910
1080
  */
911
1081
  private pickModelOption;
912
1082
  /** Live adapter routes the TUI can switch to, plus the current selection. */
@@ -945,12 +1115,17 @@ export declare class SshTui {
945
1115
  private subagentModelOptions;
946
1116
  /** /submodel: pick (or set) the model subagent children use. */
947
1117
  private runSubmodelCommand;
1118
+ /** /effort: pick or set the reasoning effort for the current model. */
1119
+ private runEffortCommand;
1120
+ private setReasoningEffort;
948
1121
  /** /subeffort: pick the reasoning effort subagent children use. */
949
1122
  private runSubeffortCommand;
950
1123
  /** /language or /lang: persist zh/en and repaint chrome immediately. */
951
1124
  private runLanguageCommand;
952
1125
  /** /view: detailed (see the work) vs compact (Codex-like summary). */
953
1126
  private runViewCommand;
1127
+ /** /disconnect: pause (default) or continue the turn after SSH drop. */
1128
+ private runDisconnectCommand;
954
1129
  /** /mode: pick an agent preset (standard / minimal / ptc / cordis / routing-suite / ...). */
955
1130
  private runModeCommand;
956
1131
  /** /resume: switch to a past session, or open a picker when no id is given. */
@@ -978,7 +1153,14 @@ export declare class SshTui {
978
1153
  private handlePasteText;
979
1154
  private handlePlainText;
980
1155
  private handleChar;
1156
+ private moveQuestionCursor;
981
1157
  private handleDialogChar;
1158
+ /**
1159
+ * The wizard's first-step list: the pinned templates plus the web-catalog
1160
+ * presets (deduped), filtered by the search box text.
1161
+ */
1162
+ private mergedProviderEntries;
1163
+ private moveProviderCursor;
982
1164
  private handleOnboardingChar;
983
1165
  private advanceOnboarding;
984
1166
  /** Fetch the endpoint's model list into the onboarding wizard's models step. */
@@ -1008,6 +1190,8 @@ export declare class SshTui {
1008
1190
  private moveCursor;
1009
1191
  private historyBack;
1010
1192
  private historyForward;
1193
+ /** Typing while browsing history detaches from the saved item. */
1194
+ private leaveHistoryBrowse;
1011
1195
  }
1012
1196
  /**
1013
1197
  * Mount the terminal channel once the configured agent exists.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-ssh-tui",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "SSH-friendly interactive terminal TUI plugin for DeepSeek Harness",
5
5
  "keywords": [
6
6
  "deepseek-harness",
@@ -80,20 +80,20 @@
80
80
  },
81
81
  "peerDependencies": {
82
82
  "@deepseek-ai/cordis": "^4.0.1",
83
- "@deepseek-ai/dsh-agent": ">=0.1.1-rc.2",
84
- "@deepseek-ai/dsh-agent-default-model": ">=0.1.1-rc.2",
85
- "@deepseek-ai/dsh-agent-loop": ">=0.1.1-rc.2",
86
- "@deepseek-ai/dsh-agent-presets": ">=0.1.1-rc.2",
87
- "@deepseek-ai/dsh-cmdline": ">=0.1.1-rc.2",
88
- "@deepseek-ai/dsh-commands": ">=0.1.1-rc.2",
89
- "@deepseek-ai/dsh-credentials": ">=0.1.1-rc.2",
90
- "@deepseek-ai/dsh-llm": ">=0.1.1-rc.2",
91
- "@deepseek-ai/dsh-session": ">=0.1.1-rc.2",
92
- "@deepseek-ai/dsh-settings": ">=0.1.1-rc.2",
93
- "@deepseek-ai/dsh-subagent": ">=0.1.1-rc.2",
94
- "@deepseek-ai/dsh-user-approval": ">=0.1.1-rc.2",
95
- "@deepseek-ai/dsh-user-questions": ">=0.1.1-rc.2",
96
- "@deepseek-ai/dsh-session-persistence": ">=0.1.1-rc.2"
83
+ "@deepseek-ai/dsh-agent": ">=0.1.1-rc.2 || >=0.1.2-a",
84
+ "@deepseek-ai/dsh-agent-default-model": ">=0.1.1-rc.2 || >=0.1.2-a",
85
+ "@deepseek-ai/dsh-agent-loop": ">=0.1.1-rc.2 || >=0.1.2-a",
86
+ "@deepseek-ai/dsh-agent-presets": ">=0.1.1-rc.2 || >=0.1.2-a",
87
+ "@deepseek-ai/dsh-cmdline": ">=0.1.1-rc.2 || >=0.1.2-a",
88
+ "@deepseek-ai/dsh-commands": ">=0.1.1-rc.2 || >=0.1.2-a",
89
+ "@deepseek-ai/dsh-credentials": ">=0.1.1-rc.2 || >=0.1.2-a",
90
+ "@deepseek-ai/dsh-llm": ">=0.1.1-rc.2 || >=0.1.2-a",
91
+ "@deepseek-ai/dsh-session": ">=0.1.1-rc.2 || >=0.1.2-a",
92
+ "@deepseek-ai/dsh-settings": ">=0.1.1-rc.2 || >=0.1.2-a",
93
+ "@deepseek-ai/dsh-subagent": ">=0.1.1-rc.2 || >=0.1.2-a",
94
+ "@deepseek-ai/dsh-user-approval": ">=0.1.1-rc.2 || >=0.1.2-a",
95
+ "@deepseek-ai/dsh-user-questions": ">=0.1.1-rc.2 || >=0.1.2-a",
96
+ "@deepseek-ai/dsh-session-persistence": ">=0.1.1-rc.2 || >=0.1.2-a"
97
97
  },
98
98
  "peerDependenciesMeta": {
99
99
  "@deepseek-ai/dsh-user-approval": {
@@ -111,21 +111,37 @@
111
111
  },
112
112
  "devDependencies": {
113
113
  "@deepseek-ai/cordis": "^4.0.1",
114
- "@deepseek-ai/dsh-agent": ">=0.1.1-rc.2",
115
- "@deepseek-ai/dsh-agent-default-model": ">=0.1.1-rc.2",
116
- "@deepseek-ai/dsh-agent-loop": ">=0.1.1-rc.2",
117
- "@deepseek-ai/dsh-agent-presets": ">=0.1.1-rc.2",
118
- "@deepseek-ai/dsh-cmdline": ">=0.1.1-rc.2",
119
- "@deepseek-ai/dsh-commands": ">=0.1.1-rc.2",
120
- "@deepseek-ai/dsh-credentials": ">=0.1.1-rc.2",
121
- "@deepseek-ai/dsh-llm": ">=0.1.1-rc.2",
122
- "@deepseek-ai/dsh-session": ">=0.1.1-rc.2",
123
- "@deepseek-ai/dsh-settings": ">=0.1.1-rc.2",
124
- "@deepseek-ai/dsh-subagent": ">=0.1.1-rc.2",
125
- "@deepseek-ai/dsh-user-approval": ">=0.1.1-rc.2",
126
- "@deepseek-ai/dsh-user-questions": ">=0.1.1-rc.2",
114
+ "@deepseek-ai/cordis-plugin-loader": "^1.0.3",
115
+ "@deepseek-ai/dsh-agent": "0.1.2-rc.1",
116
+ "@deepseek-ai/dsh-agent-default-model": "0.1.2-rc.1",
117
+ "@deepseek-ai/dsh-agent-loop": "0.1.2-rc.1",
118
+ "@deepseek-ai/dsh-agent-presets": "0.1.2-rc.1",
119
+ "@deepseek-ai/dsh-atomic-write": "0.1.2-rc.1",
120
+ "@deepseek-ai/dsh-attachment": "0.1.2-rc.1",
121
+ "@deepseek-ai/dsh-brand": "0.1.2-rc.1",
122
+ "@deepseek-ai/dsh-cmdline": "0.1.2-rc.1",
123
+ "@deepseek-ai/dsh-commands": "0.1.2-rc.1",
124
+ "@deepseek-ai/dsh-credentials": "0.1.2-rc.1",
125
+ "@deepseek-ai/dsh-home-paths": "0.1.2-rc.1",
126
+ "@deepseek-ai/dsh-invariants": "0.1.2-rc.1",
127
+ "@deepseek-ai/dsh-jobs": "0.1.2-rc.1",
128
+ "@deepseek-ai/dsh-llm": "0.1.2-rc.1",
129
+ "@deepseek-ai/dsh-sandbox": "0.1.2-rc.1",
130
+ "@deepseek-ai/dsh-sandbox-policy": "0.1.2-rc.1",
131
+ "@deepseek-ai/dsh-scope": "0.1.2-rc.1",
132
+ "@deepseek-ai/dsh-session": "0.1.2-rc.1",
133
+ "@deepseek-ai/dsh-session-persistence": "0.1.2-rc.1",
134
+ "@deepseek-ai/dsh-session-projection": "0.1.2-rc.1",
135
+ "@deepseek-ai/dsh-session-projection-cache": "0.1.2-rc.1",
136
+ "@deepseek-ai/dsh-settings": "0.1.2-rc.1",
137
+ "@deepseek-ai/dsh-subagent": "0.1.2-rc.1",
138
+ "@deepseek-ai/dsh-system-prompt": "0.1.2-rc.1",
139
+ "@deepseek-ai/dsh-timeout": "0.1.2-rc.1",
140
+ "@deepseek-ai/dsh-tools": "0.1.2-rc.1",
141
+ "@deepseek-ai/dsh-typert-protocol": "0.1.2-rc.1",
142
+ "@deepseek-ai/dsh-user-approval": "0.1.2-rc.1",
143
+ "@deepseek-ai/dsh-user-questions": "0.1.2-rc.1",
127
144
  "@types/node": "^24.0.0",
128
- "typescript": "^5.9.0",
129
- "@deepseek-ai/dsh-session-persistence": ">=0.1.1-rc.2"
145
+ "typescript": "^5.9.0"
130
146
  }
131
147
  }