@narumitw/pi-subagents 3.0.1 → 3.0.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 +35 -100
- package/dist/child-communication-bridge.ts +3 -8
- package/dist/child-communication-bridge.ts.map +2 -2
- package/dist/chunks/{chunk-UKTWUQRM.js → chunk-7JQOB375.ts} +7 -22
- package/dist/chunks/chunk-7JQOB375.ts.map +7 -0
- package/dist/index.ts +13 -46
- package/dist/index.ts.map +2 -2
- package/package.json +53 -54
- package/src/broker-credentials.ts +48 -48
- package/src/child-communication-bridge.ts +115 -123
- package/src/child-communication-tools.ts +105 -111
- package/src/completion-renderer.ts +46 -48
- package/src/message-broker.ts +531 -561
- package/src/model-output.ts +25 -28
- package/src/process.ts +595 -616
- package/src/runtime.ts +454 -478
- package/src/subagents.ts +20 -23
- package/src/tools.ts +305 -319
- package/src/types.ts +41 -58
- package/src/widget.ts +85 -91
- package/dist/chunks/chunk-UKTWUQRM.js.map +0 -7
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import type { ExtensionFactory } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { type Static, Type } from "typebox";
|
|
3
3
|
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
type BrokerSendAcknowledgement,
|
|
5
|
+
MAX_IDENTIFIER_LENGTH,
|
|
6
|
+
MAX_MESSAGE_BYTES,
|
|
7
|
+
sanitizeTerminalText,
|
|
8
|
+
validateMessage,
|
|
9
9
|
} from "./message-broker.js";
|
|
10
10
|
|
|
11
11
|
export const CHILD_COMMUNICATION_TOOL_NAMES = ["subagent_send", "subagent_wait"] as const;
|
|
@@ -14,134 +14,128 @@ const MAX_TIMEOUT_MS = 2_147_483_647;
|
|
|
14
14
|
const MAX_TIMEOUT_SECONDS = MAX_TIMEOUT_MS / 1000;
|
|
15
15
|
|
|
16
16
|
const SendParameters = Type.Object(
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
17
|
+
{
|
|
18
|
+
requestId: Type.Optional(
|
|
19
|
+
Type.String({
|
|
20
|
+
description: "Pending main-agent request to answer. Omit when starting a new request.",
|
|
21
|
+
minLength: 1,
|
|
22
|
+
maxLength: MAX_IDENTIFIER_LENGTH,
|
|
23
|
+
}),
|
|
24
|
+
),
|
|
25
|
+
message: Type.String({
|
|
26
|
+
description: "Plain-text request or response. Maximum 48 KiB of UTF-8 text and 1,992 lines.",
|
|
27
|
+
minLength: 1,
|
|
28
|
+
maxLength: MAX_MESSAGE_BYTES,
|
|
29
|
+
}),
|
|
30
|
+
},
|
|
31
|
+
{ additionalProperties: false },
|
|
32
32
|
);
|
|
33
33
|
|
|
34
34
|
const WaitParameters = Type.Object(
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
},
|
|
45
|
-
{ additionalProperties: false },
|
|
35
|
+
{
|
|
36
|
+
requestId: Type.String({
|
|
37
|
+
description: "Request ID returned by subagent_send.",
|
|
38
|
+
minLength: 1,
|
|
39
|
+
maxLength: MAX_IDENTIFIER_LENGTH,
|
|
40
|
+
}),
|
|
41
|
+
timeout: Type.Optional(Type.Number({ description: "Timeout in seconds (optional, no default timeout)" })),
|
|
42
|
+
},
|
|
43
|
+
{ additionalProperties: false },
|
|
46
44
|
);
|
|
47
45
|
|
|
48
|
-
type ChildBrokerSendArguments =
|
|
49
|
-
| { recipient: "main"; message: string }
|
|
50
|
-
| { requestId: string; message: string };
|
|
46
|
+
type ChildBrokerSendArguments = { recipient: "main"; message: string } | { requestId: string; message: string };
|
|
51
47
|
|
|
52
48
|
type WaitArguments = Static<typeof WaitParameters>;
|
|
53
49
|
|
|
54
50
|
export interface ChildCommunicationClient {
|
|
55
|
-
|
|
56
|
-
|
|
51
|
+
send(params: ChildBrokerSendArguments, signal?: AbortSignal): Promise<BrokerSendAcknowledgement>;
|
|
52
|
+
wait(requestId: string, timeoutMs: number | undefined, signal?: AbortSignal): Promise<string>;
|
|
57
53
|
}
|
|
58
54
|
|
|
59
|
-
export function createChildCommunicationExtension(
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
},
|
|
84
|
-
});
|
|
55
|
+
export function createChildCommunicationExtension(client: ChildCommunicationClient): ExtensionFactory {
|
|
56
|
+
return (pi) => {
|
|
57
|
+
pi.registerTool({
|
|
58
|
+
name: "subagent_send",
|
|
59
|
+
label: "Subagent · Send to Main",
|
|
60
|
+
description:
|
|
61
|
+
"Use subagent_send to send one request to the main agent or answer one pending main-agent request. Omit requestId to start a request. Provide requestId to answer that request.",
|
|
62
|
+
promptSnippet: "Use subagent_send to send or answer one main-agent message",
|
|
63
|
+
parameters: SendParameters,
|
|
64
|
+
async execute(_toolCallId, params, signal) {
|
|
65
|
+
validateMessage(params.message, "Subagent message");
|
|
66
|
+
const requestId = optionalIdentifier(params.requestId, "requestId");
|
|
67
|
+
const acknowledgement = await client.send(
|
|
68
|
+
requestId === undefined
|
|
69
|
+
? { recipient: "main", message: params.message }
|
|
70
|
+
: { requestId, message: params.message },
|
|
71
|
+
signal,
|
|
72
|
+
);
|
|
73
|
+
return {
|
|
74
|
+
content: [{ type: "text" as const, text: JSON.stringify(acknowledgement) }],
|
|
75
|
+
details: acknowledgement,
|
|
76
|
+
};
|
|
77
|
+
},
|
|
78
|
+
});
|
|
85
79
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
80
|
+
pi.registerTool({
|
|
81
|
+
name: "subagent_wait",
|
|
82
|
+
label: "Subagent · Wait for Main Agent",
|
|
83
|
+
description:
|
|
84
|
+
"Use subagent_wait with a request ID from subagent_send to wait for the main agent's plain-text response. A timeout, caller cancellation, or incoming main-agent request stops only this wait and does not cancel the original request. Retry the wait when its response is still needed.",
|
|
85
|
+
promptSnippet: "Use subagent_wait to receive a requested main-agent response",
|
|
86
|
+
parameters: WaitParameters,
|
|
87
|
+
prepareArguments: prepareWaitArguments,
|
|
88
|
+
async execute(_toolCallId, params, signal) {
|
|
89
|
+
const requestId = requiredIdentifier(params.requestId, "requestId");
|
|
90
|
+
const response = await client.wait(requestId, resolveTimeoutMs(params.timeout), signal);
|
|
91
|
+
return {
|
|
92
|
+
content: [{ type: "text" as const, text: sanitizeTerminalText(response) }],
|
|
93
|
+
details: { requestId },
|
|
94
|
+
};
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
};
|
|
104
98
|
}
|
|
105
99
|
|
|
106
100
|
function resolveTimeoutMs(timeout: number | undefined): number | undefined {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
101
|
+
if (timeout === undefined) return undefined;
|
|
102
|
+
if (!Number.isFinite(timeout) || timeout <= 0) {
|
|
103
|
+
throw new Error("Invalid timeout: must be a finite number of seconds");
|
|
104
|
+
}
|
|
105
|
+
const timeoutMs = timeout * 1000;
|
|
106
|
+
if (timeoutMs > MAX_TIMEOUT_MS) {
|
|
107
|
+
throw new Error(`Invalid timeout: maximum is ${MAX_TIMEOUT_SECONDS} seconds`);
|
|
108
|
+
}
|
|
109
|
+
return timeoutMs;
|
|
116
110
|
}
|
|
117
111
|
|
|
118
112
|
function prepareWaitArguments(args: unknown): WaitArguments {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
113
|
+
if (!args || typeof args !== "object") return args as WaitArguments;
|
|
114
|
+
if (!Object.hasOwn(args, "timeoutMs")) return args as WaitArguments;
|
|
115
|
+
const record = args as Record<string, unknown>;
|
|
116
|
+
if (typeof record.timeoutMs !== "number") return record as WaitArguments;
|
|
117
|
+
const { timeoutMs, ...prepared } = record;
|
|
118
|
+
if (prepared.timeout === undefined) {
|
|
119
|
+
return { ...prepared, timeout: timeoutMs / 1000 } as WaitArguments;
|
|
120
|
+
}
|
|
121
|
+
return prepared as WaitArguments;
|
|
128
122
|
}
|
|
129
123
|
|
|
130
124
|
function optionalIdentifier(value: unknown, field: string): string | undefined {
|
|
131
|
-
|
|
125
|
+
return value === undefined ? undefined : requiredIdentifier(value, field);
|
|
132
126
|
}
|
|
133
127
|
|
|
134
128
|
function requiredIdentifier(value: unknown, field: string): string {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
129
|
+
if (typeof value !== "string" || !value.trim()) throw new Error(`Subagent ${field} is required.`);
|
|
130
|
+
const identifier = value.trim();
|
|
131
|
+
if (
|
|
132
|
+
identifier.length > MAX_IDENTIFIER_LENGTH ||
|
|
133
|
+
[...identifier].some((character) => {
|
|
134
|
+
const codePoint = character.codePointAt(0) ?? 0;
|
|
135
|
+
return codePoint <= 0x1f || (codePoint >= 0x7f && codePoint <= 0x9f);
|
|
136
|
+
})
|
|
137
|
+
) {
|
|
138
|
+
throw new Error(`Subagent ${field} is invalid.`);
|
|
139
|
+
}
|
|
140
|
+
return identifier;
|
|
147
141
|
}
|
|
@@ -5,56 +5,54 @@ import { sanitizeTerminalText } from "./message-broker.js";
|
|
|
5
5
|
export const COMPLETION_MESSAGE_TYPE = "pi-subagents-completion";
|
|
6
6
|
|
|
7
7
|
function createCompletionBox(
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
children: Component[],
|
|
9
|
+
outputPad: number,
|
|
10
|
+
background: (text: string) => string,
|
|
11
11
|
): Component {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
12
|
+
return {
|
|
13
|
+
render(width) {
|
|
14
|
+
if (width <= 0) return [];
|
|
15
|
+
const maxPadding = Math.floor((width - 1) / 2);
|
|
16
|
+
const box = new Box(Math.min(Math.max(0, outputPad), maxPadding), 1, background);
|
|
17
|
+
for (const child of children) box.addChild(child);
|
|
18
|
+
return box.render(width);
|
|
19
|
+
},
|
|
20
|
+
invalidate() {
|
|
21
|
+
for (const child of children) child.invalidate();
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
export function registerCompletionRenderer(pi: ExtensionAPI): void {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
);
|
|
59
|
-
});
|
|
27
|
+
pi.registerMessageRenderer(COMPLETION_MESSAGE_TYPE, (message, options, theme) => {
|
|
28
|
+
const children: Component[] = [
|
|
29
|
+
new Text(theme.fg("customMessageLabel", theme.bold(`[${COMPLETION_MESSAGE_TYPE}]`)), 0, 0),
|
|
30
|
+
new Spacer(1),
|
|
31
|
+
];
|
|
32
|
+
if (!options.expanded) {
|
|
33
|
+
children.push(
|
|
34
|
+
new Text(
|
|
35
|
+
theme.fg("customMessageText", "Subagent job completion (") +
|
|
36
|
+
theme.fg("dim", keyText("app.tools.expand")) +
|
|
37
|
+
theme.fg("customMessageText", " to expand)"),
|
|
38
|
+
0,
|
|
39
|
+
0,
|
|
40
|
+
),
|
|
41
|
+
);
|
|
42
|
+
} else {
|
|
43
|
+
const content =
|
|
44
|
+
typeof message.content === "string"
|
|
45
|
+
? message.content
|
|
46
|
+
: message.content
|
|
47
|
+
.filter((part) => part.type === "text")
|
|
48
|
+
.map((part) => part.text)
|
|
49
|
+
.join("\n");
|
|
50
|
+
children.push(
|
|
51
|
+
new Markdown(sanitizeTerminalText(content), 0, 0, getMarkdownTheme(), {
|
|
52
|
+
color: (text) => theme.fg("customMessageText", text),
|
|
53
|
+
}),
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
return createCompletionBox(children, options.outputPad, (text) => theme.bg("customMessageBg", text));
|
|
57
|
+
});
|
|
60
58
|
}
|