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,3 +1,4 @@
|
|
|
1
|
+
import { assertDefinitionIdentity } from "./definition-describe.ts";
|
|
1
2
|
/**
|
|
2
3
|
* `delegate_chain` — a governed sequential pipeline, planned and gated as one unit (ADR-0033).
|
|
3
4
|
*
|
|
@@ -20,8 +21,9 @@ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
|
20
21
|
import { Type } from "typebox";
|
|
21
22
|
import type { InheritableApproval } from "../src/kernel/approval.ts";
|
|
22
23
|
import { DELEGATE_SUBJECT } from "../src/kernel/approval.ts";
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
24
|
+
import { reconcileDelegationCapacity, reserveDelegationCapacity } from "./session-capacity.ts";
|
|
25
|
+
import { chainStepSpec, PLACEHOLDER, HANDOFF_MAX_BYTES } from "../src/kernel/chain.ts";
|
|
26
|
+
import { MAX_CHAIN_STEPS, childSpawnId } from "../src/kernel/fanout.ts";
|
|
25
27
|
import {
|
|
26
28
|
PAINT_INTERVAL_MS,
|
|
27
29
|
appendTail,
|
|
@@ -42,9 +44,9 @@ import { chainApprovalFacts, newChainApprovalAudit, rememberChainApproval } from
|
|
|
42
44
|
import { newExecutionId } from "../src/kernel/execution-id.ts";
|
|
43
45
|
import { planChain, type GateRequest } from "./chain-plan.ts";
|
|
44
46
|
import { contextShape } from "./context-shape.ts";
|
|
45
|
-
import { preflightModel } from "../src/kernel/model-preflight.ts";
|
|
47
|
+
import { preflightModel, runtimePairUnavailable } from "../src/kernel/model-preflight.ts";
|
|
46
48
|
import { resolveDefinitionRuntime } from "./definition-runtime.ts";
|
|
47
|
-
import {
|
|
49
|
+
import { withoutRetiredDelegationArguments } from "./retired-inputs.ts";
|
|
48
50
|
import { assertDelegationAuthority } from "./delegation-authority.ts";
|
|
49
51
|
|
|
50
52
|
/** One chain step is one execution occurrence, however many capability dialogs contributed to its answer. */
|
|
@@ -72,6 +74,9 @@ function mergeGateOutcomes(outcomes: readonly ApprovalOutcome[]): ApprovalOutcom
|
|
|
72
74
|
|
|
73
75
|
export function registerChainTool(pi: ExtensionAPI, session: GrantsSession): void {
|
|
74
76
|
const stepShape = Type.Object({
|
|
77
|
+
definitionId: Type.Optional(
|
|
78
|
+
Type.String({ description: "Snapshot id returned by delegate_describe; required for Principal phases." }),
|
|
79
|
+
),
|
|
75
80
|
task: Type.String({
|
|
76
81
|
description:
|
|
77
82
|
`What this step should do. Write ${PLACEHOLDER} where the previous step's output belongs; if you omit it, ` +
|
|
@@ -102,9 +107,6 @@ export function registerChainTool(pi: ExtensionAPI, session: GrantsSession): voi
|
|
|
102
107
|
maxItems: MAX_CHAIN_STEPS,
|
|
103
108
|
description: "The steps to run IN ORDER. Each one sees the previous one's output.",
|
|
104
109
|
}),
|
|
105
|
-
episodeCostCeiling: Type.Optional(
|
|
106
|
-
Type.Number({ exclusiveMinimum: 0, description: "Override the USD ceiling for this whole episode." }),
|
|
107
|
-
),
|
|
108
110
|
});
|
|
109
111
|
|
|
110
112
|
pi.registerTool({
|
|
@@ -118,9 +120,12 @@ export function registerChainTool(pi: ExtensionAPI, session: GrantsSession): voi
|
|
|
118
120
|
"governed exactly as a single `delegate` is: it holds only what you grant it, and you cannot grant what you " +
|
|
119
121
|
"do not hold. A failed step ABORTS the rest, and you still receive everything that completed.",
|
|
120
122
|
parameters: params,
|
|
123
|
+
prepareArguments: withoutRetiredDelegationArguments,
|
|
121
124
|
async execute(_toolCallId, args, signal, onUpdate, ctx) {
|
|
122
125
|
assertDelegationAuthority(session);
|
|
123
126
|
const steps = args.steps ?? [];
|
|
127
|
+
await session.ensureDefinitions?.();
|
|
128
|
+
for (const step of steps) assertDefinitionIdentity(session, step);
|
|
124
129
|
|
|
125
130
|
// **Every cheap refusal happens before any human is asked.** Yesterday's lesson on the `delegate` path: with
|
|
126
131
|
// `PI_DADDY_HERDR=1` and herdr down, the gate ran first, an operator approved `bash`, a 30-day entry was
|
|
@@ -130,281 +135,293 @@ export function registerChainTool(pi: ExtensionAPI, session: GrantsSession): voi
|
|
|
130
135
|
if (session.executor.refusal)
|
|
131
136
|
throw new GovernanceRefusal(refusal("EXECUTOR_UNAVAILABLE", `chain refused: ${session.executor.refusal}`));
|
|
132
137
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
const split = splitBudget(session.fanoutBudget, steps.length);
|
|
136
|
-
if (!split.ok) throw new GovernanceRefusal(refusal("FANOUT_EXCEEDED", `chain refused: ${split.reason}`));
|
|
137
|
-
|
|
138
|
-
await ensureSessionModelPrompt(
|
|
139
|
-
session,
|
|
140
|
-
steps.flatMap((step) => (step.agent ? [step.agent] : [])),
|
|
141
|
-
{
|
|
142
|
-
hasUI: ctx.hasUI && typeof ctx.ui.input === "function",
|
|
143
|
-
input: (title, placeholder) => ctx.ui.input(title, placeholder),
|
|
144
|
-
notify: (message, type) => ctx.ui.notify(message, type),
|
|
145
|
-
},
|
|
146
|
-
);
|
|
138
|
+
if (steps.length < 1 || steps.length > MAX_CHAIN_STEPS)
|
|
139
|
+
throw new GovernanceRefusal(refusal("FANOUT_EXCEEDED", `chain needs 1 to ${MAX_CHAIN_STEPS} steps`));
|
|
147
140
|
|
|
148
141
|
// Plan every step first. A step that can never run refuses the chain HERE, before anyone is asked — see
|
|
149
142
|
// `planChain`.
|
|
150
143
|
const executionIds = steps.map(() => newExecutionId());
|
|
151
|
-
|
|
152
|
-
const
|
|
153
|
-
|
|
154
|
-
session
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
(
|
|
160
|
-
|
|
161
|
-
definition: step.agent,
|
|
162
|
-
explicit: { model: step.model, thinking: step.thinking },
|
|
163
|
-
session: session.definitionRuntimeOverrides,
|
|
164
|
-
settings: session.definitionRuntimeSettings,
|
|
165
|
-
piModel,
|
|
166
|
-
}).model,
|
|
167
|
-
);
|
|
168
|
-
if (chainPlan.doomed) {
|
|
169
|
-
const message =
|
|
170
|
-
`chain refused at step ${chainPlan.doomed.step}: ${chainPlan.doomed.reason} No step ran, and nobody was ` +
|
|
171
|
-
`asked to approve anything — a step that cannot run must not bank authority for a spawn that will never happen.`;
|
|
172
|
-
await recordChainRefusal({
|
|
173
|
-
session,
|
|
174
|
-
plan: chainPlan.doomed.plan,
|
|
175
|
-
stepIndex: chainPlan.doomed.step - 1,
|
|
176
|
-
executionId: executionIds[chainPlan.doomed.step - 1],
|
|
177
|
-
parentExecutionId,
|
|
178
|
-
agent: chainPlan.doomed.agent,
|
|
179
|
-
reason: message,
|
|
180
|
-
refusal: chainPlan.doomed.refusal,
|
|
181
|
-
});
|
|
182
|
-
if (chainPlan.doomed.refusal) throw new GovernanceRefusal({ ...chainPlan.doomed.refusal, message });
|
|
183
|
-
throw new Error(message);
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
// One dialog per `capability@subject`, each naming the step that needs it, all before the first step runs.
|
|
187
|
-
const preApproved: InheritableApproval[] = [];
|
|
188
|
-
const approvalAudit = newChainApprovalAudit();
|
|
189
|
-
const approvalStep = new Map<string, number>();
|
|
190
|
-
const approvedDecisions: Array<{ request: GateRequest; outcome: Awaited<ReturnType<typeof obtainApprovals>> }> =
|
|
191
|
-
[];
|
|
192
|
-
let declined: { request: GateRequest; outcome: Awaited<ReturnType<typeof obtainApprovals>> } | undefined;
|
|
193
|
-
|
|
194
|
-
for (const request of chainPlan.requests) {
|
|
195
|
-
const outcome = await obtainApprovals(
|
|
144
|
+
await reconcileDelegationCapacity(session);
|
|
145
|
+
const firstReservation = reserveDelegationCapacity(session, executionIds[0]);
|
|
146
|
+
try {
|
|
147
|
+
const parentExecutionId = session.ownExecutionId ?? null;
|
|
148
|
+
const piModel = ctx.model ? `${ctx.model.provider}/${ctx.model.id}` : undefined;
|
|
149
|
+
const runtimeChoices = new Map<
|
|
150
|
+
import("../src/kernel/chain.ts").ChainStep,
|
|
151
|
+
import("./definition-runtime.ts").ResolvedDefinitionRuntime
|
|
152
|
+
>();
|
|
153
|
+
const chainPlan = await planChain(
|
|
196
154
|
session,
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
155
|
+
steps,
|
|
156
|
+
executionIds,
|
|
157
|
+
(model) =>
|
|
158
|
+
preflightModel(model, ctx.modelRegistry, session.modelResolutionCache, session.allowUnresolvedModels),
|
|
159
|
+
(step) => {
|
|
160
|
+
const selected = resolveDefinitionRuntime({
|
|
161
|
+
definition: step.agent,
|
|
162
|
+
explicit: { model: step.model, thinking: step.thinking },
|
|
163
|
+
session: session.definitionRuntimeOverrides,
|
|
164
|
+
settings: session.definitionRuntimeSettings,
|
|
165
|
+
piModel,
|
|
166
|
+
piThinking: session.currentThinking?.(),
|
|
167
|
+
authoredPreferences: step.agent ? session.definitions.get(step.agent)?.runtimePreferences : undefined,
|
|
168
|
+
unavailable: (choice) => runtimePairUnavailable(choice, ctx.modelRegistry, session.allowUnresolvedModels),
|
|
169
|
+
});
|
|
170
|
+
runtimeChoices.set(step, selected);
|
|
171
|
+
return selected.model;
|
|
172
|
+
},
|
|
203
173
|
);
|
|
204
|
-
if (
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
declined = { request, outcome };
|
|
209
|
-
break;
|
|
210
|
-
}
|
|
211
|
-
preApproved.push({
|
|
212
|
-
capability: request.capability,
|
|
213
|
-
subject: request.subject,
|
|
214
|
-
scope: outcome.scopes[request.capability] ?? ("once" as const),
|
|
215
|
-
// Pinned to THIS subject's body (ADR-0022). Stamped from a single shared subject, the pin was verified
|
|
216
|
-
// against one definition's instructions while the capability was spent on another's.
|
|
217
|
-
bodySha256: snapshotOf(session, request.subject)?.bodySha256,
|
|
218
|
-
});
|
|
219
|
-
rememberChainApproval(approvalAudit, request.capability, request.subject, outcome);
|
|
220
|
-
approvalStep.set(`${request.capability}@${request.subject}`, request.stepIndex);
|
|
221
|
-
approvedDecisions.push({ request, outcome });
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
if (declined) {
|
|
225
|
-
const { request, outcome } = declined;
|
|
226
|
-
const message =
|
|
227
|
-
`chain refused: ${request.capability} was not approved for ${request.subject}, so no step ran. A chain ` +
|
|
228
|
-
`is gated as a unit — running only its approved steps would return a partial result that reads like a complete one.`;
|
|
229
|
-
const structured = outcome.refusalCode ? refusal(outcome.refusalCode, message) : undefined;
|
|
230
|
-
const decisions = new Map<
|
|
231
|
-
number,
|
|
232
|
-
{ request: GateRequest; outcomes: ApprovalOutcome[]; refusal?: typeof structured }
|
|
233
|
-
>();
|
|
234
|
-
for (const approved of approvedDecisions) {
|
|
235
|
-
const decision = decisions.get(approved.request.stepIndex) ?? {
|
|
236
|
-
request: approved.request,
|
|
237
|
-
outcomes: [],
|
|
238
|
-
};
|
|
239
|
-
decision.outcomes.push(approved.outcome);
|
|
240
|
-
decisions.set(approved.request.stepIndex, decision);
|
|
241
|
-
}
|
|
242
|
-
const deniedDecision = decisions.get(request.stepIndex) ?? { request, outcomes: [] };
|
|
243
|
-
deniedDecision.request = request;
|
|
244
|
-
deniedDecision.outcomes.push(outcome);
|
|
245
|
-
deniedDecision.refusal = structured;
|
|
246
|
-
decisions.set(request.stepIndex, deniedDecision);
|
|
247
|
-
|
|
248
|
-
for (const [stepIndex, decision] of [...decisions].sort(([left], [right]) => left - right)) {
|
|
174
|
+
if (chainPlan.doomed) {
|
|
175
|
+
const message =
|
|
176
|
+
`chain refused at step ${chainPlan.doomed.step}: ${chainPlan.doomed.reason} No step ran, and nobody was ` +
|
|
177
|
+
`asked to approve anything — a step that cannot run must not bank authority for a spawn that will never happen.`;
|
|
249
178
|
await recordChainRefusal({
|
|
250
179
|
session,
|
|
251
|
-
plan:
|
|
252
|
-
stepIndex,
|
|
253
|
-
executionId: executionIds[
|
|
180
|
+
plan: chainPlan.doomed.plan,
|
|
181
|
+
stepIndex: chainPlan.doomed.step - 1,
|
|
182
|
+
executionId: executionIds[chainPlan.doomed.step - 1],
|
|
254
183
|
parentExecutionId,
|
|
255
|
-
agent:
|
|
184
|
+
agent: chainPlan.doomed.agent,
|
|
256
185
|
reason: message,
|
|
257
|
-
refusal:
|
|
258
|
-
approval: mergeGateOutcomes(decision.outcomes),
|
|
186
|
+
refusal: chainPlan.doomed.refusal,
|
|
259
187
|
});
|
|
188
|
+
if (chainPlan.doomed.refusal) throw new GovernanceRefusal({ ...chainPlan.doomed.refusal, message });
|
|
189
|
+
throw new Error(message);
|
|
260
190
|
}
|
|
261
|
-
if (structured) throw new GovernanceRefusal(structured);
|
|
262
|
-
throw new Error(message);
|
|
263
|
-
}
|
|
264
191
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
192
|
+
// One dialog per `capability@subject`, each naming the step that needs it, all before the first step runs.
|
|
193
|
+
const preApproved: InheritableApproval[] = [];
|
|
194
|
+
const approvalAudit = newChainApprovalAudit();
|
|
195
|
+
const approvalStep = new Map<string, number>();
|
|
196
|
+
const approvedDecisions: Array<{ request: GateRequest; outcome: Awaited<ReturnType<typeof obtainApprovals>> }> =
|
|
197
|
+
[];
|
|
198
|
+
let declined: { request: GateRequest; outcome: Awaited<ReturnType<typeof obtainApprovals>> } | undefined;
|
|
199
|
+
|
|
200
|
+
for (const request of chainPlan.requests) {
|
|
201
|
+
const outcome = await obtainApprovals(
|
|
202
|
+
session,
|
|
203
|
+
[request.capability],
|
|
204
|
+
request.subject,
|
|
205
|
+
request.path,
|
|
206
|
+
ctx,
|
|
207
|
+
request.task,
|
|
208
|
+
signal,
|
|
209
|
+
);
|
|
210
|
+
if (!outcome.approved.includes(request.capability)) {
|
|
211
|
+
// **Stop asking.** The chain's outcome is already fixed, and every further dialog banks authority — a
|
|
212
|
+
// `session` yes into `sessionApprovals` and an `always` yes onto disk for 30 days — for a chain that will
|
|
213
|
+
// not run. The single-delegate path breaks on the first no for the same reason.
|
|
214
|
+
declined = { request, outcome };
|
|
215
|
+
break;
|
|
216
|
+
}
|
|
217
|
+
preApproved.push({
|
|
218
|
+
capability: request.capability,
|
|
219
|
+
subject: request.subject,
|
|
220
|
+
scope: outcome.scopes[request.capability] ?? ("once" as const),
|
|
221
|
+
// Pinned to THIS subject's body (ADR-0022). Stamped from a single shared subject, the pin was verified
|
|
222
|
+
// against one definition's instructions while the capability was spent on another's.
|
|
223
|
+
bodySha256: snapshotOf(session, request.subject)?.bodySha256,
|
|
275
224
|
});
|
|
276
|
-
|
|
277
|
-
|
|
225
|
+
rememberChainApproval(approvalAudit, request.capability, request.subject, outcome);
|
|
226
|
+
approvalStep.set(`${request.capability}@${request.subject}`, request.stepIndex);
|
|
227
|
+
approvedDecisions.push({ request, outcome });
|
|
278
228
|
}
|
|
279
|
-
}, PAINT_INTERVAL_MS);
|
|
280
229
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
230
|
+
if (declined) {
|
|
231
|
+
const { request, outcome } = declined;
|
|
232
|
+
const message =
|
|
233
|
+
`chain refused: ${request.capability} was not approved for ${request.subject}, so no step ran. A chain ` +
|
|
234
|
+
`is gated as a unit — running only its approved steps would return a partial result that reads like a complete one.`;
|
|
235
|
+
const structured = outcome.refusalCode ? refusal(outcome.refusalCode, message) : undefined;
|
|
236
|
+
const decisions = new Map<
|
|
237
|
+
number,
|
|
238
|
+
{ request: GateRequest; outcomes: ApprovalOutcome[]; refusal?: typeof structured }
|
|
239
|
+
>();
|
|
240
|
+
for (const approved of approvedDecisions) {
|
|
241
|
+
const decision = decisions.get(approved.request.stepIndex) ?? {
|
|
242
|
+
request: approved.request,
|
|
243
|
+
outcomes: [],
|
|
244
|
+
};
|
|
245
|
+
decision.outcomes.push(approved.outcome);
|
|
246
|
+
decisions.set(approved.request.stepIndex, decision);
|
|
247
|
+
}
|
|
248
|
+
const deniedDecision = decisions.get(request.stepIndex) ?? { request, outcomes: [] };
|
|
249
|
+
deniedDecision.request = request;
|
|
250
|
+
deniedDecision.outcomes.push(outcome);
|
|
251
|
+
deniedDecision.refusal = structured;
|
|
252
|
+
decisions.set(request.stepIndex, deniedDecision);
|
|
304
253
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
254
|
+
for (const [stepIndex, decision] of [...decisions].sort(([left], [right]) => left - right)) {
|
|
255
|
+
await recordChainRefusal({
|
|
256
|
+
session,
|
|
257
|
+
plan: decision.request.plan,
|
|
258
|
+
stepIndex,
|
|
259
|
+
executionId: executionIds[stepIndex],
|
|
260
|
+
parentExecutionId,
|
|
261
|
+
agent: steps[stepIndex]?.agent,
|
|
262
|
+
reason: message,
|
|
263
|
+
refusal: decision.refusal,
|
|
264
|
+
approval: mergeGateOutcomes(decision.outcomes),
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
if (structured) throw new GovernanceRefusal(structured);
|
|
268
|
+
throw new Error(message);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const children: ChildProgress[] = steps.map((step) => ({
|
|
272
|
+
label: step.agent ?? "delegate",
|
|
273
|
+
state: "starting",
|
|
274
|
+
startedAt: Date.now(),
|
|
275
|
+
tail: emptyTail,
|
|
276
|
+
}));
|
|
277
|
+
const paint = throttle(() => {
|
|
278
|
+
try {
|
|
279
|
+
(onUpdate as ((partial: { content: Array<{ type: "text"; text: string }> }) => void) | undefined)?.({
|
|
280
|
+
content: [{ type: "text", text: renderProgress(children, session.executor.kind, Date.now()) }],
|
|
281
|
+
});
|
|
282
|
+
} catch {
|
|
283
|
+
// Display only: a broken partial-result sink must not become a child cancellation mechanism.
|
|
284
|
+
}
|
|
285
|
+
}, PAINT_INTERVAL_MS);
|
|
286
|
+
|
|
287
|
+
const outcomes: Array<import("./execute-child.ts").DelegationOutcome & { step: number; agent?: string }> = [];
|
|
288
|
+
let previous: string | undefined;
|
|
289
|
+
let aborted = false;
|
|
290
|
+
let blockedReason: "full-final-too-large-for-handoff" | undefined;
|
|
291
|
+
/**
|
|
292
|
+
* Approvals still available to later steps.
|
|
293
|
+
*
|
|
294
|
+
* **`once` is consumed by the first step that spends it, and not honouring that was a confused deputy.**
|
|
295
|
+
* Measured: three steps of one definition, one dialog naming step 1's task — *"survey the north field"* — and
|
|
296
|
+
* three children spawned, the last of which had been told *"…and burn the evidence"*. Steps 2 and 3 were never
|
|
297
|
+
* described to anyone. Two sequential plain `delegate` calls raise two dialogs, because a `once` answer never
|
|
298
|
+
* enters `sessionApprovals`; the chain was the outlier. R-29 exists for this exact shape one level down.
|
|
299
|
+
*
|
|
300
|
+
* A later step needing the same capability now reaches its own gate and prompts with its OWN task, which is
|
|
301
|
+
* what `once` means.
|
|
302
|
+
*/
|
|
303
|
+
let available = [...preApproved];
|
|
304
|
+
|
|
305
|
+
for (const [index, step] of steps.entries()) {
|
|
306
|
+
const childId = childSpawnId(session.ownSpawnId, index);
|
|
307
|
+
const availableForStep = available.filter((approval) => {
|
|
308
|
+
const key = `${approval.capability}@${approval.subject}`;
|
|
309
|
+
return (
|
|
310
|
+
chainPlan.uses.get(index)?.has(key) && (approval.scope !== "once" || approvalStep.get(key) === index)
|
|
311
|
+
);
|
|
312
|
+
});
|
|
313
|
+
const outcome = await runOneDelegation(
|
|
314
|
+
session,
|
|
315
|
+
chainStepSpec(step, previous),
|
|
316
|
+
{
|
|
317
|
+
parentId: session.ownSpawnId,
|
|
318
|
+
childId,
|
|
319
|
+
executionId: executionIds[index],
|
|
320
|
+
parentExecutionId,
|
|
339
321
|
},
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
322
|
+
index === 0 ? firstReservation.childAllowance : undefined,
|
|
323
|
+
ctx,
|
|
324
|
+
signal,
|
|
325
|
+
{
|
|
326
|
+
...(index === 0 ? { capacityReservation: firstReservation } : {}),
|
|
327
|
+
resolvedRuntime: runtimeChoices.get(step),
|
|
328
|
+
preApproved: availableForStep,
|
|
329
|
+
toolCallId: _toolCallId,
|
|
330
|
+
// Only approvals actually offered to this step are attributed or consumed here.
|
|
331
|
+
approvalFacts: chainApprovalFacts(approvalAudit, availableForStep, step.agent ?? DELEGATE_SUBJECT),
|
|
332
|
+
onProgress: (update) => {
|
|
333
|
+
const child = children[index];
|
|
334
|
+
if (!child) return;
|
|
335
|
+
if (update.paneId) child.paneId = update.paneId;
|
|
336
|
+
if (update.agentName) child.agentName = update.agentName;
|
|
337
|
+
if (update.chunk) child.tail = appendTail(child.tail, update.chunk);
|
|
338
|
+
if (update.snapshot) child.tail = replaceTail(update.snapshot);
|
|
339
|
+
if (update.state) child.state = update.state;
|
|
340
|
+
else if (child.state === "starting") child.state = "running";
|
|
341
|
+
paint.call();
|
|
342
|
+
},
|
|
343
|
+
// Provenance: which child's output composed THIS step's task (ADR-0033). Absent for step 1.
|
|
344
|
+
taskFrom: index === 0 ? undefined : childSpawnId(session.ownSpawnId, index - 1),
|
|
345
|
+
taskFromExecutionId: index === 0 ? undefined : executionIds[index - 1],
|
|
346
|
+
},
|
|
347
|
+
);
|
|
345
348
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
agent: step.agent,
|
|
355
|
-
});
|
|
356
|
-
if (isCriticalAssuranceBlock(outcome)) throw new Error(outcome.text);
|
|
349
|
+
children[index].state = outcome.ok ? "completed" : "failed";
|
|
350
|
+
children[index].settledAt = Date.now();
|
|
351
|
+
outcomes.push({
|
|
352
|
+
...outcome,
|
|
353
|
+
step: index + 1,
|
|
354
|
+
agent: step.agent,
|
|
355
|
+
});
|
|
356
|
+
if (isCriticalAssuranceBlock(outcome)) throw new Error(outcome.text);
|
|
357
357
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
358
|
+
// Spend any `once` this step was handed, before the next step sees the list.
|
|
359
|
+
available = available.filter(
|
|
360
|
+
(approval) =>
|
|
361
|
+
approval.scope !== "once" || approvalStep.get(`${approval.capability}@${approval.subject}`) !== index,
|
|
362
|
+
);
|
|
363
363
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
364
|
+
if (!outcome.ok || outcome.control === "failed" || outcome.final?.state === "unavailable") {
|
|
365
|
+
// **Abort, and mark the rest.** Continuing would make the next step's task an error message, which is
|
|
366
|
+
// never what an orchestrator wants. Everything completed is still returned — R-03's rule.
|
|
367
|
+
aborted = true;
|
|
368
|
+
for (let rest = index + 1; rest < children.length; rest += 1) children[rest].state = "failed";
|
|
369
|
+
break;
|
|
370
|
+
}
|
|
371
|
+
if (index + 1 < steps.length && Buffer.byteLength(outcome.text, "utf8") > HANDOFF_MAX_BYTES) {
|
|
372
|
+
aborted = true;
|
|
373
|
+
blockedReason = "full-final-too-large-for-handoff";
|
|
374
|
+
for (let rest = index + 1; rest < children.length; rest += 1) children[rest].state = "failed";
|
|
375
|
+
break;
|
|
376
|
+
}
|
|
377
|
+
previous = outcome.text;
|
|
370
378
|
}
|
|
371
|
-
|
|
372
|
-
}
|
|
373
|
-
paint.flush();
|
|
379
|
+
paint.flush();
|
|
374
380
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
381
|
+
const report = outcomes
|
|
382
|
+
.map((o) => {
|
|
383
|
+
const label = `### step ${o.step}${o.agent ? ` (${o.agent})` : ""}`;
|
|
384
|
+
return o.ok && o.control !== "failed"
|
|
385
|
+
? `${label} — completed\n\n${o.text || "(no output)"}`
|
|
386
|
+
: `${label} — FAILED: ${o.reason}${o.text ? `\n\n${o.text}` : ""}`;
|
|
387
|
+
})
|
|
388
|
+
.join("\n\n---\n\n");
|
|
383
389
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
390
|
+
const skipped = steps.length - outcomes.length;
|
|
391
|
+
const tail = aborted
|
|
392
|
+
? `\n\n---\n\n**The chain stopped at step ${outcomes.length}.** ${skipped} later step(s) did not run, ` +
|
|
393
|
+
(blockedReason
|
|
394
|
+
? `because the complete final exceeds the ${HANDOFF_MAX_BYTES}-byte handoff limit; it is preserved above and no partial handoff was used.`
|
|
395
|
+
: `because the previous step did not supply a qualified successful final for the next task.`)
|
|
396
|
+
: "";
|
|
389
397
|
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
398
|
+
if (
|
|
399
|
+
outcomes.length === 1 &&
|
|
400
|
+
!outcomes[0].ok &&
|
|
401
|
+
!outcomes[0].work &&
|
|
402
|
+
!outcomes[0].final &&
|
|
403
|
+
!outcomes[0].cleanup
|
|
404
|
+
) {
|
|
405
|
+
const message = `chain failed at its first step.\n\n${report}`;
|
|
406
|
+
if (outcomes[0].refusal) throw new GovernanceRefusal({ ...outcomes[0].refusal, message });
|
|
407
|
+
throw new Error(message);
|
|
408
|
+
}
|
|
409
|
+
return {
|
|
410
|
+
isError: aborted || outcomes.some((o) => o.control === "failed"),
|
|
411
|
+
content: [{ type: "text", text: `${report}${tail}` }],
|
|
412
|
+
details: {
|
|
413
|
+
outcomes,
|
|
414
|
+
steps: steps.length,
|
|
415
|
+
completed: outcomes.filter((o) => o.ok).length,
|
|
416
|
+
aborted,
|
|
417
|
+
budgetPerStep: firstReservation.childAllowance,
|
|
418
|
+
...(blockedReason ? { blockedReason } : {}),
|
|
419
|
+
refusals: outcomes.map((outcome) => outcome.refusal ?? null),
|
|
420
|
+
},
|
|
421
|
+
};
|
|
422
|
+
} finally {
|
|
423
|
+
firstReservation.finalize({ state: "not-started", reason: "chain ended before its first execution" });
|
|
396
424
|
}
|
|
397
|
-
|
|
398
|
-
return {
|
|
399
|
-
content: [{ type: "text", text: `${report}${tail}` }],
|
|
400
|
-
details: {
|
|
401
|
-
steps: steps.length,
|
|
402
|
-
completed: outcomes.filter((o) => o.ok).length,
|
|
403
|
-
aborted,
|
|
404
|
-
budgetPerStep: split.perChild,
|
|
405
|
-
refusals: outcomes.map((outcome) => outcome.refusal ?? null),
|
|
406
|
-
},
|
|
407
|
-
};
|
|
408
425
|
},
|
|
409
426
|
});
|
|
410
427
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import type { GrantsSession } from "./session.ts";
|
|
3
3
|
|
|
4
|
-
const DELEGATION_TOOL_NAMES = ["delegate", "delegate_all", "delegate_chain"];
|
|
4
|
+
const DELEGATION_TOOL_NAMES = ["delegate", "delegate_all", "delegate_chain", "delegate_describe"];
|
|
5
5
|
|
|
6
6
|
function setDelegationActive(pi: ExtensionAPI, enabled: boolean): void {
|
|
7
7
|
const surface = pi as ExtensionAPI & { getActiveTools?: () => string[]; setActiveTools?: (names: string[]) => void };
|