dsh-ssh-tui 0.5.3 → 0.5.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.en.md +27 -6
- package/README.md +16 -8
- package/lib/approval-reviewer.js +56 -15
- package/lib/approval-reviewer.js.map +1 -1
- package/lib/auto-approval.js +251 -35
- package/lib/auto-approval.js.map +1 -1
- package/lib/footer.js +337 -0
- package/lib/footer.js.map +1 -0
- package/lib/i18n/en.js +48 -2
- package/lib/i18n/en.js.map +1 -1
- package/lib/i18n/zh.js +48 -2
- package/lib/i18n/zh.js.map +1 -1
- package/lib/json-args.js +30 -0
- package/lib/json-args.js.map +1 -0
- package/lib/paint.js +262 -0
- package/lib/paint.js.map +1 -0
- package/lib/picker.js +407 -56
- package/lib/picker.js.map +1 -1
- package/lib/plan.js +369 -0
- package/lib/plan.js.map +1 -0
- package/lib/quota.js +408 -0
- package/lib/quota.js.map +1 -0
- package/lib/session-list.js +18 -21
- package/lib/session-list.js.map +1 -1
- package/lib/term-text.js +827 -0
- package/lib/term-text.js.map +1 -0
- package/lib/tool-present.js +744 -0
- package/lib/tool-present.js.map +1 -0
- package/lib/transcript-types.js +6 -0
- package/lib/transcript-types.js.map +1 -0
- package/lib/tui.js +419 -3200
- package/lib/tui.js.map +1 -1
- package/lib/types/approval-reviewer.d.ts +8 -2
- package/lib/types/auto-approval.d.ts +28 -0
- package/lib/types/footer.d.ts +155 -0
- package/lib/types/json-args.d.ts +7 -0
- package/lib/types/paint.d.ts +78 -0
- package/lib/types/picker.d.ts +99 -7
- package/lib/types/plan.d.ts +80 -0
- package/lib/types/quota.d.ts +94 -0
- package/lib/types/session-list.d.ts +13 -0
- package/lib/types/term-text.d.ts +130 -0
- package/lib/types/tool-present.d.ts +165 -0
- package/lib/types/transcript-types.d.ts +152 -0
- package/lib/types/tui.d.ts +21 -721
- package/package.json +1 -1
|
@@ -7,14 +7,17 @@
|
|
|
7
7
|
* Injection hardening: everything inside the marked data regions is review
|
|
8
8
|
* MATERIAL, never instructions — the system prompt says so, and material that
|
|
9
9
|
* tries to instruct is treated as an injection attempt and rejected. The
|
|
10
|
-
* caller also enforces its own floor: `high` risk or `authorization
|
|
11
|
-
* is never approved, whatever the model claims.
|
|
10
|
+
* caller also enforces its own floor: `high` risk or `authorization` other
|
|
11
|
+
* than `yes` is never approved, whatever the model claims.
|
|
12
12
|
*/
|
|
13
13
|
export interface ReviewInput {
|
|
14
14
|
userText: string;
|
|
15
15
|
segments: string[];
|
|
16
16
|
toolName: string;
|
|
17
17
|
command: string;
|
|
18
|
+
args?: string;
|
|
19
|
+
reason?: string;
|
|
20
|
+
sandboxMode?: string;
|
|
18
21
|
}
|
|
19
22
|
export interface ReviewVerdict {
|
|
20
23
|
risk: 'low' | 'medium' | 'high';
|
|
@@ -22,7 +25,10 @@ export interface ReviewVerdict {
|
|
|
22
25
|
approved: boolean;
|
|
23
26
|
reason: string;
|
|
24
27
|
}
|
|
28
|
+
export type ReviewerLocale = 'zh' | 'en';
|
|
29
|
+
/** Default (zh) prompt; tests and callers that do not pass a locale use this. */
|
|
25
30
|
export declare const REVIEW_SYSTEM_PROMPT: string;
|
|
31
|
+
export declare function reviewSystemPrompt(locale?: ReviewerLocale): string;
|
|
26
32
|
/** Assemble the compact, fence-marked user message for the reviewer. */
|
|
27
33
|
export declare function buildReviewUserMessage(input: ReviewInput): string;
|
|
28
34
|
export declare function parseReviewOutput(text: string): ReviewVerdict | undefined;
|
|
@@ -11,29 +11,57 @@
|
|
|
11
11
|
*/
|
|
12
12
|
export type AutoApprovalMode = 'off' | 'auto';
|
|
13
13
|
export type ApprovalDecision = 'allow' | 'deny' | 'ask';
|
|
14
|
+
export type AutoApprovalRisk = 'low' | 'medium' | 'high';
|
|
15
|
+
export interface ClassifiedApproval {
|
|
16
|
+
decision: ApprovalDecision;
|
|
17
|
+
risk: AutoApprovalRisk;
|
|
18
|
+
/** Short stable id for i18n / model feedback. */
|
|
19
|
+
reasonKey: string;
|
|
20
|
+
}
|
|
14
21
|
/**
|
|
15
22
|
* Whole-command danger patterns, checked before anything else. A match
|
|
16
23
|
* auto-rejects. This is a UX heuristic, not a security boundary: obfuscated
|
|
17
24
|
* or interpreter-wrapped damage still has to be contained by the sandbox.
|
|
25
|
+
*
|
|
26
|
+
* Interpreter `-c`/`-e` is NOT here: the table cannot see the payload, so
|
|
27
|
+
* those shapes ask (AI review) instead of a blanket deny.
|
|
18
28
|
*/
|
|
19
29
|
export declare const DANGER_PATTERNS: RegExp[];
|
|
20
30
|
/**
|
|
21
31
|
* Segment-level allow patterns: low-risk, high-frequency reads, builds, and
|
|
22
32
|
* tests. A command auto-approves only when EVERY segment matches one of these.
|
|
33
|
+
* `env`/`printenv` stay off this list — process env often holds keys.
|
|
23
34
|
*/
|
|
24
35
|
export declare const ALLOW_SEGMENT_PATTERNS: RegExp[];
|
|
36
|
+
/**
|
|
37
|
+
* Split a shell command into segments at &&, ||, ; and | boundaries,
|
|
38
|
+
* skipping those operators when they sit inside quotes or a here-doc
|
|
39
|
+
* terminator. Nested quoting is best-effort — the sandbox still owns
|
|
40
|
+
* real damage containment.
|
|
41
|
+
*/
|
|
42
|
+
export declare function segments(command: string): string[];
|
|
43
|
+
export declare function touchesSensitivePath(text: string): boolean;
|
|
25
44
|
/**
|
|
26
45
|
* Classify one shell command line. Danger anywhere auto-rejects; otherwise
|
|
27
46
|
* the command auto-approves only when every segment is a recognized low-risk
|
|
28
47
|
* pattern — unknown shapes ask (and detach to a rejection when unattended).
|
|
29
48
|
*/
|
|
30
49
|
export declare function classifyCommand(command: string): ApprovalDecision;
|
|
50
|
+
export declare function classifyCommandDetailed(command: string): ClassifiedApproval;
|
|
51
|
+
export interface ClassifyApprovalInput {
|
|
52
|
+
toolName: string;
|
|
53
|
+
command?: string;
|
|
54
|
+
args?: string;
|
|
55
|
+
reason?: string;
|
|
56
|
+
workspaceCwd?: string;
|
|
57
|
+
}
|
|
31
58
|
/**
|
|
32
59
|
* Classify one approval request. `command` is the decoded shell command when
|
|
33
60
|
* the pending call is a shell tool. 'deny' auto-rejects (the model reads the
|
|
34
61
|
* rejection and adapts); 'ask' falls through to the interactive prompt.
|
|
35
62
|
*/
|
|
36
63
|
export declare function classifyApproval(toolName: string, command: string | undefined): ApprovalDecision;
|
|
64
|
+
export declare function classifyApprovalDetailed(input: ClassifyApprovalInput): ClassifiedApproval;
|
|
37
65
|
/**
|
|
38
66
|
* Decode the shell command of a streamed tool call from its raw JSON args.
|
|
39
67
|
* Returns undefined for non-shell tools or unparseable args.
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Footer stats, status identity, context-pressure chips, and `/status` report.
|
|
3
|
+
*/
|
|
4
|
+
import { type QuotaSnapshot } from './quota.js';
|
|
5
|
+
import type { DisconnectPolicyName } from './transcript-types.js';
|
|
6
|
+
export declare const WAIT_INDICATOR_MS = 8000;
|
|
7
|
+
/** Compact token count, matching the web stats line (517 / 12.2K / 1.2M). */
|
|
8
|
+
export declare function formatTokens(n: number): string;
|
|
9
|
+
/**
|
|
10
|
+
* Prompt occupancy of the next request, from DSH `contextPressure`.
|
|
11
|
+
* Provider-agnostic: uses the routed model's advertised window, not a
|
|
12
|
+
* hardcoded xAI size. Compaction-basic still owns in-turn pressure at 80%.
|
|
13
|
+
*/
|
|
14
|
+
export declare const CONTEXT_PRESSURE_WARN_RATIO = 0.8;
|
|
15
|
+
export declare const CONTEXT_PRESSURE_DANGER_RATIO = 0.95;
|
|
16
|
+
/** Idle auto-compact starts here so recovery finishes before the 80% in-turn trigger. */
|
|
17
|
+
export declare const CONTEXT_IDLE_COMPACT_RATIO = 0.72;
|
|
18
|
+
export interface ContextPressureSample {
|
|
19
|
+
usedTokens: number;
|
|
20
|
+
contextWindow: number;
|
|
21
|
+
}
|
|
22
|
+
export interface ContextPressureView {
|
|
23
|
+
usedTokens: number;
|
|
24
|
+
contextWindow: number;
|
|
25
|
+
percent: number;
|
|
26
|
+
level: 'ok' | 'warn' | 'danger';
|
|
27
|
+
}
|
|
28
|
+
/** Prompt-side occupancy of one usage sample: uncached input plus cache traffic. */
|
|
29
|
+
export declare function promptPressureTokens(usage: {
|
|
30
|
+
inputTokens: number;
|
|
31
|
+
cacheReadTokens?: number;
|
|
32
|
+
cacheWriteTokens?: number;
|
|
33
|
+
}): number;
|
|
34
|
+
/** Prefer the next-request projection; fall back to last-request pressure. */
|
|
35
|
+
export declare function contextPressureUsedTokens(pressure: {
|
|
36
|
+
projectedTokens?: number;
|
|
37
|
+
pressureTokens?: number;
|
|
38
|
+
} | undefined): number | undefined;
|
|
39
|
+
export declare function parseContextPressure(value: unknown): ContextPressureSample | undefined;
|
|
40
|
+
export declare function contextPressureView(sample: ContextPressureSample): ContextPressureView;
|
|
41
|
+
/**
|
|
42
|
+
* 8-segment Braille ring. Empty `⣀`; full `⣿`. Width is always 1 cell.
|
|
43
|
+
* Index is `ceil(percent / 12.5)` clamped to 0..8.
|
|
44
|
+
*/
|
|
45
|
+
export declare const CONTEXT_RING_EMPTY = "\u28C0";
|
|
46
|
+
export declare const CONTEXT_RING_SEGMENTS: readonly ["⣀", "⠉", "⠋", "⠛", "⠞", "⠟", "⠿", "⡿", "⣿"];
|
|
47
|
+
export declare function formatContextPressureRing(percent: number): string;
|
|
48
|
+
export declare function contextPressureRingColor(level: ContextPressureView['level']): string;
|
|
49
|
+
export declare function formatContextPressureChip(view: ContextPressureView, color?: boolean): string;
|
|
50
|
+
export declare function formatContextPressureStatusLine(view: ContextPressureView | undefined): string;
|
|
51
|
+
export declare function contextPressureAlertText(view: ContextPressureView): string;
|
|
52
|
+
export declare function shouldIdleAutoCompact(view: ContextPressureView | undefined): boolean;
|
|
53
|
+
/** Compact duration, matching the web stats line (45.2s / 2m42s). */
|
|
54
|
+
export declare function formatDuration(ms: number): string;
|
|
55
|
+
export declare function formatTokensPerSecond(tokensPerSecond: number): string;
|
|
56
|
+
export declare function providerShortCode(provider: string): string;
|
|
57
|
+
export interface FooterStatsInput {
|
|
58
|
+
turns: number;
|
|
59
|
+
steps: number;
|
|
60
|
+
llmMs: number;
|
|
61
|
+
toolMs: number;
|
|
62
|
+
ttftMs: number;
|
|
63
|
+
ttftSteps: number;
|
|
64
|
+
decodeMs: number;
|
|
65
|
+
decodeTokens: number;
|
|
66
|
+
inputTokens: number;
|
|
67
|
+
outputTokens: number;
|
|
68
|
+
cacheReadTokens: number;
|
|
69
|
+
cacheWriteTokens: number;
|
|
70
|
+
}
|
|
71
|
+
/** Stats groups in drop order (last is dropped first when the row is too wide). */
|
|
72
|
+
export declare function footerStatsGroups(stats: FooterStatsInput): string[];
|
|
73
|
+
export declare function fitFooterStatsLine(chip: string, groups: readonly string[], width: number): string;
|
|
74
|
+
export type FooterActivityKind = 'plan-review' | 'waiting' | 'compacting' | 'retry' | 'subagents' | 'tools' | 'plan-open' | 'plan-pending' | 'goal' | 'waiting-llm' | 'idle';
|
|
75
|
+
export interface FooterStatusInput {
|
|
76
|
+
running: boolean;
|
|
77
|
+
planReview: boolean;
|
|
78
|
+
waitingQuestion: boolean;
|
|
79
|
+
compacting: boolean;
|
|
80
|
+
retry?: {
|
|
81
|
+
retry: number;
|
|
82
|
+
maxRetries: number;
|
|
83
|
+
};
|
|
84
|
+
subagents: number;
|
|
85
|
+
tools: number;
|
|
86
|
+
planLeftOpen: boolean;
|
|
87
|
+
planPending: boolean;
|
|
88
|
+
planActive: boolean;
|
|
89
|
+
goalPhase?: 'active' | 'paused' | 'blocked';
|
|
90
|
+
idleMs: number;
|
|
91
|
+
model: string;
|
|
92
|
+
effort?: string;
|
|
93
|
+
preset?: string;
|
|
94
|
+
provider: string;
|
|
95
|
+
parentModel: string;
|
|
96
|
+
subModel: string;
|
|
97
|
+
subDiffers: boolean;
|
|
98
|
+
quotaCode?: string;
|
|
99
|
+
quotaPercent?: number;
|
|
100
|
+
contextChip?: string;
|
|
101
|
+
balanceText?: string;
|
|
102
|
+
search?: {
|
|
103
|
+
index: number;
|
|
104
|
+
total: number;
|
|
105
|
+
};
|
|
106
|
+
foldedInput: boolean;
|
|
107
|
+
multiLineInput: boolean;
|
|
108
|
+
queued: number;
|
|
109
|
+
cwdLabel?: string;
|
|
110
|
+
compactView?: boolean;
|
|
111
|
+
}
|
|
112
|
+
export declare function footerActivity(input: FooterStatusInput): {
|
|
113
|
+
kind: FooterActivityKind;
|
|
114
|
+
text: string;
|
|
115
|
+
};
|
|
116
|
+
/** Short remaining-quota bar: 8 pips, filled from the left. */
|
|
117
|
+
export declare function formatQuotaBar(remainingPercent: number, width?: number): string;
|
|
118
|
+
export declare function footerIdentityParts(input: FooterStatusInput): string[];
|
|
119
|
+
/** `SuperGrok ███████░ 82%`, or just the bar + percent when `code` is omitted. */
|
|
120
|
+
export declare function formatFooterQuota(percent: number, code?: string): string;
|
|
121
|
+
/**
|
|
122
|
+
* Drop the Go / SuperGrok plan name from a quota identity part, keeping the
|
|
123
|
+
* remaining-percent bar. Returns true when a part was rewritten.
|
|
124
|
+
*/
|
|
125
|
+
export declare function dropFooterQuotaPlanName(parts: string[]): boolean;
|
|
126
|
+
export declare function fitFooterStatusLine(activity: string, identity: readonly string[], width: number): string;
|
|
127
|
+
export interface StatusReportInput {
|
|
128
|
+
sessionId: string;
|
|
129
|
+
pluginVersion: string;
|
|
130
|
+
provider: string;
|
|
131
|
+
model: string;
|
|
132
|
+
effort?: string;
|
|
133
|
+
agentStatus: string;
|
|
134
|
+
preset: string;
|
|
135
|
+
activeSubagents: number;
|
|
136
|
+
plan: 'off' | 'pending' | 'on';
|
|
137
|
+
paint: string;
|
|
138
|
+
disconnect?: DisconnectPolicyName;
|
|
139
|
+
waitingQuestions: number;
|
|
140
|
+
quota?: QuotaSnapshot;
|
|
141
|
+
context?: ContextPressureView;
|
|
142
|
+
parentModel?: string;
|
|
143
|
+
subProvider?: string;
|
|
144
|
+
subModel: string;
|
|
145
|
+
cwd?: string;
|
|
146
|
+
}
|
|
147
|
+
/** Lines printed by `/status` — SSH first-boot diagnostics, no extra command. */
|
|
148
|
+
export declare function formatStatusReport(input: StatusReportInput): string[];
|
|
149
|
+
/** Human-facing kind for a live LLM route. */
|
|
150
|
+
export declare function describeProviderRoute(provider: string): {
|
|
151
|
+
kind: string;
|
|
152
|
+
short: string;
|
|
153
|
+
};
|
|
154
|
+
/** Routes that authenticate without a harness API-key credential. */
|
|
155
|
+
export declare function providerUsesLocalOAuth(provider: string): boolean;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Best-effort JSON argument parsing for tool cards and plan payloads.
|
|
3
|
+
*/
|
|
4
|
+
export declare function parseJsonArgs(args: string): Record<string, unknown> | null;
|
|
5
|
+
export declare function firstString(record: Record<string, unknown>, keys: readonly string[]): string;
|
|
6
|
+
/** A short scalar rendering of one argument value, or null for objects/arrays. */
|
|
7
|
+
export declare function scalarText(value: unknown): string | null;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Incremental ANSI paint, SSH cadence, hangup signals, and sliding windows.
|
|
3
|
+
*
|
|
4
|
+
* Kept off `tui.ts` so the launch picker can dirty-paint without loading
|
|
5
|
+
* the agent-backed SshTui class.
|
|
6
|
+
*/
|
|
7
|
+
export declare const DSR_PROBE_TIMEOUT_MS = 800;
|
|
8
|
+
/** Give a running turn this long to settle after cancel before we flush anyway. */
|
|
9
|
+
export declare const HANGUP_CANCEL_TIMEOUT_MS = 10000;
|
|
10
|
+
export type PaintLinkKind = 'local' | 'ssh';
|
|
11
|
+
/** In-app dialog / slash-suggestion page size. Launch picker uses 9 of its own. */
|
|
12
|
+
export declare const PICKER_WINDOW = 12;
|
|
13
|
+
/**
|
|
14
|
+
* Explicit env/config always wins. Otherwise local TTYs stay snappy and SSH
|
|
15
|
+
* sessions pick a tier from a measured round-trip (CSI 6n), falling back to
|
|
16
|
+
* 160 ms when the probe is missing.
|
|
17
|
+
*/
|
|
18
|
+
export declare function resolvePaintIntervalMs(configured?: number, env?: NodeJS.ProcessEnv, options?: {
|
|
19
|
+
ssh?: boolean;
|
|
20
|
+
rttMs?: number;
|
|
21
|
+
}): number;
|
|
22
|
+
/** True when this process is attached to an SSH session (jump host / proxy). */
|
|
23
|
+
export declare function detectSshSession(env?: NodeJS.ProcessEnv): boolean;
|
|
24
|
+
/** Node errno on a write/close that means the TTY is gone (SSH drop, HUP). */
|
|
25
|
+
export declare function isHangupErrno(error: unknown): boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Replace launcher SIGTERM/SIGINT/SIGHUP handlers with `handler`. SSH drop
|
|
28
|
+
* otherwise lets `dsh` dispose the whole tree before this plugin can detach.
|
|
29
|
+
*/
|
|
30
|
+
export declare function captureHangupSignals(handler: () => void): void;
|
|
31
|
+
export declare function releaseHangupSignals(handler: () => void): void;
|
|
32
|
+
/** After detach, extra HUP/TERM from sshd must not kill the leftover Host. */
|
|
33
|
+
export declare function ignoreFurtherHangupSignals(): void;
|
|
34
|
+
/**
|
|
35
|
+
* Wait until `isIdle` is true or `timeoutMs` elapses. Used after cancel so a
|
|
36
|
+
* hangup can flush a settled session log instead of tearing a live write.
|
|
37
|
+
*/
|
|
38
|
+
export declare function waitUntilIdleOrTimeout(isIdle: () => boolean, timeoutMs: number, now?: () => number, wait?: (ms: number) => Promise<void>): Promise<'idle' | 'timeout'>;
|
|
39
|
+
/** Map a CSI-6n round-trip to a paint cadence. Unknown RTT uses the SSH default. */
|
|
40
|
+
export declare function paintIntervalForRtt(rttMs: number | undefined): number;
|
|
41
|
+
export declare function paintLinkLabel(kind: PaintLinkKind, intervalMs: number, probed: boolean): string;
|
|
42
|
+
export type LinkQuality = 'local' | 'good' | 'ok' | 'slow' | 'poor' | 'unknown';
|
|
43
|
+
/** Signal-bar quality from a measured SSH round-trip, or local TTY. */
|
|
44
|
+
export declare function linkQualityOf(kind: PaintLinkKind, rttMs: number | undefined): LinkQuality;
|
|
45
|
+
/** How many filled signal pips: 4 local/fast, 3 ok, 2 slow, 1 poor, 0 unknown. */
|
|
46
|
+
export declare function linkSignalPips(quality: LinkQuality): number;
|
|
47
|
+
/** Compact footer chip: `SSH ●●●○ 90ms` — 1 pip red, 2 yellow, 3+ green. */
|
|
48
|
+
export declare function formatLinkQualityChip(kind: PaintLinkKind, intervalMs: number, rttMs: number | undefined, probed: boolean, color?: boolean): string;
|
|
49
|
+
/** One incremental paint as a single stdout write (one SSH packet when corked). */
|
|
50
|
+
export declare function composePaintOutput(options: {
|
|
51
|
+
width: number;
|
|
52
|
+
height: number;
|
|
53
|
+
paintRows: readonly string[];
|
|
54
|
+
previousRows: readonly string[];
|
|
55
|
+
sizeChanged: boolean;
|
|
56
|
+
chromeChanged: boolean;
|
|
57
|
+
chromeStart: number;
|
|
58
|
+
previousChromeStart?: number;
|
|
59
|
+
cursorRow: number;
|
|
60
|
+
cursorColumn: number;
|
|
61
|
+
/** Keep the cursor hidden (session picker). Default shows it on the input. */
|
|
62
|
+
hideCursor?: boolean;
|
|
63
|
+
}): string;
|
|
64
|
+
/** Whether `text` could still grow into a recognized escape sequence. */
|
|
65
|
+
export declare function isEscapePrefix(text: string): boolean;
|
|
66
|
+
/** Parse a Device Status Report cursor reply (`CSI row;col R`). */
|
|
67
|
+
export declare function parseCursorPositionReply(text: string): {
|
|
68
|
+
row: number;
|
|
69
|
+
column: number;
|
|
70
|
+
} | undefined;
|
|
71
|
+
/**
|
|
72
|
+
* Round-trip to the attached terminal via CSI 6n. Returns undefined when the
|
|
73
|
+
* reply never arrives (dumb pipe, blocked DSR). Does not interpret the
|
|
74
|
+
* coordinates — only the elapsed milliseconds matter.
|
|
75
|
+
*/
|
|
76
|
+
export declare function probeTerminalRttMs(stdin?: NodeJS.ReadStream, stdout?: NodeJS.WriteStream, timeoutMs?: number): Promise<number | undefined>;
|
|
77
|
+
/** Sliding window of `windowSize` items that keeps `cursor` visible. */
|
|
78
|
+
export declare function pickerWindowStart(cursor: number, total: number, windowSize?: number): number;
|
package/lib/types/picker.d.ts
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Startup history-session picker: a
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* Startup history-session picker: a raw-mode selector shown BEFORE the main
|
|
3
|
+
* TUI mounts, so launching without an explicit session id lands on a choice
|
|
4
|
+
* instead of a fresh main screen.
|
|
5
|
+
*
|
|
6
|
+
* The list itself is not capped. The visible page is always nine rows so
|
|
7
|
+
* digits 1-9 map onto every on-screen item. Arrow keys move a highlight,
|
|
8
|
+
* typing filters by title / id / cwd, and Enter confirms the focused row.
|
|
5
9
|
*/
|
|
6
10
|
import type { Context } from '@deepseek-ai/cordis';
|
|
11
|
+
import { type ResumableSession } from './session-list.js';
|
|
7
12
|
/** What the launch picker decided. */
|
|
8
13
|
export type SessionPickerResult = {
|
|
9
14
|
kind: 'resume';
|
|
@@ -16,10 +21,97 @@ export type SessionPickerResult = {
|
|
|
16
21
|
kind: 'new';
|
|
17
22
|
} | null;
|
|
18
23
|
/**
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
|
|
22
|
-
|
|
24
|
+
* Visible rows in the sliding window. Locked to nine so every on-screen
|
|
25
|
+
* session has a 1-9 shortcut; remaining history is reached with ↑/↓ / filter.
|
|
26
|
+
*/
|
|
27
|
+
export declare const SESSION_PICKER_WINDOW = 9;
|
|
28
|
+
/** Digit shortcuts into the current window when the filter is empty. */
|
|
29
|
+
export declare const PICKER_QUICK_KEYS = "123456789";
|
|
30
|
+
/** Mutable picker UI state. */
|
|
31
|
+
export interface SessionPickerState {
|
|
32
|
+
sessions: ResumableSession[];
|
|
33
|
+
query: string;
|
|
34
|
+
cursor: number;
|
|
35
|
+
/**
|
|
36
|
+
* When true, digits type into the filter instead of acting as 1-9 / 0
|
|
37
|
+
* shortcuts. Set automatically by the first letter, or by `/` / Ctrl+F.
|
|
38
|
+
*/
|
|
39
|
+
filterActive: boolean;
|
|
40
|
+
}
|
|
41
|
+
/** One key / control action against {@link SessionPickerState}. */
|
|
42
|
+
export type SessionPickerAction = {
|
|
43
|
+
type: 'move';
|
|
44
|
+
delta: number;
|
|
45
|
+
} | {
|
|
46
|
+
type: 'page';
|
|
47
|
+
delta: number;
|
|
48
|
+
} | {
|
|
49
|
+
type: 'home';
|
|
50
|
+
} | {
|
|
51
|
+
type: 'end';
|
|
52
|
+
} | {
|
|
53
|
+
type: 'type';
|
|
54
|
+
text: string;
|
|
55
|
+
} | {
|
|
56
|
+
type: 'backspace';
|
|
57
|
+
} | {
|
|
58
|
+
type: 'clearQuery';
|
|
59
|
+
} | {
|
|
60
|
+
type: 'startFilter';
|
|
61
|
+
} | {
|
|
62
|
+
type: 'quick';
|
|
63
|
+
key: string;
|
|
64
|
+
} | {
|
|
65
|
+
type: 'submit';
|
|
66
|
+
} | {
|
|
67
|
+
type: 'new';
|
|
68
|
+
} | {
|
|
69
|
+
type: 'escape';
|
|
70
|
+
} | {
|
|
71
|
+
type: 'cancel';
|
|
72
|
+
};
|
|
73
|
+
/** Result of applying one action: keep going, or the picker is done. */
|
|
74
|
+
export type SessionPickerStep = {
|
|
75
|
+
kind: 'continue';
|
|
76
|
+
state: SessionPickerState;
|
|
77
|
+
} | {
|
|
78
|
+
kind: 'done';
|
|
79
|
+
result: SessionPickerResult;
|
|
80
|
+
};
|
|
81
|
+
/** Haystack used by the filter: title, id, cwd, and a live pid when attached. */
|
|
82
|
+
export declare function sessionSearchHaystack(session: ResumableSession): string;
|
|
83
|
+
/** Whether one session matches a whitespace-separated query (every token). */
|
|
84
|
+
export declare function sessionMatchesQuery(session: ResumableSession, query: string): boolean;
|
|
85
|
+
/** Sessions still visible under the current filter, in list order. */
|
|
86
|
+
export declare function filterResumableSessions(sessions: readonly ResumableSession[], query: string): ResumableSession[];
|
|
87
|
+
/** Keep `cursor` inside `[0, total)`. Empty lists pin to 0. */
|
|
88
|
+
export declare function clampPickerCursor(cursor: number, total: number): number;
|
|
89
|
+
/**
|
|
90
|
+
* How many session rows to show. Prefer a full page of nine so 1-9 always
|
|
91
|
+
* map onto every on-screen item. Shrink only when the tty cannot fit that
|
|
92
|
+
* page (title + filter + nine items + hints).
|
|
93
|
+
*/
|
|
94
|
+
export declare function pickerCapacity(rows: number): number;
|
|
95
|
+
/** Whether two picker states would paint the same frame. */
|
|
96
|
+
export declare function pickerStateUnchanged(previous: SessionPickerState, next: SessionPickerState): boolean;
|
|
97
|
+
/**
|
|
98
|
+
* Apply one picker action. Pure: the TTY layer maps keystrokes onto this, and
|
|
99
|
+
* tests drive it without stdin.
|
|
100
|
+
*/
|
|
101
|
+
export declare function stepPicker(state: SessionPickerState, action: SessionPickerAction, windowSize?: number): SessionPickerStep;
|
|
102
|
+
/** Map one decoded stdin chunk (or a complete CSI sequence) to actions. */
|
|
103
|
+
export declare function actionsForInput(text: string, state?: Pick<SessionPickerState, 'query' | 'filterActive'>): SessionPickerAction[];
|
|
104
|
+
/** Split a decoded stdin chunk into CSI/SS3 sequences and individual characters. */
|
|
105
|
+
export declare function splitPickerInput(text: string): string[];
|
|
106
|
+
/**
|
|
107
|
+
* Feed a decoded stdin chunk through the picker. CSI / SS3 sequences are one
|
|
108
|
+
* unit; ordinary text is applied character by character so a pasted "fix 2"
|
|
109
|
+
* types the digit instead of treating it as a 1-9 shortcut.
|
|
110
|
+
*/
|
|
111
|
+
export declare function feedPicker(state: SessionPickerState, text: string, windowSize?: number): SessionPickerStep;
|
|
112
|
+
/**
|
|
113
|
+
* Show the picker and wait for a selection, a new-session request, or cancel.
|
|
114
|
+
* Restores the terminal before resolving; an AbortSignal cancels as well.
|
|
23
115
|
* @param ctx - boot context supplying sessionPersistence.
|
|
24
116
|
* @param color - whether to apply ANSI colors.
|
|
25
117
|
* @param signal - optional abort signal (fiber dispose) to cancel the picker.
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plan dock, todo lists, /find, prompt-injection cards, and compact errors.
|
|
3
|
+
*/
|
|
4
|
+
import type { DisplayKind, PlanTodoItem, Row, SubagentLogEntry } from './transcript-types.js';
|
|
5
|
+
export declare const MAX_SUBAGENT_LOGS = 80;
|
|
6
|
+
export declare const TODO_STATUS_MARK: Record<PlanTodoItem['status'], string>;
|
|
7
|
+
/** True while a plan still belongs in the dock (latest incomplete work). */
|
|
8
|
+
export declare function planIsLive(plan: {
|
|
9
|
+
active: boolean;
|
|
10
|
+
pending: boolean;
|
|
11
|
+
todos: readonly PlanTodoItem[];
|
|
12
|
+
planMarkdown?: string;
|
|
13
|
+
archived?: boolean;
|
|
14
|
+
}): boolean;
|
|
15
|
+
/** Open todos left behind when a turn ends without a completing todo_write. */
|
|
16
|
+
export declare function planTurnLeftOpen(plan: {
|
|
17
|
+
todos: readonly PlanTodoItem[];
|
|
18
|
+
}): boolean;
|
|
19
|
+
/** Mark leftover in-progress/pending todos as display-stale after turn/end. */
|
|
20
|
+
export declare function applyTurnEndToPlan<T extends {
|
|
21
|
+
todos: PlanTodoItem[];
|
|
22
|
+
turnLeftOpen?: boolean;
|
|
23
|
+
}>(plan: T): T;
|
|
24
|
+
/** Follow-up that asks the model to close leftover todos. One per open list. */
|
|
25
|
+
export declare function planCloseNudgeText(plan: {
|
|
26
|
+
todos: readonly PlanTodoItem[];
|
|
27
|
+
}): string;
|
|
28
|
+
export type CardCategory = 'thinking' | 'plan' | 'subagent' | 'reply' | 'tool' | 'question' | 'goal' | 'prompt';
|
|
29
|
+
/** Category for jump / search. Assistant replies are not collapsible cards. */
|
|
30
|
+
export declare function cardCategoryOf(row: {
|
|
31
|
+
kind: string;
|
|
32
|
+
}): CardCategory | undefined;
|
|
33
|
+
export declare function cardCategoryLabel(category: CardCategory): string;
|
|
34
|
+
export declare function parseCardCategoryToken(token: string): CardCategory | undefined;
|
|
35
|
+
/** Split `/find thinking padAnsi` into an optional category and a query. */
|
|
36
|
+
export declare function parseFindQuery(raw: string): {
|
|
37
|
+
category?: CardCategory;
|
|
38
|
+
query: string;
|
|
39
|
+
};
|
|
40
|
+
/** Classify one injected prompt blob into display sources. */
|
|
41
|
+
export declare function promptInjectionSources(text: string, plugin?: string): string[];
|
|
42
|
+
export declare function promptInjectionTitle(sources: readonly string[]): string;
|
|
43
|
+
export declare function isPromptInjectionMessage(sourceKind: string, text: string, plugin?: string): boolean;
|
|
44
|
+
/** Official `/compact` idle-only failures, mapped to a local sentence. */
|
|
45
|
+
export declare function formatCompactCommandError(text: string): string;
|
|
46
|
+
export declare function compactionHeaderText(row: {
|
|
47
|
+
status: 'running' | 'ok' | 'error';
|
|
48
|
+
pruneCount: number;
|
|
49
|
+
prunedTokens: number;
|
|
50
|
+
error?: string;
|
|
51
|
+
}): string;
|
|
52
|
+
/** Transcript rows matching a `/find` query, newest last. */
|
|
53
|
+
export declare function matchTranscriptRows(rows: readonly Row[], raw: string): Row[];
|
|
54
|
+
/** One-line note under an expanded plan strip. */
|
|
55
|
+
export declare function planDockNote(plan: {
|
|
56
|
+
active: boolean;
|
|
57
|
+
pending: boolean;
|
|
58
|
+
todos: readonly PlanTodoItem[];
|
|
59
|
+
planMarkdown?: string;
|
|
60
|
+
turnLeftOpen?: boolean;
|
|
61
|
+
}): string;
|
|
62
|
+
/** Compact per-status counts matching the web plan strip. */
|
|
63
|
+
export declare function todoProgressLabel(todos: readonly PlanTodoItem[]): string;
|
|
64
|
+
export declare function todoItemKind(status: PlanTodoItem['status']): DisplayKind;
|
|
65
|
+
export declare function planMarkdownFromArgs(value: unknown): string | undefined;
|
|
66
|
+
/** First markdown heading of an exit_plan_mode plan body. */
|
|
67
|
+
export declare function planTitleFromMarkdown(markdown: string): string | undefined;
|
|
68
|
+
/** Parse a todo_write payload into displayable plan items. */
|
|
69
|
+
export declare function parsePlanTodos(value: unknown): PlanTodoItem[];
|
|
70
|
+
/** Compact todo-list summary: done/total plus the first in-progress task. */
|
|
71
|
+
export declare function todoSummary(value: unknown): string;
|
|
72
|
+
/** Compact ask_user_question summary from tool arguments. */
|
|
73
|
+
export declare function askSummary(value: unknown): string;
|
|
74
|
+
/** One-line subagent card header used while collapsed. */
|
|
75
|
+
export declare function subagentHeaderText(row: Extract<Row, {
|
|
76
|
+
kind: 'subagent';
|
|
77
|
+
}>, now?: number): string;
|
|
78
|
+
export declare function appendSubagentLog(row: Extract<Row, {
|
|
79
|
+
kind: 'subagent';
|
|
80
|
+
}>, entry: SubagentLogEntry): void;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenCode / SuperGrok / DeepSeek quota and prepaid-balance parsers.
|
|
3
|
+
*/
|
|
4
|
+
/** A recognized OpenCode provider route, used by /usage and /quota. */
|
|
5
|
+
export type OpenCodeFlavor = 'zen' | 'go';
|
|
6
|
+
export interface OpenCodeSource {
|
|
7
|
+
provider: string;
|
|
8
|
+
flavor: OpenCodeFlavor;
|
|
9
|
+
label: string;
|
|
10
|
+
apiKeyEnv: string;
|
|
11
|
+
baseURL?: string;
|
|
12
|
+
}
|
|
13
|
+
export interface LlmPiAiProviderProfile {
|
|
14
|
+
displayName?: unknown;
|
|
15
|
+
apiKeyEnv?: unknown;
|
|
16
|
+
baseURL?: unknown;
|
|
17
|
+
api?: unknown;
|
|
18
|
+
models?: unknown;
|
|
19
|
+
reasoning?: unknown;
|
|
20
|
+
}
|
|
21
|
+
export interface LlmPiAiSection {
|
|
22
|
+
providers?: Record<string, LlmPiAiProviderProfile>;
|
|
23
|
+
}
|
|
24
|
+
/** Build per-model reasoningEfforts from a provider-level reasoning default. */
|
|
25
|
+
export declare function reasoningEffortsForDefault(reasoning: unknown): Record<string, string | null> | undefined;
|
|
26
|
+
export declare const OPENCODE_GO_USAGE_URL = "https://opencode.ai/zen/go/v1/usage";
|
|
27
|
+
export declare const OPENCODE_ZEN_BASE_URL = "https://opencode.ai/zen/v1";
|
|
28
|
+
export declare const SUPERGROK_BILLING_URL = "https://cli-chat-proxy.grok.com/v1/billing?format=credits";
|
|
29
|
+
export declare const DEEPSEEK_PUBLIC_BASE_URL = "https://api.deepseek.com";
|
|
30
|
+
/** OpenAI-completions gateways: probe these relative to the configured base URL. */
|
|
31
|
+
export declare const OPENAI_COMPAT_BALANCE_PATHS: readonly ["/user/balance", "/dashboard/billing/credit_grants", "/v1/dashboard/billing/credit_grants", "/v1/dashboard/billing/subscription"];
|
|
32
|
+
/**
|
|
33
|
+
* Classify the currently selected provider as an OpenCode route. Built-in
|
|
34
|
+
* `opencode`/`opencode-go` ids are recognized directly, and custom llm-pi-ai
|
|
35
|
+
* routes are recognized by their `opencode.ai` base URL.
|
|
36
|
+
*/
|
|
37
|
+
export declare function openCodeSourceFor(provider: string, llmPiAiSection: unknown): OpenCodeSource | null;
|
|
38
|
+
export type QuotaPeriod = 'hourly' | 'weekly' | 'monthly' | 'unknown';
|
|
39
|
+
export interface QuotaWindow {
|
|
40
|
+
label: string;
|
|
41
|
+
period: QuotaPeriod;
|
|
42
|
+
/** Remaining percent of the window (100 = unused). */
|
|
43
|
+
remainingPercent: number;
|
|
44
|
+
resetsAt?: string;
|
|
45
|
+
}
|
|
46
|
+
export interface QuotaSnapshot {
|
|
47
|
+
provider: string;
|
|
48
|
+
plan: string;
|
|
49
|
+
windows: QuotaWindow[];
|
|
50
|
+
}
|
|
51
|
+
export declare function remainingPercentFromUsed(usedPercent: number): number;
|
|
52
|
+
/** Cross a remaining-percent threshold from above (50 / 25 / 10 / 5).
|
|
53
|
+
* Only the tightest (lowest) crossed threshold is returned, so one drop
|
|
54
|
+
* never paints 50/25/10 as three identical warnings. */
|
|
55
|
+
export declare function crossedQuotaThresholds(previousRemaining: number | undefined, remaining: number): number[];
|
|
56
|
+
export declare function quotaAlertText(snapshot: QuotaSnapshot, window: QuotaWindow): string;
|
|
57
|
+
/**
|
|
58
|
+
* How often to re-fetch quota or prepaid balance, counted in model steps.
|
|
59
|
+
* Default is every 10 steps. Near a remaining-percent threshold, hourly
|
|
60
|
+
* windows refresh every 4 steps.
|
|
61
|
+
*/
|
|
62
|
+
export declare function quotaRefreshEverySteps(window: QuotaWindow | undefined): number;
|
|
63
|
+
/** @deprecated Same cadence as {@link quotaRefreshEverySteps}; the name predates step accounting. */
|
|
64
|
+
export declare const quotaRefreshEveryTurns: typeof quotaRefreshEverySteps;
|
|
65
|
+
export declare function parseSuperGrokBilling(payload: unknown): QuotaSnapshot;
|
|
66
|
+
export declare function parseOpenCodeGoQuota(payload: unknown, provider: string): QuotaSnapshot;
|
|
67
|
+
export declare function formatQuotaSnapshot(snapshot: QuotaSnapshot): string;
|
|
68
|
+
/** Compact `/status` quota line: tightest window first, then the rest. */
|
|
69
|
+
export declare function formatQuotaStatusLine(snapshot: QuotaSnapshot | undefined): string;
|
|
70
|
+
/** Tightest remaining window — used for threshold alerts. */
|
|
71
|
+
export declare function tightestQuotaWindow(snapshot: QuotaSnapshot): QuotaWindow | undefined;
|
|
72
|
+
/** Render the OpenCode Go quota payload as a transcript block. */
|
|
73
|
+
export declare function formatOpenCodeGoUsage(payload: unknown, source: OpenCodeSource): string;
|
|
74
|
+
export interface AccountBalanceLine {
|
|
75
|
+
label: string;
|
|
76
|
+
amount: string;
|
|
77
|
+
currency?: string;
|
|
78
|
+
}
|
|
79
|
+
export interface AccountBalanceSnapshot {
|
|
80
|
+
provider: string;
|
|
81
|
+
plan: string;
|
|
82
|
+
available?: boolean;
|
|
83
|
+
lines: AccountBalanceLine[];
|
|
84
|
+
sourcePath?: string;
|
|
85
|
+
}
|
|
86
|
+
export declare function joinUrl(base: string, path: string): string;
|
|
87
|
+
export declare function parseDeepSeekBalance(payload: unknown, provider?: string): AccountBalanceSnapshot;
|
|
88
|
+
/** Best-effort parse of OpenAI-compatible credit/balance JSON. */
|
|
89
|
+
export declare function parseOpenAiCompatibleBalance(payload: unknown, provider: string, path: string): AccountBalanceSnapshot | undefined;
|
|
90
|
+
/** Compact footer chip: `余额 86.42 CNY`. Prefers remaining/available lines. */
|
|
91
|
+
export declare function formatFooterBalance(snapshot: AccountBalanceSnapshot): string | undefined;
|
|
92
|
+
export declare function formatAccountBalance(snapshot: AccountBalanceSnapshot): string;
|
|
93
|
+
/** Extract a safe human-readable message from an OpenCode error payload. */
|
|
94
|
+
export declare function openCodeApiErrorMessage(payload: unknown): string;
|
|
@@ -38,4 +38,17 @@ export interface ResumableSession {
|
|
|
38
38
|
}
|
|
39
39
|
/** `MM-DD HH:mm` local-time label for session lists. */
|
|
40
40
|
export declare function formatSessionTime(timestamp: number): string;
|
|
41
|
+
/**
|
|
42
|
+
* List resumable top-level sessions, newest first.
|
|
43
|
+
*
|
|
44
|
+
* Subagent-owned sessions and the current session are excluded. Sessions
|
|
45
|
+
* whose event log cannot be inspected are kept (marked `unreadable`) instead
|
|
46
|
+
* of silently disappearing from history; readable sessions with user input
|
|
47
|
+
* sort first. The label is the persisted title, then the user's first input,
|
|
48
|
+
* then the id.
|
|
49
|
+
* @param persistence - the session persistence service.
|
|
50
|
+
* @param currentId - the live session to exclude (empty at launch).
|
|
51
|
+
* @returns every resumable candidate in display order (attachable live
|
|
52
|
+
* hosts first, then readable logs, then unreadable).
|
|
53
|
+
*/
|
|
41
54
|
export declare function listResumableSessions(persistence: SessionPersistence, currentId: string, listHosts?: typeof listAttachableHosts): Promise<ResumableSession[]>;
|