dsh-ssh-tui 0.7.1 → 0.7.2
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 +76 -7
- package/README.md +50 -7
- package/lib/approval-cache.js +12 -9
- package/lib/approval-cache.js.map +1 -1
- package/lib/color-depth.js +10 -4
- package/lib/color-depth.js.map +1 -1
- package/lib/diag.js +41 -17
- package/lib/diag.js.map +1 -1
- package/lib/dialogs.js.map +1 -1
- package/lib/display-sock.js +68 -10
- package/lib/display-sock.js.map +1 -1
- package/lib/footer.js +35 -7
- package/lib/footer.js.map +1 -1
- package/lib/gateway-protocol.js +202 -0
- package/lib/gateway-protocol.js.map +1 -0
- package/lib/i18n/en.js +61 -41
- package/lib/i18n/en.js.map +1 -1
- package/lib/i18n/zh.js +61 -41
- package/lib/i18n/zh.js.map +1 -1
- package/lib/index.js +99 -23
- package/lib/index.js.map +1 -1
- package/lib/job-label.js +99 -17
- package/lib/job-label.js.map +1 -1
- package/lib/plan.js +269 -17
- package/lib/plan.js.map +1 -1
- package/lib/platform.js +88 -0
- package/lib/platform.js.map +1 -0
- package/lib/provider-catalog.js +3 -0
- package/lib/provider-catalog.js.map +1 -1
- package/lib/route-memory.js +8 -1
- package/lib/route-memory.js.map +1 -1
- package/lib/session-list.js +18 -1
- package/lib/session-list.js.map +1 -1
- package/lib/session-lock.js +75 -23
- package/lib/session-lock.js.map +1 -1
- package/lib/session-route.js +331 -0
- package/lib/session-route.js.map +1 -0
- package/lib/subagent-model.js +79 -1
- package/lib/subagent-model.js.map +1 -1
- package/lib/term-text.js +7 -4
- package/lib/term-text.js.map +1 -1
- package/lib/tool-present.js +46 -14
- package/lib/tool-present.js.map +1 -1
- package/lib/tui.js +1789 -275
- package/lib/tui.js.map +1 -1
- package/lib/types/color-depth.d.ts +3 -3
- package/lib/types/dialogs.d.ts +4 -0
- package/lib/types/display-sock.d.ts +52 -1
- package/lib/types/footer.d.ts +18 -4
- package/lib/types/gateway-protocol.d.ts +88 -0
- package/lib/types/job-label.d.ts +38 -7
- package/lib/types/plan.d.ts +94 -4
- package/lib/types/platform.d.ts +50 -0
- package/lib/types/route-memory.d.ts +17 -22
- package/lib/types/session-list.d.ts +5 -0
- package/lib/types/session-lock.d.ts +9 -0
- package/lib/types/session-route.d.ts +191 -0
- package/lib/types/subagent-model.d.ts +53 -0
- package/lib/types/term-text.d.ts +3 -2
- package/lib/types/tool-present.d.ts +2 -17
- package/lib/types/transcript-types.d.ts +44 -1
- package/lib/types/tui.d.ts +292 -2
- package/package.json +1 -1
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-session provider/model memory.
|
|
3
|
+
*
|
|
4
|
+
* The parent route and the subagent route used to be one global choice: `/model`
|
|
5
|
+
* and `/submodel` wrote a default that every later session inherited. Once a
|
|
6
|
+
* child could run on a *different* supplier that stopped being enough — resuming
|
|
7
|
+
* an old conversation silently re-ran it on whatever the last conversation used,
|
|
8
|
+
* and there was no way to see which supplier had paid for the earlier one.
|
|
9
|
+
*
|
|
10
|
+
* Each session's effective route is recorded here, next to the TUI's other
|
|
11
|
+
* per-session state under `$DSH_HOME`, and applied when that session starts
|
|
12
|
+
* again. A new session still starts from the global default; the record only
|
|
13
|
+
* ever overrides it for the session it belongs to, and never for another one.
|
|
14
|
+
*
|
|
15
|
+
* The record is a cache of a decision, not a source of truth: every field is
|
|
16
|
+
* parsed defensively, and a route whose provider no longer exists is still
|
|
17
|
+
* applied (the launcher reports the failure the same way it would for a CLI
|
|
18
|
+
* flag).
|
|
19
|
+
*
|
|
20
|
+
* @module dsh-ssh-tui/session-route
|
|
21
|
+
*/
|
|
22
|
+
import { type SubagentSelectionRef } from './subagent-model.js';
|
|
23
|
+
/** Subagent route as this session ran it; no provider means "follows parent". */
|
|
24
|
+
export interface SessionSubagentRoute {
|
|
25
|
+
provider?: string;
|
|
26
|
+
model: string;
|
|
27
|
+
reasoningEffort?: string;
|
|
28
|
+
}
|
|
29
|
+
/** One session's route: what `/model`, `/provider`, `/submodel` settled on. */
|
|
30
|
+
export interface SessionRoute {
|
|
31
|
+
provider: string;
|
|
32
|
+
model: string;
|
|
33
|
+
reasoningEffort?: string;
|
|
34
|
+
subagent?: SessionSubagentRoute;
|
|
35
|
+
/** Epoch ms of the last write; the oldest records are pruned first. */
|
|
36
|
+
updatedAt: number;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* How many sessions keep a record. The file is a convenience cache, and a
|
|
40
|
+
* hundred-odd conversations is more history than a resume picker offers.
|
|
41
|
+
*/
|
|
42
|
+
export declare const SESSION_ROUTE_LIMIT = 200;
|
|
43
|
+
export declare function sessionRoutePath(dshHome?: string): string;
|
|
44
|
+
/** Parse one record; anything that cannot name a route is dropped. */
|
|
45
|
+
export declare function parseSessionRoute(raw: unknown): SessionRoute | undefined;
|
|
46
|
+
/** Parse the whole file; a file from another version reads as empty. */
|
|
47
|
+
export declare function parseSessionRoutes(raw: unknown): Map<string, SessionRoute>;
|
|
48
|
+
export declare function loadSessionRoutes(path?: string): Promise<Map<string, SessionRoute>>;
|
|
49
|
+
/** The record for one session, or undefined when it never had one. */
|
|
50
|
+
export declare function loadSessionRoute(sessionId: string, path?: string): Promise<SessionRoute | undefined>;
|
|
51
|
+
/** Keep the newest `limit` records, so the file cannot grow without bound. */
|
|
52
|
+
export declare function pruneSessionRoutes(entries: ReadonlyMap<string, SessionRoute>, limit?: number): Map<string, SessionRoute>;
|
|
53
|
+
export declare function saveSessionRoutes(path: string, entries: ReadonlyMap<string, SessionRoute>): Promise<boolean>;
|
|
54
|
+
/**
|
|
55
|
+
* Record one session's route, leaving every other session alone.
|
|
56
|
+
*
|
|
57
|
+
* Read-modify-write on purpose: two Hosts (two SSH windows) can be open at
|
|
58
|
+
* once, and neither may drop the other's entry.
|
|
59
|
+
*/
|
|
60
|
+
export declare function saveSessionRoute(sessionId: string, route: Omit<SessionRoute, 'updatedAt'>, path?: string): Promise<boolean>;
|
|
61
|
+
/** Whether two records describe the same route (the timestamp is ignored). */
|
|
62
|
+
export declare function sameSessionRoute(left: SessionRoute | undefined, right: Omit<SessionRoute, 'updatedAt'> | undefined): boolean;
|
|
63
|
+
/**
|
|
64
|
+
* Shape a live parent route plus the subagent selection into a record.
|
|
65
|
+
*
|
|
66
|
+
* Shared by the TUI (which reports what its refs hold) and the launcher (which
|
|
67
|
+
* writes it), so the two cannot disagree about what a session was running on.
|
|
68
|
+
* An empty provider or model yields nothing: a record that cannot name a route
|
|
69
|
+
* would come back as a lie.
|
|
70
|
+
*/
|
|
71
|
+
export declare function sessionRouteInput(input: {
|
|
72
|
+
provider: string;
|
|
73
|
+
model: string;
|
|
74
|
+
reasoningEffort?: string;
|
|
75
|
+
subagent?: {
|
|
76
|
+
provider?: string;
|
|
77
|
+
model: string;
|
|
78
|
+
reasoningEffort?: string;
|
|
79
|
+
};
|
|
80
|
+
}): Omit<SessionRoute, 'updatedAt'> | undefined;
|
|
81
|
+
/**
|
|
82
|
+
* Providers the harness ships with. They are routable in any install even when
|
|
83
|
+
* the adapter list is still warming up, so a record naming one is never treated
|
|
84
|
+
* as stale on that basis alone.
|
|
85
|
+
*/
|
|
86
|
+
export declare const BUILTIN_ROUTABLE_PROVIDERS: readonly ["deepseek-official", "xai", "opencode-go", "opencode"];
|
|
87
|
+
/**
|
|
88
|
+
* Whether this install can still route to a provider.
|
|
89
|
+
*
|
|
90
|
+
* Deliberately permissive: the cost of wrongly *keeping* a record is an error the
|
|
91
|
+
* harness already reports clearly (an unknown route, the same one a launch flag
|
|
92
|
+
* would hit), while wrongly *dropping* one silently loses the session's route —
|
|
93
|
+
* which is the whole feature. So a provider counts as routable when it is a
|
|
94
|
+
* built-in, appears in the adapter list, has a configured `llm-pi-ai` profile, or
|
|
95
|
+
* is listed by the adapter's own model catalog.
|
|
96
|
+
*/
|
|
97
|
+
export declare function providerIsRoutable(input: {
|
|
98
|
+
provider: string;
|
|
99
|
+
/** Ids from `llm.listProviders()`. */
|
|
100
|
+
listed?: readonly string[];
|
|
101
|
+
/** Ids with an `llm-pi-ai.providers.<id>` section. */
|
|
102
|
+
configured?: readonly string[];
|
|
103
|
+
}): boolean;
|
|
104
|
+
/** What a resumed session's record is worth in this install. */
|
|
105
|
+
export interface RouteAvailabilityPlan {
|
|
106
|
+
/** The parent route, when the provider it names can still be routed to. */
|
|
107
|
+
route?: SessionRoute;
|
|
108
|
+
/** The subagent route, when its own pin can be routed to as well. */
|
|
109
|
+
subagent?: SessionSubagentRoute;
|
|
110
|
+
/** Recorded parent provider that is gone; the caller says so on screen. */
|
|
111
|
+
droppedProvider?: string;
|
|
112
|
+
/** Recorded subagent pin that is gone; the app-level setting takes over. */
|
|
113
|
+
droppedSubagentProvider?: string;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Decide what of a recorded route still applies.
|
|
117
|
+
*
|
|
118
|
+
* A provider that no longer exists takes its whole record with it: the session
|
|
119
|
+
* was configured around that supplier (its model, its effort, and the children
|
|
120
|
+
* it pinned), so the honest answer is the app-level default for all of it rather
|
|
121
|
+
* than a half-restored route. A pinned *child* provider that is gone is narrower:
|
|
122
|
+
* the parent route is kept and the children fall back to the app-level subagent
|
|
123
|
+
* setting. An inherited child route has no pin to check — it follows the parent,
|
|
124
|
+
* which was just found routable.
|
|
125
|
+
*/
|
|
126
|
+
export declare function routeAvailabilityPlan(input: {
|
|
127
|
+
recorded?: SessionRoute;
|
|
128
|
+
routable: (provider: string) => boolean;
|
|
129
|
+
}): RouteAvailabilityPlan;
|
|
130
|
+
/**
|
|
131
|
+
* Apply a resumed session's record to the live selection.
|
|
132
|
+
*
|
|
133
|
+
* Returns what the record was worth (see `routeAvailabilityPlan`) so the
|
|
134
|
+
* launcher can say which route the session came back on — and, when a supplier
|
|
135
|
+
* has been removed since, which one it fell back from. A new session is left
|
|
136
|
+
* alone, and so is a resumed one that never had a record: both start from the
|
|
137
|
+
* app-level defaults, which is the behaviour before any of this existed.
|
|
138
|
+
*
|
|
139
|
+
* The subagent route is applied in memory only: it belongs to the conversation
|
|
140
|
+
* being resumed, and writing it back to the settings would make one session's
|
|
141
|
+
* pin the default for every later session.
|
|
142
|
+
*/
|
|
143
|
+
export declare function restoreSessionRoute(input: {
|
|
144
|
+
sessionId: string;
|
|
145
|
+
resume: boolean;
|
|
146
|
+
subagentSelection: SubagentSelectionRef;
|
|
147
|
+
path?: string;
|
|
148
|
+
/** Availability test; omitted means "everything is routable". */
|
|
149
|
+
routable?: (provider: string) => boolean;
|
|
150
|
+
}): Promise<RouteAvailabilityPlan>;
|
|
151
|
+
/** One remembered route as the launch waterfall sees it. */
|
|
152
|
+
export interface LaunchRouteSource {
|
|
153
|
+
provider: string;
|
|
154
|
+
model: string;
|
|
155
|
+
reasoningEffort?: string;
|
|
156
|
+
}
|
|
157
|
+
export interface LaunchRouteInput {
|
|
158
|
+
/** In-process change from `/model` or `/setup` earlier in this process. */
|
|
159
|
+
live?: LaunchRouteSource;
|
|
160
|
+
/** Launch flags (`--provider` / `--model`). */
|
|
161
|
+
cli?: {
|
|
162
|
+
provider?: string;
|
|
163
|
+
model?: string;
|
|
164
|
+
};
|
|
165
|
+
/** This session's own record, when the launch resumes it. */
|
|
166
|
+
session?: SessionRoute;
|
|
167
|
+
/** Global default (the settings file, or `agent-default-model`). */
|
|
168
|
+
saved?: LaunchRouteSource;
|
|
169
|
+
/** Last model per provider, used only when no user default section exists. */
|
|
170
|
+
remembered?: LaunchRouteSource;
|
|
171
|
+
}
|
|
172
|
+
export interface LaunchRoute {
|
|
173
|
+
provider: string;
|
|
174
|
+
model: string;
|
|
175
|
+
reasoningEffort?: string;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* The route a launch should run on, in one place.
|
|
179
|
+
*
|
|
180
|
+
* Precedence: an in-process change, then launch flags, then the session's own
|
|
181
|
+
* record, then the global default, then the last remembered route. A resumed
|
|
182
|
+
* conversation therefore comes back on the supplier it was actually using, while
|
|
183
|
+
* a new one still starts from the default — and an explicit flag still wins,
|
|
184
|
+
* because that is this launch's ask.
|
|
185
|
+
*
|
|
186
|
+
* The reasoning effort travels with the route it was chosen for: it is taken
|
|
187
|
+
* from whichever source supplied the provider/model, and only when the flags did
|
|
188
|
+
* not move the route away from it. A CLI that changes either half must not
|
|
189
|
+
* inherit the saved effort of a different model.
|
|
190
|
+
*/
|
|
191
|
+
export declare function resolveLaunchRoute(input: LaunchRouteInput): LaunchRoute;
|
|
@@ -24,6 +24,33 @@ export declare const DEFAULT_XAI_SUBAGENT_MODEL = "grok-4.5";
|
|
|
24
24
|
* with flash-like ids ranked first.
|
|
25
25
|
*/
|
|
26
26
|
export declare function defaultSubagentModelForProvider(provider: string, listed?: readonly string[], parentModel?: string): string;
|
|
27
|
+
/**
|
|
28
|
+
* Canonical id of the supplier behind a route.
|
|
29
|
+
*
|
|
30
|
+
* The settings file can name the same supplier two ways — `xai` against
|
|
31
|
+
* `grok`, `deepseek-official` against `deepseek` — and a `/submodel` pin that
|
|
32
|
+
* spells the parent's own provider differently is still the parent's provider.
|
|
33
|
+
* Only those aliases collapse: an id the TUI cannot vouch for (`deepseek-eu`,
|
|
34
|
+
* `grok-mirror`, a relay) stays itself, because two routes it cannot prove
|
|
35
|
+
* share a bill are two providers.
|
|
36
|
+
*/
|
|
37
|
+
export declare function canonicalProviderId(provider: string | undefined): string;
|
|
38
|
+
/**
|
|
39
|
+
* True when `child` is a different supplier from `parent`.
|
|
40
|
+
*
|
|
41
|
+
* This is the one condition that earns a subagent an accent colour: a child
|
|
42
|
+
* that follows the parent — or that was pinned back onto the parent's own
|
|
43
|
+
* provider — is on the parent's route and must read as such.
|
|
44
|
+
*/
|
|
45
|
+
export declare function subagentProviderDiffers(parent: string | undefined, child: string | undefined): boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Identity SGR for a subagent chip title. Same provider as the parent stays
|
|
48
|
+
* the violet used since the courtesy-name work; a different provider uses
|
|
49
|
+
* cyan so the foreign route is visible without a second color layer.
|
|
50
|
+
*/
|
|
51
|
+
export declare const SUBAGENT_IDENTITY_SGR = "38;5;141";
|
|
52
|
+
export declare const SUBAGENT_FOREIGN_SGR = "38;5;80";
|
|
53
|
+
export declare function subagentIdentitySgr(foreign: boolean): typeof SUBAGENT_IDENTITY_SGR | typeof SUBAGENT_FOREIGN_SGR;
|
|
27
54
|
/**
|
|
28
55
|
* True when the stored subagent model still belongs to the parent provider
|
|
29
56
|
* family. An explicit leftover DeepSeek flash id after switching to xAI is
|
|
@@ -75,7 +102,33 @@ export interface SubagentSelection {
|
|
|
75
102
|
/** Mutable selection handle shared by the settings watcher and the TUI. */
|
|
76
103
|
export interface SubagentSelectionRef {
|
|
77
104
|
current: SubagentSelection;
|
|
105
|
+
/**
|
|
106
|
+
* Where `current` came from.
|
|
107
|
+
*
|
|
108
|
+
* A route restored from a session's own record (`session`) outranks the
|
|
109
|
+
* settings watcher: the host fires `onChange` on writes to *any* section, so a
|
|
110
|
+
* `/model` or a `/doctor --fix` would otherwise put the app-level default back
|
|
111
|
+
* and silently drop the pin the conversation was resumed with. A user change
|
|
112
|
+
* through `/submodel` hands ownership back to the settings.
|
|
113
|
+
*/
|
|
114
|
+
source?: 'settings' | 'session';
|
|
78
115
|
}
|
|
116
|
+
/**
|
|
117
|
+
* Adopt a session's own subagent route, outranking the settings watcher.
|
|
118
|
+
* @param ref - the shared selection handle.
|
|
119
|
+
* @param selection - the route the session's record names.
|
|
120
|
+
*/
|
|
121
|
+
export declare function adoptSessionSubagentSelection(ref: SubagentSelectionRef, selection: SubagentSelection): void;
|
|
122
|
+
/**
|
|
123
|
+
* Hand the ref back to the settings watcher.
|
|
124
|
+
*
|
|
125
|
+
* Called when a user change makes the settings the source again, and before a
|
|
126
|
+
* new session starts in this process — a session-scoped route must not leak into
|
|
127
|
+
* the next conversation. Only a readable section may replace the current value:
|
|
128
|
+
* resetting on a settings service that is not up yet would swap the route for
|
|
129
|
+
* the built-in default instead.
|
|
130
|
+
*/
|
|
131
|
+
export declare function releaseSessionSubagentSelection(ref: SubagentSelectionRef, settingsValue: unknown): void;
|
|
79
132
|
/** Settings schema for `$DSH_HOME/settings.yaml`. */
|
|
80
133
|
export declare const SUBAGENT_SETTINGS_SCHEMA: z<Schemastery.ObjectS<{
|
|
81
134
|
provider: z<string, string>;
|
package/lib/types/term-text.d.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Isolated so the launch session picker can clip labels without loading
|
|
5
5
|
* the interactive TUI class.
|
|
6
6
|
*/
|
|
7
|
+
import { type ColorDepth } from './color-depth.js';
|
|
7
8
|
/**
|
|
8
9
|
* Codex-style compact elapsed: `0s`, `1m 05s`, `1h 01m 01s`.
|
|
9
10
|
* Used by the workspace wait card while the model has not streamed yet.
|
|
@@ -116,9 +117,9 @@ export declare function wrapTracked(text: string, width: number): {
|
|
|
116
117
|
end: number;
|
|
117
118
|
}[];
|
|
118
119
|
/** Paint one already-wrapped output line by the segments overlapping its range. */
|
|
119
|
-
export declare function paintSegmentedLine(line: string, start: number, end: number, segments: readonly TextSegment[]): string;
|
|
120
|
+
export declare function paintSegmentedLine(line: string, start: number, end: number, segments: readonly TextSegment[], depth?: ColorDepth): string;
|
|
120
121
|
/** Wrap `text` and color each output line by overlapping `segments`. */
|
|
121
|
-
export declare function wrapSegmented(text: string, width: number, segments: readonly TextSegment[]): string[];
|
|
122
|
+
export declare function wrapSegmented(text: string, width: number, segments: readonly TextSegment[], depth?: ColorDepth): string[];
|
|
122
123
|
export declare function truncate(text: string, maxLines: number): string;
|
|
123
124
|
/** One OSC-8 hyperlink span in a painted row, in display columns. */
|
|
124
125
|
export interface PaintedLinkHit {
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import type { AskUserQuestionItem } from '@deepseek-ai/dsh-user-questions';
|
|
5
5
|
import { type TextSegment } from './term-text.js';
|
|
6
|
-
import type {
|
|
6
|
+
import type { DiffDisplayLine, 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
9
|
export declare const JOB_TOOL_NAMES: Set<string>;
|
|
@@ -12,7 +12,6 @@ export declare function formatModelList(models: readonly string[], max?: number)
|
|
|
12
12
|
/** Prefer the fields a human scans for; fall back to the first scalar pairs. */
|
|
13
13
|
export declare function friendlyArgsSummary(name: string, args: string): string;
|
|
14
14
|
export declare function countDiffLines(hunks: readonly ToolDiffHunk[] | undefined): number;
|
|
15
|
-
/** Added / removed line counts for a diff (`oldText: null` means a new file). */
|
|
16
15
|
export declare function countDiffAddDel(hunks: readonly ToolDiffHunk[] | undefined): {
|
|
17
16
|
add: number;
|
|
18
17
|
del: number;
|
|
@@ -121,21 +120,7 @@ export declare function presentToolCall(name: string, args: string): {
|
|
|
121
120
|
export declare function diffMetaDiffs(meta: unknown): ToolDiffHunk[] | null;
|
|
122
121
|
/** Split one diff side into content lines (trailing newline is a terminator). */
|
|
123
122
|
export declare function diffContentLines(text: string): string[];
|
|
124
|
-
|
|
125
|
-
export interface DiffDisplayLine {
|
|
126
|
-
kind: DisplayKind;
|
|
127
|
-
text: string;
|
|
128
|
-
/**
|
|
129
|
-
* Ranges of `text` to emphasise, in UTF-16 offsets. They travel with the line
|
|
130
|
-
* rather than being baked in as escapes because the painter sanitises the text
|
|
131
|
-
* it styles — an escape inserted down here would be stripped, leaving a
|
|
132
|
-
* literal `[7m` on screen.
|
|
133
|
-
*/
|
|
134
|
-
spans?: readonly {
|
|
135
|
-
start: number;
|
|
136
|
-
end: number;
|
|
137
|
-
}[];
|
|
138
|
-
}
|
|
123
|
+
export type { DiffDisplayLine } from './transcript-types.js';
|
|
139
124
|
/** Cap one flat diff/body row list to `maxLines` while preserving the final line. */
|
|
140
125
|
export declare function capDisplayLines(lines: readonly DiffDisplayLine[], maxLines: number): DiffDisplayLine[];
|
|
141
126
|
/** Running / ok / error → ANSI color for the status dot and status word only. */
|
|
@@ -8,6 +8,10 @@ export type SubagentLogKind = 'user' | 'assistant' | 'tool' | 'result' | 'turn'
|
|
|
8
8
|
export interface SubagentLogEntry {
|
|
9
9
|
kind: SubagentLogKind;
|
|
10
10
|
text: string;
|
|
11
|
+
/** Parent tool-call id, so a later result can settle on the same log line. */
|
|
12
|
+
callId?: string;
|
|
13
|
+
/** Result body kept beside the call line, when the caller has one. */
|
|
14
|
+
detail?: string;
|
|
11
15
|
}
|
|
12
16
|
/** One todo-list item as the plan card renders it. */
|
|
13
17
|
export interface PlanTodoItem {
|
|
@@ -62,14 +66,24 @@ export type Row = {
|
|
|
62
66
|
} | {
|
|
63
67
|
kind: 'subagent';
|
|
64
68
|
sessionId: string;
|
|
69
|
+
/** Live child id when this chip was first built from the parent spawn tool. */
|
|
70
|
+
childSessionId?: string;
|
|
71
|
+
/** Parent spawn tool-call id, so the wrapper's result can find this child. */
|
|
72
|
+
spawnCallId?: string;
|
|
73
|
+
/** Model route the child runs on. Never the subagent backend name (`spawn`). */
|
|
74
|
+
modelProvider?: string;
|
|
65
75
|
runId: string;
|
|
66
76
|
provider: string;
|
|
67
77
|
local: boolean;
|
|
68
78
|
label: string;
|
|
79
|
+
/** Parent `subagent` tool description, when the spawn call named the job. */
|
|
80
|
+
task?: string;
|
|
69
81
|
status: 'running' | 'ok' | 'error' | 'aborted';
|
|
70
82
|
startedAt: number;
|
|
71
83
|
endedAt?: number;
|
|
72
84
|
stopReason?: string;
|
|
85
|
+
/** One-line failure explanation shown on the collapsed chip. */
|
|
86
|
+
failHint?: string;
|
|
73
87
|
lastActivity: string;
|
|
74
88
|
logs: SubagentLogEntry[];
|
|
75
89
|
expanded: boolean;
|
|
@@ -161,7 +175,36 @@ export type CollapsibleBlock = Extract<Row, {
|
|
|
161
175
|
kind: 'streaming-reasoning';
|
|
162
176
|
expanded: boolean;
|
|
163
177
|
};
|
|
164
|
-
export type DisplayKind = Row['kind'] | 'tool-result' | 'diff-add' | 'diff-del' | 'diff-path' | 'todo-done' | 'todo-active' | 'todo-pending' | 'todo-failed' | 'todo-skipped' | 'plan-dock';
|
|
178
|
+
export type DisplayKind = Row['kind'] | 'tool-result' | 'diff-add' | 'diff-del' | 'diff-path' | 'todo-done' | 'todo-active' | 'todo-pending' | 'todo-failed' | 'todo-skipped' | 'plan-dock' | 'subagent-header';
|
|
179
|
+
/** One rendered diff/inspect body line with its display role. */
|
|
180
|
+
export interface DiffDisplayLine {
|
|
181
|
+
kind: DisplayKind;
|
|
182
|
+
text: string;
|
|
183
|
+
/**
|
|
184
|
+
* Ranges of `text` to emphasise, in UTF-16 offsets. They travel with the line
|
|
185
|
+
* rather than being baked in as escapes because the painter sanitises the text
|
|
186
|
+
* it styles — an escape inserted down here would be stripped, leaving a
|
|
187
|
+
* literal `[7m` on screen.
|
|
188
|
+
*/
|
|
189
|
+
spans?: readonly {
|
|
190
|
+
start: number;
|
|
191
|
+
end: number;
|
|
192
|
+
}[];
|
|
193
|
+
/**
|
|
194
|
+
* Parts of `text` that carry their own role, for a line that holds two at
|
|
195
|
+
* once: the columns of a side-by-side diff are a removal on the left and an
|
|
196
|
+
* addition on the right, and painting the row as either one would mislabel
|
|
197
|
+
* half of it. Offsets are UTF-16 into `text`; anything not covered keeps the
|
|
198
|
+
* line's own `kind`.
|
|
199
|
+
*/
|
|
200
|
+
columns?: readonly DiffDisplayColumn[];
|
|
201
|
+
}
|
|
202
|
+
/** One part of a line, painted with its own display role. */
|
|
203
|
+
export interface DiffDisplayColumn {
|
|
204
|
+
start: number;
|
|
205
|
+
end: number;
|
|
206
|
+
kind: DisplayKind;
|
|
207
|
+
}
|
|
165
208
|
/** One file's change, matching the web diff-card contract (`card: 'diff'`). */
|
|
166
209
|
export interface ToolDiffHunk {
|
|
167
210
|
path: string;
|