pi-daddy 0.43.1 → 0.44.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/CHANGELOG.md +93 -31
- package/README.md +15 -0
- package/THIRD_PARTY_LICENSES/musl-COPYRIGHT.txt +193 -0
- package/THIRD_PARTY_LICENSES/zig-MIT.txt +21 -0
- package/THIRD_PARTY_NOTICES.md +9 -0
- package/dist/cli.d.ts +2 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +10 -5
- package/dist/cli.js.map +1 -1
- package/dist/executors/activity-session.js +116 -65
- package/dist/executors/activity-session.js.map +1 -1
- package/dist/executors/captured-execution.d.ts +13 -0
- package/dist/executors/captured-execution.d.ts.map +1 -0
- package/dist/executors/captured-execution.js +53 -0
- package/dist/executors/captured-execution.js.map +1 -0
- package/dist/executors/child-final.d.ts +31 -0
- package/dist/executors/child-final.d.ts.map +1 -0
- package/dist/executors/child-final.js +197 -0
- package/dist/executors/child-final.js.map +1 -0
- package/dist/executors/executor.d.ts +2 -1
- package/dist/executors/executor.d.ts.map +1 -1
- package/dist/executors/executor.js +12 -0
- package/dist/executors/executor.js.map +1 -1
- package/dist/executors/owned-worker.d.ts +18 -0
- package/dist/executors/owned-worker.d.ts.map +1 -0
- package/dist/executors/owned-worker.js +295 -0
- package/dist/executors/owned-worker.js.map +1 -0
- package/dist/executors/worker-artifact.d.ts +3 -0
- package/dist/executors/worker-artifact.d.ts.map +1 -0
- package/dist/executors/worker-artifact.js +3 -0
- package/dist/executors/worker-artifact.js.map +1 -0
- package/dist/governance/approval-prompt.d.ts +4 -3
- package/dist/governance/approval-prompt.d.ts.map +1 -1
- package/dist/governance/approval-prompt.js +13 -6
- package/dist/governance/approval-prompt.js.map +1 -1
- package/dist/governance/captured-worker-record.d.ts +9 -0
- package/dist/governance/captured-worker-record.d.ts.map +1 -0
- package/dist/governance/captured-worker-record.js +123 -0
- package/dist/governance/captured-worker-record.js.map +1 -0
- package/dist/governance/lease-record.d.ts +4 -0
- package/dist/governance/lease-record.d.ts.map +1 -1
- package/dist/governance/lease-record.js +19 -2
- package/dist/governance/lease-record.js.map +1 -1
- package/dist/governance/ledger-events.d.ts +5 -5
- package/dist/governance/ledger-events.d.ts.map +1 -1
- package/dist/governance/ledger-v3-validation.js +1 -1
- package/dist/governance/ledger-v3-validation.js.map +1 -1
- package/dist/governance/workspace-acceptance.d.ts +3 -1
- package/dist/governance/workspace-acceptance.d.ts.map +1 -1
- package/dist/governance/workspace-acceptance.js +21 -5
- package/dist/governance/workspace-acceptance.js.map +1 -1
- package/dist/governance/workspace-lease.d.ts.map +1 -1
- package/dist/governance/workspace-lease.js +44 -1
- package/dist/governance/workspace-lease.js.map +1 -1
- package/dist/kernel/authority-text.d.ts +3 -0
- package/dist/kernel/authority-text.d.ts.map +1 -0
- package/dist/kernel/authority-text.js +22 -0
- package/dist/kernel/authority-text.js.map +1 -0
- package/dist/kernel/bounded-read-failures.d.ts +9 -0
- package/dist/kernel/bounded-read-failures.d.ts.map +1 -0
- package/dist/kernel/bounded-read-failures.js +33 -0
- package/dist/kernel/bounded-read-failures.js.map +1 -0
- package/dist/kernel/bounded-read-sync.d.ts +24 -0
- package/dist/kernel/bounded-read-sync.d.ts.map +1 -0
- package/dist/kernel/bounded-read-sync.js +75 -0
- package/dist/kernel/bounded-read-sync.js.map +1 -0
- package/dist/kernel/bounded-read.d.ts +29 -2
- package/dist/kernel/bounded-read.d.ts.map +1 -1
- package/dist/kernel/bounded-read.js +98 -9
- package/dist/kernel/bounded-read.js.map +1 -1
- package/dist/kernel/capacity.d.ts +36 -0
- package/dist/kernel/capacity.d.ts.map +1 -0
- package/dist/kernel/capacity.js +94 -0
- package/dist/kernel/capacity.js.map +1 -0
- package/dist/kernel/captured-worker-contract.d.ts +38 -0
- package/dist/kernel/captured-worker-contract.d.ts.map +1 -0
- package/dist/kernel/captured-worker-contract.js +2 -0
- package/dist/kernel/captured-worker-contract.js.map +1 -0
- package/dist/kernel/catalog.d.ts +12 -3
- package/dist/kernel/catalog.d.ts.map +1 -1
- package/dist/kernel/catalog.js +28 -8
- package/dist/kernel/catalog.js.map +1 -1
- package/dist/kernel/chain.d.ts +1 -0
- package/dist/kernel/chain.d.ts.map +1 -1
- package/dist/kernel/chain.js.map +1 -1
- package/dist/kernel/definitions.d.ts +11 -1
- package/dist/kernel/definitions.d.ts.map +1 -1
- package/dist/kernel/definitions.js +6 -3
- package/dist/kernel/definitions.js.map +1 -1
- package/dist/kernel/fanout.d.ts +5 -25
- package/dist/kernel/fanout.d.ts.map +1 -1
- package/dist/kernel/fanout.js +15 -30
- package/dist/kernel/fanout.js.map +1 -1
- package/dist/kernel/grant-env.d.ts +0 -2
- package/dist/kernel/grant-env.d.ts.map +1 -1
- package/dist/kernel/grant-env.js +0 -1
- package/dist/kernel/grant-env.js.map +1 -1
- package/dist/kernel/model-preflight.d.ts +8 -0
- package/dist/kernel/model-preflight.d.ts.map +1 -1
- package/dist/kernel/model-preflight.js +23 -0
- package/dist/kernel/model-preflight.js.map +1 -1
- package/dist/kernel/selected-definitions.d.ts +15 -0
- package/dist/kernel/selected-definitions.d.ts.map +1 -0
- package/dist/kernel/selected-definitions.js +146 -0
- package/dist/kernel/selected-definitions.js.map +1 -0
- package/dist/kernel/skill-packages.d.ts +4 -2
- package/dist/kernel/skill-packages.d.ts.map +1 -1
- package/dist/kernel/skill-packages.js +34 -41
- package/dist/kernel/skill-packages.js.map +1 -1
- package/dist/kernel/skill-resources.d.ts.map +1 -1
- package/dist/kernel/skill-resources.js +8 -11
- package/dist/kernel/skill-resources.js.map +1 -1
- package/dist/kernel/workspace.d.ts +6 -3
- package/dist/kernel/workspace.d.ts.map +1 -1
- package/dist/kernel/workspace.js +10 -6
- package/dist/kernel/workspace.js.map +1 -1
- package/dist/products/dashboard-cli.js +1 -1
- package/dist/products/dashboard-cli.js.map +1 -1
- package/dist/products/dashboard-render.d.ts +1 -2
- package/dist/products/dashboard-render.d.ts.map +1 -1
- package/dist/products/dashboard-render.js +3 -1
- package/dist/products/dashboard-render.js.map +1 -1
- package/dist/products/dashboard-session-client.d.ts +1 -2
- package/dist/products/dashboard-session-client.d.ts.map +1 -1
- package/dist/products/dashboard-session-client.js.map +1 -1
- package/dist/products/episode-report.d.ts +12 -8
- package/dist/products/episode-report.d.ts.map +1 -1
- package/dist/products/episode-report.js +61 -12
- package/dist/products/episode-report.js.map +1 -1
- package/extensions/approvals.ts +1 -1
- package/extensions/chain-plan.ts +9 -3
- package/extensions/context-staging.ts +30 -35
- package/extensions/dashboard-session-server.ts +1 -2
- package/extensions/definition-describe.ts +44 -0
- package/extensions/definition-runtime.ts +89 -26
- package/extensions/delegate-chain.ts +273 -256
- package/extensions/delegation-activation.ts +1 -1
- package/extensions/delegation.ts +41 -53
- package/extensions/execute-child.ts +167 -134
- package/extensions/fanout-outcome.ts +1 -9
- package/extensions/grants-command.ts +2 -9
- package/extensions/grants.ts +106 -38
- package/extensions/init-command.ts +52 -12
- package/extensions/reload-environment.ts +21 -2
- package/extensions/retired-inputs.ts +7 -0
- package/extensions/run-delegation.ts +212 -265
- package/extensions/session-capacity.ts +53 -0
- package/extensions/session-environment.ts +5 -2
- package/extensions/session-model-prompt.ts +33 -32
- package/extensions/session-report.ts +28 -11
- package/extensions/session.ts +200 -157
- package/extensions/workspace-runtime.ts +2 -1
- package/native/README.md +25 -0
- package/native/linux-x64/worker +0 -0
- package/native/linux-x64/worker.sha256 +1 -0
- package/native/provenance.json +42 -0
- package/native/toolchain.json +18 -0
- package/native/worker.c +209 -0
- package/package.json +15 -7
- package/src/cli.ts +16 -6
- package/src/executors/activity-session.ts +120 -66
- package/src/executors/captured-execution.ts +61 -0
- package/src/executors/child-final.ts +190 -0
- package/src/executors/executor.ts +14 -1
- package/src/executors/owned-worker.ts +322 -0
- package/src/executors/worker-artifact.ts +2 -0
- package/src/governance/approval-prompt.ts +18 -8
- package/src/governance/captured-worker-record.ts +138 -0
- package/src/governance/lease-record.ts +21 -2
- package/src/governance/ledger-events.ts +5 -5
- package/src/governance/ledger-v3-validation.ts +1 -1
- package/src/governance/workspace-acceptance.ts +20 -4
- package/src/governance/workspace-lease.ts +59 -1
- package/src/kernel/authority-text.ts +17 -0
- package/src/kernel/bounded-read-failures.ts +42 -0
- package/src/kernel/bounded-read-sync.ts +87 -0
- package/src/kernel/bounded-read.ts +128 -11
- package/src/kernel/capacity.ts +123 -0
- package/src/kernel/captured-worker-contract.ts +29 -0
- package/src/kernel/catalog.ts +45 -16
- package/src/kernel/chain.ts +1 -0
- package/src/kernel/definitions.ts +11 -2
- package/src/kernel/fanout.ts +17 -32
- package/src/kernel/grant-env.ts +0 -3
- package/src/kernel/model-preflight.ts +29 -0
- package/src/kernel/selected-definitions.ts +156 -0
- package/src/kernel/skill-packages.ts +40 -38
- package/src/kernel/skill-resources.ts +8 -9
- package/src/kernel/workspace.ts +12 -5
- package/src/products/dashboard-cli.ts +1 -1
- package/src/products/dashboard-render.ts +6 -2
- package/src/products/dashboard-session-client.ts +1 -2
- package/src/products/episode-report.ts +80 -17
- package/dist/advisors/advisor.d.ts +0 -90
- package/dist/advisors/advisor.d.ts.map +0 -1
- package/dist/advisors/advisor.js +0 -154
- package/dist/advisors/advisor.js.map +0 -1
- package/dist/advisors/decider.d.ts +0 -88
- package/dist/advisors/decider.d.ts.map +0 -1
- package/dist/advisors/decider.js +0 -29
- package/dist/advisors/decider.js.map +0 -1
- package/dist/advisors/jev.d.ts +0 -41
- package/dist/advisors/jev.d.ts.map +0 -1
- package/dist/advisors/jev.js +0 -139
- package/dist/advisors/jev.js.map +0 -1
- package/dist/advisors/settings.d.ts +0 -49
- package/dist/advisors/settings.d.ts.map +0 -1
- package/dist/advisors/settings.js +0 -99
- package/dist/advisors/settings.js.map +0 -1
- package/dist/governance/episode-cost-gate.d.ts +0 -31
- package/dist/governance/episode-cost-gate.d.ts.map +0 -1
- package/dist/governance/episode-cost-gate.js +0 -112
- package/dist/governance/episode-cost-gate.js.map +0 -1
- package/extensions/advisor-session.ts +0 -67
- package/extensions/effort-advice.ts +0 -112
- package/extensions/pruning-advice.ts +0 -133
- package/src/advisors/advisor.ts +0 -246
- package/src/advisors/decider.ts +0 -74
- package/src/advisors/jev.ts +0 -163
- package/src/advisors/settings.ts +0 -115
- package/src/governance/episode-cost-gate.ts +0 -128
|
@@ -1,10 +1,17 @@
|
|
|
1
|
+
import { BoundedReadCleanupError } from "../src/kernel/bounded-read.ts";
|
|
2
|
+
import { retainDiscoveryCleanupFailure } from "./session.ts";
|
|
3
|
+
import type { CapacityReservation } from "../src/kernel/capacity.ts";
|
|
4
|
+
import { HERDR_UNQUALIFIED_REASON } from "../src/executors/executor.ts";
|
|
5
|
+
import { runCapturedExecution } from "../src/executors/captured-execution.ts";
|
|
6
|
+
import type { ChildFinal } from "../src/executors/child-final.ts";
|
|
7
|
+
import type { CapturedWorkerCleanup } from "../src/kernel/captured-worker-contract.ts";
|
|
1
8
|
import type { Delegation } from "../src/kernel/delegate.ts";
|
|
2
9
|
import {
|
|
3
10
|
beginExecutionRetention,
|
|
4
11
|
retentionConfigurationDigest,
|
|
5
12
|
type RetentionStatus,
|
|
6
13
|
} from "../src/governance/execution-retention.ts";
|
|
7
|
-
import { appendLedgerEvent, buildChildLifecycleEvent
|
|
14
|
+
import { appendLedgerEvent, buildChildLifecycleEvent } from "../src/governance/ledger.ts";
|
|
8
15
|
import { hasFinalizerError } from "../src/governance/finalization.ts";
|
|
9
16
|
import { mergeChildEnv } from "../src/kernel/propagation.ts";
|
|
10
17
|
import type { Capability } from "../src/kernel/resolve.ts";
|
|
@@ -25,7 +32,6 @@ import { ENV_HERDR_KEEP_PANE, type GrantsSession } from "./session.ts";
|
|
|
25
32
|
import { CHILD_ATTRIBUTION_ENV_KEYS, ENV_EPISODE_ID } from "../src/kernel/env-names.ts";
|
|
26
33
|
import { releaseDelegationWorkspace, type PreparedWorkspace } from "./workspace-runtime.ts";
|
|
27
34
|
import { ActivityTimelineRecorder, ENV_ACTIVITY_PARENT_TASK } from "../src/products/activity-timeline.ts";
|
|
28
|
-
import { loadEpisodeCosts } from "../src/governance/episode-cost-gate.ts";
|
|
29
35
|
import { resolvedModelOf, type ResolvedDefinitionRuntime } from "./definition-runtime.ts";
|
|
30
36
|
export interface DelegationOutcome {
|
|
31
37
|
ok: boolean;
|
|
@@ -43,6 +49,12 @@ export interface DelegationOutcome {
|
|
|
43
49
|
retention?: RetentionStatus;
|
|
44
50
|
/** A loud best-effort post-execution observation failure; execution ownership is still settled. */
|
|
45
51
|
control?: "failed";
|
|
52
|
+
work?: "succeeded" | "failed" | "unknown";
|
|
53
|
+
final?: ChildFinal;
|
|
54
|
+
cleanup?: CapturedWorkerCleanup;
|
|
55
|
+
observation?: { state: "complete" | "incomplete"; reasons: string[] };
|
|
56
|
+
diagnostics?: string;
|
|
57
|
+
diagnosticsTruncated?: boolean;
|
|
46
58
|
}
|
|
47
59
|
/**
|
|
48
60
|
* The upstream controller's token, honoured ONLY when the child otherwise exited cleanly non-zero.
|
|
@@ -54,14 +66,26 @@ export interface DelegationOutcome {
|
|
|
54
66
|
* by anybody's gate, so the token cannot be taken at its word there.
|
|
55
67
|
*/
|
|
56
68
|
export function isCriticalAssuranceBlock(
|
|
57
|
-
outcome: Pick<
|
|
69
|
+
outcome: Pick<
|
|
70
|
+
DelegationOutcome,
|
|
71
|
+
"ok" | "text" | "exitCode" | "timedOut" | "aborted" | "truncated" | "spawnFailed" | "control" | "cleanup"
|
|
72
|
+
>,
|
|
58
73
|
): boolean {
|
|
59
74
|
// `truncated` is deliberately NOT here. The process executor keeps the HEAD of the output
|
|
60
75
|
// (`run-child.ts` slices to the cap and stops appending) and the token is matched at byte 0, so a
|
|
61
76
|
// genuine veto with a rationale over the output cap is still a genuine veto — rejecting it broke the
|
|
62
77
|
// pass-through ADR-0034 pins, in the fix that was supposed to protect it. What must be rejected is a
|
|
63
78
|
// child that never finished speaking: killed, cancelled, or never started.
|
|
64
|
-
if (
|
|
79
|
+
if (
|
|
80
|
+
outcome.ok ||
|
|
81
|
+
outcome.timedOut ||
|
|
82
|
+
outcome.aborted ||
|
|
83
|
+
outcome.spawnFailed ||
|
|
84
|
+
outcome.control === "failed" ||
|
|
85
|
+
outcome.cleanup?.state === "unknown" ||
|
|
86
|
+
!(outcome.exitCode !== null && outcome.exitCode > 0)
|
|
87
|
+
)
|
|
88
|
+
return false;
|
|
65
89
|
return outcome.text.trimStart().startsWith("BLOCKED_CRITICAL_ASSURANCE");
|
|
66
90
|
}
|
|
67
91
|
|
|
@@ -97,8 +121,9 @@ export interface ChildProgressUpdate {
|
|
|
97
121
|
* has already run, so failing closed there prevents nothing and used to discard completed work while
|
|
98
122
|
* blaming "ledger" (R-99). The failure is reported alongside the outcome instead of replacing it.
|
|
99
123
|
*/
|
|
100
|
-
export
|
|
124
|
+
export interface ChildExecutionInput {
|
|
101
125
|
session: GrantsSession;
|
|
126
|
+
capacityReservation?: CapacityReservation;
|
|
102
127
|
plan: Delegation;
|
|
103
128
|
agent?: string;
|
|
104
129
|
childId: string;
|
|
@@ -110,14 +135,56 @@ export async function executePlannedChild(input: {
|
|
|
110
135
|
resolvedRuntime?: ResolvedDefinitionRuntime;
|
|
111
136
|
signal?: AbortSignal;
|
|
112
137
|
onProgress?: (update: ChildProgressUpdate) => void;
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
138
|
+
}
|
|
139
|
+
export async function executePlannedChild(input: ChildExecutionInput): Promise<DelegationOutcome> {
|
|
140
|
+
let executorEntered = false;
|
|
141
|
+
try {
|
|
142
|
+
return await executePreparedChild(input, () => {
|
|
143
|
+
executorEntered = true;
|
|
144
|
+
});
|
|
145
|
+
} catch (error) {
|
|
146
|
+
if (executorEntered) throw error;
|
|
147
|
+
// Every operation above the executor-entry marker is local setup: no helper or worker exists yet.
|
|
148
|
+
input.capacityReservation?.finalize({ state: "not-started", reason: "child setup failed before executor entry" });
|
|
149
|
+
const failures: unknown[] = [error];
|
|
150
|
+
try {
|
|
151
|
+
const released = await releaseDelegationWorkspace({
|
|
152
|
+
prepared: input.preparedWorkspace,
|
|
153
|
+
childId: input.childId,
|
|
154
|
+
episodeId: input.session.episodeId,
|
|
155
|
+
executionId: input.executionId,
|
|
156
|
+
parentExecutionId: input.parentExecutionId,
|
|
157
|
+
ledgerPath: input.session.ledgerPath,
|
|
158
|
+
reason: "setup-failed",
|
|
159
|
+
});
|
|
160
|
+
if (released === "lost" || released === "retained")
|
|
161
|
+
failures.push(new Error(`workspace setup release is ${released}`));
|
|
162
|
+
} catch (releaseError) {
|
|
163
|
+
failures.push(releaseError);
|
|
164
|
+
}
|
|
165
|
+
try {
|
|
166
|
+
input.plan.disposeHandoff?.();
|
|
167
|
+
} catch (disposeError) {
|
|
168
|
+
failures.push(disposeError);
|
|
169
|
+
}
|
|
170
|
+
if (failures.length > 1) throw new AggregateError(failures, failures.map(String).join("; "));
|
|
171
|
+
throw error;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
async function executePreparedChild(
|
|
175
|
+
input: ChildExecutionInput,
|
|
176
|
+
onExecutorEntry: () => void,
|
|
177
|
+
): Promise<DelegationOutcome> {
|
|
119
178
|
const { session, plan, childId, executionId, parentExecutionId, preparedWorkspace, signal, onProgress } = input;
|
|
120
179
|
const ledgerPath = session.ledgerPath;
|
|
180
|
+
let executorEntered = false;
|
|
181
|
+
const teardownFailures: string[] = [];
|
|
182
|
+
const controlFailures: string[] = [];
|
|
183
|
+
const lifecycle = session.reloadLifecycle;
|
|
184
|
+
const onReadCleanup = (error: BoundedReadCleanupError): void => {
|
|
185
|
+
if (lifecycle) retainDiscoveryCleanupFailure(session, error, lifecycle);
|
|
186
|
+
controlFailures.push(`session read cleanup unresolved: ${error.message}`);
|
|
187
|
+
};
|
|
121
188
|
const runtimeAttribution = (observed?: {
|
|
122
189
|
resolvedModel?: { provider: string; modelId: string };
|
|
123
190
|
thinking?: string;
|
|
@@ -177,95 +244,25 @@ export async function executePlannedChild(input: {
|
|
|
177
244
|
}
|
|
178
245
|
}
|
|
179
246
|
|
|
247
|
+
async function observeUsage(): Promise<import("../src/executors/activity-session.ts").ChildUsageObservation> {
|
|
248
|
+
try {
|
|
249
|
+
return await activitySession.usage();
|
|
250
|
+
} catch (error) {
|
|
251
|
+
if (error instanceof BoundedReadCleanupError) onReadCleanup(error);
|
|
252
|
+
teardownFailures.push(`usage observation unavailable: ${String(error)}`);
|
|
253
|
+
return { unavailable: "session-invalid" };
|
|
254
|
+
}
|
|
255
|
+
}
|
|
180
256
|
async function executeWithActivitySession(): Promise<DelegationOutcome> {
|
|
181
257
|
const args = activitySession.args;
|
|
182
258
|
// The process executor adds a second signal that is live during a tool call: the child tree's CPU time and
|
|
183
259
|
// descendants (`process-activity.ts`). Herdr children are started by the daemon, so their pid is not known here.
|
|
184
260
|
let childPid: number | undefined;
|
|
185
|
-
let costStopped = false;
|
|
186
|
-
const costUI = input.costGateUI ?? {
|
|
187
|
-
hasUI: false,
|
|
188
|
-
input: async () => undefined,
|
|
189
|
-
notify: () => undefined,
|
|
190
|
-
};
|
|
191
|
-
const costAbort = new AbortController();
|
|
192
|
-
const observeCost = async (cost: number | undefined, terminal: boolean) => {
|
|
193
|
-
if (!session.episodeCostGate) return;
|
|
194
|
-
if (ledgerPath) {
|
|
195
|
-
const recorded = await loadEpisodeCosts(ledgerPath, session.episodeId);
|
|
196
|
-
for (const [recordedExecution, recordedCost] of recorded) {
|
|
197
|
-
session.episodeCostGate.seedCost(recordedExecution, recordedCost);
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
await session.episodeCostGate.observe(executionId, cost, terminal, {
|
|
201
|
-
warn: (message) => costUI.notify(message),
|
|
202
|
-
pause: async () => {
|
|
203
|
-
if (session.executor.kind === "herdr") {
|
|
204
|
-
costUI.notify("pi-daddy: Herdr cannot pause a pane; stopping it at the episode cost ceiling");
|
|
205
|
-
return;
|
|
206
|
-
}
|
|
207
|
-
if (childPid !== undefined)
|
|
208
|
-
try {
|
|
209
|
-
process.kill(childPid, "SIGSTOP");
|
|
210
|
-
} catch {
|
|
211
|
-
/* the child may have exited between its final usage write and this observation */
|
|
212
|
-
}
|
|
213
|
-
},
|
|
214
|
-
ask: async () => {
|
|
215
|
-
if (session.executor.kind === "herdr" || !costUI.hasUI) return null;
|
|
216
|
-
const value = await costUI.input(
|
|
217
|
-
"pi-daddy: episode cost ceiling crossed",
|
|
218
|
-
"New USD ceiling above the current cost; leave blank to stop",
|
|
219
|
-
signal,
|
|
220
|
-
);
|
|
221
|
-
if (value === undefined || value.trim() === "") return null;
|
|
222
|
-
const parsed = Number(value);
|
|
223
|
-
return Number.isFinite(parsed) ? parsed : null;
|
|
224
|
-
},
|
|
225
|
-
resume: async () => {
|
|
226
|
-
if (childPid !== undefined)
|
|
227
|
-
try {
|
|
228
|
-
process.kill(childPid, "SIGCONT");
|
|
229
|
-
} catch {
|
|
230
|
-
/* already exited */
|
|
231
|
-
}
|
|
232
|
-
},
|
|
233
|
-
stop: async () => {
|
|
234
|
-
costStopped = true;
|
|
235
|
-
if (childPid !== undefined)
|
|
236
|
-
try {
|
|
237
|
-
process.kill(childPid, "SIGCONT");
|
|
238
|
-
} catch {
|
|
239
|
-
/* already exited */
|
|
240
|
-
}
|
|
241
|
-
costAbort.abort();
|
|
242
|
-
},
|
|
243
|
-
gate: async (event) => {
|
|
244
|
-
if (!ledgerPath) return;
|
|
245
|
-
await appendLedgerEvent(
|
|
246
|
-
{
|
|
247
|
-
path: ledgerPath,
|
|
248
|
-
strict: true,
|
|
249
|
-
},
|
|
250
|
-
buildEpisodeCostGateEvent({
|
|
251
|
-
episodeId: session.episodeId,
|
|
252
|
-
executionId,
|
|
253
|
-
parentExecutionId,
|
|
254
|
-
childId,
|
|
255
|
-
...event,
|
|
256
|
-
now: new Date(),
|
|
257
|
-
}),
|
|
258
|
-
);
|
|
259
|
-
},
|
|
260
|
-
});
|
|
261
|
-
};
|
|
262
261
|
const probe = async () => {
|
|
263
|
-
const [file, tree
|
|
262
|
+
const [file, tree] = await Promise.all([
|
|
264
263
|
activitySession.probe(),
|
|
265
264
|
childPid === undefined ? undefined : processTreeActivity(childPid),
|
|
266
|
-
activitySession.usage(),
|
|
267
265
|
]);
|
|
268
|
-
await observeCost(usage.usage?.cost.total, false);
|
|
269
266
|
return file === undefined && tree === undefined ? undefined : `${file ?? "-"}|${tree ?? "-"}`;
|
|
270
267
|
};
|
|
271
268
|
const startedAt = new Date();
|
|
@@ -273,7 +270,11 @@ export async function executePlannedChild(input: {
|
|
|
273
270
|
if (ledgerPath) {
|
|
274
271
|
try {
|
|
275
272
|
await appendLedgerEvent(
|
|
276
|
-
{
|
|
273
|
+
{
|
|
274
|
+
path: ledgerPath,
|
|
275
|
+
strict: false,
|
|
276
|
+
onFailure: (cause) => teardownFailures.push(`starting observation failed: ${String(cause)}`),
|
|
277
|
+
},
|
|
277
278
|
buildChildLifecycleEvent({
|
|
278
279
|
episodeId: session.episodeId,
|
|
279
280
|
executionId,
|
|
@@ -292,14 +293,6 @@ export async function executePlannedChild(input: {
|
|
|
292
293
|
}),
|
|
293
294
|
);
|
|
294
295
|
} catch (error) {
|
|
295
|
-
await releaseDelegationWorkspace({
|
|
296
|
-
prepared: preparedWorkspace,
|
|
297
|
-
childId,
|
|
298
|
-
episodeId: session.episodeId,
|
|
299
|
-
executionId,
|
|
300
|
-
parentExecutionId,
|
|
301
|
-
reason: "ledger-failed",
|
|
302
|
-
});
|
|
303
296
|
throw error;
|
|
304
297
|
}
|
|
305
298
|
}
|
|
@@ -319,11 +312,7 @@ export async function executePlannedChild(input: {
|
|
|
319
312
|
leaseLost = true;
|
|
320
313
|
leaseAbort.abort();
|
|
321
314
|
});
|
|
322
|
-
const executionSignals = [
|
|
323
|
-
costAbort.signal,
|
|
324
|
-
...(signal ? [signal] : []),
|
|
325
|
-
...(writerLease ? [leaseAbort.signal] : []),
|
|
326
|
-
];
|
|
315
|
+
const executionSignals = [...(signal ? [signal] : []), ...(writerLease ? [leaseAbort.signal] : [])];
|
|
327
316
|
const executionSignal = AbortSignal.any(executionSignals);
|
|
328
317
|
const retention = beginExecutionRetention({
|
|
329
318
|
executionId,
|
|
@@ -345,7 +334,7 @@ export async function executePlannedChild(input: {
|
|
|
345
334
|
let releaseReason = "failed";
|
|
346
335
|
let retainWriterLease = false;
|
|
347
336
|
let terminalAttempted = false;
|
|
348
|
-
|
|
337
|
+
|
|
349
338
|
let runtimeRecord: Promise<void> | undefined;
|
|
350
339
|
const recordRunning = (executor: "process" | "herdr", pane?: { id: string; agentName: string }): void => {
|
|
351
340
|
if (!ledgerPath) return;
|
|
@@ -379,6 +368,10 @@ export async function executePlannedChild(input: {
|
|
|
379
368
|
}
|
|
380
369
|
};
|
|
381
370
|
try {
|
|
371
|
+
if (String(session.executor.kind) === "herdr")
|
|
372
|
+
throw new GovernanceRefusal(refusal("EXECUTOR_UNAVAILABLE", HERDR_UNQUALIFIED_REASON));
|
|
373
|
+
executorEntered = true;
|
|
374
|
+
onExecutorEntry();
|
|
382
375
|
const output =
|
|
383
376
|
session.executor.kind === "herdr"
|
|
384
377
|
? await runHerdrPane({
|
|
@@ -410,9 +403,16 @@ export async function executePlannedChild(input: {
|
|
|
410
403
|
onNativeTab: (tabId) => retention.native({ tabId }),
|
|
411
404
|
onSnapshot: onProgress ? (snapshot) => onProgress({ snapshot }) : undefined,
|
|
412
405
|
})
|
|
413
|
-
: await
|
|
414
|
-
|
|
415
|
-
|
|
406
|
+
: await runCapturedExecution({
|
|
407
|
+
executionId,
|
|
408
|
+
sessionPath: activitySession.path,
|
|
409
|
+
onReadCleanup,
|
|
410
|
+
onOwnership: async (identity) => {
|
|
411
|
+
input.capacityReservation?.bindOwnership(identity);
|
|
412
|
+
await preparedWorkspace?.lease.attachCapturedWorker(identity);
|
|
413
|
+
},
|
|
414
|
+
command: "pi",
|
|
415
|
+
args,
|
|
416
416
|
env: mergeChildEnv(process.env, plan.env),
|
|
417
417
|
cwd,
|
|
418
418
|
signal: executionSignal,
|
|
@@ -429,7 +429,7 @@ export async function executePlannedChild(input: {
|
|
|
429
429
|
// Lease attachment is a security hook and may fail the spawn. The shared reporters isolate
|
|
430
430
|
// display exceptions before they reach this callback; runChild deliberately kills on any error
|
|
431
431
|
// here, so presentation must never be added directly without that reporter boundary.
|
|
432
|
-
|
|
432
|
+
|
|
433
433
|
retention.native({ pid });
|
|
434
434
|
recordRunning("process");
|
|
435
435
|
onProgress?.({ state: "running" });
|
|
@@ -438,7 +438,18 @@ export async function executePlannedChild(input: {
|
|
|
438
438
|
|
|
439
439
|
if (sessionFlag >= 0) retention.observeSession({ source: "pi-session-file", value: plan.args[sessionFlag + 1] });
|
|
440
440
|
retention.capture("result", Buffer.from(output.text), true);
|
|
441
|
-
|
|
441
|
+
if ("diagnosticsTruncated" in output && output.diagnosticsTruncated)
|
|
442
|
+
teardownFailures.push("diagnostic output truncated");
|
|
443
|
+
const final = "final" in output ? (output.final as ChildFinal) : undefined;
|
|
444
|
+
const cleanup = "cleanup" in output ? (output.cleanup as CapturedWorkerCleanup) : undefined;
|
|
445
|
+
input.capacityReservation?.finalize(
|
|
446
|
+
cleanup ?? { state: "unknown", reason: "executor supplied no qualified cleanup" },
|
|
447
|
+
);
|
|
448
|
+
const workFailed = Boolean(output.spawnError || output.aborted || output.timedOut || output.code !== 0);
|
|
449
|
+
const finalUnavailable = final?.state === "unavailable";
|
|
450
|
+
const cleanupUnknown = cleanup?.state === "unknown";
|
|
451
|
+
retainWriterLease = Boolean(writerLease && cleanupUnknown);
|
|
452
|
+
const childFailed = workFailed || finalUnavailable || cleanupUnknown;
|
|
442
453
|
retention.finish({
|
|
443
454
|
code: output.code,
|
|
444
455
|
signal: output.signal ?? null,
|
|
@@ -447,8 +458,8 @@ export async function executePlannedChild(input: {
|
|
|
447
458
|
truncated: output.truncated,
|
|
448
459
|
failed: childFailed,
|
|
449
460
|
});
|
|
450
|
-
const usageObservation = await
|
|
451
|
-
|
|
461
|
+
const usageObservation = await observeUsage();
|
|
462
|
+
|
|
452
463
|
releaseReason = output.timedOut ? "timeout" : output.aborted ? "cancelled" : childFailed ? "failed" : "completed";
|
|
453
464
|
if (activityStarted)
|
|
454
465
|
try {
|
|
@@ -522,16 +533,18 @@ export async function executePlannedChild(input: {
|
|
|
522
533
|
: output.aborted
|
|
523
534
|
? leaseWasLost
|
|
524
535
|
? "lost the exclusive writer lease protecting its workspace and was stopped"
|
|
525
|
-
:
|
|
526
|
-
? "crossed the episode cost ceiling and was stopped"
|
|
527
|
-
: "was cancelled"
|
|
536
|
+
: "was cancelled"
|
|
528
537
|
: output.timedOut
|
|
529
538
|
? output.idle
|
|
530
539
|
? `showed no activity for ${describeBound(configuredIdleMs)} and was killed ` +
|
|
531
540
|
`(${ENV_CHILD_IDLE_TIMEOUT} sets the bound in seconds)`
|
|
532
541
|
: `ran past its ${describeBound(configuredTimeoutMs)} ceiling and was killed ` +
|
|
533
542
|
`(${ENV_CHILD_TIMEOUT} sets it in seconds)`
|
|
534
|
-
:
|
|
543
|
+
: finalUnavailable
|
|
544
|
+
? `has no attributable complete final: ${final.reason}`
|
|
545
|
+
: cleanupUnknown
|
|
546
|
+
? `has unverified subtree cleanup: ${cleanup.reason}`
|
|
547
|
+
: `exited with code ${output.code}`;
|
|
535
548
|
// A stable code for every execution failure. Without these an external controller could tell a
|
|
536
549
|
// policy refusal from an internal error, but not a lost writer lease from a user pressing stop
|
|
537
550
|
// (R-103), and not a missing `setpriv` from an ordinary crash (R-107).
|
|
@@ -543,10 +556,18 @@ export async function executePlannedChild(input: {
|
|
|
543
556
|
? "CHILD_TIMED_OUT"
|
|
544
557
|
: output.aborted
|
|
545
558
|
? "CHILD_CANCELLED"
|
|
546
|
-
:
|
|
559
|
+
: output.code === 0 && (finalUnavailable || cleanupUnknown)
|
|
560
|
+
? "EXECUTOR_UNAVAILABLE"
|
|
561
|
+
: "CHILD_EXIT_NONZERO";
|
|
547
562
|
const failed: DelegationOutcome = {
|
|
548
563
|
ok: false,
|
|
549
|
-
|
|
564
|
+
work: workFailed ? "failed" : final?.state === "complete" ? "succeeded" : "unknown",
|
|
565
|
+
...(final ? { final } : {}),
|
|
566
|
+
...(cleanup ? { cleanup } : {}),
|
|
567
|
+
...("diagnostics" in output ? { diagnostics: String(output.diagnostics) } : {}),
|
|
568
|
+
...("diagnosticsTruncated" in output ? { diagnosticsTruncated: Boolean(output.diagnosticsTruncated) } : {}),
|
|
569
|
+
...(cleanupUnknown ? { control: "failed" as const } : {}),
|
|
570
|
+
text: output.text,
|
|
550
571
|
reason: `the sub-agent ${why}`,
|
|
551
572
|
granted: plan.effective,
|
|
552
573
|
depth: plan.childDepth,
|
|
@@ -563,7 +584,12 @@ export async function executePlannedChild(input: {
|
|
|
563
584
|
|
|
564
585
|
const succeeded: DelegationOutcome = {
|
|
565
586
|
ok: true,
|
|
566
|
-
|
|
587
|
+
work: "succeeded",
|
|
588
|
+
...(final ? { final } : {}),
|
|
589
|
+
...(cleanup ? { cleanup } : {}),
|
|
590
|
+
...("diagnostics" in output ? { diagnostics: String(output.diagnostics) } : {}),
|
|
591
|
+
...("diagnosticsTruncated" in output ? { diagnosticsTruncated: Boolean(output.diagnosticsTruncated) } : {}),
|
|
592
|
+
text: output.text,
|
|
567
593
|
granted: plan.effective,
|
|
568
594
|
depth: plan.childDepth,
|
|
569
595
|
exitCode: output.code,
|
|
@@ -572,6 +598,7 @@ export async function executePlannedChild(input: {
|
|
|
572
598
|
await teardown();
|
|
573
599
|
return withTeardownNotes(succeeded);
|
|
574
600
|
} catch (error) {
|
|
601
|
+
if (!executorEntered) throw error;
|
|
575
602
|
if (activityStarted && !activityFinished)
|
|
576
603
|
try {
|
|
577
604
|
await activity.childFinished(
|
|
@@ -592,13 +619,8 @@ export async function executePlannedChild(input: {
|
|
|
592
619
|
truncated: false,
|
|
593
620
|
failed: true,
|
|
594
621
|
});
|
|
595
|
-
retainWriterLease
|
|
596
|
-
const usageObservation = await
|
|
597
|
-
try {
|
|
598
|
-
await observeCost(usageObservation.usage?.cost.total, usageObservation.usage === undefined);
|
|
599
|
-
} catch {
|
|
600
|
-
/* cost observation must not replace the executor's primary failure */
|
|
601
|
-
}
|
|
622
|
+
retainWriterLease ||= Boolean(writerLease && isHerdrWriterCloseFailure(error));
|
|
623
|
+
const usageObservation = await observeUsage();
|
|
602
624
|
if (ledgerPath && !terminalAttempted) {
|
|
603
625
|
// Best-effort: this records the failure, so it must not REPLACE the failure. A strict append that
|
|
604
626
|
// throws here would discard the original error — including HerdrWriterCloseError, whose whole
|
|
@@ -650,7 +672,7 @@ export async function executePlannedChild(input: {
|
|
|
650
672
|
// Attached, not dropped. `withTeardownNotes` was applied on both return paths and neither throw path,
|
|
651
673
|
// so a failed lease-release record — the thing that makes the NEXT owner report a phantom crash — was
|
|
652
674
|
// collected into an array nothing read.
|
|
653
|
-
throw errorWithTeardownNotes(error, teardownFailures);
|
|
675
|
+
throw errorWithTeardownNotes(error, [...controlFailures, ...teardownFailures]);
|
|
654
676
|
}
|
|
655
677
|
/**
|
|
656
678
|
* Surfaces a teardown failure WITHOUT discarding the result. The child already ran; telling the
|
|
@@ -676,18 +698,25 @@ export async function executePlannedChild(input: {
|
|
|
676
698
|
return error;
|
|
677
699
|
}
|
|
678
700
|
function withTeardownNotes(outcome: DelegationOutcome): DelegationOutcome {
|
|
679
|
-
const observed = {
|
|
680
|
-
|
|
701
|
+
const observed = {
|
|
702
|
+
...outcome,
|
|
703
|
+
retention: retention.status(),
|
|
704
|
+
observation: {
|
|
705
|
+
state: teardownFailures.length ? ("incomplete" as const) : ("complete" as const),
|
|
706
|
+
reasons: [...teardownFailures],
|
|
707
|
+
},
|
|
708
|
+
};
|
|
709
|
+
if (teardownFailures.length === 0 && controlFailures.length === 0) return observed;
|
|
681
710
|
return {
|
|
682
711
|
...observed,
|
|
683
|
-
control: "failed",
|
|
684
|
-
reason: [outcome.reason, ...teardownFailures].filter(Boolean).join("; "),
|
|
712
|
+
...(controlFailures.length ? { control: "failed" as const } : {}),
|
|
713
|
+
reason: [outcome.reason, ...controlFailures, ...teardownFailures].filter(Boolean).join("; "),
|
|
685
714
|
};
|
|
686
715
|
}
|
|
687
716
|
|
|
688
717
|
async function teardown(): Promise<void> {
|
|
689
718
|
try {
|
|
690
|
-
await releaseDelegationWorkspace({
|
|
719
|
+
const outcome = await releaseDelegationWorkspace({
|
|
691
720
|
prepared: preparedWorkspace,
|
|
692
721
|
childId,
|
|
693
722
|
episodeId: session.episodeId,
|
|
@@ -696,9 +725,13 @@ export async function executePlannedChild(input: {
|
|
|
696
725
|
ledgerPath,
|
|
697
726
|
reason: releaseReason,
|
|
698
727
|
retain: retainWriterLease,
|
|
728
|
+
onObservationFailure: (error) =>
|
|
729
|
+
teardownFailures.push(`workspace release observation failed: ${String(error)}`),
|
|
699
730
|
});
|
|
731
|
+
if (outcome === "lost" || (outcome === "retained" && !retainWriterLease))
|
|
732
|
+
controlFailures.push(`workspace release is ${outcome}`);
|
|
700
733
|
} catch (error) {
|
|
701
|
-
|
|
734
|
+
controlFailures.push(`workspace release failed: ${String(error)}`);
|
|
702
735
|
}
|
|
703
736
|
}
|
|
704
737
|
}
|
|
@@ -43,7 +43,7 @@ export function buildFanoutReport(
|
|
|
43
43
|
return outcomes
|
|
44
44
|
.map((outcome, index) => {
|
|
45
45
|
const label = `### child ${index + 1}${children[index]?.agent ? ` (${children[index].agent})` : ""}`;
|
|
46
|
-
return outcome.ok
|
|
46
|
+
return outcome.ok && outcome.control !== "failed"
|
|
47
47
|
? `${label} — completed\n\n${outcome.text || "(no output)"}`
|
|
48
48
|
: `${label} — FAILED: ${outcome.reason}${outcome.text ? `\n\n${outcome.text}` : ""}`;
|
|
49
49
|
})
|
|
@@ -68,14 +68,6 @@ export function throwFanoutInfrastructure(
|
|
|
68
68
|
if (infrastructureErrors.length > 0) throw new AggregateError(infrastructureErrors, criticalBlock.text);
|
|
69
69
|
throw new Error(criticalBlock.text);
|
|
70
70
|
}
|
|
71
|
-
if (infrastructureErrors.length === 1) throw infrastructureErrors[0];
|
|
72
|
-
if (infrastructureErrors.length > 1) {
|
|
73
|
-
throw new AggregateError(
|
|
74
|
-
infrastructureErrors,
|
|
75
|
-
`fan-out hit ${infrastructureErrors.length} infrastructure failures: ` +
|
|
76
|
-
infrastructureErrors.map((error) => String(error instanceof Error ? error.message : error)).join("; "),
|
|
77
|
-
);
|
|
78
|
-
}
|
|
79
71
|
}
|
|
80
72
|
|
|
81
73
|
/**
|
|
@@ -34,7 +34,8 @@ export interface GrantsCommandContext {
|
|
|
34
34
|
*/
|
|
35
35
|
executor: ExecutorChoice;
|
|
36
36
|
/** ADR-0077: which advisor and task-egress mode are in force, or why none is. */
|
|
37
|
-
|
|
37
|
+
/** Historical callers may carry this inert field. It is never read. */
|
|
38
|
+
advisor?: unknown;
|
|
38
39
|
/** ADR-0042: which ids this session pinned, so a routing refusal is discoverable before it happens. */
|
|
39
40
|
workspacePin?: ReadonlyMap<string, string>;
|
|
40
41
|
/** Registry ids this machine has never accepted: present, not routable, and worth naming (rule 8). */
|
|
@@ -113,7 +114,6 @@ export const grantsCommand = {
|
|
|
113
114
|
governed,
|
|
114
115
|
ownGrant,
|
|
115
116
|
executor,
|
|
116
|
-
advisor,
|
|
117
117
|
workspacePin,
|
|
118
118
|
unacceptedWorkspaces,
|
|
119
119
|
observed,
|
|
@@ -408,13 +408,6 @@ export const grantsCommand = {
|
|
|
408
408
|
// two facts about what a spawn will be sit together.
|
|
409
409
|
` executor ${executor.disclosure}`,
|
|
410
410
|
` depth ${depth} of max ${maxDepth}${maxDepth <= 0 ? " (spawning disabled)" : ""}`,
|
|
411
|
-
// Rule 8's loud half, which review found missing: an operator who upgraded from 0.34.0, or who mistyped the
|
|
412
|
-
// variable, saw an advisor silently absent and nothing saying why. This is also where an operator sees that
|
|
413
|
-
// which representation of the task leaves the machine, which no other status surface says.
|
|
414
|
-
advisor.decider === "none"
|
|
415
|
-
? ` advisor off${advisor.refusal ? ` — ${advisor.refusal}` : ""}`
|
|
416
|
-
: ` advisor ${advisor.decider} — task egress ${advisor.taskEgress}; ` +
|
|
417
|
-
`a pruned context handoff also sends session turns`,
|
|
418
411
|
` ledger ${ledgerPath || "(not recording — set PI_DADDY_LEDGER)"}`,
|
|
419
412
|
` approvals ${sessionApprovals.size} this session, ${valid.size} persisted` +
|
|
420
413
|
`${inheritedApprovals.size > 0 ? `, ${inheritedApprovals.size} inherited` : ""}` +
|