@narumitw/pi-subagents 2.0.6 → 2.1.0
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 +152 -50
- package/dist/chunks/{auto-transport-SY2VHUFH.ts → auto-transport-FUUKFDIG.ts} +6 -6
- package/dist/chunks/{capability-grant-CGEWOEKE.ts → capability-grant-PR72SWWS.ts} +4 -4
- package/dist/chunks/{chunk-DPPVEQAM.ts → chunk-4AQSF7AS.ts} +1 -1
- package/dist/chunks/chunk-4AQSF7AS.ts.map +7 -0
- package/dist/chunks/chunk-6H6TBBED.ts +108 -0
- package/dist/chunks/chunk-6H6TBBED.ts.map +7 -0
- package/dist/chunks/{chunk-DSOOH73Y.ts → chunk-7AAJEUSL.ts} +3 -3
- package/dist/chunks/{chunk-DSOOH73Y.ts.map → chunk-7AAJEUSL.ts.map} +2 -2
- package/dist/chunks/{chunk-7QPPBBXZ.ts → chunk-D4CR7T73.ts} +21 -3
- package/dist/chunks/chunk-D4CR7T73.ts.map +7 -0
- package/dist/chunks/{chunk-434NII74.ts → chunk-DIHBUR2E.ts} +54 -8
- package/dist/chunks/chunk-DIHBUR2E.ts.map +7 -0
- package/dist/chunks/{chunk-NIF42QMF.ts → chunk-FEVPWRMU.ts} +7 -4
- package/dist/chunks/chunk-FEVPWRMU.ts.map +7 -0
- package/dist/chunks/{chunk-YBUWBRF7.ts → chunk-FXI45N3J.ts} +5 -5
- package/dist/chunks/{chunk-S2IWVK3J.ts → chunk-ILEQ27AL.ts} +3 -2
- package/dist/chunks/chunk-ILEQ27AL.ts.map +7 -0
- package/dist/chunks/{chunk-DQMN4OYM.ts → chunk-ITVWPNU4.ts} +12 -106
- package/dist/chunks/chunk-ITVWPNU4.ts.map +7 -0
- package/dist/chunks/{chunk-QDVGH2X7.ts → chunk-IWC32VPY.ts} +2 -1
- package/dist/chunks/{chunk-QDVGH2X7.ts.map → chunk-IWC32VPY.ts.map} +2 -2
- package/dist/chunks/{chunk-I2FAK44T.ts → chunk-JSZIP73U.ts} +48 -10
- package/dist/chunks/chunk-JSZIP73U.ts.map +7 -0
- package/dist/chunks/{chunk-ZHTNCZIA.ts → chunk-JU6LUNLP.ts} +2 -2
- package/dist/chunks/{chunk-PSK43Y6A.ts → chunk-LASD73CM.ts} +2 -2
- package/dist/chunks/{chunk-C4L6P266.ts → chunk-LEOYDZI3.ts} +1 -1
- package/dist/chunks/{chunk-C4L6P266.ts.map → chunk-LEOYDZI3.ts.map} +2 -2
- package/dist/chunks/{chunk-H3BFJ7HJ.ts → chunk-LL4LP2T7.ts} +5 -5
- package/dist/chunks/chunk-N2T5IN4X.ts +18 -0
- package/dist/chunks/chunk-N2T5IN4X.ts.map +7 -0
- package/dist/chunks/{chunk-G3RSMSXJ.ts → chunk-NTRPLF46.ts} +1 -1
- package/dist/chunks/chunk-NTRPLF46.ts.map +7 -0
- package/dist/chunks/{chunk-CBP76ARQ.ts → chunk-PABJJYP6.ts} +174 -47
- package/dist/chunks/chunk-PABJJYP6.ts.map +7 -0
- package/dist/chunks/{chunk-EFBPISNT.ts → chunk-PBZMBTNJ.ts} +104 -103
- package/dist/chunks/chunk-PBZMBTNJ.ts.map +7 -0
- package/dist/chunks/{chunk-SCZ33MYW.ts → chunk-PGLSFLYW.ts} +2 -2
- package/dist/chunks/{chunk-O4VO6JUJ.ts → chunk-TM2R67J3.ts} +3 -3
- package/dist/chunks/{chunk-QBORTI4W.ts → chunk-TMZRHIIK.ts} +32 -3
- package/dist/chunks/chunk-TMZRHIIK.ts.map +7 -0
- package/dist/chunks/chunk-TZ34IQ3M.ts +59 -0
- package/dist/chunks/chunk-TZ34IQ3M.ts.map +7 -0
- package/dist/chunks/chunk-VBDGNNLM.ts +214 -0
- package/dist/chunks/chunk-VBDGNNLM.ts.map +7 -0
- package/dist/chunks/{chunk-7OBINKFM.ts → chunk-VDG7LTYE.ts} +11 -11
- package/dist/chunks/chunk-VDG7LTYE.ts.map +7 -0
- package/dist/chunks/{chunk-AEUYS7JC.ts → chunk-X4NMONPE.ts} +1 -1
- package/dist/chunks/chunk-X4NMONPE.ts.map +7 -0
- package/dist/chunks/{chunk-HKC4ES4B.ts → chunk-YPJEN6NU.ts} +2 -2
- package/dist/chunks/{completion-delivery-YPOWSSV3.ts → completion-delivery-JVLNQRWX.ts} +5 -4
- package/dist/chunks/{config-status-J4GPWP46.ts → config-status-FKGDECZ3.ts} +7 -6
- package/dist/chunks/{config-ui-2YUCUEBM.ts → config-ui-DDKERQHI.ts} +297 -231
- package/dist/chunks/config-ui-DDKERQHI.ts.map +7 -0
- package/dist/chunks/{consult-IV7UBNQQ.ts → consult-PQ6PRAKC.ts} +14 -13
- package/dist/chunks/consult-PQ6PRAKC.ts.map +7 -0
- package/dist/chunks/{create-stateful-transport-ZP3IXNV3.ts → create-stateful-transport-JWC2EFYL.ts} +6 -6
- package/dist/chunks/{delegation-contract-WJPT4FVS.ts → delegation-contract-LA56I5DT.ts} +2 -2
- package/dist/chunks/{discovery-K25T3SH4.ts → discovery-MIFB2U4Y.ts} +3 -3
- package/dist/chunks/{execution-LA7HN5YF.ts → execution-Q2JZLKJA.ts} +19 -16
- package/dist/chunks/execution-Q2JZLKJA.ts.map +7 -0
- package/dist/chunks/{in-process-transport-4RF3J6XK.ts → in-process-transport-HJ6TZXC3.ts} +9 -9
- package/dist/chunks/{inspect-XV2QALMR.ts → inspect-UH2TKH6E.ts} +25 -10
- package/dist/chunks/inspect-UH2TKH6E.ts.map +7 -0
- package/dist/chunks/{persistence-VSGXAW4P.ts → persistence-XHPJBZL7.ts} +11 -7
- package/dist/chunks/persistence-XHPJBZL7.ts.map +7 -0
- package/dist/chunks/{registry-E6XPJB7L.ts → registry-XDXPECWF.ts} +136 -15
- package/dist/chunks/registry-XDXPECWF.ts.map +7 -0
- package/dist/chunks/{retained-semantic-state-7WXSGRJU.ts → retained-semantic-state-GM75FZGE.ts} +3 -3
- package/dist/chunks/{rpc-transport-NEHBHWX4.ts → rpc-transport-7R7DVCEB.ts} +12 -16
- package/dist/chunks/rpc-transport-7R7DVCEB.ts.map +7 -0
- package/dist/chunks/{spawn-idempotency-QGHO2WKC.ts → spawn-idempotency-BNHOSMZW.ts} +2 -2
- package/dist/chunks/{subprocess-transport-NSHOGDC4.ts → subprocess-transport-VHZJRBTW.ts} +15 -14
- package/dist/chunks/subprocess-transport-VHZJRBTW.ts.map +7 -0
- package/dist/chunks/usage-recording-store-CW3EH2SZ.ts +164 -0
- package/dist/chunks/usage-recording-store-CW3EH2SZ.ts.map +7 -0
- package/dist/index.ts +427 -30
- package/dist/index.ts.map +3 -3
- package/docs/async-runtime-protocol.md +69 -0
- package/docs/implementation-notes/pi-subagents-capability-matrix.md +62 -0
- package/docs/implementation-notes/pi-subagents-current-direction.md +99 -0
- package/docs/implementation-notes/pi-subagents-rpc-v1.md +153 -0
- package/docs/pi-subagents-diagrams.md +183 -0
- package/package.json +9 -8
- package/src/agents/types.ts +2 -0
- package/src/async-subagent-benchmark.ts +532 -0
- package/src/completion-delivery.ts +71 -4
- package/src/completion-render.ts +1 -0
- package/src/completion-requirement.ts +308 -0
- package/src/config-registration.ts +5 -5
- package/src/config-status.ts +66 -70
- package/src/config-ui.ts +244 -150
- package/src/consult-resources.ts +1 -1
- package/src/consult.ts +3 -8
- package/src/delegation-contract.ts +55 -9
- package/src/execution-plan.ts +1 -1
- package/src/execution-ui.ts +40 -29
- package/src/execution.ts +2 -3
- package/src/inspect.ts +24 -1
- package/src/orchestration-metrics.ts +1 -1
- package/src/panel-execution.ts +2 -3
- package/src/panel-failure.ts +1 -1
- package/src/panel-render.ts +1 -1
- package/src/parallel-limit-ui.ts +6 -5
- package/src/params.ts +2 -1
- package/src/persistence.ts +9 -0
- package/src/process-control.ts +43 -0
- package/src/registry-types.ts +5 -0
- package/src/registry.ts +162 -18
- package/src/render.ts +2 -1
- package/src/rpc-transport.ts +1 -1
- package/src/runner-outcome.ts +1 -1
- package/src/runner-result.ts +1 -1
- package/src/runner-types.ts +102 -0
- package/src/runner.ts +11 -192
- package/src/settings/inspection.ts +27 -0
- package/src/settings/schema.ts +9 -0
- package/src/settings-reader.ts +7 -0
- package/src/settings.ts +20 -0
- package/src/spawn-idempotency.ts +5 -0
- package/src/stateful-agent-view.ts +15 -19
- package/src/stateful-guidance.ts +14 -4
- package/src/stateful-limit-ui.ts +23 -20
- package/src/stateful-limits.ts +10 -10
- package/src/stateful-registration.ts +126 -9
- package/src/stateful-render.ts +27 -2
- package/src/subagent-details.ts +43 -0
- package/src/subagents-extension.ts +66 -8
- package/src/subagents.ts +2 -0
- package/src/subprocess-transport.ts +2 -1
- package/src/supervision.ts +2 -1
- package/src/timeout-finalization.ts +1 -1
- package/src/tool-schema-compatibility.ts +73 -0
- package/src/transport-types.ts +6 -0
- package/src/transport-ui.ts +18 -46
- package/src/usage-recording-config.ts +13 -0
- package/src/usage-recording-store.ts +183 -0
- package/src/usage-recording.ts +478 -0
- package/src/verification-harness.ts +1 -1
- package/src/workflow-ui.ts +17 -9
- package/dist/chunks/chunk-434NII74.ts.map +0 -7
- package/dist/chunks/chunk-7OBINKFM.ts.map +0 -7
- package/dist/chunks/chunk-7QPPBBXZ.ts.map +0 -7
- package/dist/chunks/chunk-AEUYS7JC.ts.map +0 -7
- package/dist/chunks/chunk-CBP76ARQ.ts.map +0 -7
- package/dist/chunks/chunk-DPPVEQAM.ts.map +0 -7
- package/dist/chunks/chunk-DQMN4OYM.ts.map +0 -7
- package/dist/chunks/chunk-EFBPISNT.ts.map +0 -7
- package/dist/chunks/chunk-G3RSMSXJ.ts.map +0 -7
- package/dist/chunks/chunk-I2FAK44T.ts.map +0 -7
- package/dist/chunks/chunk-NIF42QMF.ts.map +0 -7
- package/dist/chunks/chunk-QBORTI4W.ts.map +0 -7
- package/dist/chunks/chunk-S2IWVK3J.ts.map +0 -7
- package/dist/chunks/config-ui-2YUCUEBM.ts.map +0 -7
- package/dist/chunks/consult-IV7UBNQQ.ts.map +0 -7
- package/dist/chunks/execution-LA7HN5YF.ts.map +0 -7
- package/dist/chunks/inspect-XV2QALMR.ts.map +0 -7
- package/dist/chunks/persistence-VSGXAW4P.ts.map +0 -7
- package/dist/chunks/registry-E6XPJB7L.ts.map +0 -7
- package/dist/chunks/rpc-transport-NEHBHWX4.ts.map +0 -7
- package/dist/chunks/subprocess-transport-NSHOGDC4.ts.map +0 -7
- /package/dist/chunks/{auto-transport-SY2VHUFH.ts.map → auto-transport-FUUKFDIG.ts.map} +0 -0
- /package/dist/chunks/{capability-grant-CGEWOEKE.ts.map → capability-grant-PR72SWWS.ts.map} +0 -0
- /package/dist/chunks/{chunk-YBUWBRF7.ts.map → chunk-FXI45N3J.ts.map} +0 -0
- /package/dist/chunks/{chunk-ZHTNCZIA.ts.map → chunk-JU6LUNLP.ts.map} +0 -0
- /package/dist/chunks/{chunk-PSK43Y6A.ts.map → chunk-LASD73CM.ts.map} +0 -0
- /package/dist/chunks/{chunk-H3BFJ7HJ.ts.map → chunk-LL4LP2T7.ts.map} +0 -0
- /package/dist/chunks/{chunk-SCZ33MYW.ts.map → chunk-PGLSFLYW.ts.map} +0 -0
- /package/dist/chunks/{chunk-O4VO6JUJ.ts.map → chunk-TM2R67J3.ts.map} +0 -0
- /package/dist/chunks/{chunk-HKC4ES4B.ts.map → chunk-YPJEN6NU.ts.map} +0 -0
- /package/dist/chunks/{completion-delivery-YPOWSSV3.ts.map → completion-delivery-JVLNQRWX.ts.map} +0 -0
- /package/dist/chunks/{config-status-J4GPWP46.ts.map → config-status-FKGDECZ3.ts.map} +0 -0
- /package/dist/chunks/{create-stateful-transport-ZP3IXNV3.ts.map → create-stateful-transport-JWC2EFYL.ts.map} +0 -0
- /package/dist/chunks/{delegation-contract-WJPT4FVS.ts.map → delegation-contract-LA56I5DT.ts.map} +0 -0
- /package/dist/chunks/{discovery-K25T3SH4.ts.map → discovery-MIFB2U4Y.ts.map} +0 -0
- /package/dist/chunks/{in-process-transport-4RF3J6XK.ts.map → in-process-transport-HJ6TZXC3.ts.map} +0 -0
- /package/dist/chunks/{retained-semantic-state-7WXSGRJU.ts.map → retained-semantic-state-GM75FZGE.ts.map} +0 -0
- /package/dist/chunks/{spawn-idempotency-QGHO2WKC.ts.map → spawn-idempotency-BNHOSMZW.ts.map} +0 -0
package/src/stateful-limits.ts
CHANGED
|
@@ -29,36 +29,36 @@ export interface StatefulLimitDefinition {
|
|
|
29
29
|
export const STATEFUL_LIMIT_DEFINITIONS: readonly StatefulLimitDefinition[] = [
|
|
30
30
|
{
|
|
31
31
|
field: "maxAgents",
|
|
32
|
-
label: "
|
|
33
|
-
description: "
|
|
32
|
+
label: "Subagents saved for follow-up",
|
|
33
|
+
description: "Working, queued, and reusable background subagents kept in one session",
|
|
34
34
|
defaultValue: 16,
|
|
35
35
|
minimum: 1,
|
|
36
36
|
},
|
|
37
37
|
{
|
|
38
38
|
field: "maxActiveTurns",
|
|
39
|
-
label: "
|
|
40
|
-
description: "
|
|
39
|
+
label: "Subagents working at once",
|
|
40
|
+
description: "Background subagent turns that may run at the same time",
|
|
41
41
|
defaultValue: 4,
|
|
42
42
|
minimum: 1,
|
|
43
43
|
},
|
|
44
44
|
{
|
|
45
45
|
field: "maxChildrenPerAgent",
|
|
46
|
-
label: "
|
|
47
|
-
description: "
|
|
46
|
+
label: "Direct children per subagent",
|
|
47
|
+
description: "Subagents that one parent subagent may keep for follow-up",
|
|
48
48
|
defaultValue: 8,
|
|
49
49
|
minimum: 1,
|
|
50
50
|
},
|
|
51
51
|
{
|
|
52
52
|
field: "maxDepth",
|
|
53
|
-
label: "
|
|
54
|
-
description: "
|
|
53
|
+
label: "Nested subagent levels",
|
|
54
|
+
description: "Child levels allowed below a top-level subagent",
|
|
55
55
|
defaultValue: 3,
|
|
56
56
|
minimum: 0,
|
|
57
57
|
},
|
|
58
58
|
{
|
|
59
59
|
field: "maxStoredAgents",
|
|
60
|
-
label: "Stored
|
|
61
|
-
description: "
|
|
60
|
+
label: "Stored subagent records",
|
|
61
|
+
description: "Background subagent records kept on disk for each session",
|
|
62
62
|
defaultValue: 50,
|
|
63
63
|
minimum: 1,
|
|
64
64
|
},
|
|
@@ -17,6 +17,11 @@ import {
|
|
|
17
17
|
THINKING_LEVELS,
|
|
18
18
|
} from "./agents/types.js";
|
|
19
19
|
import type { CompletionDeliveryBroker } from "./completion-delivery.js";
|
|
20
|
+
import {
|
|
21
|
+
CompletionRequirementModeSchema,
|
|
22
|
+
completionRequirementsFromBranch,
|
|
23
|
+
reconcileRequiredCompletionContext,
|
|
24
|
+
} from "./completion-requirement.js";
|
|
20
25
|
import type { ContextMode } from "./context.js";
|
|
21
26
|
import type { CreateStatefulTransportOptions } from "./create-stateful-transport.js";
|
|
22
27
|
import { DelegationContractSchema } from "./delegation-contract.js";
|
|
@@ -55,7 +60,9 @@ import {
|
|
|
55
60
|
validateManageParams,
|
|
56
61
|
} from "./stateful-tool-params.js";
|
|
57
62
|
import { MAX_TASK_NAME_LENGTH } from "./task-path.js";
|
|
63
|
+
import { grammarSafeToolObject } from "./tool-schema-compatibility.js";
|
|
58
64
|
import { MAX_SUBAGENT_TOOL_CALLS, MAX_SUBAGENT_TURNS } from "./turn-budget.js";
|
|
65
|
+
import type { UsageRecordingController } from "./usage-recording.js";
|
|
59
66
|
import type { WorkspaceManager } from "./workspace.js";
|
|
60
67
|
|
|
61
68
|
type CwdPolicyModule = typeof import("./cwd-policy.js");
|
|
@@ -196,6 +203,21 @@ const StatefulTimeoutSchema = Type.Integer({
|
|
|
196
203
|
description:
|
|
197
204
|
"Work deadline in milliseconds selected for the task difficulty. On expiry, Pi aborts the work and makes one separately bounded summary attempt. Retained as the agent default.",
|
|
198
205
|
});
|
|
206
|
+
const DEFAULT_SUBAGENT_AWAIT_TIMEOUT_MS = 30_000;
|
|
207
|
+
const SubagentAwaitParams = Type.Object({
|
|
208
|
+
agentId: Type.String({
|
|
209
|
+
minLength: 1,
|
|
210
|
+
description: "Retained agent ID or canonical task path.",
|
|
211
|
+
}),
|
|
212
|
+
timeoutMs: Type.Optional(
|
|
213
|
+
Type.Integer({
|
|
214
|
+
minimum: 1,
|
|
215
|
+
maximum: MAX_SUBAGENT_TIMEOUT_MS,
|
|
216
|
+
description:
|
|
217
|
+
"Maximum time to wait in milliseconds. A wait timeout stops only this tool call and does not interrupt or close the subagent. Defaults to 30000.",
|
|
218
|
+
}),
|
|
219
|
+
),
|
|
220
|
+
});
|
|
199
221
|
const StatefulTurnLimitFields = {
|
|
200
222
|
idleTimeoutMs: Type.Optional(
|
|
201
223
|
Type.Integer({
|
|
@@ -209,14 +231,16 @@ const StatefulTurnLimitFields = {
|
|
|
209
231
|
Type.Integer({
|
|
210
232
|
minimum: 1,
|
|
211
233
|
maximum: MAX_SUBAGENT_TURNS,
|
|
212
|
-
description:
|
|
234
|
+
description:
|
|
235
|
+
"Maximum unfinished assistant turns; retained as the agent default. Omit rather than guessing a tight bound.",
|
|
213
236
|
}),
|
|
214
237
|
),
|
|
215
238
|
maxToolCalls: Type.Optional(
|
|
216
239
|
Type.Integer({
|
|
217
240
|
minimum: 1,
|
|
218
241
|
maximum: MAX_SUBAGENT_TOOL_CALLS,
|
|
219
|
-
description:
|
|
242
|
+
description:
|
|
243
|
+
"Maximum tool calls; retained as the agent default. Omit rather than guessing a tight bound.",
|
|
220
244
|
}),
|
|
221
245
|
),
|
|
222
246
|
};
|
|
@@ -227,6 +251,7 @@ export interface StatefulSubagentDependencies {
|
|
|
227
251
|
settings?: SubagentRuntimeSettings;
|
|
228
252
|
getSettings?: () => SubagentSettings | undefined;
|
|
229
253
|
loadTransport?: CreateStatefulTransportOptions["loadTransport"];
|
|
254
|
+
usageRecording?: UsageRecordingController;
|
|
230
255
|
}
|
|
231
256
|
|
|
232
257
|
export interface StatefulSubagentRuntimeStatus {
|
|
@@ -362,7 +387,7 @@ export function registerStatefulSubagents(
|
|
|
362
387
|
if (!registry) throw new Error("Stateful subagents are not initialized for this session");
|
|
363
388
|
return registry;
|
|
364
389
|
};
|
|
365
|
-
pi.on("session_start", async (
|
|
390
|
+
pi.on("session_start", async (event, ctx) => {
|
|
366
391
|
const generation = ++runtimeGeneration;
|
|
367
392
|
completionBroker?.close();
|
|
368
393
|
completionBroker = undefined;
|
|
@@ -415,9 +440,16 @@ export function registerStatefulSubagents(
|
|
|
415
440
|
const reason = error instanceof Error ? error.message : String(error);
|
|
416
441
|
ctx.ui.notify(`Subagent completion delivery failed: ${reason}`, "warning");
|
|
417
442
|
},
|
|
443
|
+
onDeliveryAttempt: (completions, input) => {
|
|
444
|
+
if (generation !== runtimeGeneration) return;
|
|
445
|
+
for (const completion of completions) {
|
|
446
|
+
dependencies.usageRecording?.recordCompletionDeliveryAttempt(completion, input);
|
|
447
|
+
}
|
|
448
|
+
},
|
|
418
449
|
onAcknowledged: (completions, deliveredAt) => {
|
|
419
450
|
if (generation !== runtimeGeneration) return;
|
|
420
451
|
for (const completion of completions) {
|
|
452
|
+
dependencies.usageRecording?.recordCompletionVisible(completion);
|
|
421
453
|
void nextRegistry
|
|
422
454
|
.markCompletionDelivered(completion.completionId, deliveredAt)
|
|
423
455
|
.catch((error: unknown) => {
|
|
@@ -479,6 +511,7 @@ export function registerStatefulSubagents(
|
|
|
479
511
|
if (generation !== runtimeGeneration) return;
|
|
480
512
|
await sessionPersistence.save(agents);
|
|
481
513
|
if (generation !== runtimeGeneration) return;
|
|
514
|
+
dependencies.usageRecording?.observeAgents(agents);
|
|
482
515
|
for (const agent of agents) {
|
|
483
516
|
for (const message of agent.mailbox) {
|
|
484
517
|
if (seenMessageIds.has(message.id)) continue;
|
|
@@ -506,9 +539,9 @@ export function registerStatefulSubagents(
|
|
|
506
539
|
}
|
|
507
540
|
},
|
|
508
541
|
onTurnComplete: (completion) => {
|
|
509
|
-
if (generation
|
|
510
|
-
|
|
511
|
-
|
|
542
|
+
if (generation !== runtimeGeneration) return;
|
|
543
|
+
dependencies.usageRecording?.recordChildCompletion(completion);
|
|
544
|
+
if (completion.recipientId === "root") sessionBroker.enqueue(completion);
|
|
512
545
|
},
|
|
513
546
|
});
|
|
514
547
|
const persisted = sessionPersistence.load();
|
|
@@ -550,6 +583,26 @@ export function registerStatefulSubagents(
|
|
|
550
583
|
for (const message of agent.mailbox) seenMessageIds.add(message.id);
|
|
551
584
|
}
|
|
552
585
|
nextRegistry.restore(restored);
|
|
586
|
+
const branchRequirements = completionRequirementsFromBranch(ctx.sessionManager.getBranch());
|
|
587
|
+
const branchOwnsRequirementState =
|
|
588
|
+
branchRequirements.observedState || event.reason === "fork" || event.reason === "new";
|
|
589
|
+
nextRegistry.reconcileCompletionRequirements(
|
|
590
|
+
branchRequirements.records,
|
|
591
|
+
branchOwnsRequirementState,
|
|
592
|
+
);
|
|
593
|
+
if (branchOwnsRequirementState) {
|
|
594
|
+
try {
|
|
595
|
+
await sessionPersistence.save(nextRegistry.list(true));
|
|
596
|
+
} catch (error) {
|
|
597
|
+
if (generation === runtimeGeneration && ctx.hasUI) {
|
|
598
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
599
|
+
ctx.ui.notify(
|
|
600
|
+
`Subagent requirement reconciliation could not be persisted yet: ${reason}`,
|
|
601
|
+
"warning",
|
|
602
|
+
);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
}
|
|
553
606
|
if (generation !== runtimeGeneration) {
|
|
554
607
|
sessionBroker.close();
|
|
555
608
|
await sessionPeerBroker.close();
|
|
@@ -589,6 +642,9 @@ export function registerStatefulSubagents(
|
|
|
589
642
|
|
|
590
643
|
pi.on("context", (event) => {
|
|
591
644
|
completionBroker?.onParentContext(event.messages);
|
|
645
|
+
const agents = registry?.list() ?? [];
|
|
646
|
+
const messages = reconcileRequiredCompletionContext(event.messages, agents);
|
|
647
|
+
if (messages !== event.messages) return { messages };
|
|
592
648
|
});
|
|
593
649
|
|
|
594
650
|
pi.on("agent_settled", () => {
|
|
@@ -639,7 +695,7 @@ export function registerStatefulSubagents(
|
|
|
639
695
|
description: appendAgentCatalog(baseSpawnDescription(), agentCatalog),
|
|
640
696
|
promptSnippet: "Start a reusable detached subagent; completion is delivered asynchronously",
|
|
641
697
|
promptGuidelines: createSpawnPromptGuidelines(completionDelivery, blockingEnabled),
|
|
642
|
-
parameters:
|
|
698
|
+
parameters: grammarSafeToolObject({
|
|
643
699
|
agent: Type.String({ minLength: 1 }),
|
|
644
700
|
taskName: Type.Optional(
|
|
645
701
|
Type.String({
|
|
@@ -689,6 +745,7 @@ export function registerStatefulSubagents(
|
|
|
689
745
|
"Use text (default), structured-v1, or the evidence-preserving structured-v2 completion contract.",
|
|
690
746
|
}),
|
|
691
747
|
),
|
|
748
|
+
completionRequirement: Type.Optional(CompletionRequirementModeSchema),
|
|
692
749
|
}),
|
|
693
750
|
...createStatefulToolRenderer("spawn"),
|
|
694
751
|
async execute(_id, params, signal, _update, ctx) {
|
|
@@ -780,6 +837,7 @@ export function registerStatefulSubagents(
|
|
|
780
837
|
allowConcurrentWrites: params.allowConcurrentWrites ?? false,
|
|
781
838
|
contract,
|
|
782
839
|
resultFormat,
|
|
840
|
+
completionRequirement: params.completionRequirement ?? "background",
|
|
783
841
|
});
|
|
784
842
|
if (!capturedRegistry) {
|
|
785
843
|
throw new Error("Stateful subagents are not initialized for this session");
|
|
@@ -873,6 +931,7 @@ export function registerStatefulSubagents(
|
|
|
873
931
|
spawnRequestHash: params.idempotencyKey ? requestHash : undefined,
|
|
874
932
|
contract,
|
|
875
933
|
resultFormat: resultFormat === "text" ? undefined : resultFormat,
|
|
934
|
+
completionRequirement: params.completionRequirement ?? "background",
|
|
876
935
|
executionPlan,
|
|
877
936
|
capabilityGrant,
|
|
878
937
|
semanticSnapshot,
|
|
@@ -892,11 +951,15 @@ export function registerStatefulSubagents(
|
|
|
892
951
|
resolvePending?.(agent);
|
|
893
952
|
const deliveryNote =
|
|
894
953
|
completionDelivery === "auto-resume"
|
|
895
|
-
? "Auto-resume
|
|
954
|
+
? "Auto-resume steers completions into active parent work or requests synthesis when idle. Treat this response as progress, not final synthesis, until every final-answer-required completion message is visible. If local work is exhausted while required children remain active, emit at most one brief progress sentence and end the turn; do not repeat waiting updates or use the requested final format, verdict, or conclusion. Do not redo a running child's assigned work."
|
|
896
955
|
: "The current response must not depend on the result because next-turn delivery will not wake an idle root.";
|
|
956
|
+
const requirementNote =
|
|
957
|
+
params.completionRequirement === "required"
|
|
958
|
+
? "This exact run is runtime-tracked as required until its completion becomes visible or reaches an explicit terminal state. Current Pi versions still cannot prevent already-streamed premature output."
|
|
959
|
+
: "This run is background work and does not block the parent final answer.";
|
|
897
960
|
return result(
|
|
898
961
|
agent,
|
|
899
|
-
`Spawned ${agent.agent} as ${agent.taskPath ?? agent.id} (${agent.id}). Continue the identified non-overlapping local work immediately; do not merely announce the spawn or end while useful local work remains. Only an explicit user-requested specialist model, tool-profile, or isolation exception may lack concurrent local work. ${deliveryNote} Do not poll for progress.`,
|
|
962
|
+
`Spawned ${agent.agent} as ${agent.taskPath ?? agent.id} (${agent.id}). ${requirementNote} Continue the identified non-overlapping local work immediately; do not merely announce the spawn or end while useful local work remains. Only an explicit user-requested specialist model, tool-profile, or isolation exception may lack concurrent local work. ${deliveryNote} Do not poll for progress.`,
|
|
900
963
|
);
|
|
901
964
|
} catch (error) {
|
|
902
965
|
rejectPending?.(error);
|
|
@@ -937,6 +1000,7 @@ export function registerStatefulSubagents(
|
|
|
937
1000
|
}),
|
|
938
1001
|
),
|
|
939
1002
|
...StatefulTurnLimitFields,
|
|
1003
|
+
completionRequirement: Type.Optional(CompletionRequirementModeSchema),
|
|
940
1004
|
revalidate: Type.Optional(
|
|
941
1005
|
Type.Boolean({
|
|
942
1006
|
description:
|
|
@@ -1055,12 +1119,37 @@ export function registerStatefulSubagents(
|
|
|
1055
1119
|
idleTimeoutMs: params.idleTimeoutMs,
|
|
1056
1120
|
maxTurns: params.maxTurns,
|
|
1057
1121
|
maxToolCalls: params.maxToolCalls,
|
|
1122
|
+
completionRequirement: params.completionRequirement ?? "background",
|
|
1058
1123
|
});
|
|
1059
1124
|
assertCurrentSpawn(signal, generation, runtimeGeneration);
|
|
1060
1125
|
return result(agent, `Started follow-up for ${agent.id}.`);
|
|
1061
1126
|
},
|
|
1062
1127
|
});
|
|
1063
1128
|
|
|
1129
|
+
if (blockingEnabled) {
|
|
1130
|
+
pi.registerTool({
|
|
1131
|
+
name: "subagent_await",
|
|
1132
|
+
label: "Await Subagent",
|
|
1133
|
+
description:
|
|
1134
|
+
"Wait for one retained subagent's current turn to settle and return its latest bounded output. This blocks Pi from processing queued steering until the wait finishes. A wait timeout or caller cancellation stops only the wait and never interrupts or closes the subagent; use subagent_manage for lifecycle changes. Automatic completion delivery remains active and may later repeat the same at-least-once completion.",
|
|
1135
|
+
promptSnippet:
|
|
1136
|
+
"Intentionally block until one retained subagent settles or the wait times out",
|
|
1137
|
+
promptGuidelines: [
|
|
1138
|
+
"Use subagent_await only when the retained result is required before the next action, useful overlapping main-agent work is complete, and blocking Pi is intentional.",
|
|
1139
|
+
"Do not repeatedly call subagent_await after a timeout; the subagent keeps running and completion delivery remains active. Use subagent_manage only when the user wants to interrupt or close it.",
|
|
1140
|
+
],
|
|
1141
|
+
parameters: SubagentAwaitParams,
|
|
1142
|
+
...createStatefulToolRenderer("await"),
|
|
1143
|
+
async execute(_id, params, signal): Promise<StatefulActionToolResult> {
|
|
1144
|
+
const generation = runtimeGeneration;
|
|
1145
|
+
const timeoutMs = params.timeoutMs ?? DEFAULT_SUBAGENT_AWAIT_TIMEOUT_MS;
|
|
1146
|
+
const waited = await requireRegistry().wait(params.agentId, timeoutMs, signal);
|
|
1147
|
+
assertCurrentSpawn(signal, generation, runtimeGeneration);
|
|
1148
|
+
return awaitResult(waited.agent, waited.timedOut, timeoutMs);
|
|
1149
|
+
},
|
|
1150
|
+
});
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1064
1153
|
pi.registerTool({
|
|
1065
1154
|
name: "subagent_manage",
|
|
1066
1155
|
label: "Manage Subagents",
|
|
@@ -1225,6 +1314,34 @@ function result(agent: ManagedAgent, text: string) {
|
|
|
1225
1314
|
};
|
|
1226
1315
|
}
|
|
1227
1316
|
|
|
1317
|
+
function awaitResult(agent: ManagedAgent, timedOut: boolean, timeoutMs: number) {
|
|
1318
|
+
const latestTurn = agent.history.at(-1);
|
|
1319
|
+
const output = timedOut
|
|
1320
|
+
? ""
|
|
1321
|
+
: truncateUtf8(latestTurn?.output ?? "", DEFAULT_MAX_CONTEXT_BYTES).text;
|
|
1322
|
+
const error = timedOut ? "" : truncateUtf8(agent.error ?? "", MAX_TOOL_MESSAGE_BYTES).text;
|
|
1323
|
+
const text = timedOut
|
|
1324
|
+
? `Stopped waiting for ${agent.taskPath ?? agent.id} after ${timeoutMs}ms; it remains ${agent.state}. The wait did not interrupt or close the subagent.`
|
|
1325
|
+
: [`Subagent ${agent.taskPath ?? agent.id} settled as ${agent.state}.`, output || error]
|
|
1326
|
+
.filter(Boolean)
|
|
1327
|
+
.join("\n\n");
|
|
1328
|
+
return {
|
|
1329
|
+
content: [
|
|
1330
|
+
{
|
|
1331
|
+
type: "text" as const,
|
|
1332
|
+
text: truncateUtf8(text, DEFAULT_MAX_CONTEXT_BYTES).text,
|
|
1333
|
+
},
|
|
1334
|
+
],
|
|
1335
|
+
details: {
|
|
1336
|
+
agent: summarizeStatefulAgent(agent),
|
|
1337
|
+
timedOut,
|
|
1338
|
+
timeoutMs,
|
|
1339
|
+
...(output ? { output } : {}),
|
|
1340
|
+
...(error ? { error } : {}),
|
|
1341
|
+
},
|
|
1342
|
+
};
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1228
1345
|
function normalizeRuntimeThinkingLevel(value: string): ParentRuntimeSnapshot["thinkingLevel"] {
|
|
1229
1346
|
return isThinkingLevel(value) ? value : "off";
|
|
1230
1347
|
}
|
package/src/stateful-render.ts
CHANGED
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
toolHeader,
|
|
18
18
|
} from "./render-common.js";
|
|
19
19
|
|
|
20
|
-
export type StatefulRenderTool = "spawn" | "send" | "manage" | "mailbox";
|
|
20
|
+
export type StatefulRenderTool = "spawn" | "send" | "await" | "manage" | "mailbox";
|
|
21
21
|
|
|
22
22
|
export function createStatefulToolRenderer(tool: StatefulRenderTool) {
|
|
23
23
|
return {
|
|
@@ -71,6 +71,11 @@ function renderStatefulCall(tool: StatefulRenderTool, args: Record<string, unkno
|
|
|
71
71
|
0,
|
|
72
72
|
);
|
|
73
73
|
}
|
|
74
|
+
if (tool === "await") {
|
|
75
|
+
const metadata = ["blocking"];
|
|
76
|
+
if (typeof args.timeoutMs === "number") metadata.push(`timeout:${args.timeoutMs}ms`);
|
|
77
|
+
return new Text(toolHeader(theme, "subagent_await", args.agentId, metadata), 0, 0);
|
|
78
|
+
}
|
|
74
79
|
if (tool === "manage") {
|
|
75
80
|
const metadata: string[] = [];
|
|
76
81
|
if (typeof args.agentId === "string") metadata.push(`id:${safeLine(args.agentId, "", 256)}`);
|
|
@@ -98,9 +103,12 @@ function renderStatefulResult(
|
|
|
98
103
|
const details = recordValue(result.details);
|
|
99
104
|
const args = recordValue(context.args) ?? {};
|
|
100
105
|
if (!details) return renderFallbackResult(result, options, theme, context.isError);
|
|
101
|
-
if (tool === "spawn" || tool === "send") {
|
|
106
|
+
if (tool === "spawn" || tool === "send" || tool === "await") {
|
|
102
107
|
const agent = recordValue(details.agent);
|
|
103
108
|
if (!agent) return renderFallbackResult(result, options, theme, context.isError);
|
|
109
|
+
if (tool === "await" && details.timedOut === true) {
|
|
110
|
+
return new Text(renderAwaitTimeout(agent, result, options.expanded, theme), 0, 0);
|
|
111
|
+
}
|
|
104
112
|
return new Text(renderAgentResult(agent, result, options.expanded, theme), 0, 0);
|
|
105
113
|
}
|
|
106
114
|
if (tool === "manage") {
|
|
@@ -173,6 +181,22 @@ function renderAgentResult(
|
|
|
173
181
|
return lines.join("\n");
|
|
174
182
|
}
|
|
175
183
|
|
|
184
|
+
function renderAwaitTimeout(
|
|
185
|
+
agent: Record<string, unknown>,
|
|
186
|
+
result: AgentToolResult<unknown>,
|
|
187
|
+
expanded: boolean,
|
|
188
|
+
theme: Theme,
|
|
189
|
+
): string {
|
|
190
|
+
const lines = [
|
|
191
|
+
`${statusBadge(theme, "running")} · ${theme.fg("accent", safeLine(agent.id, "agent", 256))} · ${theme.fg("warning", "wait timed out")} · ${theme.fg("muted", safeLine(agent.state, "unknown", 128))}`,
|
|
192
|
+
];
|
|
193
|
+
if (expanded) {
|
|
194
|
+
const content = safeBlock(textResult(result), "", 8 * 1024).trim();
|
|
195
|
+
if (content) lines.push(theme.fg("toolOutput", content));
|
|
196
|
+
} else lines.push(expansionHint());
|
|
197
|
+
return lines.join("\n");
|
|
198
|
+
}
|
|
199
|
+
|
|
176
200
|
function renderManageResult(
|
|
177
201
|
args: Record<string, unknown>,
|
|
178
202
|
details: Record<string, unknown>,
|
|
@@ -276,6 +300,7 @@ function lifecycleStatus(state: string): RenderStatus {
|
|
|
276
300
|
return "running";
|
|
277
301
|
case "idle":
|
|
278
302
|
return "idle";
|
|
303
|
+
case "partial":
|
|
279
304
|
case "blocked":
|
|
280
305
|
case "needs-input":
|
|
281
306
|
case "abstained":
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { AgentToolResult } from "@earendil-works/pi-agent-core";
|
|
2
|
+
import type { SchedulingDecision } from "./adaptive-scheduler.js";
|
|
3
|
+
import type { AgentScope } from "./agents/types.js";
|
|
4
|
+
import type { OrchestrationMetrics } from "./orchestration-metrics.js";
|
|
5
|
+
import type { PanelSynthesis } from "./panel-contract.js";
|
|
6
|
+
import type { PanelEvidenceArtifact } from "./panel-evidence.js";
|
|
7
|
+
import type { PanelFailure } from "./panel-failure.js";
|
|
8
|
+
import type { PanelPhaseBudgets, PanelPreset } from "./panel-planning.js";
|
|
9
|
+
import type { SingleResult } from "./runner-types.js";
|
|
10
|
+
import type { WorkItemLedgerSnapshot } from "./work-item-ledger.js";
|
|
11
|
+
|
|
12
|
+
export interface PanelDetails {
|
|
13
|
+
id: string;
|
|
14
|
+
preset: PanelPreset;
|
|
15
|
+
sharedTaskPreview: string;
|
|
16
|
+
state: "running" | "completed" | "degraded" | "insufficient-panel" | "failed" | "cancelled";
|
|
17
|
+
reviewerIds: string[];
|
|
18
|
+
validReviewCount: number;
|
|
19
|
+
failedReviewCount: number;
|
|
20
|
+
blockingObjectionCount: number;
|
|
21
|
+
dissentCount: number;
|
|
22
|
+
budgets: PanelPhaseBudgets;
|
|
23
|
+
evidence: PanelEvidenceArtifact[];
|
|
24
|
+
failures: PanelFailure[];
|
|
25
|
+
synthesis?: PanelSynthesis;
|
|
26
|
+
synthesizerResult?: SingleResult;
|
|
27
|
+
cleanupComplete: boolean;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface SubagentDetails {
|
|
31
|
+
mode: "single" | "parallel" | "chain" | "workflow" | "panel";
|
|
32
|
+
agentScope: AgentScope;
|
|
33
|
+
projectAgentsDir: string | null;
|
|
34
|
+
results: SingleResult[];
|
|
35
|
+
aggregator?: SingleResult;
|
|
36
|
+
workflow?: WorkItemLedgerSnapshot;
|
|
37
|
+
schedulerDecisions?: SchedulingDecision[];
|
|
38
|
+
metrics?: OrchestrationMetrics;
|
|
39
|
+
panel?: PanelDetails;
|
|
40
|
+
isError?: boolean;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export type OnUpdateCallback = (partial: AgentToolResult<SubagentDetails>) => void;
|
|
@@ -39,7 +39,6 @@ import {
|
|
|
39
39
|
import { MAX_BLOCKING_PARALLEL_CONCURRENCY } from "./limits.js";
|
|
40
40
|
import { SubagentParams } from "./params.js";
|
|
41
41
|
import { renderSubagentCall, renderSubagentResult } from "./render.js";
|
|
42
|
-
import type { SubagentDetails } from "./runner.js";
|
|
43
42
|
import {
|
|
44
43
|
consumeSubagentSettingsNotice,
|
|
45
44
|
DEFAULT_CONSULT_RESOURCE_POLICY,
|
|
@@ -50,7 +49,14 @@ import {
|
|
|
50
49
|
resolveBlockingMaxParallelTasks,
|
|
51
50
|
} from "./settings-reader.js";
|
|
52
51
|
import { registerStatefulSubagents } from "./stateful-registration.js";
|
|
52
|
+
import type { SubagentDetails } from "./subagent-details.js";
|
|
53
53
|
import type { SubagentTransport } from "./transport.js";
|
|
54
|
+
import {
|
|
55
|
+
registerUsageRecording,
|
|
56
|
+
type UsageRecordingDependencies,
|
|
57
|
+
type UsageSurfaceArm,
|
|
58
|
+
} from "./usage-recording.js";
|
|
59
|
+
import { resolveUsageRecordingEnabled } from "./usage-recording-config.js";
|
|
54
60
|
|
|
55
61
|
type BlockingExecutionModule = Pick<typeof import("./execution.js"), "executeSubagent">;
|
|
56
62
|
|
|
@@ -60,6 +66,7 @@ export interface SubagentsDependencies {
|
|
|
60
66
|
config?: ConfigRegistrationDependencies;
|
|
61
67
|
consult?: ConsultRegistrationDependencies;
|
|
62
68
|
inspect?: InspectRegistrationDependencies;
|
|
69
|
+
usageRecording?: Partial<UsageRecordingDependencies>;
|
|
63
70
|
}
|
|
64
71
|
|
|
65
72
|
export default function (pi: ExtensionAPI, dependencies: SubagentsDependencies = {}) {
|
|
@@ -68,6 +75,7 @@ export default function (pi: ExtensionAPI, dependencies: SubagentsDependencies =
|
|
|
68
75
|
dependencies.loadBlockingExecution ?? (() => import("./execution.js")),
|
|
69
76
|
);
|
|
70
77
|
const configOwner = registerSubagentConfigLifecycle(pi);
|
|
78
|
+
const usageRecording = registerUsageRecording(pi, dependencies.usageRecording);
|
|
71
79
|
const settings = readSubagentSettings();
|
|
72
80
|
let currentSettings: SubagentSettings | undefined = settings;
|
|
73
81
|
let currentCatalog = "";
|
|
@@ -78,7 +86,7 @@ export default function (pi: ExtensionAPI, dependencies: SubagentsDependencies =
|
|
|
78
86
|
let refreshStatefulCatalog: (catalog: string) => void = () => undefined;
|
|
79
87
|
let refreshConsultCatalog: (catalog: string) => void = () => undefined;
|
|
80
88
|
|
|
81
|
-
pi.on("session_start", (
|
|
89
|
+
pi.on("session_start", async (event, ctx) => {
|
|
82
90
|
// Preserve a one-shot migration notice from extension load while refreshing
|
|
83
91
|
// validation against settings that may have changed before this session.
|
|
84
92
|
const loadNotice = consumeSubagentSettingsNotice();
|
|
@@ -96,6 +104,14 @@ export default function (pi: ExtensionAPI, dependencies: SubagentsDependencies =
|
|
|
96
104
|
refreshBlockingCatalog(currentCatalog);
|
|
97
105
|
refreshStatefulCatalog(currentCatalog);
|
|
98
106
|
refreshConsultCatalog(currentCatalog);
|
|
107
|
+
await usageRecording.startSession({
|
|
108
|
+
enabled: resolveUsageRecordingEnabled(currentSettings?.usageRecording),
|
|
109
|
+
surfaceArm: usageSurfaceArm(blockingEnabled, statefulRuntime.getRuntimeStatus().enabled),
|
|
110
|
+
reason: event.reason,
|
|
111
|
+
onWarning: (message) => {
|
|
112
|
+
if (ctx.hasUI) ctx.ui.notify(message, "warning");
|
|
113
|
+
},
|
|
114
|
+
});
|
|
99
115
|
});
|
|
100
116
|
|
|
101
117
|
const statefulRuntime = registerStatefulSubagents(pi, {
|
|
@@ -103,6 +119,7 @@ export default function (pi: ExtensionAPI, dependencies: SubagentsDependencies =
|
|
|
103
119
|
settings: settings?.stateful,
|
|
104
120
|
getSettings: () => currentSettings,
|
|
105
121
|
loadTransport: dependencies.loadStatefulTransport,
|
|
122
|
+
usageRecording,
|
|
106
123
|
});
|
|
107
124
|
refreshStatefulCatalog = statefulRuntime.setAgentCatalog;
|
|
108
125
|
const getBlockingEnabled = () => blockingEnabled;
|
|
@@ -122,6 +139,7 @@ export default function (pi: ExtensionAPI, dependencies: SubagentsDependencies =
|
|
|
122
139
|
getConsultResourcePolicy,
|
|
123
140
|
getConsultationCwdPolicy,
|
|
124
141
|
getDelegationCwdPolicy,
|
|
142
|
+
getUsageRecordingStatus: () => usageRecording.getStatus(),
|
|
125
143
|
},
|
|
126
144
|
dependencies.inspect,
|
|
127
145
|
);
|
|
@@ -141,6 +159,15 @@ export default function (pi: ExtensionAPI, dependencies: SubagentsDependencies =
|
|
|
141
159
|
getConsultResourcePolicy,
|
|
142
160
|
getConsultationCwdPolicy,
|
|
143
161
|
getDelegationCwdPolicy,
|
|
162
|
+
getUsageRecordingEnabled: () => usageRecording.getStatus().enabled,
|
|
163
|
+
getUsageRecordingStatus: () => usageRecording.getStatus(),
|
|
164
|
+
setUsageRecordingEnabled: async (value: boolean) => {
|
|
165
|
+
await usageRecording.setEnabled(value);
|
|
166
|
+
currentSettings = {
|
|
167
|
+
...(currentSettings ?? {}),
|
|
168
|
+
usageRecording: { ...(currentSettings?.usageRecording ?? {}), enabled: value },
|
|
169
|
+
};
|
|
170
|
+
},
|
|
144
171
|
setMaxParallelTasks(value: number) {
|
|
145
172
|
const previousSettings = currentSettings;
|
|
146
173
|
currentSettings = {
|
|
@@ -188,6 +215,14 @@ export default function (pi: ExtensionAPI, dependencies: SubagentsDependencies =
|
|
|
188
215
|
configOwner,
|
|
189
216
|
dependencies.config,
|
|
190
217
|
);
|
|
218
|
+
pi.on("session_shutdown", (event) => usageRecording.shutdown(event.reason));
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function usageSurfaceArm(blockingEnabled: boolean, statefulEnabled: boolean): UsageSurfaceArm {
|
|
222
|
+
if (blockingEnabled && statefulEnabled) return "all";
|
|
223
|
+
if (statefulEnabled) return "async-only";
|
|
224
|
+
if (blockingEnabled) return "blocking-only";
|
|
225
|
+
return "disabled";
|
|
191
226
|
}
|
|
192
227
|
|
|
193
228
|
function registerBlockingSubagent(
|
|
@@ -196,6 +231,7 @@ function registerBlockingSubagent(
|
|
|
196
231
|
loadExecution: () => Promise<BlockingExecutionModule>,
|
|
197
232
|
): (catalog: string) => void {
|
|
198
233
|
let catalog = "";
|
|
234
|
+
let deprecationWarningShown = false;
|
|
199
235
|
const activeControllers = new Set<AbortController>();
|
|
200
236
|
const activeWork = new Set<Promise<unknown>>();
|
|
201
237
|
const cancelAndWaitForWork = async (reason: string) => {
|
|
@@ -204,10 +240,20 @@ function registerBlockingSubagent(
|
|
|
204
240
|
}
|
|
205
241
|
await Promise.allSettled([...activeWork]);
|
|
206
242
|
};
|
|
207
|
-
pi.on("session_start", () =>
|
|
243
|
+
pi.on("session_start", () => {
|
|
244
|
+
deprecationWarningShown = false;
|
|
245
|
+
return cancelAndWaitForWork("Blocking subagent session replaced");
|
|
246
|
+
});
|
|
208
247
|
pi.on("session_shutdown", () => cancelAndWaitForWork("Blocking subagent session shut down"));
|
|
248
|
+
const statefulEnabled = () => getSettings()?.stateful?.enabled !== false;
|
|
249
|
+
const deprecationAlternatives = () =>
|
|
250
|
+
statefulEnabled()
|
|
251
|
+
? "Prefer the main agent for tightly coupled work, subagent_spawn for detached work, subagent_await for an intentional retained-agent join, or subagent_consult for bounded synchronous read-only evidence."
|
|
252
|
+
: "Prefer the main agent for tightly coupled work or subagent_consult for bounded synchronous read-only evidence; enable the background workflow before using detached alternatives.";
|
|
209
253
|
const baseDescription = () =>
|
|
210
254
|
[
|
|
255
|
+
"Deprecated compatibility tool: do not choose subagent for new work.",
|
|
256
|
+
deprecationAlternatives(),
|
|
211
257
|
"Run specialized subagents as a blocking operation with isolated contexts.",
|
|
212
258
|
"The call blocks the main agent until every worker and optional aggregator finishes, so queued steering waits.",
|
|
213
259
|
"Modes: single (agent + task), parallel (tasks array), chain (sequential with {previous} placeholder), workflow (named dependency tasks with optional capability routing), or panel (independent reviewers plus evidence-preserving synthesis).",
|
|
@@ -218,14 +264,17 @@ function registerBlockingSubagent(
|
|
|
218
264
|
`Working-directory target policy: ${getSettings()?.cwdPolicy?.delegation ?? DEFAULT_DELEGATION_CWD_POLICY}. This controls launch targets and protected project resources, not filesystem access or sandboxing.`,
|
|
219
265
|
].join(" ");
|
|
220
266
|
const promptGuidelines = () => [
|
|
221
|
-
|
|
267
|
+
statefulEnabled()
|
|
268
|
+
? "The subagent tool is deprecated for new work; prefer the main agent, subagent_spawn with supported completion delivery, subagent_await for an intentional retained-agent join, or subagent_consult for bounded synchronous read-only evidence."
|
|
269
|
+
: "The subagent tool is deprecated for new work; prefer the main agent or subagent_consult for bounded synchronous read-only evidence, and enable the background workflow before using detached alternatives.",
|
|
270
|
+
"Use deprecated subagent only for an existing caller or an explicit user request whose blocking chain, fan-in, panel, or workflow semantics do not yet have a detached replacement.",
|
|
271
|
+
"When compatibility requires subagent, decide how many subagents to spawn from task shape instead of waiting for the user to specify a count.",
|
|
222
272
|
"The main agent retains overall planning, immediate critical-path work, integration, final verification, and the final answer.",
|
|
223
273
|
"Use no subagent for simple answers, quick targeted edits, latency-sensitive one-step work, tasks requiring frequent user back-and-forth, or critical-path work the main agent can perform directly.",
|
|
224
274
|
"One ordinary implementation worker should not replace work the main agent can perform directly; use a blocking single only when intentional synchronous isolation or a user-requested specialist justifies waiting.",
|
|
225
275
|
"Keep ordinary planning in the main agent, or use explicit workflow mode when a genuine dependency graph requires caller-authored orchestration.",
|
|
226
276
|
"Keep ordinary review in the main agent with a review skill and deterministic checks; reserve panel mode or custom verifier agents for consequential independent verification.",
|
|
227
|
-
"
|
|
228
|
-
"Use a blocking subagent single, parallel, chain, workflow, panel, or fan-in call only when synchronous context or output isolation is worth making the main agent unavailable while it runs.",
|
|
277
|
+
"A compatibility subagent call blocks the main agent from processing queued steering until it returns; use it only when the explicit legacy workflow justifies making Pi unavailable.",
|
|
229
278
|
`If a blocking parallel subagent call is genuinely required, keep tasks independent, stay within the configured max ${resolveBlockingMaxParallelTasks(getSettings())}, and avoid write-heavy implementation touching the same files or shared state.`,
|
|
230
279
|
"For parallel subagent calls, omit the aggregator key entirely unless a fan-in step is required; do not send null, empty strings, or an empty object for unused optional fields.",
|
|
231
280
|
"Use workflow mode for explicit dependencies or capability routing; declare read/write or ownership scopes, require structured-v2 artifacts when downstream tasks consume them, and use retry or hedging only with the required side-effect contract.",
|
|
@@ -236,10 +285,10 @@ function registerBlockingSubagent(
|
|
|
236
285
|
];
|
|
237
286
|
const definition: ToolDefinition<typeof SubagentParams, SubagentDetails> = {
|
|
238
287
|
name: "subagent",
|
|
239
|
-
label: "Blocking Subagent",
|
|
288
|
+
label: "Blocking Subagent · Deprecated",
|
|
240
289
|
description: appendAgentCatalog(baseDescription(), catalog),
|
|
241
290
|
promptSnippet:
|
|
242
|
-
"
|
|
291
|
+
"Deprecated blocking subagent compatibility tool; prefer detached or read-only alternatives.",
|
|
243
292
|
promptGuidelines: promptGuidelines(),
|
|
244
293
|
parameters: SubagentParams,
|
|
245
294
|
|
|
@@ -251,6 +300,15 @@ function registerBlockingSubagent(
|
|
|
251
300
|
: lifecycleController.signal;
|
|
252
301
|
const work = (async () => {
|
|
253
302
|
throwIfAborted(effectiveSignal, "Blocking subagent execution was cancelled");
|
|
303
|
+
if (!deprecationWarningShown && ctx.hasUI) {
|
|
304
|
+
deprecationWarningShown = true;
|
|
305
|
+
ctx.ui.notify(
|
|
306
|
+
statefulEnabled()
|
|
307
|
+
? "subagent is deprecated for new work. Prefer the main agent, subagent_spawn with completion delivery, subagent_await for an intentional join, or subagent_consult for synchronous read-only evidence."
|
|
308
|
+
: "subagent is deprecated for new work. Prefer the main agent or subagent_consult; enable the background workflow before using detached alternatives.",
|
|
309
|
+
"warning",
|
|
310
|
+
);
|
|
311
|
+
}
|
|
254
312
|
let executionModule: BlockingExecutionModule;
|
|
255
313
|
try {
|
|
256
314
|
executionModule = await loadExecution();
|
package/src/subagents.ts
CHANGED
|
@@ -11,6 +11,7 @@ export {
|
|
|
11
11
|
inspectDelegationWorkflowSettings,
|
|
12
12
|
inspectStatefulLimitSettings,
|
|
13
13
|
inspectSubagentSettings,
|
|
14
|
+
inspectUsageRecordingSettings,
|
|
14
15
|
normalizeAgentSettings,
|
|
15
16
|
normalizeSubagentSettings,
|
|
16
17
|
readSubagentSettings,
|
|
@@ -27,6 +28,7 @@ export {
|
|
|
27
28
|
updateCwdPolicySetting,
|
|
28
29
|
updateDelegationWorkflowSetting,
|
|
29
30
|
updateStatefulLimitSetting,
|
|
31
|
+
updateUsageRecordingSetting,
|
|
30
32
|
} from "./settings.js";
|
|
31
33
|
export { default, type SubagentsDependencies } from "./subagents-extension.js";
|
|
32
34
|
export { formatTokens, formatUsageStats } from "./usage-format.js";
|
|
@@ -8,9 +8,10 @@ import {
|
|
|
8
8
|
} from "./peer-transport.js";
|
|
9
9
|
import { resolvePiPromptResources } from "./prompt-resources.js";
|
|
10
10
|
import type { ManagedAgent, TurnOutcome } from "./registry.js";
|
|
11
|
-
import { getResultFinalOutput, runSingleAgent
|
|
11
|
+
import { getResultFinalOutput, runSingleAgent } from "./runner.js";
|
|
12
12
|
import { readSubagentSettings, resolveSubagentThinkingLevel } from "./settings.js";
|
|
13
13
|
import { buildStatefulTurnPrompt, resolveStatefulTurnTimeout } from "./stateful-prompt.js";
|
|
14
|
+
import type { SubagentDetails } from "./subagent-details.js";
|
|
14
15
|
import type { SubagentTransport } from "./transport.js";
|
|
15
16
|
import type { TransportProgressCallback, TransportTelemetry } from "./transport-types.js";
|
|
16
17
|
|
package/src/supervision.ts
CHANGED