@narumitw/pi-subagents 0.53.0 → 1.0.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.
Files changed (66) hide show
  1. package/README.md +85 -15
  2. package/package.json +1 -1
  3. package/src/agents/built-ins.ts +124 -0
  4. package/src/agents/catalog.ts +224 -0
  5. package/src/agents/discovery.ts +249 -0
  6. package/src/agents/types.ts +98 -0
  7. package/src/agents.ts +47 -670
  8. package/src/auto-transport.ts +2 -1
  9. package/src/automation.ts +7 -2
  10. package/src/capability-router.ts +1 -1
  11. package/src/completion-delivery.ts +82 -11
  12. package/src/config-status.ts +2 -2
  13. package/src/config-ui.ts +8 -8
  14. package/src/consult-resources.ts +1 -1
  15. package/src/consult.ts +9 -7
  16. package/src/create-stateful-transport.ts +2 -1
  17. package/src/cwd-policy.ts +1 -1
  18. package/src/execution/budget.ts +56 -0
  19. package/src/execution/runtime-policy.ts +19 -0
  20. package/src/execution-plan.ts +1 -1
  21. package/src/execution-profiles.ts +1 -1
  22. package/src/execution-ui.ts +1 -1
  23. package/src/execution.ts +269 -100
  24. package/src/in-process-transport.ts +3 -2
  25. package/src/inspect.ts +39 -8
  26. package/src/limits.ts +1 -0
  27. package/src/orchestration-metrics.ts +12 -5
  28. package/src/panel-execution.ts +1 -1
  29. package/src/panel-planning.ts +1 -1
  30. package/src/params.ts +3 -1
  31. package/src/persistence.ts +106 -7
  32. package/src/registry-types.ts +22 -5
  33. package/src/registry.ts +205 -37
  34. package/src/render.ts +1 -1
  35. package/src/retained-semantic-state.ts +1 -1
  36. package/src/rpc-transport-metadata.ts +1 -1
  37. package/src/rpc-transport.ts +2 -1
  38. package/src/runner.ts +6 -1
  39. package/src/settings/inspection.ts +275 -0
  40. package/src/settings/schema.ts +186 -0
  41. package/src/settings.ts +72 -420
  42. package/src/spawn-idempotency.ts +1 -1
  43. package/src/stateful-agent-view.ts +87 -0
  44. package/src/stateful-config.ts +1 -1
  45. package/src/stateful-guidance.ts +2 -2
  46. package/src/stateful-limits.ts +1 -1
  47. package/src/stateful-prompt.ts +2 -2
  48. package/src/stateful-render.ts +0 -1
  49. package/src/stateful-safety.ts +2 -1
  50. package/src/stateful-tool-params.ts +13 -20
  51. package/src/stateful.ts +36 -117
  52. package/src/subagents.ts +8 -9
  53. package/src/subprocess-transport.ts +2 -6
  54. package/src/transport-types.ts +1 -1
  55. package/src/transport-ui.ts +1 -1
  56. package/src/verification-harness.ts +516 -0
  57. package/src/verification-receipt.ts +275 -0
  58. package/src/verified-execution-benchmark.ts +86 -0
  59. package/src/verified-execution-contract.ts +219 -0
  60. package/src/work-item-ledger.ts +510 -37
  61. package/src/work-item-persistence.ts +31 -0
  62. package/src/workflow-completion-controller.ts +397 -0
  63. package/src/workflow-plan-compiler.ts +1 -1
  64. package/src/workflow-plan-patch.ts +1 -1
  65. package/src/workflow-planning.ts +11 -1
  66. package/src/workflow-ui.ts +1 -1
@@ -1,6 +1,6 @@
1
1
  import type { AgentToolResult, AgentToolUpdateCallback } from "@earendil-works/pi-agent-core";
2
2
  import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
3
- import type { AgentConfig, AgentScope, SubagentThinkingLevel } from "./agents.js";
3
+ import type { AgentConfig, AgentScope, SubagentThinkingLevel } from "./agents/types.js";
4
4
  import { panelReviewStatus, panelSynthesisStatus, startSubagentStatus } from "./blocking-status.js";
5
5
  import { issueCapabilityGrant, revokeCapabilityGrant } from "./capability-grant.js";
6
6
  import type { ResolvedSubagentTarget } from "./cwd-policy.js";
@@ -1,4 +1,4 @@
1
- import type { AgentConfig } from "./agents.js";
1
+ import type { AgentConfig } from "./agents/types.js";
2
2
  import { DEFAULT_MAX_CONTEXT_BYTES, truncateUtf8 } from "./limits.js";
3
3
  import { type WorkItemDefinition, WorkItemLedger } from "./work-item-ledger.js";
4
4
 
package/src/params.ts CHANGED
@@ -1,11 +1,12 @@
1
1
  import { StringEnum } from "@earendil-works/pi-ai";
2
2
  import { type Static, Type } from "typebox";
3
- import { THINKING_LEVELS } from "./agents.js";
3
+ import { THINKING_LEVELS } from "./agents/types.js";
4
4
  import { DelegationContractSchema } from "./delegation-contract.js";
5
5
  import { MAX_CONFIGURABLE_PARALLEL_TASKS, MAX_SUBAGENT_TIMEOUT_MS } from "./limits.js";
6
6
  import { PANEL_PRESETS } from "./panel-planning.js";
7
7
  import { SUBAGENT_RESULT_FORMATS } from "./result-contract.js";
8
8
  import { MAX_SUBAGENT_TOOL_CALLS, MAX_SUBAGENT_TURNS } from "./turn-budget.js";
9
+ import { VerifiedExecutionContractSchema } from "./verified-execution-contract.js";
9
10
 
10
11
  const TimeoutMs = Type.Number({
11
12
  description:
@@ -221,6 +222,7 @@ export const SubagentParams = Type.Object({
221
222
  Type.Object(
222
223
  {
223
224
  id: Type.Optional(Type.String({ minLength: 1, maxLength: 256 })),
225
+ verifiedExecution: Type.Optional(VerifiedExecutionContractSchema),
224
226
  honorAdmission: Type.Optional(
225
227
  Type.Boolean({
226
228
  description:
@@ -3,7 +3,7 @@ import * as fs from "node:fs";
3
3
  import * as path from "node:path";
4
4
  import { getAgentDir, withFileMutationQueue } from "@earendil-works/pi-coding-agent";
5
5
  import { projectAgentRecords } from "./agent-projection.js";
6
- import { isThinkingLevel } from "./agents.js";
6
+ import { isThinkingLevel } from "./agents/types.js";
7
7
  import { isCapabilityGrant, revokeCapabilityGrant } from "./capability-grant.js";
8
8
  import { redactPrivateText } from "./context.js";
9
9
  import { normalizeDelegationContract } from "./delegation-contract.js";
@@ -16,12 +16,13 @@ import { resolveStatefulLimits } from "./stateful-limits.js";
16
16
  import { copyTurnTerminationReport, type TurnTerminationReport } from "./timeout-checkpoint.js";
17
17
  import { MAX_SUBAGENT_TOOL_CALLS, MAX_SUBAGENT_TURNS } from "./turn-budget.js";
18
18
 
19
- const STATE_VERSION = 2;
19
+ const STATE_VERSION = 3;
20
+ const MAX_STORED_COMPLETIONS_PER_AGENT = 20;
20
21
  const DEFAULT_STATEFUL_LIMITS = resolveStatefulLimits();
21
- const MAX_STATE_BYTES = 1024 * 1024;
22
+ const MAX_STATE_BYTES = 5 * 1024 * 1024;
22
23
 
23
24
  interface StoredState {
24
- version: 2;
25
+ version: 3;
25
26
  updatedAt: number;
26
27
  agents: ManagedAgent[];
27
28
  }
@@ -86,8 +87,19 @@ export class AgentPersistence {
86
87
  const state: StoredState = { version: STATE_VERSION, updatedAt: Date.now(), agents: records };
87
88
  let content = `${JSON.stringify(state, null, "\t")}\n`;
88
89
  while (Buffer.byteLength(content, "utf8") > MAX_STATE_BYTES && state.agents.length > 0) {
89
- const oldestRootId = state.agents[0].rootId;
90
- state.agents = state.agents.filter((agent) => agent.rootId !== oldestRootId);
90
+ const rootsWithPendingCompletions = new Set(
91
+ state.agents
92
+ .filter((agent) => (agent.pendingCompletions?.length ?? 0) > 0)
93
+ .map((agent) => agent.rootId),
94
+ );
95
+ const droppableRoot = state.agents.find(
96
+ (agent) => !rootsWithPendingCompletions.has(agent.rootId),
97
+ )?.rootId;
98
+ if (droppableRoot) {
99
+ state.agents = state.agents.filter((agent) => agent.rootId !== droppableRoot);
100
+ } else if (!trimOldestHistory(state.agents) && !clearOldestContext(state.agents)) {
101
+ throw new Error("Subagent state exceeds its durable completion storage limit");
102
+ }
91
103
  content = `${JSON.stringify(state, null, "\t")}\n`;
92
104
  }
93
105
  await fs.promises.mkdir(path.dirname(this.filePath), { recursive: true });
@@ -113,6 +125,27 @@ export class AgentPersistence {
113
125
  }
114
126
  }
115
127
 
128
+ function trimOldestHistory(agents: ManagedAgent[]): boolean {
129
+ const candidate = agents
130
+ .filter((agent) => agent.history.length > 0)
131
+ .sort(
132
+ (left, right) =>
133
+ (left.history[0]?.completedAt ?? Number.POSITIVE_INFINITY) -
134
+ (right.history[0]?.completedAt ?? Number.POSITIVE_INFINITY),
135
+ )[0];
136
+ if (!candidate) return false;
137
+ candidate.history.shift();
138
+ return true;
139
+ }
140
+
141
+ function clearOldestContext(agents: ManagedAgent[]): boolean {
142
+ const candidate = agents.find((agent) => agent.context !== undefined);
143
+ if (!candidate) return false;
144
+ candidate.context = undefined;
145
+ candidate.contextTruncated = true;
146
+ return true;
147
+ }
148
+
116
149
  function sanitizeAgent(agent: ManagedAgent): ManagedAgent {
117
150
  return {
118
151
  ...agent,
@@ -126,6 +159,17 @@ function sanitizeAgent(agent: ManagedAgent): ManagedAgent {
126
159
  })),
127
160
  state: agent.state === "running" || agent.state === "starting" ? "interrupted" : agent.state,
128
161
  currentTask: undefined,
162
+ currentRunId: undefined,
163
+ currentTurnGeneration: undefined,
164
+ turnGeneration: agent.turnGeneration ?? 0,
165
+ pendingCompletions: (agent.pendingCompletions ?? [])
166
+ .slice(-MAX_STORED_COMPLETIONS_PER_AGENT)
167
+ .map((completion) => ({
168
+ ...completion,
169
+ task: redactPrivateText(completion.task),
170
+ output: redactPrivateText(completion.output),
171
+ error: completion.error ? redactPrivateText(completion.error) : undefined,
172
+ })),
129
173
  currentTimeoutMs: undefined,
130
174
  currentIdleTimeoutMs: undefined,
131
175
  currentMaxTurns: undefined,
@@ -184,7 +228,10 @@ function sanitizeTermination(report: TurnTerminationReport): TurnTerminationRepo
184
228
  function isStoredState(value: unknown): value is StoredState {
185
229
  if (!value || typeof value !== "object") return false;
186
230
  const state = value as { version?: unknown; agents?: unknown };
187
- if ((state.version !== 1 && state.version !== STATE_VERSION) || !Array.isArray(state.agents)) {
231
+ if (
232
+ (state.version !== 1 && state.version !== 2 && state.version !== STATE_VERSION) ||
233
+ !Array.isArray(state.agents)
234
+ ) {
188
235
  return false;
189
236
  }
190
237
  return state.agents.every((agent) => {
@@ -208,6 +255,9 @@ function isStoredState(value: unknown): value is StoredState {
208
255
  (record.termination === undefined || isTerminationReport(record.termination)) &&
209
256
  (record.contextTurns === undefined || isNonNegativeInteger(record.contextTurns)) &&
210
257
  (record.contextBytes === undefined || isNonNegativeInteger(record.contextBytes)) &&
258
+ (record.turnGeneration === undefined || isNonNegativeInteger(record.turnGeneration)) &&
259
+ (record.pendingCompletions === undefined ||
260
+ isCompletionOutbox(record.pendingCompletions, record.turnGeneration ?? 0)) &&
211
261
  (record.spawnIdempotencyKey === undefined ||
212
262
  (typeof record.spawnIdempotencyKey === "string" &&
213
263
  record.spawnIdempotencyKey.length > 0 &&
@@ -356,10 +406,59 @@ function isTargetPolicyAudit(value: unknown): boolean {
356
406
  );
357
407
  }
358
408
 
409
+ function isCompletionOutbox(value: unknown, turnGeneration: number): boolean {
410
+ if (!Array.isArray(value) || value.length > MAX_STORED_COMPLETIONS_PER_AGENT) return false;
411
+ const ids = new Set<string>();
412
+ let previousGeneration = 0;
413
+ let previousCreatedAt = Number.NEGATIVE_INFINITY;
414
+ for (const completion of value) {
415
+ if (!isPersistedCompletion(completion, turnGeneration)) return false;
416
+ if (ids.has(completion.completionId)) return false;
417
+ if (completion.generation <= previousGeneration || completion.createdAt <= previousCreatedAt) {
418
+ return false;
419
+ }
420
+ ids.add(completion.completionId);
421
+ previousGeneration = completion.generation;
422
+ previousCreatedAt = completion.createdAt;
423
+ }
424
+ return true;
425
+ }
426
+
427
+ function isPersistedCompletion(
428
+ value: unknown,
429
+ turnGeneration: number,
430
+ ): value is import("./registry.js").PersistedAgentCompletion {
431
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
432
+ const completion = value as Record<string, unknown>;
433
+ return (
434
+ typeof completion.completionId === "string" &&
435
+ completion.completionId.length > 0 &&
436
+ completion.completionId.length <= 256 &&
437
+ typeof completion.runId === "string" &&
438
+ completion.runId.length > 0 &&
439
+ completion.runId.length <= 256 &&
440
+ typeof completion.generation === "number" &&
441
+ Number.isSafeInteger(completion.generation) &&
442
+ completion.generation >= 1 &&
443
+ completion.generation <= turnGeneration &&
444
+ typeof completion.task === "string" &&
445
+ typeof completion.output === "string" &&
446
+ (completion.error === undefined || typeof completion.error === "string") &&
447
+ typeof completion.createdAt === "number" &&
448
+ Number.isFinite(completion.createdAt)
449
+ );
450
+ }
451
+
359
452
  function isAgentTurn(value: unknown): boolean {
360
453
  if (!value || typeof value !== "object") return false;
361
454
  const turn = value as Record<string, unknown>;
362
455
  return (
456
+ (turn.runId === undefined ||
457
+ (typeof turn.runId === "string" && turn.runId.length > 0 && turn.runId.length <= 256)) &&
458
+ (turn.generation === undefined ||
459
+ (typeof turn.generation === "number" &&
460
+ Number.isSafeInteger(turn.generation) &&
461
+ turn.generation >= 1)) &&
363
462
  typeof turn.task === "string" &&
364
463
  typeof turn.output === "string" &&
365
464
  typeof turn.startedAt === "number" &&
@@ -1,4 +1,4 @@
1
- import type { SubagentThinkingLevel } from "./agents.js";
1
+ import type { SubagentThinkingLevel } from "./agents/types.js";
2
2
  import type { CapabilityGrant } from "./capability-grant.js";
3
3
  import type { TargetPolicyAudit } from "./cwd-policy.js";
4
4
  import type { DelegationContract } from "./delegation-contract.js";
@@ -23,6 +23,8 @@ export type AgentLifecycleState =
23
23
  | "closed";
24
24
 
25
25
  export interface AgentTurn {
26
+ runId?: string;
27
+ generation?: number;
26
28
  task: string;
27
29
  output: string;
28
30
  startedAt: number;
@@ -32,6 +34,16 @@ export interface AgentTurn {
32
34
  termination?: TurnTerminationReport;
33
35
  }
34
36
 
37
+ export interface PersistedAgentCompletion {
38
+ completionId: string;
39
+ runId: string;
40
+ generation: number;
41
+ task: string;
42
+ output: string;
43
+ error?: string;
44
+ createdAt: number;
45
+ }
46
+
35
47
  export interface AgentMailboxMessage {
36
48
  id: string;
37
49
  senderId: string;
@@ -64,6 +76,10 @@ export interface ManagedAgent {
64
76
  maxToolCalls?: number;
65
77
  currentMaxToolCalls?: number;
66
78
  currentTask?: string;
79
+ turnGeneration?: number;
80
+ currentRunId?: string;
81
+ currentTurnGeneration?: number;
82
+ pendingCompletions?: PersistedAgentCompletion[];
67
83
  history: AgentTurn[];
68
84
  error?: string;
69
85
  context?: string;
@@ -98,6 +114,8 @@ export interface AgentRunInspectionSummary {
98
114
  updatedAt: number;
99
115
  historyCount: number;
100
116
  unreadMessages: number;
117
+ turnGeneration: number;
118
+ pendingCompletionCount: number;
101
119
  }
102
120
 
103
121
  export interface AgentRunInspectionDetail extends AgentRunInspectionSummary {
@@ -112,6 +130,8 @@ export interface AgentRunInspectionDetail extends AgentRunInspectionSummary {
112
130
  maxToolCalls?: number;
113
131
  currentMaxToolCalls?: number;
114
132
  currentTask?: string;
133
+ currentRunId?: string;
134
+ currentTurnGeneration?: number;
115
135
  error?: string;
116
136
  workspaceMode?: "worktree";
117
137
  contextTurns?: number;
@@ -151,11 +171,8 @@ export interface TurnOutcome {
151
171
  telemetry?: TransportTelemetry;
152
172
  }
153
173
 
154
- export interface AgentTurnCompletion {
174
+ export interface AgentTurnCompletion extends PersistedAgentCompletion {
155
175
  agent: ManagedAgent;
156
- task: string;
157
- output: string;
158
- error?: string;
159
176
  }
160
177
 
161
178
  export interface AgentRegistryOptions {