@jwilger/pi-development-system 0.48.0 → 0.51.1
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/extensions/development-system.ts +1 -1
- package/package.json +2 -2
- package/skills/strict-lints/SKILL.md +2 -1
- package/src/core/profile.ts +3 -27
- package/src/core/spawn-overrides.ts +2 -2
- package/src/core/test-runner.ts +10 -3
- package/src/review/digest.ts +7 -1
- package/src/state/profile-detect.ts +27 -0
- package/src/subagents/VENDORED.md +1 -1
- package/src/subagents/index.ts +144 -60
- package/src/subagents/orch/inherited-tools.ts +3 -3
- package/src/subagents/orch/mailbox.ts +11 -7
- package/src/subagents/orch/manager.ts +90 -57
- package/src/subagents/orch/runtime.ts +38 -33
- package/src/subagents/orch/tools.ts +12 -10
- package/src/subagents/prefs/agent-import.ts +13 -13
- package/src/subagents/prefs/config.ts +85 -185
- package/src/subagents/prefs/import-discovery.ts +6 -6
- package/src/subagents/prefs/models.ts +6 -10
- package/src/subagents/prefs/settings.ts +38 -38
- package/src/subagents/types.ts +37 -34
- package/src/subagents/ui/agent-navigation-editor.ts +20 -13
- package/src/subagents/ui/dialog.ts +99 -84
- package/src/subagents/ui/import-picker.ts +28 -11
- package/src/subagents/ui/live-agent-view.ts +17 -5
- package/src/subagents/ui/model-picker.ts +26 -17
- package/src/subagents/ui/settings-ui.ts +16 -34
- package/src/subagents/ui/status-ui.ts +140 -51
- package/src/subagents/ui/thread-tree.ts +3 -9
- package/src/subagents/ui/ui.ts +162 -265
|
@@ -1,23 +1,19 @@
|
|
|
1
|
-
//
|
|
1
|
+
// biome-ignore-all lint/complexity/noExcessiveCognitiveComplexity: vendored upstream TUI/config code with no tests; rewriting it risks behaviour (see src/subagents/VENDORED.md)
|
|
2
|
+
// biome-ignore-all lint/suspicious/noUnnecessaryConditions: vendored upstream TUI/config code with no tests; rewriting it risks behaviour (see src/subagents/VENDORED.md)
|
|
2
3
|
import type { ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
|
|
3
4
|
import type { ConfigStore } from "../prefs/config.ts";
|
|
4
|
-
import {
|
|
5
|
-
canOpenDialog,
|
|
6
|
-
dialogInput,
|
|
7
|
-
dialogMenu,
|
|
8
|
-
withDialogSession,
|
|
9
|
-
} from "./dialog.ts";
|
|
10
5
|
import {
|
|
11
6
|
DEFAULT_MANAGER_SETTINGS,
|
|
12
7
|
loadManagerSaveScope,
|
|
13
|
-
saveManagerSaveScope,
|
|
14
|
-
saveManagerSettings,
|
|
15
8
|
type ManagerSettings,
|
|
16
9
|
type ModelSelectionMode,
|
|
17
10
|
type SubagentMode,
|
|
11
|
+
saveManagerSaveScope,
|
|
12
|
+
saveManagerSettings,
|
|
18
13
|
type ToolFilteringMode,
|
|
19
14
|
type WidgetMode,
|
|
20
15
|
} from "../prefs/settings.ts";
|
|
16
|
+
import { canOpenDialog, dialogInput, dialogMenu, withDialogSession } from "./dialog.ts";
|
|
21
17
|
import { editAgentTypes } from "./ui.ts";
|
|
22
18
|
|
|
23
19
|
const FIELDS = [
|
|
@@ -138,9 +134,7 @@ export async function configureAgents(
|
|
|
138
134
|
},
|
|
139
135
|
): Promise<void> {
|
|
140
136
|
if (!canOpenDialog(ctx)) return;
|
|
141
|
-
await withDialogSession(ctx, (scoped) =>
|
|
142
|
-
configureAgentsDialog(scoped, options),
|
|
143
|
-
);
|
|
137
|
+
await withDialogSession(ctx, (scoped) => configureAgentsDialog(scoped, options));
|
|
144
138
|
}
|
|
145
139
|
|
|
146
140
|
async function configureAgentsDialog(
|
|
@@ -167,13 +161,13 @@ async function configureAgentsDialog(
|
|
|
167
161
|
{
|
|
168
162
|
id: "subagentMode",
|
|
169
163
|
label: "Subagent Mode",
|
|
170
|
-
value: MODE_OPTIONS.find((mode) => mode.id === draft.subagentMode)
|
|
164
|
+
value: MODE_OPTIONS.find((mode) => mode.id === draft.subagentMode)?.label,
|
|
171
165
|
help: MODE_HELP,
|
|
172
166
|
},
|
|
173
167
|
{
|
|
174
168
|
id: "toolFiltering",
|
|
175
169
|
label: "Tool Filtering",
|
|
176
|
-
value: TOOL_FILTERING_OPTIONS.find((mode) => mode.id === draft.toolFiltering)
|
|
170
|
+
value: TOOL_FILTERING_OPTIONS.find((mode) => mode.id === draft.toolFiltering)?.label,
|
|
177
171
|
help: TOOL_FILTERING_HELP,
|
|
178
172
|
},
|
|
179
173
|
...FIELDS.map((field) => ({
|
|
@@ -183,13 +177,13 @@ async function configureAgentsDialog(
|
|
|
183
177
|
{
|
|
184
178
|
id: "modelSelection",
|
|
185
179
|
label: "Model Picking",
|
|
186
|
-
value: MODEL_SELECTION_OPTIONS.find((mode) => mode.id === draft.modelSelection)
|
|
180
|
+
value: MODEL_SELECTION_OPTIONS.find((mode) => mode.id === draft.modelSelection)?.label,
|
|
187
181
|
help: MODEL_SELECTION_HELP,
|
|
188
182
|
},
|
|
189
183
|
{
|
|
190
184
|
id: "widgetMode",
|
|
191
185
|
label: "Status Widget",
|
|
192
|
-
value: WIDGET_MODE_OPTIONS.find((mode) => mode.id === draft.widgetMode)
|
|
186
|
+
value: WIDGET_MODE_OPTIONS.find((mode) => mode.id === draft.widgetMode)?.label,
|
|
193
187
|
help: WIDGET_MODE_HELP,
|
|
194
188
|
},
|
|
195
189
|
{
|
|
@@ -225,7 +219,7 @@ async function configureAgentsDialog(
|
|
|
225
219
|
id: "save",
|
|
226
220
|
label: "Save and apply",
|
|
227
221
|
value: dirty ? "(changes)" : undefined,
|
|
228
|
-
valueColor: dirty ? "warning" as const : undefined,
|
|
222
|
+
valueColor: dirty ? ("warning" as const) : undefined,
|
|
229
223
|
help: "Save all values to the selected scope and reload settings now.\nProject overrides still take precedence.\nRunning agents and retained sessions are kept.",
|
|
230
224
|
},
|
|
231
225
|
{
|
|
@@ -243,19 +237,13 @@ async function configureAgentsDialog(
|
|
|
243
237
|
if (!action || action === "cancel") return;
|
|
244
238
|
selectedId = action;
|
|
245
239
|
try {
|
|
246
|
-
const field = FIELDS.find((
|
|
240
|
+
const field = FIELDS.find((candidate) => candidate.id === action);
|
|
247
241
|
if (field) {
|
|
248
|
-
const value = await dialogInput(
|
|
249
|
-
ctx,
|
|
250
|
-
field.label,
|
|
251
|
-
String(draft[field.id]),
|
|
252
|
-
field.help,
|
|
253
|
-
);
|
|
242
|
+
const value = await dialogInput(ctx, field.label, String(draft[field.id]), field.help);
|
|
254
243
|
if (value === undefined) continue;
|
|
255
244
|
const number = Number(value.trim());
|
|
256
245
|
if (
|
|
257
|
-
|
|
258
|
-
!Number.isSafeInteger(number) ||
|
|
246
|
+
!(/^\d+$/.test(value.trim()) && Number.isSafeInteger(number)) ||
|
|
259
247
|
number < 1 ||
|
|
260
248
|
(field.id === "maxLevels" && number > 32)
|
|
261
249
|
)
|
|
@@ -291,10 +279,7 @@ async function configureAgentsDialog(
|
|
|
291
279
|
draft.finalRecap = !draft.finalRecap;
|
|
292
280
|
} else if (action === "scope") {
|
|
293
281
|
if (!ctx.isProjectTrusted())
|
|
294
|
-
ctx.ui.notify(
|
|
295
|
-
"Project settings require a trusted project; saving globally.",
|
|
296
|
-
"warning",
|
|
297
|
-
);
|
|
282
|
+
ctx.ui.notify("Project settings require a trusted project; saving globally.", "warning");
|
|
298
283
|
else {
|
|
299
284
|
const nextScope = scope === "user" ? "project" : "user";
|
|
300
285
|
saveManagerSaveScope(options.agentDir, nextScope);
|
|
@@ -320,10 +305,7 @@ async function configureAgentsDialog(
|
|
|
320
305
|
return;
|
|
321
306
|
}
|
|
322
307
|
} catch (error) {
|
|
323
|
-
ctx.ui.notify(
|
|
324
|
-
error instanceof Error ? error.message : String(error),
|
|
325
|
-
"error",
|
|
326
|
-
);
|
|
308
|
+
ctx.ui.notify(error instanceof Error ? error.message : String(error), "error");
|
|
327
309
|
}
|
|
328
310
|
}
|
|
329
311
|
}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
//
|
|
1
|
+
// biome-ignore-all lint/complexity/noExcessiveCognitiveComplexity: vendored upstream TUI/config code with no tests; rewriting it risks behaviour (see src/subagents/VENDORED.md)
|
|
2
|
+
// biome-ignore-all lint/complexity/noVoid: vendored upstream TUI/config code with no tests; rewriting it risks behaviour (see src/subagents/VENDORED.md)
|
|
3
|
+
// biome-ignore-all lint/suspicious/noShadow: vendored upstream TUI/config code with no tests; rewriting it risks behaviour (see src/subagents/VENDORED.md)
|
|
4
|
+
// biome-ignore-all lint/suspicious/noUnnecessaryConditions: vendored upstream TUI/config code with no tests; rewriting it risks behaviour (see src/subagents/VENDORED.md)
|
|
2
5
|
import type {
|
|
3
6
|
ExtensionCommandContext,
|
|
4
7
|
ExtensionContext,
|
|
@@ -7,30 +10,34 @@ import type {
|
|
|
7
10
|
} from "@earendil-works/pi-coding-agent";
|
|
8
11
|
import {
|
|
9
12
|
getKeybindings,
|
|
10
|
-
setKeybindings,
|
|
11
13
|
Key,
|
|
12
|
-
matchesKey,
|
|
13
14
|
type Keybinding,
|
|
15
|
+
matchesKey,
|
|
16
|
+
setKeybindings,
|
|
14
17
|
} from "@earendil-works/pi-tui";
|
|
18
|
+
import type { ThreadService } from "../types.ts";
|
|
15
19
|
import {
|
|
16
20
|
canOpenDialog,
|
|
17
21
|
DIALOG_OPTIONS,
|
|
18
22
|
type DialogHost,
|
|
19
23
|
DialogSession,
|
|
20
|
-
scopeDialogContext,
|
|
21
|
-
dialogMenu,
|
|
22
24
|
dialogEditor,
|
|
23
25
|
dialogHeight,
|
|
26
|
+
dialogMenu,
|
|
24
27
|
dialogText,
|
|
25
28
|
frameDialog,
|
|
29
|
+
scopeDialogContext,
|
|
26
30
|
} from "./dialog.ts";
|
|
27
|
-
import type
|
|
31
|
+
import { type AgentViewportState, fillViewport, LiveAgentView } from "./live-agent-view.ts";
|
|
28
32
|
import { buildStatusTree, type StatusRow } from "./thread-tree.ts";
|
|
29
33
|
import {
|
|
30
|
-
AGENT_PROGRESS_INTERVAL,
|
|
34
|
+
AGENT_PROGRESS_INTERVAL,
|
|
35
|
+
agentProgressIcon,
|
|
36
|
+
agentTypeBadge,
|
|
37
|
+
agentTypeLabel,
|
|
38
|
+
showThreads,
|
|
39
|
+
threadMetrics,
|
|
31
40
|
} from "./ui.ts";
|
|
32
|
-
import { LiveAgentView, fillViewport, type AgentViewportState } from "./live-agent-view.ts";
|
|
33
|
-
|
|
34
41
|
|
|
35
42
|
const ROOT = "/root";
|
|
36
43
|
const INACTIVE_STATES = new Set(["paused", "stopped", "failed", "completed"]);
|
|
@@ -55,7 +62,7 @@ function lexicalParent(path: string): string | null {
|
|
|
55
62
|
function treeParent(rows: StatusRow[], index: number): StatusRow | undefined {
|
|
56
63
|
const depth = rows[index]?.prefix.length ?? 0;
|
|
57
64
|
for (let cursor = index - 1; cursor >= 0; cursor--) {
|
|
58
|
-
if (rows[cursor]
|
|
65
|
+
if ((rows[cursor]?.prefix.length ?? 0) < depth) return rows[cursor];
|
|
59
66
|
}
|
|
60
67
|
return undefined;
|
|
61
68
|
}
|
|
@@ -96,24 +103,59 @@ class StatusDialog {
|
|
|
96
103
|
private refreshDelay = 0;
|
|
97
104
|
private refreshing = false;
|
|
98
105
|
|
|
106
|
+
private host: DialogHost;
|
|
107
|
+
|
|
108
|
+
private theme: Theme;
|
|
109
|
+
|
|
110
|
+
private service: ThreadService;
|
|
111
|
+
|
|
112
|
+
private done: (path: string | undefined) => void;
|
|
113
|
+
|
|
114
|
+
private title: string;
|
|
115
|
+
|
|
116
|
+
private navigation:
|
|
117
|
+
| {
|
|
118
|
+
state: AgentTreeState;
|
|
119
|
+
actions(path: string): void;
|
|
120
|
+
fullscreen?: boolean;
|
|
121
|
+
}
|
|
122
|
+
| undefined;
|
|
123
|
+
|
|
124
|
+
private nerdFontIcons: boolean;
|
|
125
|
+
|
|
99
126
|
constructor(
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
127
|
+
host: DialogHost,
|
|
128
|
+
theme: Theme,
|
|
129
|
+
service: ThreadService,
|
|
130
|
+
done: (path: string | undefined) => void,
|
|
104
131
|
selected?: string,
|
|
105
|
-
|
|
106
|
-
|
|
132
|
+
title: string = TITLE,
|
|
133
|
+
navigation?: {
|
|
107
134
|
state: AgentTreeState;
|
|
108
135
|
actions(path: string): void;
|
|
109
136
|
fullscreen?: boolean;
|
|
110
137
|
},
|
|
111
|
-
|
|
138
|
+
nerdFontIcons: boolean = false,
|
|
112
139
|
) {
|
|
140
|
+
this.host = host;
|
|
141
|
+
|
|
142
|
+
this.theme = theme;
|
|
143
|
+
|
|
144
|
+
this.service = service;
|
|
145
|
+
|
|
146
|
+
this.done = done;
|
|
147
|
+
|
|
148
|
+
this.title = title;
|
|
149
|
+
|
|
150
|
+
this.navigation = navigation;
|
|
151
|
+
|
|
152
|
+
this.nerdFontIcons = nerdFontIcons;
|
|
113
153
|
this.state = navigation?.state ?? { selectedPath: selected || ROOT, collapsed: new Set() };
|
|
114
154
|
}
|
|
115
155
|
|
|
116
|
-
invalidate(): void {
|
|
156
|
+
invalidate(): void {
|
|
157
|
+
// Nothing is cached, so there is nothing to invalidate.
|
|
158
|
+
}
|
|
117
159
|
|
|
118
160
|
/** Poll retained status, accelerating the existing timer only for animated live agents. */
|
|
119
161
|
startRefresh(): void {
|
|
@@ -130,9 +172,15 @@ class StatusDialog {
|
|
|
130
172
|
|
|
131
173
|
private syncRefresh(): void {
|
|
132
174
|
if (!this.refreshing) return;
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
|
|
175
|
+
const animated =
|
|
176
|
+
this.nerdFontIcons &&
|
|
177
|
+
this.service
|
|
178
|
+
.list()
|
|
179
|
+
.some(
|
|
180
|
+
(thread) =>
|
|
181
|
+
thread.path !== ROOT && (thread.state === "starting" || thread.state === "running"),
|
|
182
|
+
);
|
|
183
|
+
const delay = animated ? AGENT_PROGRESS_INTERVAL : 1000;
|
|
136
184
|
if (this.timer !== undefined && this.refreshDelay === delay) return;
|
|
137
185
|
this.stopRefresh();
|
|
138
186
|
this.refreshDelay = delay;
|
|
@@ -154,14 +202,18 @@ class StatusDialog {
|
|
|
154
202
|
private stopAll(): void {
|
|
155
203
|
const live = this.service
|
|
156
204
|
.list()
|
|
157
|
-
.filter(
|
|
205
|
+
.filter(
|
|
206
|
+
(thread) =>
|
|
207
|
+
thread.path !== ROOT && (thread.state === "starting" || thread.state === "running"),
|
|
208
|
+
)
|
|
158
209
|
.map((thread) => thread.path);
|
|
159
210
|
const tops = live.filter((path) => !live.some((other) => path.startsWith(`${other}/`)));
|
|
160
211
|
for (const path of tops) {
|
|
161
|
-
|
|
212
|
+
this.service
|
|
162
213
|
.stop(path)
|
|
163
|
-
.catch(() =>
|
|
164
|
-
.finally(() => this.host.requestRender())
|
|
214
|
+
.catch(() => undefined)
|
|
215
|
+
.finally(() => this.host.requestRender())
|
|
216
|
+
.catch(() => undefined);
|
|
165
217
|
}
|
|
166
218
|
this.host.requestRender();
|
|
167
219
|
}
|
|
@@ -241,8 +293,18 @@ class StatusDialog {
|
|
|
241
293
|
this.host.requestRender();
|
|
242
294
|
}
|
|
243
295
|
|
|
296
|
+
private markerFor(row: StatusRow): string {
|
|
297
|
+
if (!row.hasChildren) return "";
|
|
298
|
+
return this.collapsed.has(row.path) ? "▸ " : "▾ ";
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
private missingThreadText(row: StatusRow | undefined): string {
|
|
302
|
+
if (row?.path !== ROOT) return "Missing parent";
|
|
303
|
+
return agentCount(this.service.list()) ? "Main Pi session" : "Main Pi session — No agents yet";
|
|
304
|
+
}
|
|
305
|
+
|
|
244
306
|
private line(row: StatusRow, selected: boolean): string {
|
|
245
|
-
const marker =
|
|
307
|
+
const marker = this.markerFor(row);
|
|
246
308
|
const head = ` ${row.prefix}${selected ? "›" : " "} ${marker}`;
|
|
247
309
|
const thread = row.thread;
|
|
248
310
|
if (!thread) {
|
|
@@ -253,14 +315,16 @@ class StatusDialog {
|
|
|
253
315
|
if (inactive) {
|
|
254
316
|
// Completed fades furthest (dim); the state word keeps a theme hint for done/failed.
|
|
255
317
|
const base = thread.state === "completed" ? "dim" : "muted";
|
|
256
|
-
const hint =
|
|
257
|
-
thread.state === "completed" ? "success" : thread.state === "failed" ? "error" : base;
|
|
318
|
+
const hint = hintFor(thread.state, base);
|
|
258
319
|
const state = dialogText(thread.state);
|
|
259
320
|
const path = dialogText(thread.path);
|
|
260
321
|
const status = dialogText(thread.status);
|
|
261
322
|
return [
|
|
262
323
|
this.theme.fg(selected ? "accent" : base, head),
|
|
263
|
-
this.theme.fg(
|
|
324
|
+
this.theme.fg(
|
|
325
|
+
base,
|
|
326
|
+
`${agentTypeLabel(thread.type, thread.icon, this.nerdFontIcons)} ${path} `,
|
|
327
|
+
),
|
|
264
328
|
this.theme.fg(hint, state),
|
|
265
329
|
this.theme.fg(base, ` ${status}`),
|
|
266
330
|
].join("");
|
|
@@ -268,8 +332,13 @@ class StatusDialog {
|
|
|
268
332
|
const rest = [thread.path, thread.state, thread.status]
|
|
269
333
|
.map((part) => dialogText(part))
|
|
270
334
|
.join(" ");
|
|
271
|
-
const badge = agentTypeBadge(
|
|
272
|
-
|
|
335
|
+
const badge = agentTypeBadge(
|
|
336
|
+
thread.type,
|
|
337
|
+
thread.color,
|
|
338
|
+
this.theme,
|
|
339
|
+
agentProgressIcon(thread, this.nerdFontIcons),
|
|
340
|
+
this.nerdFontIcons,
|
|
341
|
+
);
|
|
273
342
|
if (selected)
|
|
274
343
|
return `${this.theme.fg("accent", head)}${badge} ${this.theme.fg("accent", rest)}`;
|
|
275
344
|
return `${head}${badge} ${rest}`;
|
|
@@ -277,10 +346,7 @@ class StatusDialog {
|
|
|
277
346
|
|
|
278
347
|
private detail(row: StatusRow | undefined): string[] {
|
|
279
348
|
if (!row?.thread) {
|
|
280
|
-
return [
|
|
281
|
-
` ${row?.path === ROOT ? (agentCount(this.service.list()) ? "Main Pi session" : "Main Pi session — No agents yet") : "Missing parent"}`,
|
|
282
|
-
"",
|
|
283
|
-
];
|
|
349
|
+
return [` ${this.missingThreadText(row)}`, ""];
|
|
284
350
|
}
|
|
285
351
|
return [` task ${dialogText(row.thread.task)}`, ` ${threadMetrics(row.thread)}`];
|
|
286
352
|
}
|
|
@@ -312,9 +378,7 @@ class StatusDialog {
|
|
|
312
378
|
height,
|
|
313
379
|
this.heading(rows, start, treeCount),
|
|
314
380
|
body,
|
|
315
|
-
this.navigation
|
|
316
|
-
? "Esc main · ↑↓ select · ←→ fold · i Actions · Ctrl+C stop all"
|
|
317
|
-
: FOOTER,
|
|
381
|
+
this.navigation ? "Esc main · ↑↓ select · ←→ fold · i Actions · Ctrl+C stop all" : FOOTER,
|
|
318
382
|
);
|
|
319
383
|
}
|
|
320
384
|
|
|
@@ -333,10 +397,10 @@ async function showAgentDialog(
|
|
|
333
397
|
ctx: ExtensionCommandContext,
|
|
334
398
|
service: ThreadService,
|
|
335
399
|
options: {
|
|
336
|
-
title?: string;
|
|
337
|
-
selected?: string;
|
|
338
|
-
inspect?: (path: string) => Promise<void
|
|
339
|
-
nerdFontIcons?: boolean;
|
|
400
|
+
title?: string | undefined;
|
|
401
|
+
selected?: string | undefined;
|
|
402
|
+
inspect?: ((path: string) => Promise<void>) | undefined;
|
|
403
|
+
nerdFontIcons?: boolean | undefined;
|
|
340
404
|
} = {},
|
|
341
405
|
): Promise<void> {
|
|
342
406
|
const open = options.inspect ?? ((path: string) => showThreads(ctx, service, path));
|
|
@@ -347,7 +411,16 @@ async function showAgentDialog(
|
|
|
347
411
|
let dialog: StatusDialog | undefined;
|
|
348
412
|
try {
|
|
349
413
|
const chosen = await ctx.ui.custom<string | undefined>((host, theme, _keys, done) => {
|
|
350
|
-
dialog = new StatusDialog(
|
|
414
|
+
dialog = new StatusDialog(
|
|
415
|
+
host,
|
|
416
|
+
theme,
|
|
417
|
+
service,
|
|
418
|
+
done,
|
|
419
|
+
selected,
|
|
420
|
+
options.title,
|
|
421
|
+
undefined,
|
|
422
|
+
options.nerdFontIcons,
|
|
423
|
+
);
|
|
351
424
|
dialog.startRefresh();
|
|
352
425
|
return dialog;
|
|
353
426
|
}, DIALOG_OPTIONS);
|
|
@@ -390,7 +463,12 @@ export class AgentNavigationController {
|
|
|
390
463
|
this.session = undefined;
|
|
391
464
|
}
|
|
392
465
|
|
|
393
|
-
open(
|
|
466
|
+
open(
|
|
467
|
+
ctx: ExtensionContext,
|
|
468
|
+
service: ThreadService,
|
|
469
|
+
selectedPath?: string,
|
|
470
|
+
nerdFontIcons = false,
|
|
471
|
+
): Promise<void> {
|
|
394
472
|
if (this.opening) return this.opening;
|
|
395
473
|
if (!canOpenDialog(ctx)) return Promise.resolve();
|
|
396
474
|
if (selectedPath) this.tree.selectedPath = selectedPath;
|
|
@@ -481,14 +559,20 @@ export class AgentNavigationController {
|
|
|
481
559
|
await showThreads(scoped, service, chosen.path);
|
|
482
560
|
continue;
|
|
483
561
|
}
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
this.viewports.set(chosen.path, viewport);
|
|
488
|
-
}
|
|
562
|
+
const existing = this.viewports.get(chosen.path);
|
|
563
|
+
const viewport: AgentViewportState = existing ?? { scrollTop: 0, follow: true };
|
|
564
|
+
if (existing === undefined) this.viewports.set(chosen.path, viewport);
|
|
489
565
|
const result = await session.mount<"back" | "main">(
|
|
490
566
|
(tui, activeTheme, _keys, finish) =>
|
|
491
|
-
new LiveAgentView(
|
|
567
|
+
new LiveAgentView(
|
|
568
|
+
tui,
|
|
569
|
+
activeTheme,
|
|
570
|
+
service,
|
|
571
|
+
chosen.path,
|
|
572
|
+
viewport,
|
|
573
|
+
finish,
|
|
574
|
+
nerdFontIcons,
|
|
575
|
+
),
|
|
492
576
|
);
|
|
493
577
|
if (result !== "back") break;
|
|
494
578
|
}
|
|
@@ -529,7 +613,7 @@ export class AgentNavigationController {
|
|
|
529
613
|
|
|
530
614
|
const navigationByService = new WeakMap<ThreadService, AgentNavigationController>();
|
|
531
615
|
|
|
532
|
-
async function
|
|
616
|
+
async function _showAgentTree(
|
|
533
617
|
ctx: ExtensionContext,
|
|
534
618
|
service: ThreadService,
|
|
535
619
|
selectedPath?: string,
|
|
@@ -542,3 +626,8 @@ async function showAgentTree(
|
|
|
542
626
|
}
|
|
543
627
|
await navigation.open(ctx, service, selectedPath, nerdFontIcons);
|
|
544
628
|
}
|
|
629
|
+
|
|
630
|
+
function hintFor<T extends string>(state: string, base: T): T | "success" | "error" {
|
|
631
|
+
if (state === "completed") return "success";
|
|
632
|
+
return state === "failed" ? "error" : base;
|
|
633
|
+
}
|
|
@@ -25,11 +25,7 @@ function comparePath(a: string, b: string): number {
|
|
|
25
25
|
return a < b ? -1 : a > b ? 1 : 0;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
function chainHas(
|
|
29
|
-
parentOf: Map<string, string | null>,
|
|
30
|
-
start: string,
|
|
31
|
-
target: string,
|
|
32
|
-
): boolean {
|
|
28
|
+
function chainHas(parentOf: Map<string, string | null>, start: string, target: string): boolean {
|
|
33
29
|
const seen = new Set<string>();
|
|
34
30
|
let current: string | null = start;
|
|
35
31
|
while (current) {
|
|
@@ -50,8 +46,7 @@ export function buildStatusTree(
|
|
|
50
46
|
): StatusRow[] {
|
|
51
47
|
const threadsByPath = new Map<string, ThreadView>();
|
|
52
48
|
for (const thread of threads) {
|
|
53
|
-
if (thread?.path && !threadsByPath.has(thread.path))
|
|
54
|
-
threadsByPath.set(thread.path, thread);
|
|
49
|
+
if (thread?.path && !threadsByPath.has(thread.path)) threadsByPath.set(thread.path, thread);
|
|
55
50
|
}
|
|
56
51
|
const nodes = new Map<string, StatusNode>();
|
|
57
52
|
const parentOf = new Map<string, string | null>();
|
|
@@ -66,8 +61,7 @@ export function buildStatusTree(
|
|
|
66
61
|
const declaredParent = (path: string): string | null => {
|
|
67
62
|
if (path === ROOT) return null;
|
|
68
63
|
const thread = threadsByPath.get(path);
|
|
69
|
-
if (thread)
|
|
70
|
-
return thread.parent && thread.parent !== path ? thread.parent : null;
|
|
64
|
+
if (thread) return thread.parent && thread.parent !== path ? thread.parent : null;
|
|
71
65
|
return lexicalParent(path);
|
|
72
66
|
};
|
|
73
67
|
const ensure = (path: string, seen = new Set<string>()): void => {
|