@narumitw/pi-subagents 3.2.1 → 3.3.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.md +50 -6
- package/dist/chunks/chunk-FY5HIU6Y.ts +42 -0
- package/dist/chunks/chunk-FY5HIU6Y.ts.map +7 -0
- package/dist/chunks/menu-KAIUEWL7.ts +171 -0
- package/dist/chunks/menu-KAIUEWL7.ts.map +7 -0
- package/dist/chunks/settings-ui-24GHO6E2.ts +185 -0
- package/dist/chunks/settings-ui-24GHO6E2.ts.map +7 -0
- package/dist/index.ts +342 -109
- package/dist/index.ts.map +4 -4
- package/package.json +7 -6
- package/src/display-text.ts +14 -0
- package/src/menu.ts +188 -0
- package/src/message-renderers.ts +60 -0
- package/src/runtime.ts +5 -1
- package/src/settings-ui.ts +204 -0
- package/src/settings.ts +142 -0
- package/src/subagents.ts +139 -11
- package/src/tools.ts +2 -2
- package/src/widget.ts +61 -40
- package/src/completion-renderer.ts +0 -58
package/dist/index.ts
CHANGED
|
@@ -29,12 +29,29 @@ import {
|
|
|
29
29
|
serializeChildBootstrap,
|
|
30
30
|
validateMessage
|
|
31
31
|
} from "./chunks/chunk-YMVSJ5GE.ts";
|
|
32
|
+
import {
|
|
33
|
+
CHILD_CORE_TOOL_NAMES,
|
|
34
|
+
DEFAULT_SUBAGENT_TOOLS,
|
|
35
|
+
SUBAGENT_THINKING_LEVELS,
|
|
36
|
+
TERMINAL_JOB_STATES
|
|
37
|
+
} from "./chunks/chunk-FY5HIU6Y.ts";
|
|
32
38
|
|
|
33
|
-
// src/
|
|
39
|
+
// src/display-text.ts
|
|
40
|
+
function sanitizeSubagentDisplayText(value) {
|
|
41
|
+
const text = value.replace(
|
|
42
|
+
// biome-ignore lint/suspicious/noControlCharactersInRegex: Strip untrusted terminal sequences before display.
|
|
43
|
+
/(?:\u001b\[|\u009b)[0-?]*[ -/]*[@-~]|(?:\u001b[\]PX^_]|[\u0090\u0098\u009d-\u009f])[^\u0007\u001b\u0080-\u009f]*(?:\u0007|\u001b\\|\u009c)|\u001b[ -/]*[0-~]/gu,
|
|
44
|
+
""
|
|
45
|
+
);
|
|
46
|
+
return sanitizeTerminalText(text.replace(/\p{Bidi_Control}/gu, "")).replace(/\s+/gu, " ").trim();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// src/message-renderers.ts
|
|
34
50
|
import { getMarkdownTheme, keyText } from "@earendil-works/pi-coding-agent";
|
|
35
51
|
import { Box, Markdown, Spacer, Text } from "@earendil-works/pi-tui";
|
|
36
52
|
var COMPLETION_MESSAGE_TYPE = "pi-subagents-completion";
|
|
37
|
-
|
|
53
|
+
var BROKER_MESSAGE_TYPE = "pi-subagents-message";
|
|
54
|
+
function createMessageBox(children, outputPad, background) {
|
|
38
55
|
return {
|
|
39
56
|
render(width) {
|
|
40
57
|
if (width <= 0) return [];
|
|
@@ -48,30 +65,157 @@ function createCompletionBox(children, outputPad, background) {
|
|
|
48
65
|
}
|
|
49
66
|
};
|
|
50
67
|
}
|
|
51
|
-
function
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
new
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
68
|
+
function registerMessageRenderers(pi) {
|
|
69
|
+
for (const [messageType, summary] of [
|
|
70
|
+
[COMPLETION_MESSAGE_TYPE, "Subagent job completion"],
|
|
71
|
+
[BROKER_MESSAGE_TYPE, "Subagent message"]
|
|
72
|
+
]) {
|
|
73
|
+
pi.registerMessageRenderer(messageType, (message, options, theme) => {
|
|
74
|
+
const children = [
|
|
75
|
+
new Text(theme.fg("customMessageLabel", theme.bold(`[${messageType}]`)), 0, 0),
|
|
76
|
+
new Spacer(1)
|
|
77
|
+
];
|
|
78
|
+
if (!options.expanded) {
|
|
79
|
+
children.push(
|
|
80
|
+
new Text(
|
|
81
|
+
theme.fg("customMessageText", `${summary} (`) + theme.fg("dim", keyText("app.tools.expand")) + theme.fg("customMessageText", " to expand)"),
|
|
82
|
+
0,
|
|
83
|
+
0
|
|
84
|
+
)
|
|
85
|
+
);
|
|
86
|
+
} else {
|
|
87
|
+
const content = typeof message.content === "string" ? message.content : message.content.filter((part) => part.type === "text").map((part) => part.text).join("\n");
|
|
88
|
+
children.push(
|
|
89
|
+
new Markdown(sanitizeTerminalText(content), 0, 0, getMarkdownTheme(), {
|
|
90
|
+
color: (text) => theme.fg("customMessageText", text)
|
|
91
|
+
})
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
return createMessageBox(children, options.outputPad, (text) => theme.bg("customMessageBg", text));
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// src/settings.ts
|
|
100
|
+
import fs from "node:fs/promises";
|
|
101
|
+
import { dirname, join } from "node:path";
|
|
102
|
+
import { getAgentDir, withFileMutationQueue } from "@earendil-works/pi-coding-agent";
|
|
103
|
+
var DEFAULT_SUBAGENT_SETTINGS = Object.freeze({
|
|
104
|
+
showWidget: true,
|
|
105
|
+
showJobId: true,
|
|
106
|
+
showTools: true,
|
|
107
|
+
showTiming: true
|
|
108
|
+
});
|
|
109
|
+
var SETTINGS_KEYS = Object.keys(DEFAULT_SUBAGENT_SETTINGS);
|
|
110
|
+
function subagentSettingsPath() {
|
|
111
|
+
return join(getAgentDir(), "pi-subagents.json");
|
|
112
|
+
}
|
|
113
|
+
var SubagentSettingsStore = class {
|
|
114
|
+
constructor(userPath) {
|
|
115
|
+
this.userPath = userPath;
|
|
116
|
+
}
|
|
117
|
+
userPath;
|
|
118
|
+
effective = { ...DEFAULT_SUBAGENT_SETTINGS };
|
|
119
|
+
tail = Promise.resolve();
|
|
120
|
+
get settings() {
|
|
121
|
+
return { ...this.effective };
|
|
122
|
+
}
|
|
123
|
+
load() {
|
|
124
|
+
return this.enqueue(async () => {
|
|
125
|
+
const loaded = await readDocument(this.userPath);
|
|
126
|
+
const settings = normalizeSettings(loaded.document ?? {}, loaded.diagnostics);
|
|
127
|
+
this.effective = settings;
|
|
128
|
+
return { settings: this.settings, diagnostics: loaded.diagnostics };
|
|
129
|
+
}).catch((error) => ({
|
|
130
|
+
settings: this.settings,
|
|
131
|
+
diagnostics: [readDiagnostic(this.userPath, error)]
|
|
132
|
+
}));
|
|
133
|
+
}
|
|
134
|
+
save(key, value, signal) {
|
|
135
|
+
return this.enqueue(async () => {
|
|
136
|
+
signal.throwIfAborted();
|
|
137
|
+
if (!SETTINGS_KEYS.includes(key) || typeof value !== "boolean") {
|
|
138
|
+
throw new Error("Invalid subagent settings update");
|
|
139
|
+
}
|
|
140
|
+
const loaded = await readDocument(this.userPath);
|
|
141
|
+
signal.throwIfAborted();
|
|
142
|
+
if (!loaded.document) {
|
|
143
|
+
throw new Error(`Cannot save subagent settings: ${loaded.diagnostics.join("; ")}`);
|
|
144
|
+
}
|
|
145
|
+
const document = { ...loaded.document, [key]: value };
|
|
146
|
+
const settings = normalizeSettings(document, []);
|
|
147
|
+
const parent = dirname(this.userPath);
|
|
148
|
+
await fs.mkdir(parent, { recursive: true });
|
|
149
|
+
signal.throwIfAborted();
|
|
150
|
+
const temporary = await fs.mkdtemp(join(parent, ".pi-subagents-"));
|
|
151
|
+
try {
|
|
152
|
+
signal.throwIfAborted();
|
|
153
|
+
const staged = join(temporary, "settings.json");
|
|
154
|
+
await fs.writeFile(staged, `${JSON.stringify(document, null, 2)}
|
|
155
|
+
`, { flag: "wx", signal });
|
|
156
|
+
signal.throwIfAborted();
|
|
157
|
+
await fs.rename(staged, this.userPath);
|
|
158
|
+
this.effective = settings;
|
|
159
|
+
return this.settings;
|
|
160
|
+
} finally {
|
|
161
|
+
await fs.rm(temporary, { recursive: true, force: true }).catch(() => {
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
flush() {
|
|
167
|
+
return this.tail;
|
|
168
|
+
}
|
|
169
|
+
enqueue(operation) {
|
|
170
|
+
const previous = this.tail;
|
|
171
|
+
const result = withFileMutationQueue(this.userPath, async () => {
|
|
172
|
+
await previous;
|
|
173
|
+
return operation();
|
|
174
|
+
});
|
|
175
|
+
this.tail = result.then(
|
|
176
|
+
() => {
|
|
177
|
+
},
|
|
178
|
+
() => {
|
|
179
|
+
}
|
|
180
|
+
);
|
|
181
|
+
return result;
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
async function readDocument(path4) {
|
|
185
|
+
let text;
|
|
186
|
+
try {
|
|
187
|
+
text = await fs.readFile(path4, "utf8");
|
|
188
|
+
} catch (error) {
|
|
189
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
190
|
+
return { document: {}, diagnostics: [] };
|
|
191
|
+
}
|
|
192
|
+
return { diagnostics: [readDiagnostic(path4, error)] };
|
|
193
|
+
}
|
|
194
|
+
let document;
|
|
195
|
+
try {
|
|
196
|
+
document = JSON.parse(text);
|
|
197
|
+
} catch {
|
|
198
|
+
return { diagnostics: [`Cannot load subagent settings at ${path4}: invalid JSON`] };
|
|
199
|
+
}
|
|
200
|
+
if (typeof document !== "object" || document === null || Array.isArray(document)) {
|
|
201
|
+
return { diagnostics: [`Cannot load subagent settings at ${path4}: expected a JSON object`] };
|
|
202
|
+
}
|
|
203
|
+
return { document, diagnostics: [] };
|
|
204
|
+
}
|
|
205
|
+
function normalizeSettings(document, diagnostics) {
|
|
206
|
+
const settings = { ...DEFAULT_SUBAGENT_SETTINGS };
|
|
207
|
+
for (const key of SETTINGS_KEYS) {
|
|
208
|
+
if (!Object.hasOwn(document, key)) continue;
|
|
209
|
+
if (typeof document[key] === "boolean") {
|
|
210
|
+
settings[key] = document[key];
|
|
65
211
|
} else {
|
|
66
|
-
|
|
67
|
-
children.push(
|
|
68
|
-
new Markdown(sanitizeTerminalText(content), 0, 0, getMarkdownTheme(), {
|
|
69
|
-
color: (text) => theme.fg("customMessageText", text)
|
|
70
|
-
})
|
|
71
|
-
);
|
|
212
|
+
diagnostics.push(`Ignored invalid subagent setting ${key}: expected a boolean`);
|
|
72
213
|
}
|
|
73
|
-
|
|
74
|
-
|
|
214
|
+
}
|
|
215
|
+
return settings;
|
|
216
|
+
}
|
|
217
|
+
function readDiagnostic(path4, error) {
|
|
218
|
+
return `Cannot load subagent settings at ${path4}: ${error instanceof Error ? error.message : String(error)}`;
|
|
75
219
|
}
|
|
76
220
|
|
|
77
221
|
// src/tools.ts
|
|
@@ -114,7 +258,7 @@ function modelVisibleJson(value, options = {}) {
|
|
|
114
258
|
|
|
115
259
|
// src/process.ts
|
|
116
260
|
import { spawn } from "node:child_process";
|
|
117
|
-
import * as
|
|
261
|
+
import * as fs2 from "node:fs";
|
|
118
262
|
import * as path from "node:path";
|
|
119
263
|
import { StringDecoder } from "node:string_decoder";
|
|
120
264
|
import { fileURLToPath } from "node:url";
|
|
@@ -696,23 +840,23 @@ async function executeProcess(invocation, request) {
|
|
|
696
840
|
};
|
|
697
841
|
}
|
|
698
842
|
function resolvePiInvocation(args) {
|
|
699
|
-
const packageDirectory =
|
|
843
|
+
const packageDirectory = fs2.realpathSync(getPackageDir());
|
|
700
844
|
const manifestPath = path.join(packageDirectory, "package.json");
|
|
701
|
-
const manifest = JSON.parse(
|
|
845
|
+
const manifest = JSON.parse(fs2.readFileSync(manifestPath, "utf8"));
|
|
702
846
|
if (manifest.name !== CORE_PACKAGE_NAME || typeof manifest.bin?.pi !== "string") {
|
|
703
847
|
throw new Error("Loaded Pi core package does not declare a valid bin.pi entry.");
|
|
704
848
|
}
|
|
705
849
|
const declared = manifest.bin.pi;
|
|
706
850
|
if (path.isAbsolute(declared)) throw new Error("Pi core bin.pi must be package-relative.");
|
|
707
|
-
if (globalThis.process.versions.bun && /^pi(?:\.exe)?$/iu.test(path.basename(globalThis.process.execPath)) && path.dirname(
|
|
851
|
+
if (globalThis.process.versions.bun && /^pi(?:\.exe)?$/iu.test(path.basename(globalThis.process.execPath)) && path.dirname(fs2.realpathSync(globalThis.process.execPath)) === packageDirectory) {
|
|
708
852
|
return { command: globalThis.process.execPath, args };
|
|
709
853
|
}
|
|
710
|
-
const cliPath =
|
|
854
|
+
const cliPath = fs2.realpathSync(path.resolve(packageDirectory, declared));
|
|
711
855
|
const relative4 = path.relative(packageDirectory, cliPath);
|
|
712
856
|
if (relative4 === ".." || relative4.startsWith(`..${path.sep}`) || path.isAbsolute(relative4)) {
|
|
713
857
|
throw new Error("Pi core bin.pi escapes its package directory.");
|
|
714
858
|
}
|
|
715
|
-
if (!
|
|
859
|
+
if (!fs2.statSync(cliPath).isFile()) throw new Error("Pi core bin.pi is not a file.");
|
|
716
860
|
return { command: globalThis.process.execPath, args: [cliPath, ...args] };
|
|
717
861
|
}
|
|
718
862
|
function signalPosixProcess(process2, signal) {
|
|
@@ -783,7 +927,7 @@ function assertTrustedChildResources(value, cwd) {
|
|
|
783
927
|
if (!Array.isArray(commands)) throw invalid();
|
|
784
928
|
let canonicalCwd;
|
|
785
929
|
try {
|
|
786
|
-
canonicalCwd =
|
|
930
|
+
canonicalCwd = fs2.realpathSync(cwd);
|
|
787
931
|
} catch {
|
|
788
932
|
throw invalid();
|
|
789
933
|
}
|
|
@@ -796,7 +940,7 @@ function assertTrustedChildResources(value, cwd) {
|
|
|
796
940
|
const lexicalPath = path.resolve(cwd, resourcePath);
|
|
797
941
|
let canonicalPath;
|
|
798
942
|
try {
|
|
799
|
-
canonicalPath =
|
|
943
|
+
canonicalPath = fs2.realpathSync(lexicalPath);
|
|
800
944
|
} catch {
|
|
801
945
|
throw invalid();
|
|
802
946
|
}
|
|
@@ -1193,36 +1337,6 @@ function deduplicatePackageSkillPaths(packageSkillPaths, explicitSkillPaths) {
|
|
|
1193
1337
|
return uniquePaths;
|
|
1194
1338
|
}
|
|
1195
1339
|
|
|
1196
|
-
// src/types.ts
|
|
1197
|
-
var TERMINAL_JOB_STATES = /* @__PURE__ */ new Set([
|
|
1198
|
-
"completed",
|
|
1199
|
-
"partial",
|
|
1200
|
-
"failed",
|
|
1201
|
-
"timed_out",
|
|
1202
|
-
"cancelled"
|
|
1203
|
-
]);
|
|
1204
|
-
var CHILD_CORE_TOOL_NAMES = [
|
|
1205
|
-
"read",
|
|
1206
|
-
"bash",
|
|
1207
|
-
"powershell",
|
|
1208
|
-
"edit",
|
|
1209
|
-
"write",
|
|
1210
|
-
"grep",
|
|
1211
|
-
"find",
|
|
1212
|
-
"ls",
|
|
1213
|
-
"codemode"
|
|
1214
|
-
];
|
|
1215
|
-
var DEFAULT_SUBAGENT_TOOLS = ["read", "grep", "find", "ls"];
|
|
1216
|
-
var SUBAGENT_THINKING_LEVELS = [
|
|
1217
|
-
"off",
|
|
1218
|
-
"minimal",
|
|
1219
|
-
"low",
|
|
1220
|
-
"medium",
|
|
1221
|
-
"high",
|
|
1222
|
-
"xhigh",
|
|
1223
|
-
"max"
|
|
1224
|
-
];
|
|
1225
|
-
|
|
1226
1340
|
// src/resource-attachments.ts
|
|
1227
1341
|
var MAX_ATTACHED_SKILLS = 16;
|
|
1228
1342
|
var MAX_ATTACHED_EXTENSIONS = 16;
|
|
@@ -1775,6 +1889,7 @@ var SubagentRuntime = class {
|
|
|
1775
1889
|
jobId: job.jobId,
|
|
1776
1890
|
task: job.taskText,
|
|
1777
1891
|
state: job.state,
|
|
1892
|
+
thinkingLevel: job.thinkingLevel,
|
|
1778
1893
|
elapsedMs: Math.max(0, now - (job.startedAt ?? job.createdAt)),
|
|
1779
1894
|
...job.timeout !== void 0 ? { timeout: job.timeout } : {},
|
|
1780
1895
|
tools: [...job.tools]
|
|
@@ -1816,6 +1931,7 @@ var SubagentRuntime = class {
|
|
|
1816
1931
|
controller,
|
|
1817
1932
|
tools: effectiveTools,
|
|
1818
1933
|
taskText: input.task,
|
|
1934
|
+
thinkingLevel: input.thinkingLevel,
|
|
1819
1935
|
terminal,
|
|
1820
1936
|
resolveTerminal,
|
|
1821
1937
|
controlReady,
|
|
@@ -2152,7 +2268,6 @@ function abortError2(message) {
|
|
|
2152
2268
|
// src/tools.ts
|
|
2153
2269
|
var MAX_TASK_BYTES = 50 * 1024;
|
|
2154
2270
|
var MAX_TOOLS = MAX_SELECTED_TOOLS;
|
|
2155
|
-
var MESSAGE_TYPE = "pi-subagents-message";
|
|
2156
2271
|
var CHILD_CORE_TOOL_SET = new Set(CHILD_CORE_TOOL_NAMES);
|
|
2157
2272
|
var THINKING_LEVEL_SET = new Set(SUBAGENT_THINKING_LEVELS);
|
|
2158
2273
|
var SpawnParameters = Type.Object(
|
|
@@ -2420,7 +2535,7 @@ function deliverMessage(pi, message) {
|
|
|
2420
2535
|
);
|
|
2421
2536
|
pi.sendMessage(
|
|
2422
2537
|
{
|
|
2423
|
-
customType:
|
|
2538
|
+
customType: BROKER_MESSAGE_TYPE,
|
|
2424
2539
|
content,
|
|
2425
2540
|
display: true,
|
|
2426
2541
|
details: {
|
|
@@ -2532,6 +2647,7 @@ function toolResult(value) {
|
|
|
2532
2647
|
|
|
2533
2648
|
// src/widget.ts
|
|
2534
2649
|
import { stripTerminalSequences, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
2650
|
+
import { EditorStatusWidget } from "@narumitw/pi-tui-kit/editor-status-widget";
|
|
2535
2651
|
var SUBAGENT_WIDGET_KEY = "subagents";
|
|
2536
2652
|
var SUBAGENT_WIDGET_REFRESH_INTERVAL_MS = 1e3;
|
|
2537
2653
|
var WIDGET_OPTIONS = { placement: "aboveEditor" };
|
|
@@ -2541,6 +2657,8 @@ function createSubagentWidgetController(runtime) {
|
|
|
2541
2657
|
let refreshTimer;
|
|
2542
2658
|
let unsubscribeJobs;
|
|
2543
2659
|
let publishedValue;
|
|
2660
|
+
let settings = { ...DEFAULT_SUBAGENT_SETTINGS };
|
|
2661
|
+
let clearWidget;
|
|
2544
2662
|
const ownsSession = (ctx) => ctx.sessionManager === activeSession;
|
|
2545
2663
|
const stop = () => {
|
|
2546
2664
|
if (refreshTimer) clearInterval(refreshTimer);
|
|
@@ -2550,87 +2668,93 @@ function createSubagentWidgetController(runtime) {
|
|
|
2550
2668
|
};
|
|
2551
2669
|
const publish = (ctx) => {
|
|
2552
2670
|
if (ctx.mode !== "tui" || !ownsSession(ctx)) return;
|
|
2553
|
-
const jobs = runtime.activeJobsForDisplay();
|
|
2554
|
-
const value =
|
|
2671
|
+
const jobs = settings.showWidget ? runtime.activeJobsForDisplay() : [];
|
|
2672
|
+
const value = `${JSON.stringify(settings)}
|
|
2673
|
+
${widgetValue(jobs)}`;
|
|
2555
2674
|
if (value === publishedValue) return;
|
|
2556
2675
|
if (jobs.length === 0) {
|
|
2557
2676
|
ctx.ui.setWidget(SUBAGENT_WIDGET_KEY, void 0);
|
|
2558
2677
|
} else {
|
|
2559
2678
|
const snapshot = jobs.map(cloneDisplayJob);
|
|
2679
|
+
const preferences = { ...settings };
|
|
2560
2680
|
ctx.ui.setWidget(
|
|
2561
2681
|
SUBAGENT_WIDGET_KEY,
|
|
2562
|
-
(_tui, theme) => (
|
|
2563
|
-
render: (width) => renderSubagentWidget(snapshot, theme, width),
|
|
2564
|
-
invalidate: () => {
|
|
2565
|
-
}
|
|
2566
|
-
}),
|
|
2682
|
+
(_tui, theme) => createSubagentWidget(snapshot, theme, preferences),
|
|
2567
2683
|
WIDGET_OPTIONS
|
|
2568
2684
|
);
|
|
2569
2685
|
}
|
|
2570
2686
|
publishedValue = value;
|
|
2571
2687
|
};
|
|
2572
2688
|
return {
|
|
2573
|
-
start(ctx) {
|
|
2689
|
+
start(ctx, preferences = DEFAULT_SUBAGENT_SETTINGS) {
|
|
2574
2690
|
stop();
|
|
2691
|
+
clearWidget?.();
|
|
2692
|
+
clearWidget = void 0;
|
|
2693
|
+
settings = { ...preferences };
|
|
2575
2694
|
activeSession = ctx.sessionManager;
|
|
2576
2695
|
publishedValue = void 0;
|
|
2577
2696
|
if (ctx.mode !== "tui") return;
|
|
2578
|
-
ctx.ui.setWidget(SUBAGENT_WIDGET_KEY, void 0);
|
|
2697
|
+
clearWidget = () => ctx.ui.setWidget(SUBAGENT_WIDGET_KEY, void 0);
|
|
2698
|
+
clearWidget();
|
|
2579
2699
|
unsubscribeJobs = runtime.subscribeJobs(() => publish(ctx));
|
|
2580
2700
|
refreshTimer = setInterval(() => publish(ctx), SUBAGENT_WIDGET_REFRESH_INTERVAL_MS);
|
|
2581
2701
|
refreshTimer.unref();
|
|
2582
2702
|
publish(ctx);
|
|
2583
2703
|
},
|
|
2704
|
+
update(ctx, preferences) {
|
|
2705
|
+
if (!ownsSession(ctx)) return;
|
|
2706
|
+
settings = { ...preferences };
|
|
2707
|
+
publish(ctx);
|
|
2708
|
+
},
|
|
2584
2709
|
shutdown(ctx) {
|
|
2585
2710
|
if (!ownsSession(ctx)) return;
|
|
2586
2711
|
stop();
|
|
2587
|
-
|
|
2712
|
+
clearWidget?.();
|
|
2713
|
+
clearWidget = void 0;
|
|
2588
2714
|
publishedValue = void 0;
|
|
2589
2715
|
activeSession = void 0;
|
|
2590
2716
|
}
|
|
2591
2717
|
};
|
|
2592
2718
|
}
|
|
2593
|
-
function
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2719
|
+
function createSubagentWidget(jobs, theme, settings) {
|
|
2720
|
+
return new EditorStatusWidget({
|
|
2721
|
+
theme,
|
|
2722
|
+
renderBody: () => [
|
|
2723
|
+
theme.fg("muted", `Subagents \xB7 ${jobs.length} active`),
|
|
2724
|
+
...jobs.map((job) => renderJob(job, theme, settings))
|
|
2725
|
+
]
|
|
2726
|
+
});
|
|
2601
2727
|
}
|
|
2602
|
-
function renderJob(job, theme) {
|
|
2728
|
+
function renderJob(job, theme, settings) {
|
|
2603
2729
|
const running = job.state === "running";
|
|
2604
2730
|
const symbol = theme.fg(running ? "accent" : "dim", running ? "\u25B6 " : "\u25CB ");
|
|
2605
2731
|
const state = theme.fg(running ? "accent" : "muted", job.state);
|
|
2606
|
-
const jobId =
|
|
2607
|
-
const taskText =
|
|
2732
|
+
const jobId = sanitizeSubagentDisplayText(job.jobId);
|
|
2733
|
+
const taskText = sanitizeSubagentDisplayText(job.task);
|
|
2608
2734
|
const task = stripTerminalSequences(truncateToWidth(taskText, TASK_PREVIEW_WIDTH, "\u2026"));
|
|
2609
|
-
const
|
|
2610
|
-
|
|
2735
|
+
const labels = [
|
|
2736
|
+
...settings.showJobId ? [theme.fg("text", jobId)] : [],
|
|
2737
|
+
...visibleWidth(task) > 0 ? [theme.fg("text", task)] : [],
|
|
2738
|
+
state
|
|
2739
|
+
];
|
|
2740
|
+
const tools = job.tools.length > 0 ? job.tools.map(sanitizeSubagentDisplayText).join(", ") : "none";
|
|
2611
2741
|
const timeout = job.timeout === void 0 ? "no timeout" : formatSeconds(job.timeout);
|
|
2612
|
-
const
|
|
2613
|
-
|
|
2742
|
+
const details = [
|
|
2743
|
+
`thinking: ${sanitizeSubagentDisplayText(job.thinkingLevel)}`,
|
|
2744
|
+
...settings.showTiming ? [`${formatSeconds(Math.floor(job.elapsedMs / 1e3))} / ${timeout}`] : [],
|
|
2745
|
+
...settings.showTools ? [`tools: ${tools}`] : []
|
|
2746
|
+
];
|
|
2747
|
+
const detail = details.length > 0 ? theme.fg("muted", ` \xB7 ${details.join(" \xB7 ")}`) : "";
|
|
2748
|
+
return `${symbol}${labels.join(" \xB7 ")}${detail}`;
|
|
2614
2749
|
}
|
|
2615
2750
|
function widgetValue(jobs) {
|
|
2616
2751
|
return jobs.map(
|
|
2617
|
-
(job) => `${job.jobId}\0${job.task}\0${job.state}\0${Math.floor(job.elapsedMs / 1e3)}\0${job.timeout ?? ""}\0${job.tools.join(",")}`
|
|
2752
|
+
(job) => `${job.jobId}\0${job.task}\0${job.state}\0${job.thinkingLevel}\0${Math.floor(job.elapsedMs / 1e3)}\0${job.timeout ?? ""}\0${job.tools.join(",")}`
|
|
2618
2753
|
).join("\n");
|
|
2619
2754
|
}
|
|
2620
2755
|
function cloneDisplayJob(job) {
|
|
2621
2756
|
return { ...job, tools: [...job.tools] };
|
|
2622
2757
|
}
|
|
2623
|
-
function sanitizeTask(value) {
|
|
2624
|
-
const text = value.replace(
|
|
2625
|
-
// biome-ignore lint/suspicious/noControlCharactersInRegex: Remove untrusted terminal escape sequences before display.
|
|
2626
|
-
/(?:\u001b\[|\u009b)[0-?]*[ -/]*[@-~]|(?:\u001b[\]PX^_]|[\u0090\u0098\u009d-\u009f])[^\u0007\u001b\u0080-\u009f]*(?:\u0007|\u001b\\|\u009c)|\u001b[ -/]*[0-~]/gu,
|
|
2627
|
-
""
|
|
2628
|
-
);
|
|
2629
|
-
return sanitizeLabel(text.replace(/\p{Bidi_Control}/gu, ""));
|
|
2630
|
-
}
|
|
2631
|
-
function sanitizeLabel(value) {
|
|
2632
|
-
return sanitizeTerminalText(value).replace(/\s+/gu, " ").trim();
|
|
2633
|
-
}
|
|
2634
2758
|
function formatSeconds(value) {
|
|
2635
2759
|
if (value < 60) return `${formatNumber(value)}s`;
|
|
2636
2760
|
const wholeSeconds = Math.floor(value);
|
|
@@ -2646,24 +2770,133 @@ function formatNumber(value) {
|
|
|
2646
2770
|
|
|
2647
2771
|
// src/subagents.ts
|
|
2648
2772
|
function subagents(pi, dependencies = {}) {
|
|
2649
|
-
|
|
2773
|
+
registerMessageRenderers(pi);
|
|
2650
2774
|
const tools = registerSubagentTools(pi, dependencies);
|
|
2651
2775
|
const widget = createSubagentWidgetController(tools.runtime);
|
|
2652
|
-
|
|
2776
|
+
const store = dependencies.settingsStore ?? new SubagentSettingsStore(subagentSettingsPath());
|
|
2777
|
+
let activeContext;
|
|
2653
2778
|
let sessionGeneration = 0;
|
|
2779
|
+
let owner = new AbortController();
|
|
2780
|
+
let ready = false;
|
|
2781
|
+
let menuOpen = false;
|
|
2782
|
+
let settings = { ...DEFAULT_SUBAGENT_SETTINGS };
|
|
2783
|
+
const menuTasks = /* @__PURE__ */ new Set();
|
|
2784
|
+
const ownsSession = (ctx) => ctx.sessionManager === activeContext?.sessionManager;
|
|
2785
|
+
const openSettings = async (ctx, signal, isCurrent) => {
|
|
2786
|
+
if (signal.aborted || !isCurrent()) return "stale";
|
|
2787
|
+
if (ctx.mode !== "tui") {
|
|
2788
|
+
ctx.ui.notify(sanitizeSubagentDisplayText(`Edit user settings: ${store.userPath}. Then run /reload.`), "info");
|
|
2789
|
+
return "back";
|
|
2790
|
+
}
|
|
2791
|
+
const { showSubagentSettings } = await import("./chunks/settings-ui-24GHO6E2.ts");
|
|
2792
|
+
if (signal.aborted || !isCurrent()) return "stale";
|
|
2793
|
+
return showSubagentSettings(ctx, {
|
|
2794
|
+
signal,
|
|
2795
|
+
isCurrent,
|
|
2796
|
+
get: () => ({ ...settings }),
|
|
2797
|
+
save: (key, value, actionSignal) => {
|
|
2798
|
+
actionSignal.throwIfAborted();
|
|
2799
|
+
if (!isCurrent()) throw new Error("The subagents session changed.");
|
|
2800
|
+
return store.save(key, value, actionSignal);
|
|
2801
|
+
},
|
|
2802
|
+
apply: (next) => {
|
|
2803
|
+
if (!isCurrent()) return;
|
|
2804
|
+
settings = { ...next };
|
|
2805
|
+
widget.update(ctx, settings);
|
|
2806
|
+
}
|
|
2807
|
+
});
|
|
2808
|
+
};
|
|
2809
|
+
pi.registerCommand("subagents", {
|
|
2810
|
+
description: "Manage subagent jobs and widget settings",
|
|
2811
|
+
getArgumentCompletions: (prefix) => "settings".startsWith(prefix) ? [{ value: "settings", label: "settings", description: "Configure the subagent widget" }] : null,
|
|
2812
|
+
handler: async (args, ctx) => {
|
|
2813
|
+
if (!ctx.hasUI || ctx.mode !== "tui" && ctx.mode !== "rpc") {
|
|
2814
|
+
throw new Error("/subagents requires TUI or RPC mode.");
|
|
2815
|
+
}
|
|
2816
|
+
const route = args.trim();
|
|
2817
|
+
if (route !== "" && route !== "settings") {
|
|
2818
|
+
ctx.ui.notify("Usage: /subagents | /subagents settings", "warning");
|
|
2819
|
+
return;
|
|
2820
|
+
}
|
|
2821
|
+
if (!ownsSession(ctx) || !ready || menuOpen) {
|
|
2822
|
+
ctx.ui.notify("Subagent controls are loading, unavailable, or already open.", "warning");
|
|
2823
|
+
return;
|
|
2824
|
+
}
|
|
2825
|
+
menuOpen = true;
|
|
2826
|
+
const generation = sessionGeneration;
|
|
2827
|
+
const signal = owner.signal;
|
|
2828
|
+
const isCurrent = () => generation === sessionGeneration && ownsSession(ctx) && !signal.aborted;
|
|
2829
|
+
const task = (async () => {
|
|
2830
|
+
if (route === "settings") {
|
|
2831
|
+
await openSettings(ctx, signal, isCurrent);
|
|
2832
|
+
return;
|
|
2833
|
+
}
|
|
2834
|
+
const { showSubagentsMenu } = await import("./chunks/menu-KAIUEWL7.ts");
|
|
2835
|
+
if (!isCurrent()) return;
|
|
2836
|
+
await showSubagentsMenu(ctx, {
|
|
2837
|
+
signal,
|
|
2838
|
+
isCurrent,
|
|
2839
|
+
getState: () => ({
|
|
2840
|
+
settings: { ...settings },
|
|
2841
|
+
settingsPath: store.userPath,
|
|
2842
|
+
...tools.runtime.inspectJobs()
|
|
2843
|
+
}),
|
|
2844
|
+
cancelJob: async (jobId, actionSignal) => {
|
|
2845
|
+
actionSignal.throwIfAborted();
|
|
2846
|
+
if (!isCurrent()) throw new Error("The subagents session changed.");
|
|
2847
|
+
await tools.runtime.cancel(jobId);
|
|
2848
|
+
},
|
|
2849
|
+
showSettings: (actionSignal) => openSettings(ctx, actionSignal, isCurrent)
|
|
2850
|
+
});
|
|
2851
|
+
})();
|
|
2852
|
+
menuTasks.add(task);
|
|
2853
|
+
try {
|
|
2854
|
+
await task;
|
|
2855
|
+
} catch (error) {
|
|
2856
|
+
if (isCurrent()) {
|
|
2857
|
+
ctx.ui.notify(sanitizeSubagentDisplayText(error instanceof Error ? error.message : String(error)), "error");
|
|
2858
|
+
}
|
|
2859
|
+
} finally {
|
|
2860
|
+
menuTasks.delete(task);
|
|
2861
|
+
if (isCurrent()) menuOpen = false;
|
|
2862
|
+
}
|
|
2863
|
+
}
|
|
2864
|
+
});
|
|
2654
2865
|
pi.on("session_start", async (_event, ctx) => {
|
|
2655
|
-
|
|
2866
|
+
owner.abort();
|
|
2867
|
+
if (activeContext) widget.shutdown(activeContext);
|
|
2868
|
+
owner = new AbortController();
|
|
2869
|
+
activeContext = ctx;
|
|
2870
|
+
ready = false;
|
|
2871
|
+
menuOpen = false;
|
|
2656
2872
|
const generation = ++sessionGeneration;
|
|
2873
|
+
const isCurrent = () => generation === sessionGeneration && ownsSession(ctx) && !owner.signal.aborted;
|
|
2657
2874
|
await tools.startSession();
|
|
2658
|
-
if (
|
|
2659
|
-
|
|
2875
|
+
if (!isCurrent()) return;
|
|
2876
|
+
await Promise.allSettled([...menuTasks]);
|
|
2877
|
+
if (!isCurrent()) return;
|
|
2878
|
+
const loaded = await store.load();
|
|
2879
|
+
if (!isCurrent()) return;
|
|
2880
|
+
settings = { ...loaded.settings };
|
|
2881
|
+
ready = true;
|
|
2882
|
+
widget.start(ctx, settings);
|
|
2883
|
+
if (loaded.diagnostics.length > 0) {
|
|
2884
|
+
const message = sanitizeSubagentDisplayText(`Subagent settings: ${loaded.diagnostics.join(" ")}`);
|
|
2885
|
+
if (ctx.hasUI) ctx.ui.notify(message, "warning");
|
|
2886
|
+
else throw new Error(message);
|
|
2887
|
+
}
|
|
2660
2888
|
});
|
|
2661
2889
|
pi.on("session_shutdown", async (_event, ctx) => {
|
|
2662
|
-
if (ctx
|
|
2890
|
+
if (!ownsSession(ctx)) return;
|
|
2663
2891
|
sessionGeneration++;
|
|
2664
|
-
|
|
2892
|
+
owner.abort();
|
|
2893
|
+
ready = false;
|
|
2894
|
+
menuOpen = false;
|
|
2895
|
+
activeContext = void 0;
|
|
2665
2896
|
widget.shutdown(ctx);
|
|
2666
|
-
await tools.shutdown();
|
|
2897
|
+
const results = await Promise.allSettled([tools.shutdown(), ...menuTasks, store.flush()]);
|
|
2898
|
+
const failed = results.find((result) => result.status === "rejected");
|
|
2899
|
+
if (failed?.status === "rejected") throw failed.reason;
|
|
2667
2900
|
});
|
|
2668
2901
|
}
|
|
2669
2902
|
export {
|