@narumitw/pi-subagents 0.54.0 → 1.0.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.
@@ -1,25 +1,18 @@
1
1
  import { StringEnum } from "@earendil-works/pi-ai";
2
2
  import { Type } from "typebox";
3
3
 
4
- const MANAGE_ACTIONS = ["list", "interrupt", "close"] as const;
4
+ const MANAGE_ACTIONS = ["interrupt", "close"] as const;
5
5
  const MAILBOX_ACTIONS = ["send", "read"] as const;
6
6
  const MAX_MAILBOX_MESSAGE_LENGTH = 16 * 1024;
7
7
 
8
8
  export const ManageParamsSchema = Type.Object(
9
9
  {
10
10
  action: StringEnum(MANAGE_ACTIONS, {
11
- description:
12
- "Use list to inspect agents, interrupt to stop active work, or close to release agents.",
11
+ description: "Interrupt active work or close an agent and release its resources.",
13
12
  }),
14
13
  agentId: Type.Optional(
15
14
  Type.String({ minLength: 1, description: "Required for interrupt and close." }),
16
15
  ),
17
- includeClosed: Type.Optional(
18
- Type.Boolean({
19
- default: false,
20
- description: "List closed records as well as retained agents.",
21
- }),
22
- ),
23
16
  subtree: Type.Optional(
24
17
  Type.Boolean({
25
18
  default: false,
@@ -57,9 +50,11 @@ export const MailboxParamsSchema = Type.Object(
57
50
  { additionalProperties: false },
58
51
  );
59
52
 
60
- export type ValidatedManageParams =
61
- | { action: "list"; includeClosed?: boolean }
62
- | { action: "interrupt" | "close"; agentId: string; subtree?: boolean };
53
+ export type ValidatedManageParams = {
54
+ action: "interrupt" | "close";
55
+ agentId: string;
56
+ subtree?: boolean;
57
+ };
63
58
 
64
59
  export type ValidatedMailboxParams =
65
60
  | {
@@ -73,21 +68,19 @@ export type ValidatedMailboxParams =
73
68
 
74
69
  export function validateManageParams(params: unknown): ValidatedManageParams {
75
70
  const values = parameterRecord(params, "subagent_manage");
76
- assertKnownKeys("subagent_manage", values, ["action", "agentId", "includeClosed", "subtree"]);
71
+ assertKnownKeys("subagent_manage", values, ["action", "agentId", "subtree"]);
77
72
  const action = values.action;
73
+ if (action === "list") {
74
+ throw new Error(
75
+ 'subagent_manage no longer supports "list"; use subagent_inspect action "list_runs"',
76
+ );
77
+ }
78
78
  if (
79
79
  typeof action !== "string" ||
80
80
  !MANAGE_ACTIONS.includes(action as (typeof MANAGE_ACTIONS)[number])
81
81
  ) {
82
82
  throw new Error(`subagent_manage action must be one of: ${MANAGE_ACTIONS.join(", ")}`);
83
83
  }
84
- if (action === "list") {
85
- assertOptionalBoolean("subagent_manage", action, values, "includeClosed");
86
- return {
87
- action,
88
- ...(values.includeClosed === undefined ? {} : { includeClosed: values.includeClosed }),
89
- } as ValidatedManageParams;
90
- }
91
84
  assertRequiredString("subagent_manage", action, values, "agentId");
92
85
  assertOptionalBoolean("subagent_manage", action, values, "subtree");
93
86
  return {
package/src/stateful.ts CHANGED
@@ -20,7 +20,10 @@ import {
20
20
  import { issueCapabilityGrant } from "./capability-grant.js";
21
21
  import { CompletionDeliveryBroker } from "./completion-delivery.js";
22
22
  import { buildContextSnapshot, type ContextMode, redactPrivateText } from "./context.js";
23
- import { createStatefulTransport } from "./create-stateful-transport.js";
23
+ import {
24
+ type CreateStatefulTransportOptions,
25
+ createStatefulTransport,
26
+ } from "./create-stateful-transport.js";
24
27
  import {
25
28
  assertDelegationTargetAllowed,
26
29
  resolveSubagentTarget,
@@ -52,7 +55,7 @@ import {
52
55
  hashSpawnRequest,
53
56
  MAX_SPAWN_IDEMPOTENCY_KEY_LENGTH,
54
57
  } from "./spawn-idempotency.js";
55
- import { formatStatefulAgentLine, summarizeStatefulAgent } from "./stateful-agent-view.js";
58
+ import { summarizeStatefulAgent } from "./stateful-agent-view.js";
56
59
  import { resolveCompletionDelivery, resolveStatefulTransportKind } from "./stateful-config.js";
57
60
  import { createSpawnPromptGuidelines } from "./stateful-guidance.js";
58
61
  import {
@@ -133,6 +136,7 @@ export interface StatefulSubagentDependencies {
133
136
  workspaceManager?: WorkspaceManager;
134
137
  settings?: SubagentRuntimeSettings;
135
138
  getSettings?: () => SubagentSettings | undefined;
139
+ loadTransport?: CreateStatefulTransportOptions["loadTransport"];
136
140
  }
137
141
 
138
142
  export interface StatefulSubagentRuntimeStatus {
@@ -300,10 +304,16 @@ export function registerStatefulSubagents(
300
304
  const reason = error instanceof Error ? error.message : String(error);
301
305
  ctx.ui.notify(`Subagent completion delivery failed: ${reason}`, "warning");
302
306
  },
303
- onDelivered: (completions, deliveredAt) => {
307
+ onAcknowledged: (completions, deliveredAt) => {
304
308
  if (generation !== runtimeGeneration) return;
305
309
  for (const completion of completions) {
306
- nextRegistry.markCompletionDelivered(completion.agent.id, deliveredAt);
310
+ void nextRegistry
311
+ .markCompletionDelivered(completion.completionId, deliveredAt)
312
+ .catch((error: unknown) => {
313
+ if (!ctx.hasUI || generation !== runtimeGeneration) return;
314
+ const reason = error instanceof Error ? error.message : String(error);
315
+ ctx.ui.notify(`Subagent completion acknowledgement failed: ${reason}`, "warning");
316
+ });
307
317
  }
308
318
  },
309
319
  });
@@ -313,6 +323,7 @@ export function registerStatefulSubagents(
313
323
  getParentRuntime: () => ({ ...parentRuntime }),
314
324
  getSettings: getCurrentSettings,
315
325
  createInProcessSession: dependencies.createInProcessSession,
326
+ loadTransport: dependencies.loadTransport,
316
327
  });
317
328
  nextRegistry = new AgentRegistry(transport, {
318
329
  maxAgents: nextLimits.maxAgents,
@@ -378,6 +389,9 @@ export function registerStatefulSubagents(
378
389
  registry = nextRegistry;
379
390
  persistence = sessionPersistence;
380
391
  completionBroker = sessionBroker;
392
+ for (const completion of nextRegistry.listPendingCompletions()) {
393
+ sessionBroker.enqueue(completion);
394
+ }
381
395
  runtimeLimits = nextLimits;
382
396
  refreshSpawnToolRegistration?.();
383
397
  const sweepEveryMs = Math.max(
@@ -402,6 +416,10 @@ export function registerStatefulSubagents(
402
416
  completionBroker?.onParentTurnStart();
403
417
  });
404
418
 
419
+ pi.on("context", (event) => {
420
+ completionBroker?.onParentContext(event.messages);
421
+ });
422
+
405
423
  pi.on("agent_settled", () => {
406
424
  completionBroker?.onParentSettled();
407
425
  });
@@ -835,8 +853,8 @@ export function registerStatefulSubagents(
835
853
  name: "subagent_manage",
836
854
  label: "Manage Subagents",
837
855
  description:
838
- "List retained subagents through the compatibility route, interrupt active work while keeping an agent reusable, or close agents and release their resources. Prefer subagent_inspect when the whole activated capability must be read-only.",
839
- promptSnippet: "List or control retained detached subagents",
856
+ "Interrupt active work while keeping an agent reusable, or close agents and release their resources. Use subagent_inspect for every read-only list, detail, status, and diagnostic operation.",
857
+ promptSnippet: "Interrupt or close retained detached subagents",
840
858
  parameters: ManageParamsSchema,
841
859
  ...createStatefulToolRenderer("manage"),
842
860
  async execute(_id, params, signal): Promise<StatefulActionToolResult> {
@@ -848,20 +866,6 @@ export function registerStatefulSubagents(
848
866
  return value;
849
867
  };
850
868
  const operation = validateManageParams(params);
851
- if (operation.action === "list") {
852
- const agents = ownedRegistry.list(operation.includeClosed);
853
- return {
854
- content: [
855
- {
856
- type: "text",
857
- text: agents.length
858
- ? agents.map(formatStatefulAgentLine).join("\n")
859
- : "No stateful subagents.",
860
- },
861
- ],
862
- details: { agents: agents.map(summarizeStatefulAgent) },
863
- };
864
- }
865
869
  const agentId = operation.agentId;
866
870
  if (operation.action === "interrupt") {
867
871
  if (operation.subtree) {
package/src/subagents.ts CHANGED
@@ -21,11 +21,24 @@ import type {
21
21
  DelegationCwdPolicy,
22
22
  SubagentSettings,
23
23
  } from "./agents/types.js";
24
- import { registerSubagentAutomation } from "./automation.js";
25
- import { registerSubagentConfigCommand, registerSubagentConfigLifecycle } from "./config-ui.js";
26
- import { registerSubagentConsult } from "./consult.js";
27
- import { executeSubagent } from "./execution.js";
28
- import { registerSubagentInspect } from "./inspect.js";
24
+ import {
25
+ type AutomationRegistrationDependencies,
26
+ registerSubagentAutomation,
27
+ } from "./automation-registration.js";
28
+ import { cachedModuleLoader, throwIfAborted } from "./cached-module-loader.js";
29
+ import {
30
+ type ConfigRegistrationDependencies,
31
+ registerSubagentConfigCommand,
32
+ registerSubagentConfigLifecycle,
33
+ } from "./config-registration.js";
34
+ import {
35
+ type ConsultRegistrationDependencies,
36
+ registerSubagentConsult,
37
+ } from "./consult-registration.js";
38
+ import {
39
+ type InspectRegistrationDependencies,
40
+ registerSubagentInspect,
41
+ } from "./inspect-registration.js";
29
42
  import { MAX_BLOCKING_PARALLEL_CONCURRENCY } from "./limits.js";
30
43
  import { SubagentParams } from "./params.js";
31
44
  import { renderSubagentCall, renderSubagentResult } from "./render.js";
@@ -40,17 +53,34 @@ import {
40
53
  resolveBlockingMaxParallelTasks,
41
54
  } from "./settings.js";
42
55
  import { registerStatefulSubagents } from "./stateful.js";
56
+ import type { SubagentTransport } from "./transport.js";
57
+
58
+ type BlockingExecutionModule = Pick<typeof import("./execution.js"), "executeSubagent">;
59
+
60
+ export interface SubagentsDependencies {
61
+ loadBlockingExecution?: () => Promise<BlockingExecutionModule>;
62
+ loadStatefulTransport?: () => Promise<SubagentTransport>;
63
+ automation?: AutomationRegistrationDependencies;
64
+ config?: ConfigRegistrationDependencies;
65
+ consult?: ConsultRegistrationDependencies;
66
+ inspect?: InspectRegistrationDependencies;
67
+ }
43
68
 
44
- export default function (pi: ExtensionAPI) {
69
+ export default function (pi: ExtensionAPI, dependencies: SubagentsDependencies = {}) {
70
+ const loadBlockingExecution = cachedModuleLoader(
71
+ dependencies.loadBlockingExecution ?? (() => import("./execution.js")),
72
+ );
45
73
  const configOwner = registerSubagentConfigLifecycle(pi);
46
74
  const settings = readSubagentSettings();
47
75
  let currentSettings: SubagentSettings | undefined = settings;
48
76
  let currentCatalog = "";
49
77
  const blockingEnabled = settings?.blocking?.enabled !== false;
50
78
  const refreshBlockingCatalog = blockingEnabled
51
- ? registerBlockingSubagent(pi, () => currentSettings)
79
+ ? registerBlockingSubagent(pi, () => currentSettings, loadBlockingExecution)
52
80
  : () => undefined;
53
- if (blockingEnabled) registerSubagentAutomation(pi, { getSettings: () => currentSettings });
81
+ if (blockingEnabled) {
82
+ registerSubagentAutomation(pi, { getSettings: () => currentSettings }, dependencies.automation);
83
+ }
54
84
  let refreshStatefulCatalog: (catalog: string) => void = () => undefined;
55
85
  let refreshConsultCatalog: (catalog: string) => void = () => undefined;
56
86
 
@@ -78,6 +108,7 @@ export default function (pi: ExtensionAPI) {
78
108
  blockingEnabled,
79
109
  settings: settings?.stateful,
80
110
  getSettings: () => currentSettings,
111
+ loadTransport: dependencies.loadStatefulTransport,
81
112
  });
82
113
  refreshStatefulCatalog = statefulRuntime.setAgentCatalog;
83
114
  const getBlockingEnabled = () => blockingEnabled;
@@ -88,18 +119,24 @@ export default function (pi: ExtensionAPI) {
88
119
  currentSettings?.cwdPolicy?.consultation ?? DEFAULT_CONSULTATION_CWD_POLICY;
89
120
  const getDelegationCwdPolicy = () =>
90
121
  currentSettings?.cwdPolicy?.delegation ?? DEFAULT_DELEGATION_CWD_POLICY;
91
- registerSubagentInspect(pi, {
92
- ...statefulRuntime,
93
- getBlockingEnabled,
94
- getMaxParallelTasks,
95
- getConsultResourcePolicy,
96
- getConsultationCwdPolicy,
97
- getDelegationCwdPolicy,
98
- });
122
+ registerSubagentInspect(
123
+ pi,
124
+ {
125
+ ...statefulRuntime,
126
+ getBlockingEnabled,
127
+ getMaxParallelTasks,
128
+ getConsultResourcePolicy,
129
+ getConsultationCwdPolicy,
130
+ getDelegationCwdPolicy,
131
+ },
132
+ dependencies.inspect,
133
+ );
99
134
  if (blockingEnabled) {
100
- refreshConsultCatalog = registerSubagentConsult(pi, {
101
- getSettings: () => currentSettings,
102
- });
135
+ refreshConsultCatalog = registerSubagentConsult(
136
+ pi,
137
+ { getSettings: () => currentSettings },
138
+ dependencies.consult,
139
+ );
103
140
  }
104
141
  registerSubagentConfigCommand(
105
142
  pi,
@@ -155,12 +192,14 @@ export default function (pi: ExtensionAPI) {
155
192
  },
156
193
  },
157
194
  configOwner,
195
+ dependencies.config,
158
196
  );
159
197
  }
160
198
 
161
199
  function registerBlockingSubagent(
162
200
  pi: ExtensionAPI,
163
201
  getSettings: () => SubagentSettings | undefined,
202
+ loadExecution: () => Promise<BlockingExecutionModule>,
164
203
  ): (catalog: string) => void {
165
204
  let catalog = "";
166
205
  const activeControllers = new Set<AbortController>();
@@ -212,14 +251,28 @@ function registerBlockingSubagent(
212
251
  const effectiveSignal = signal
213
252
  ? AbortSignal.any([signal, lifecycleController.signal])
214
253
  : lifecycleController.signal;
215
- const work = executeSubagent(
216
- toolCallId,
217
- params,
218
- effectiveSignal,
219
- onUpdate,
220
- ctx,
221
- getSettings(),
222
- );
254
+ const work = (async () => {
255
+ throwIfAborted(effectiveSignal, "Blocking subagent execution was cancelled");
256
+ let executionModule: BlockingExecutionModule;
257
+ try {
258
+ executionModule = await loadExecution();
259
+ } catch (error) {
260
+ throwIfAborted(
261
+ effectiveSignal,
262
+ "Blocking subagent execution was cancelled while loading",
263
+ );
264
+ throw error;
265
+ }
266
+ throwIfAborted(effectiveSignal, "Blocking subagent execution was cancelled while loading");
267
+ return executionModule.executeSubagent(
268
+ toolCallId,
269
+ params,
270
+ effectiveSignal,
271
+ onUpdate,
272
+ ctx,
273
+ getSettings(),
274
+ );
275
+ })();
223
276
  activeWork.add(work);
224
277
  try {
225
278
  return await work;
@@ -256,8 +309,8 @@ function appendAgentCatalog(baseDescription: string, catalog: string): string {
256
309
  }
257
310
 
258
311
  export { parsePositiveInteger } from "./execution/runtime-policy.js";
312
+ export { buildPiArgs } from "./pi-args.js";
259
313
  export { formatTokens, formatUsageStats } from "./render.js";
260
- export { buildPiArgs } from "./runner.js";
261
314
  export {
262
315
  DEFAULT_CONSULT_RESOURCE_POLICY,
263
316
  DEFAULT_CONSULTATION_CWD_POLICY,
@@ -1,41 +1,13 @@
1
- import { StringEnum } from "@earendil-works/pi-ai";
2
- import { type Static, Type } from "typebox";
3
1
  import { normalizeDelegationContract } from "./delegation-contract.js";
4
2
  import {
5
3
  type VerificationCheckRequest,
6
4
  validateVerificationChecks,
7
5
  } from "./verification-harness.js";
6
+ import type { VerifiedExecutionContract } from "./verified-execution-schema.js";
8
7
  import type { ResolvedWorkflowTask } from "./workflow-planning.js";
9
8
 
10
- const VerificationCheckSchema = Type.Object(
11
- {
12
- id: Type.String({
13
- minLength: 1,
14
- maxLength: 256,
15
- pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$",
16
- }),
17
- command: StringEnum(["git", "node", "npm", "npx"] as const),
18
- args: Type.Optional(Type.Array(Type.String({ maxLength: 4096 }), { maxItems: 64 })),
19
- cwd: Type.Optional(Type.String({ minLength: 1, maxLength: 4096 })),
20
- timeoutMs: Type.Optional(Type.Integer({ minimum: 1, maximum: 600_000 })),
21
- },
22
- { additionalProperties: false },
23
- );
24
-
25
- export const VerifiedExecutionContractSchema = Type.Object(
26
- {
27
- verifierAgent: Type.String({ minLength: 1, maxLength: 256 }),
28
- maxReworkCycles: Type.Optional(Type.Integer({ minimum: 0, maximum: 1, default: 1 })),
29
- checks: Type.Optional(Type.Array(VerificationCheckSchema, { maxItems: 32 })),
30
- },
31
- {
32
- additionalProperties: false,
33
- description:
34
- "Explicitly gate mutating workflow success on executor-owned deterministic checks, one least-authority independent verifier, exact submitted-state identity, and managed integration acceptance.",
35
- },
36
- );
37
-
38
- export type VerifiedExecutionContract = Static<typeof VerifiedExecutionContractSchema>;
9
+ export type { VerifiedExecutionContract } from "./verified-execution-schema.js";
10
+ export { VerifiedExecutionContractSchema } from "./verified-execution-schema.js";
39
11
 
40
12
  export interface PreparedVerifiedWorkflow {
41
13
  tasks: ResolvedWorkflowTask[];
@@ -0,0 +1,32 @@
1
+ import { StringEnum } from "@earendil-works/pi-ai";
2
+ import { type Static, Type } from "typebox";
3
+
4
+ const VerificationCheckSchema = Type.Object(
5
+ {
6
+ id: Type.String({
7
+ minLength: 1,
8
+ maxLength: 256,
9
+ pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$",
10
+ }),
11
+ command: StringEnum(["git", "node", "npm", "npx"] as const),
12
+ args: Type.Optional(Type.Array(Type.String({ maxLength: 4096 }), { maxItems: 64 })),
13
+ cwd: Type.Optional(Type.String({ minLength: 1, maxLength: 4096 })),
14
+ timeoutMs: Type.Optional(Type.Integer({ minimum: 1, maximum: 600_000 })),
15
+ },
16
+ { additionalProperties: false },
17
+ );
18
+
19
+ export const VerifiedExecutionContractSchema = Type.Object(
20
+ {
21
+ verifierAgent: Type.String({ minLength: 1, maxLength: 256 }),
22
+ maxReworkCycles: Type.Optional(Type.Integer({ minimum: 0, maximum: 1, default: 1 })),
23
+ checks: Type.Optional(Type.Array(VerificationCheckSchema, { maxItems: 32 })),
24
+ },
25
+ {
26
+ additionalProperties: false,
27
+ description:
28
+ "Explicitly gate mutating workflow success on executor-owned deterministic checks, one least-authority independent verifier, exact submitted-state identity, and managed integration acceptance.",
29
+ },
30
+ );
31
+
32
+ export type VerifiedExecutionContract = Static<typeof VerifiedExecutionContractSchema>;