@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.
- package/README.md +85 -15
- package/package.json +1 -1
- package/src/agents/built-ins.ts +124 -0
- package/src/agents/catalog.ts +224 -0
- package/src/agents/discovery.ts +249 -0
- package/src/agents/types.ts +98 -0
- package/src/agents.ts +47 -670
- package/src/auto-transport.ts +2 -1
- package/src/automation.ts +7 -2
- package/src/capability-router.ts +1 -1
- package/src/completion-delivery.ts +82 -11
- package/src/config-status.ts +2 -2
- package/src/config-ui.ts +8 -8
- package/src/consult-resources.ts +1 -1
- package/src/consult.ts +9 -7
- package/src/create-stateful-transport.ts +2 -1
- package/src/cwd-policy.ts +1 -1
- package/src/execution/budget.ts +56 -0
- package/src/execution/runtime-policy.ts +19 -0
- package/src/execution-plan.ts +1 -1
- package/src/execution-profiles.ts +1 -1
- package/src/execution-ui.ts +1 -1
- package/src/execution.ts +269 -100
- package/src/in-process-transport.ts +3 -2
- package/src/inspect.ts +39 -8
- package/src/limits.ts +1 -0
- package/src/orchestration-metrics.ts +12 -5
- package/src/panel-execution.ts +1 -1
- package/src/panel-planning.ts +1 -1
- package/src/params.ts +3 -1
- package/src/persistence.ts +106 -7
- package/src/registry-types.ts +22 -5
- package/src/registry.ts +205 -37
- package/src/render.ts +1 -1
- package/src/retained-semantic-state.ts +1 -1
- package/src/rpc-transport-metadata.ts +1 -1
- package/src/rpc-transport.ts +2 -1
- package/src/runner.ts +6 -1
- package/src/settings/inspection.ts +275 -0
- package/src/settings/schema.ts +186 -0
- package/src/settings.ts +72 -420
- package/src/spawn-idempotency.ts +1 -1
- package/src/stateful-agent-view.ts +87 -0
- package/src/stateful-config.ts +1 -1
- package/src/stateful-guidance.ts +2 -2
- package/src/stateful-limits.ts +1 -1
- package/src/stateful-prompt.ts +2 -2
- package/src/stateful-render.ts +0 -1
- package/src/stateful-safety.ts +2 -1
- package/src/stateful-tool-params.ts +13 -20
- package/src/stateful.ts +36 -117
- package/src/subagents.ts +8 -9
- package/src/subprocess-transport.ts +2 -6
- package/src/transport-types.ts +1 -1
- package/src/transport-ui.ts +1 -1
- package/src/verification-harness.ts +516 -0
- package/src/verification-receipt.ts +275 -0
- package/src/verified-execution-benchmark.ts +86 -0
- package/src/verified-execution-contract.ts +219 -0
- package/src/work-item-ledger.ts +510 -37
- package/src/work-item-persistence.ts +31 -0
- package/src/workflow-completion-controller.ts +397 -0
- package/src/workflow-plan-compiler.ts +1 -1
- package/src/workflow-plan-patch.ts +1 -1
- package/src/workflow-planning.ts +11 -1
- package/src/workflow-ui.ts +1 -1
package/src/registry.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { randomUUID } from "node:crypto";
|
|
6
6
|
import { projectAgentRecords } from "./agent-projection.js";
|
|
7
|
-
import type { SubagentThinkingLevel } from "./agents.js";
|
|
7
|
+
import type { SubagentThinkingLevel } from "./agents/types.js";
|
|
8
8
|
import {
|
|
9
9
|
type CapabilityGrant,
|
|
10
10
|
isCapabilityGrantActive,
|
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
DEFAULT_MAX_CONTEXT_BYTES,
|
|
22
22
|
DEFAULT_MAX_OUTPUT_BYTES,
|
|
23
23
|
MAX_SUBAGENT_TIMEOUT_MS,
|
|
24
|
+
MAX_TOOL_MESSAGE_BYTES,
|
|
24
25
|
truncateUtf8,
|
|
25
26
|
} from "./limits.js";
|
|
26
27
|
import { classifyStructuredOutcome } from "./outcome.js";
|
|
@@ -47,6 +48,9 @@ import type { TransportTelemetry } from "./transport-types.js";
|
|
|
47
48
|
import { type TurnLimits, validateTurnLimits } from "./turn-budget.js";
|
|
48
49
|
|
|
49
50
|
const DEFAULT_STATEFUL_LIMITS = resolveStatefulLimits();
|
|
51
|
+
const MAX_PENDING_COMPLETIONS_PER_AGENT = 20;
|
|
52
|
+
const INITIAL_PERSISTENCE_RETRY_DELAY_MS = 25;
|
|
53
|
+
const MAX_PERSISTENCE_RETRY_DELAY_MS = 1_000;
|
|
50
54
|
|
|
51
55
|
export type * from "./registry-types.js";
|
|
52
56
|
|
|
@@ -73,6 +77,8 @@ function validateTurnTimeout(value: number): number {
|
|
|
73
77
|
|
|
74
78
|
function clearCurrentTurn(agent: ManagedAgent): void {
|
|
75
79
|
agent.currentTask = undefined;
|
|
80
|
+
agent.currentRunId = undefined;
|
|
81
|
+
agent.currentTurnGeneration = undefined;
|
|
76
82
|
agent.currentTimeoutMs = undefined;
|
|
77
83
|
agent.currentIdleTimeoutMs = undefined;
|
|
78
84
|
agent.currentMaxTurns = undefined;
|
|
@@ -86,6 +92,21 @@ function waitAbortError(): Error {
|
|
|
86
92
|
return error;
|
|
87
93
|
}
|
|
88
94
|
|
|
95
|
+
function waitForPersistenceRetry(milliseconds: number, signal: AbortSignal): Promise<void> {
|
|
96
|
+
if (signal.aborted) return Promise.reject(signal.reason);
|
|
97
|
+
return new Promise((resolve, reject) => {
|
|
98
|
+
const onAbort = () => {
|
|
99
|
+
clearTimeout(timer);
|
|
100
|
+
reject(signal.reason);
|
|
101
|
+
};
|
|
102
|
+
const timer = setTimeout(() => {
|
|
103
|
+
signal.removeEventListener("abort", onAbort);
|
|
104
|
+
resolve();
|
|
105
|
+
}, milliseconds);
|
|
106
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
89
110
|
export class AgentRegistry {
|
|
90
111
|
private readonly agents = new Map<string, ManagedAgent>();
|
|
91
112
|
private readonly controllers = new Map<string, AbortController>();
|
|
@@ -96,6 +117,7 @@ export class AgentRegistry {
|
|
|
96
117
|
resolve: (agent: ManagedAgent) => void;
|
|
97
118
|
}> = [];
|
|
98
119
|
private changeQueue: Promise<void> = Promise.resolve();
|
|
120
|
+
private readonly shutdownController = new AbortController();
|
|
99
121
|
private readonly maxAgents: number;
|
|
100
122
|
private readonly maxActiveTurns: number;
|
|
101
123
|
private readonly maxHistoryTurns: number;
|
|
@@ -108,6 +130,7 @@ export class AgentRegistry {
|
|
|
108
130
|
private readonly idleTtlMs: number;
|
|
109
131
|
private readonly transport: SubagentTransport;
|
|
110
132
|
private readonly now: () => number;
|
|
133
|
+
private lastCompletionAt = 0;
|
|
111
134
|
|
|
112
135
|
constructor(
|
|
113
136
|
transport: SubagentTransport | AgentTurnRunner,
|
|
@@ -176,6 +199,9 @@ export class AgentRegistry {
|
|
|
176
199
|
}
|
|
177
200
|
const depth = seen.size - 1;
|
|
178
201
|
if (cyclic || depth > this.maxDepth) continue;
|
|
202
|
+
for (const completion of record.pendingCompletions ?? []) {
|
|
203
|
+
this.lastCompletionAt = Math.max(this.lastCompletionAt, completion.createdAt);
|
|
204
|
+
}
|
|
179
205
|
this.agents.set(record.id, {
|
|
180
206
|
...record,
|
|
181
207
|
state:
|
|
@@ -183,6 +209,12 @@ export class AgentRegistry {
|
|
|
183
209
|
rootId,
|
|
184
210
|
depth,
|
|
185
211
|
currentTask: undefined,
|
|
212
|
+
turnGeneration: record.turnGeneration ?? 0,
|
|
213
|
+
currentRunId: undefined,
|
|
214
|
+
currentTurnGeneration: undefined,
|
|
215
|
+
pendingCompletions: (record.pendingCompletions ?? []).map((completion) => ({
|
|
216
|
+
...completion,
|
|
217
|
+
})),
|
|
186
218
|
currentTimeoutMs: undefined,
|
|
187
219
|
currentIdleTimeoutMs: undefined,
|
|
188
220
|
currentMaxTurns: undefined,
|
|
@@ -286,6 +318,8 @@ export class AgentRegistry {
|
|
|
286
318
|
maxToolCalls: input.maxToolCalls,
|
|
287
319
|
currentMaxToolCalls: input.maxToolCalls,
|
|
288
320
|
currentTask: task,
|
|
321
|
+
turnGeneration: 0,
|
|
322
|
+
pendingCompletions: [],
|
|
289
323
|
history: [],
|
|
290
324
|
mailbox: [],
|
|
291
325
|
context: input.context,
|
|
@@ -486,19 +520,30 @@ export class AgentRegistry {
|
|
|
486
520
|
const index = this.queue.findIndex((entry) => entry.agent.id === id);
|
|
487
521
|
if (index >= 0) {
|
|
488
522
|
const [entry] = this.queue.splice(index, 1);
|
|
523
|
+
const persistedCompletion = {
|
|
524
|
+
completionId: `completion:${agent.id}:${randomUUID()}`,
|
|
525
|
+
runId: agent.currentRunId ?? `run:${agent.id}:${randomUUID()}`,
|
|
526
|
+
generation: agent.currentTurnGeneration ?? agent.turnGeneration ?? 1,
|
|
527
|
+
task: truncateUtf8(entry.task, 256).text,
|
|
528
|
+
output: "",
|
|
529
|
+
error: "Interrupted before execution",
|
|
530
|
+
createdAt: this.completionCreatedAt(),
|
|
531
|
+
};
|
|
489
532
|
agent.state = "interrupted";
|
|
533
|
+
agent.pendingCompletions = [...(agent.pendingCompletions ?? []), persistedCompletion];
|
|
490
534
|
clearCurrentTurn(agent);
|
|
491
535
|
agent.updatedAt = this.now();
|
|
536
|
+
const persisted = await this.persistTerminalState().then(
|
|
537
|
+
() => true,
|
|
538
|
+
() => false,
|
|
539
|
+
);
|
|
492
540
|
const completion: AgentTurnCompletion = {
|
|
541
|
+
...persistedCompletion,
|
|
493
542
|
agent: this.copy(agent),
|
|
494
|
-
task: entry.task,
|
|
495
|
-
output: "",
|
|
496
|
-
error: "Interrupted before execution",
|
|
497
543
|
};
|
|
498
544
|
entry.resolve(agent);
|
|
499
545
|
this.running.delete(id);
|
|
500
|
-
await this.notifyTurnComplete(completion);
|
|
501
|
-
await this.changed();
|
|
546
|
+
if (persisted) await this.notifyTurnComplete(completion);
|
|
502
547
|
return this.copy(agent);
|
|
503
548
|
}
|
|
504
549
|
}
|
|
@@ -584,6 +629,7 @@ export class AgentRegistry {
|
|
|
584
629
|
}
|
|
585
630
|
|
|
586
631
|
async shutdown(): Promise<void> {
|
|
632
|
+
this.shutdownController.abort(new Error("Subagent registry is shutting down"));
|
|
587
633
|
for (const entry of this.queue.splice(0)) {
|
|
588
634
|
if (entry.agent.capabilityGrant?.state === "active") {
|
|
589
635
|
entry.agent.capabilityGrant = revokeCapabilityGrant(
|
|
@@ -629,7 +675,7 @@ export class AgentRegistry {
|
|
|
629
675
|
} catch (error) {
|
|
630
676
|
shutdownError = error;
|
|
631
677
|
}
|
|
632
|
-
await this.changed();
|
|
678
|
+
await this.changed(true);
|
|
633
679
|
if (shutdownError) throw shutdownError;
|
|
634
680
|
}
|
|
635
681
|
|
|
@@ -666,6 +712,8 @@ export class AgentRegistry {
|
|
|
666
712
|
maxToolCalls: agent.maxToolCalls,
|
|
667
713
|
currentMaxToolCalls: agent.currentMaxToolCalls,
|
|
668
714
|
currentTask: agent.currentTask,
|
|
715
|
+
currentRunId: agent.currentRunId,
|
|
716
|
+
currentTurnGeneration: agent.currentTurnGeneration,
|
|
669
717
|
error: agent.error,
|
|
670
718
|
workspaceMode: agent.workspaceMode,
|
|
671
719
|
contextTurns: agent.contextTurns,
|
|
@@ -712,14 +760,60 @@ export class AgentRegistry {
|
|
|
712
760
|
return agent ? this.copy(agent) : undefined;
|
|
713
761
|
}
|
|
714
762
|
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
763
|
+
listPendingCompletions(): AgentTurnCompletion[] {
|
|
764
|
+
return [...this.agents.values()]
|
|
765
|
+
.flatMap((agent) =>
|
|
766
|
+
(agent.pendingCompletions ?? []).map((completion) => ({
|
|
767
|
+
...completion,
|
|
768
|
+
agent: this.copy(agent),
|
|
769
|
+
})),
|
|
770
|
+
)
|
|
771
|
+
.sort(
|
|
772
|
+
(left, right) =>
|
|
773
|
+
left.createdAt - right.createdAt ||
|
|
774
|
+
left.generation - right.generation ||
|
|
775
|
+
left.completionId.localeCompare(right.completionId),
|
|
776
|
+
);
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
async markCompletionDelivered(completionId: string, deliveredAt: number): Promise<void> {
|
|
780
|
+
const agent = [...this.agents.values()].find((candidate) =>
|
|
781
|
+
candidate.pendingCompletions?.some((completion) => completion.completionId === completionId),
|
|
782
|
+
);
|
|
783
|
+
if (!agent) return;
|
|
784
|
+
const acknowledged = (agent.pendingCompletions ?? []).find(
|
|
785
|
+
(completion) => completion.completionId === completionId,
|
|
786
|
+
);
|
|
787
|
+
if (!acknowledged) return;
|
|
788
|
+
agent.pendingCompletions = (agent.pendingCompletions ?? []).filter(
|
|
789
|
+
(completion) => completion.completionId !== completionId,
|
|
790
|
+
);
|
|
791
|
+
if (agent.telemetry) {
|
|
792
|
+
agent.telemetry = {
|
|
793
|
+
...agent.telemetry,
|
|
794
|
+
updatedAt: deliveredAt,
|
|
795
|
+
timing: { ...agent.telemetry.timing, completionDeliveredAt: deliveredAt },
|
|
796
|
+
};
|
|
797
|
+
}
|
|
798
|
+
agent.updatedAt = Math.max(agent.updatedAt, deliveredAt);
|
|
799
|
+
try {
|
|
800
|
+
await this.changed(true);
|
|
801
|
+
} catch (error) {
|
|
802
|
+
if (
|
|
803
|
+
!agent.pendingCompletions?.some(
|
|
804
|
+
(completion) => completion.completionId === acknowledged.completionId,
|
|
805
|
+
)
|
|
806
|
+
) {
|
|
807
|
+
agent.pendingCompletions = [...(agent.pendingCompletions ?? []), acknowledged].sort(
|
|
808
|
+
(left, right) => left.createdAt - right.createdAt || left.generation - right.generation,
|
|
809
|
+
);
|
|
810
|
+
}
|
|
811
|
+
if (agent.telemetry?.timing.completionDeliveredAt === deliveredAt) {
|
|
812
|
+
const { completionDeliveredAt: _discarded, ...timing } = agent.telemetry.timing;
|
|
813
|
+
agent.telemetry = { ...agent.telemetry, timing };
|
|
814
|
+
}
|
|
815
|
+
throw error;
|
|
816
|
+
}
|
|
723
817
|
}
|
|
724
818
|
|
|
725
819
|
async sweepExpired(): Promise<number> {
|
|
@@ -740,6 +834,14 @@ export class AgentRegistry {
|
|
|
740
834
|
task: string,
|
|
741
835
|
limits: TurnLimits & { timeoutMs?: number } = {},
|
|
742
836
|
): void {
|
|
837
|
+
if ((agent.pendingCompletions?.length ?? 0) >= MAX_PENDING_COMPLETIONS_PER_AGENT) {
|
|
838
|
+
throw new Error(
|
|
839
|
+
`Agent ${agent.id} has ${MAX_PENDING_COMPLETIONS_PER_AGENT} undelivered completions; wait for delivery before another turn`,
|
|
840
|
+
);
|
|
841
|
+
}
|
|
842
|
+
agent.turnGeneration = (agent.turnGeneration ?? 0) + 1;
|
|
843
|
+
agent.currentTurnGeneration = agent.turnGeneration;
|
|
844
|
+
agent.currentRunId = `run:${agent.id}:${randomUUID()}`;
|
|
743
845
|
agent.state = "starting";
|
|
744
846
|
agent.error = undefined;
|
|
745
847
|
agent.currentTask = task;
|
|
@@ -790,17 +892,30 @@ export class AgentRegistry {
|
|
|
790
892
|
agent.state = "failed";
|
|
791
893
|
agent.error = "Capability grant expired or no longer matches the accepted plan";
|
|
792
894
|
agent.outcome = classifyStructuredOutcome("failed", "capability-grant-invalid");
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
void this.notifyTurnComplete({
|
|
799
|
-
agent: this.copy(agent),
|
|
800
|
-
task,
|
|
895
|
+
const persistedCompletion = {
|
|
896
|
+
completionId: `completion:${agent.id}:${randomUUID()}`,
|
|
897
|
+
runId: agent.currentRunId ?? `run:${agent.id}:${randomUUID()}`,
|
|
898
|
+
generation: agent.currentTurnGeneration ?? agent.turnGeneration ?? 1,
|
|
899
|
+
task: truncateUtf8(task, 256).text,
|
|
801
900
|
output: "",
|
|
802
|
-
error: agent.error,
|
|
803
|
-
|
|
901
|
+
error: truncateUtf8(agent.error, 512).text,
|
|
902
|
+
createdAt: this.completionCreatedAt(),
|
|
903
|
+
};
|
|
904
|
+
agent.pendingCompletions = [...(agent.pendingCompletions ?? []), persistedCompletion];
|
|
905
|
+
clearCurrentTurn(agent);
|
|
906
|
+
agent.updatedAt = this.now();
|
|
907
|
+
void this.persistTerminalState()
|
|
908
|
+
.then(() =>
|
|
909
|
+
this.notifyTurnComplete({
|
|
910
|
+
...persistedCompletion,
|
|
911
|
+
agent: this.copy(agent),
|
|
912
|
+
}),
|
|
913
|
+
)
|
|
914
|
+
.catch(() => undefined)
|
|
915
|
+
.finally(() => {
|
|
916
|
+
resolveQueued(agent);
|
|
917
|
+
this.running.delete(agent.id);
|
|
918
|
+
});
|
|
804
919
|
return;
|
|
805
920
|
}
|
|
806
921
|
const controller = new AbortController();
|
|
@@ -808,6 +923,8 @@ export class AgentRegistry {
|
|
|
808
923
|
agent.state = "running";
|
|
809
924
|
agent.updatedAt = this.now();
|
|
810
925
|
const startedAt = this.now();
|
|
926
|
+
const runId = agent.currentRunId ?? `run:${agent.id}:${randomUUID()}`;
|
|
927
|
+
const turnGeneration = agent.currentTurnGeneration ?? agent.turnGeneration ?? 1;
|
|
811
928
|
const completionKey = `completion:${agent.id}:${randomUUID()}`;
|
|
812
929
|
const acceptedPlanId = agent.executionPlan?.id;
|
|
813
930
|
let completionContent = "";
|
|
@@ -830,6 +947,8 @@ export class AgentRegistry {
|
|
|
830
947
|
? truncateUtf8(outcome.error, this.maxTurnOutputBytes).text
|
|
831
948
|
: undefined;
|
|
832
949
|
agent.history.push({
|
|
950
|
+
runId,
|
|
951
|
+
generation: turnGeneration,
|
|
833
952
|
task,
|
|
834
953
|
output,
|
|
835
954
|
startedAt,
|
|
@@ -925,6 +1044,8 @@ export class AgentRegistry {
|
|
|
925
1044
|
this.maxTurnOutputBytes,
|
|
926
1045
|
).text;
|
|
927
1046
|
agent.history.push({
|
|
1047
|
+
runId,
|
|
1048
|
+
generation: turnGeneration,
|
|
928
1049
|
task,
|
|
929
1050
|
output: "",
|
|
930
1051
|
startedAt,
|
|
@@ -947,12 +1068,16 @@ export class AgentRegistry {
|
|
|
947
1068
|
return agent;
|
|
948
1069
|
})
|
|
949
1070
|
.finally(async () => {
|
|
950
|
-
const
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
1071
|
+
const persistedCompletion = {
|
|
1072
|
+
completionId: completionKey,
|
|
1073
|
+
runId,
|
|
1074
|
+
generation: turnGeneration,
|
|
1075
|
+
task: truncateUtf8(task, 256).text,
|
|
1076
|
+
output: truncateUtf8(completionOutput, MAX_TOOL_MESSAGE_BYTES).text,
|
|
1077
|
+
error: completionError ? truncateUtf8(completionError, 512).text : undefined,
|
|
1078
|
+
createdAt: this.completionCreatedAt(),
|
|
955
1079
|
};
|
|
1080
|
+
agent.pendingCompletions = [...(agent.pendingCompletions ?? []), persistedCompletion];
|
|
956
1081
|
if (agent.parentId) {
|
|
957
1082
|
const parent = this.agents.get(agent.parentId);
|
|
958
1083
|
if (parent && parent.state !== "closed") {
|
|
@@ -961,12 +1086,19 @@ export class AgentRegistry {
|
|
|
961
1086
|
}
|
|
962
1087
|
clearCurrentTurn(agent);
|
|
963
1088
|
agent.updatedAt = this.now();
|
|
1089
|
+
const persisted = await this.persistTerminalState().then(
|
|
1090
|
+
() => true,
|
|
1091
|
+
() => false,
|
|
1092
|
+
);
|
|
964
1093
|
this.controllers.delete(agent.id);
|
|
1094
|
+
const turnCompletion: AgentTurnCompletion = {
|
|
1095
|
+
...persistedCompletion,
|
|
1096
|
+
agent: this.copy(agent),
|
|
1097
|
+
};
|
|
965
1098
|
this.running.delete(agent.id);
|
|
966
1099
|
resolveQueued(agent);
|
|
967
1100
|
this.pumpQueue();
|
|
968
|
-
await this.notifyTurnComplete(turnCompletion);
|
|
969
|
-
await this.changed();
|
|
1101
|
+
if (persisted) await this.notifyTurnComplete(turnCompletion);
|
|
970
1102
|
});
|
|
971
1103
|
}
|
|
972
1104
|
|
|
@@ -1032,11 +1164,22 @@ export class AgentRegistry {
|
|
|
1032
1164
|
return [...this.agents.values()].filter((agent) => agent.state !== "closed").length;
|
|
1033
1165
|
}
|
|
1034
1166
|
|
|
1167
|
+
private completionCreatedAt(): number {
|
|
1168
|
+
this.lastCompletionAt = Math.max(this.now(), this.lastCompletionAt + 1);
|
|
1169
|
+
return this.lastCompletionAt;
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1035
1172
|
private evictExpired(): ManagedAgent[] {
|
|
1036
1173
|
const cutoff = this.now() - this.idleTtlMs;
|
|
1037
1174
|
const protectedIds = new Set<string>();
|
|
1038
1175
|
for (const agent of this.agents.values()) {
|
|
1039
|
-
if (
|
|
1176
|
+
if (
|
|
1177
|
+
agent.state !== "running" &&
|
|
1178
|
+
agent.state !== "starting" &&
|
|
1179
|
+
(agent.pendingCompletions?.length ?? 0) === 0
|
|
1180
|
+
) {
|
|
1181
|
+
continue;
|
|
1182
|
+
}
|
|
1040
1183
|
let current: ManagedAgent | undefined = agent;
|
|
1041
1184
|
while (current) {
|
|
1042
1185
|
protectedIds.add(current.id);
|
|
@@ -1081,6 +1224,25 @@ export class AgentRegistry {
|
|
|
1081
1224
|
for (const agent of closed.slice(this.maxAgents)) this.agents.delete(agent.id);
|
|
1082
1225
|
}
|
|
1083
1226
|
|
|
1227
|
+
private async persistTerminalState(): Promise<void> {
|
|
1228
|
+
let failures = 0;
|
|
1229
|
+
for (;;) {
|
|
1230
|
+
try {
|
|
1231
|
+
await this.changed(true);
|
|
1232
|
+
return;
|
|
1233
|
+
} catch (error) {
|
|
1234
|
+
if (this.shutdownController.signal.aborted) throw error;
|
|
1235
|
+
failures++;
|
|
1236
|
+
if (failures === 1) continue;
|
|
1237
|
+
const delay = Math.min(
|
|
1238
|
+
INITIAL_PERSISTENCE_RETRY_DELAY_MS * 2 ** (failures - 2),
|
|
1239
|
+
MAX_PERSISTENCE_RETRY_DELAY_MS,
|
|
1240
|
+
);
|
|
1241
|
+
await waitForPersistenceRetry(delay, this.shutdownController.signal);
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1084
1246
|
private async notifyTurnComplete(completion: AgentTurnCompletion): Promise<void> {
|
|
1085
1247
|
try {
|
|
1086
1248
|
await this.options.onTurnComplete?.(completion);
|
|
@@ -1089,16 +1251,17 @@ export class AgentRegistry {
|
|
|
1089
1251
|
}
|
|
1090
1252
|
}
|
|
1091
1253
|
|
|
1092
|
-
private changed(): Promise<void> {
|
|
1254
|
+
private changed(propagateError = false): Promise<void> {
|
|
1093
1255
|
const snapshot = this.list(true);
|
|
1094
1256
|
const next = this.changeQueue.then(async () => {
|
|
1095
1257
|
try {
|
|
1096
1258
|
await this.options.onChange?.(snapshot);
|
|
1097
|
-
} catch {
|
|
1098
|
-
|
|
1259
|
+
} catch (error) {
|
|
1260
|
+
if (propagateError) throw error;
|
|
1261
|
+
// Non-terminal persistence remains best-effort so lifecycle controls stay usable.
|
|
1099
1262
|
}
|
|
1100
1263
|
});
|
|
1101
|
-
this.changeQueue = next;
|
|
1264
|
+
this.changeQueue = next.catch(() => undefined);
|
|
1102
1265
|
return next;
|
|
1103
1266
|
}
|
|
1104
1267
|
|
|
@@ -1115,6 +1278,8 @@ export class AgentRegistry {
|
|
|
1115
1278
|
updatedAt: agent.updatedAt,
|
|
1116
1279
|
historyCount: agent.history.length,
|
|
1117
1280
|
unreadMessages,
|
|
1281
|
+
turnGeneration: agent.turnGeneration ?? 0,
|
|
1282
|
+
pendingCompletionCount: agent.pendingCompletions?.length ?? 0,
|
|
1118
1283
|
};
|
|
1119
1284
|
}
|
|
1120
1285
|
|
|
@@ -1126,6 +1291,9 @@ export class AgentRegistry {
|
|
|
1126
1291
|
currentMailboxMessageIds: agent.currentMailboxMessageIds
|
|
1127
1292
|
? [...agent.currentMailboxMessageIds]
|
|
1128
1293
|
: undefined,
|
|
1294
|
+
pendingCompletions: (agent.pendingCompletions ?? []).map((completion) => ({
|
|
1295
|
+
...completion,
|
|
1296
|
+
})),
|
|
1129
1297
|
history: agent.history.map((turn) => ({ ...turn })),
|
|
1130
1298
|
mailbox: agent.mailbox.map((message) => ({ ...message })),
|
|
1131
1299
|
contract: agent.contract ? structuredClone(agent.contract) : undefined,
|
package/src/render.ts
CHANGED
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
type ToolRenderResultOptions,
|
|
8
8
|
} from "@earendil-works/pi-coding-agent";
|
|
9
9
|
import { Container, Markdown, Spacer, Text } from "@earendil-works/pi-tui";
|
|
10
|
-
import type { AgentScope, SubagentThinkingLevel } from "./agents.js";
|
|
10
|
+
import type { AgentScope, SubagentThinkingLevel } from "./agents/types.js";
|
|
11
11
|
import { renderPanelCall, renderPanelResult } from "./panel-render.js";
|
|
12
12
|
import { hasUsableAggregator, type SubagentParams } from "./params.js";
|
|
13
13
|
import { expansionHint, formatToolActivity, safeBlock, safeLine } from "./render-common.js";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as path from "node:path";
|
|
2
2
|
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
3
|
-
import type { AgentConfig, SubagentThinkingLevel, SubagentTransportKind } from "./agents.js";
|
|
3
|
+
import type { AgentConfig, SubagentThinkingLevel, SubagentTransportKind } from "./agents/types.js";
|
|
4
4
|
import type { TargetPolicyAudit } from "./cwd-policy.js";
|
|
5
5
|
import type { DelegationContract } from "./delegation-contract.js";
|
|
6
6
|
import {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AgentConfig, SubagentThinkingLevel } from "./agents.js";
|
|
1
|
+
import type { AgentConfig, SubagentThinkingLevel } from "./agents/types.js";
|
|
2
2
|
import { DEFAULT_MAX_OUTPUT_BYTES } from "./limits.js";
|
|
3
3
|
import type { ManagedAgent, TurnOutcome } from "./registry.js";
|
|
4
4
|
import { boundedPrivateText } from "./safe-text.js";
|
package/src/rpc-transport.ts
CHANGED
|
@@ -3,7 +3,8 @@ import * as fs from "node:fs";
|
|
|
3
3
|
import * as os from "node:os";
|
|
4
4
|
import * as path from "node:path";
|
|
5
5
|
import type { RpcSessionState } from "@earendil-works/pi-coding-agent";
|
|
6
|
-
import {
|
|
6
|
+
import { discoverAgents } from "./agents/discovery.js";
|
|
7
|
+
import type { AgentConfig, SubagentSettings } from "./agents/types.js";
|
|
7
8
|
import {
|
|
8
9
|
buildCurrentTurnPrompt,
|
|
9
10
|
type ParentRuntimeSnapshot,
|
package/src/runner.ts
CHANGED
|
@@ -6,7 +6,12 @@ import type { AgentToolResult } from "@earendil-works/pi-agent-core";
|
|
|
6
6
|
import type { Message } from "@earendil-works/pi-ai";
|
|
7
7
|
import { withFileMutationQueue } from "@earendil-works/pi-coding-agent";
|
|
8
8
|
import type { SchedulingDecision } from "./adaptive-scheduler.js";
|
|
9
|
-
import type {
|
|
9
|
+
import type {
|
|
10
|
+
AgentConfig,
|
|
11
|
+
AgentScope,
|
|
12
|
+
AgentSource,
|
|
13
|
+
SubagentThinkingLevel,
|
|
14
|
+
} from "./agents/types.js";
|
|
10
15
|
import { type CapabilityGrant, revokeCapabilityGrant } from "./capability-grant.js";
|
|
11
16
|
import type { TargetPolicyAudit } from "./cwd-policy.js";
|
|
12
17
|
import type { DelegationContract } from "./delegation-contract.js";
|