@narumitw/pi-subagents 3.1.1 → 3.2.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/README.md +14 -2
- package/dist/index.ts +31 -6
- package/dist/index.ts.map +2 -2
- package/docs/tools.md +6 -1
- package/package.json +7 -7
- package/src/process.ts +3 -2
- package/src/runtime.ts +4 -0
- package/src/tools.ts +1 -1
- package/src/types.ts +11 -1
- package/src/widget.ts +19 -3
package/docs/tools.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
| Parameter | Type | Required | Constraint / default |
|
|
6
6
|
| --- | --- | --- | --- |
|
|
7
7
|
| `task` | `string` | Yes | Self-contained task, up to 50 KiB of UTF-8 text. |
|
|
8
|
-
| `tools` | `string[]` | No | Pi
|
|
8
|
+
| `tools` | `string[]` | No | Pi tools: `read`, `bash`, `powershell`, `edit`, `write`, `grep`, `find`, `ls`, `codemode`; defaults to `read`, `grep`, `find`, `ls`. |
|
|
9
9
|
| `skills` | `string[]` | No | Up to 16 local skill files or directories; Pi uses progressive disclosure. |
|
|
10
10
|
| `extensions` | `{ path: string; tools: string[] }[]` | No | Up to 16 trusted local extensions and their initially selected tools. |
|
|
11
11
|
| `thinkingLevel` | `string` | No | `off`, `minimal`, `low`, `medium`, `high`, `xhigh`, or `max`; defaults to the main agent's level. |
|
|
@@ -13,6 +13,11 @@
|
|
|
13
13
|
|
|
14
14
|
Starts one job and returns its `jobId` immediately. The child inherits the main agent's effective model and always receives `subagent_send` and `subagent_wait`.
|
|
15
15
|
|
|
16
|
+
Select `codemode` explicitly with the other required tools, for example `tools: ["read", "grep", "codemode"]`.
|
|
17
|
+
Pi's built-in codemode extension runs scripts against the child's callable tools, including `subagent_send` and `subagent_wait`; automatic resource discovery stays disabled.
|
|
18
|
+
Codemode also exposes Pi's classifier and image model API using child-visible credentials; these calls can send data to providers and incur costs.
|
|
19
|
+
The default tools do not include codemode.
|
|
20
|
+
|
|
16
21
|
Skills do not add tools or inject their entire bodies. An extension's `tools` list selects its initial tools; use an empty list for provider or lifecycle behavior only. An attached extension runs trusted code with full child-process permissions, not in a sandbox.
|
|
17
22
|
|
|
18
23
|
Attachments must be existing local paths. Every non-ignored declared skill must load successfully, each skill name must be unique, and each skill path must contain at least one loadable Pi skill. Missing or misattributed extension tools fail before the child model receives the task. See [Attachment behavior](./attachments.md) for validation, trust, limits, provider credentials, and startup failures.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@narumitw/pi-subagents",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.2.1",
|
|
4
4
|
"description": "Subagent jobs with asynchronous main-agent messaging for Pi.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -39,12 +39,12 @@
|
|
|
39
39
|
"typebox": "*"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
|
-
"@earendil-works/pi-ai": "1.
|
|
43
|
-
"@earendil-works/pi-coding-agent": "1.
|
|
44
|
-
"@earendil-works/pi-tui": "1.
|
|
45
|
-
"@types/node": "26.6.
|
|
42
|
+
"@earendil-works/pi-ai": "1.2.0",
|
|
43
|
+
"@earendil-works/pi-coding-agent": "1.2.0",
|
|
44
|
+
"@earendil-works/pi-tui": "1.2.0",
|
|
45
|
+
"@types/node": "26.6.5",
|
|
46
46
|
"esbuild": "0.28.2",
|
|
47
|
-
"typebox": "1.3.
|
|
47
|
+
"typebox": "1.3.37",
|
|
48
48
|
"typescript": "7.0.2"
|
|
49
49
|
},
|
|
50
50
|
"repository": {
|
|
@@ -53,6 +53,6 @@
|
|
|
53
53
|
"directory": "packages/pi-subagents"
|
|
54
54
|
},
|
|
55
55
|
"dependencies": {
|
|
56
|
-
"ignore": "^7.0.
|
|
56
|
+
"ignore": "^7.0.12"
|
|
57
57
|
}
|
|
58
58
|
}
|
package/src/process.ts
CHANGED
|
@@ -100,6 +100,7 @@ export function buildPiArgs(request: ChildCommandRequest): string[] {
|
|
|
100
100
|
"-e",
|
|
101
101
|
childCommunicationBridgePath(),
|
|
102
102
|
];
|
|
103
|
+
if (request.tools.includes("codemode")) args.push("-e", "builtin:codemode");
|
|
103
104
|
for (const extension of request.extensions) args.push("-e", extension.path);
|
|
104
105
|
if (requiresReadinessAttestation(request)) args.push("-e", childReadinessProbePath());
|
|
105
106
|
for (const skill of request.skills) args.push("--skill", skill);
|
|
@@ -153,8 +154,8 @@ export function childReadinessProbePath(): string {
|
|
|
153
154
|
return fileURLToPath(new URL("./child-readiness-probe.ts", import.meta.url));
|
|
154
155
|
}
|
|
155
156
|
|
|
156
|
-
function requiresReadinessAttestation(request: Pick<ChildRequest, "extensions">): boolean {
|
|
157
|
-
return request.extensions.length > 0;
|
|
157
|
+
function requiresReadinessAttestation(request: Pick<ChildRequest, "tools" | "extensions">): boolean {
|
|
158
|
+
return request.tools.includes("codemode") || request.extensions.length > 0;
|
|
158
159
|
}
|
|
159
160
|
|
|
160
161
|
function selectedChildTools(request: Pick<ChildRequest, "tools" | "extensions">): string[] {
|
package/src/runtime.ts
CHANGED
|
@@ -28,6 +28,7 @@ interface InternalJob extends JobSummary {
|
|
|
28
28
|
terminal: Promise<void>;
|
|
29
29
|
resolveTerminal: () => void;
|
|
30
30
|
task?: Promise<void>;
|
|
31
|
+
taskText: string;
|
|
31
32
|
stopRequest?: StopRequest;
|
|
32
33
|
control?: ChildControl;
|
|
33
34
|
controlReady: Promise<ChildControl>;
|
|
@@ -48,6 +49,7 @@ export interface RuntimeDependencies {
|
|
|
48
49
|
|
|
49
50
|
export interface ActiveJobDisplay {
|
|
50
51
|
jobId: string;
|
|
52
|
+
task: string;
|
|
51
53
|
state: Extract<SubagentJobState, "queued" | "running">;
|
|
52
54
|
elapsedMs: number;
|
|
53
55
|
timeout?: number;
|
|
@@ -113,6 +115,7 @@ export class SubagentRuntime {
|
|
|
113
115
|
.sort((left, right) => left.createdAt - right.createdAt)
|
|
114
116
|
.map((job) => ({
|
|
115
117
|
jobId: job.jobId,
|
|
118
|
+
task: job.taskText,
|
|
116
119
|
state: job.state,
|
|
117
120
|
elapsedMs: Math.max(0, now - (job.startedAt ?? job.createdAt)),
|
|
118
121
|
...(job.timeout !== undefined ? { timeout: job.timeout } : {}),
|
|
@@ -155,6 +158,7 @@ export class SubagentRuntime {
|
|
|
155
158
|
...(input.timeout !== undefined ? { timeout: input.timeout } : {}),
|
|
156
159
|
controller,
|
|
157
160
|
tools: effectiveTools,
|
|
161
|
+
taskText: input.task,
|
|
158
162
|
terminal,
|
|
159
163
|
resolveTerminal,
|
|
160
164
|
controlReady,
|
package/src/tools.ts
CHANGED
|
@@ -209,7 +209,7 @@ export function registerSubagentTools(
|
|
|
209
209
|
if (ctx.cwd !== cwd || ctx.isProjectTrusted() !== projectTrusted) {
|
|
210
210
|
throw new Error("Subagent spawn context changed during attachment validation; retry the request.");
|
|
211
211
|
}
|
|
212
|
-
if (attachments.extensions.length > 0) {
|
|
212
|
+
if (tools.includes("codemode") || attachments.extensions.length > 0) {
|
|
213
213
|
assertChildBootstrapCapacity([...new Set([...attachments.effectiveTools, ...CHILD_COMMUNICATION_TOOL_NAMES])]);
|
|
214
214
|
}
|
|
215
215
|
const model = resolveChildModel(ctx, attachments.extensions.length > 0);
|
package/src/types.ts
CHANGED
|
@@ -13,7 +13,17 @@ export const TERMINAL_JOB_STATES = new Set<SubagentJobState>([
|
|
|
13
13
|
"cancelled",
|
|
14
14
|
]);
|
|
15
15
|
|
|
16
|
-
export const CHILD_CORE_TOOL_NAMES = [
|
|
16
|
+
export const CHILD_CORE_TOOL_NAMES = [
|
|
17
|
+
"read",
|
|
18
|
+
"bash",
|
|
19
|
+
"powershell",
|
|
20
|
+
"edit",
|
|
21
|
+
"write",
|
|
22
|
+
"grep",
|
|
23
|
+
"find",
|
|
24
|
+
"ls",
|
|
25
|
+
"codemode",
|
|
26
|
+
] as const;
|
|
17
27
|
export const DEFAULT_SUBAGENT_TOOLS = ["read", "grep", "find", "ls"] as const;
|
|
18
28
|
export const SUBAGENT_THINKING_LEVELS = [
|
|
19
29
|
"off",
|
package/src/widget.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ExtensionContext, Theme } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import { truncateToWidth } from "@earendil-works/pi-tui";
|
|
2
|
+
import { stripTerminalSequences, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
3
3
|
import { sanitizeTerminalText } from "./message-broker.js";
|
|
4
4
|
import type { ActiveJobDisplay, SubagentRuntime } from "./runtime.js";
|
|
5
5
|
|
|
@@ -7,6 +7,7 @@ export const SUBAGENT_WIDGET_KEY = "subagents";
|
|
|
7
7
|
export const SUBAGENT_WIDGET_REFRESH_INTERVAL_MS = 1_000;
|
|
8
8
|
|
|
9
9
|
const WIDGET_OPTIONS = { placement: "aboveEditor" } as const;
|
|
10
|
+
const TASK_PREVIEW_WIDTH = 40;
|
|
10
11
|
|
|
11
12
|
export interface SubagentWidgetController {
|
|
12
13
|
start(ctx: ExtensionContext): void;
|
|
@@ -86,17 +87,21 @@ function renderJob(job: ActiveJobDisplay, theme: Theme): string {
|
|
|
86
87
|
const symbol = theme.fg(running ? "accent" : "dim", running ? "▶ " : "○ ");
|
|
87
88
|
const state = theme.fg(running ? "accent" : "muted", job.state);
|
|
88
89
|
const jobId = sanitizeLabel(job.jobId);
|
|
90
|
+
const taskText = sanitizeTask(job.task);
|
|
91
|
+
// Pi's truncation adds ANSI resets; remove them before applying the callback theme.
|
|
92
|
+
const task = stripTerminalSequences(truncateToWidth(taskText, TASK_PREVIEW_WIDTH, "…"));
|
|
93
|
+
const preview = visibleWidth(task) > 0 ? ` · ${theme.fg("text", task)}` : "";
|
|
89
94
|
const tools = job.tools.length > 0 ? job.tools.map(sanitizeLabel).join(", ") : "none";
|
|
90
95
|
const timeout = job.timeout === undefined ? "no timeout" : formatSeconds(job.timeout);
|
|
91
96
|
const detail = ` · ${formatSeconds(Math.floor(job.elapsedMs / 1_000))} / ${timeout} · tools: ${tools}`;
|
|
92
|
-
return `${symbol}${theme.fg("text", jobId)} · ${state}${theme.fg("muted", detail)}`;
|
|
97
|
+
return `${symbol}${theme.fg("text", jobId)}${preview} · ${state}${theme.fg("muted", detail)}`;
|
|
93
98
|
}
|
|
94
99
|
|
|
95
100
|
function widgetValue(jobs: readonly ActiveJobDisplay[]): string {
|
|
96
101
|
return jobs
|
|
97
102
|
.map(
|
|
98
103
|
(job) =>
|
|
99
|
-
`${job.jobId}\0${job.state}\0${Math.floor(job.elapsedMs / 1_000)}\0${job.timeout ?? ""}\0${job.tools.join(",")}`,
|
|
104
|
+
`${job.jobId}\0${job.task}\0${job.state}\0${Math.floor(job.elapsedMs / 1_000)}\0${job.timeout ?? ""}\0${job.tools.join(",")}`,
|
|
100
105
|
)
|
|
101
106
|
.join("\n");
|
|
102
107
|
}
|
|
@@ -105,6 +110,17 @@ function cloneDisplayJob(job: ActiveJobDisplay): ActiveJobDisplay {
|
|
|
105
110
|
return { ...job, tools: [...job.tools] };
|
|
106
111
|
}
|
|
107
112
|
|
|
113
|
+
function sanitizeTask(value: string): string {
|
|
114
|
+
// Stop sequence bodies at another control introducer so malformed input cannot
|
|
115
|
+
// repeatedly scan the rest of a maximum-size task, as Pi's escape stripper can.
|
|
116
|
+
const text = value.replace(
|
|
117
|
+
// biome-ignore lint/suspicious/noControlCharactersInRegex: Remove untrusted terminal escape sequences before display.
|
|
118
|
+
/(?:\u001b\[|\u009b)[0-?]*[ -/]*[@-~]|(?:\u001b[\]PX^_]|[\u0090\u0098\u009d-\u009f])[^\u0007\u001b\u0080-\u009f]*(?:\u0007|\u001b\\|\u009c)|\u001b[ -/]*[0-~]/gu,
|
|
119
|
+
"",
|
|
120
|
+
);
|
|
121
|
+
return sanitizeLabel(text.replace(/\p{Bidi_Control}/gu, ""));
|
|
122
|
+
}
|
|
123
|
+
|
|
108
124
|
function sanitizeLabel(value: string): string {
|
|
109
125
|
return sanitizeTerminalText(value).replace(/\s+/gu, " ").trim();
|
|
110
126
|
}
|