pi-daddy 0.41.1 → 0.43.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 +31 -0
- package/contracts/ledger-record/v1/fixtures/capability-decision.json +3 -1
- package/contracts/ledger-record/v1/fixtures/child-lifecycle.json +25 -0
- package/contracts/ledger-record/v1/fixtures/episode-cost-gate.json +14 -0
- package/contracts/ledger-record/v1/fixtures/episode-outcome.json +12 -0
- package/contracts/ledger-record/v1/fixtures/ledger-record.jsonl +6 -3
- package/contracts/ledger-record/v1/fixtures/session-config.json +14 -0
- package/contracts/ledger-record/v1/governance-event.schema.json +356 -0
- package/dist/advisors/advisor.d.ts +24 -1
- package/dist/advisors/advisor.d.ts.map +1 -1
- package/dist/advisors/advisor.js +19 -1
- package/dist/advisors/advisor.js.map +1 -1
- package/dist/advisors/decider.d.ts +11 -0
- package/dist/advisors/decider.d.ts.map +1 -1
- package/dist/advisors/decider.js.map +1 -1
- package/dist/advisors/jev.d.ts.map +1 -1
- package/dist/advisors/jev.js +32 -1
- package/dist/advisors/jev.js.map +1 -1
- package/dist/cli.d.ts +7 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +87 -1
- package/dist/cli.js.map +1 -1
- package/dist/executors/activity-session.d.ts +13 -0
- package/dist/executors/activity-session.d.ts.map +1 -1
- package/dist/executors/activity-session.js +44 -2
- package/dist/executors/activity-session.js.map +1 -1
- package/dist/governance/episode-cost-gate.d.ts +31 -0
- package/dist/governance/episode-cost-gate.d.ts.map +1 -0
- package/dist/governance/episode-cost-gate.js +112 -0
- package/dist/governance/episode-cost-gate.js.map +1 -0
- package/dist/governance/ledger-events.d.ts +108 -1
- package/dist/governance/ledger-events.d.ts.map +1 -1
- package/dist/governance/ledger-events.js +64 -0
- package/dist/governance/ledger-events.js.map +1 -1
- package/dist/governance/ledger-v3-validation.d.ts.map +1 -1
- package/dist/governance/ledger-v3-validation.js +124 -0
- package/dist/governance/ledger-v3-validation.js.map +1 -1
- package/dist/governance/ledger.d.ts +8 -2
- package/dist/governance/ledger.d.ts.map +1 -1
- package/dist/governance/ledger.js +18 -2
- package/dist/governance/ledger.js.map +1 -1
- package/dist/kernel/definitions.d.ts +5 -0
- package/dist/kernel/definitions.d.ts.map +1 -1
- package/dist/kernel/definitions.js +30 -1
- package/dist/kernel/definitions.js.map +1 -1
- package/dist/kernel/delegate-types.d.ts +4 -0
- package/dist/kernel/delegate-types.d.ts.map +1 -1
- package/dist/kernel/delegate-types.js.map +1 -1
- package/dist/kernel/delegate.d.ts.map +1 -1
- package/dist/kernel/delegate.js +20 -5
- package/dist/kernel/delegate.js.map +1 -1
- package/dist/kernel/env-names.d.ts +5 -0
- package/dist/kernel/env-names.d.ts.map +1 -1
- package/dist/kernel/env-names.js +9 -0
- package/dist/kernel/env-names.js.map +1 -1
- package/dist/kernel/grant-env.d.ts +2 -0
- package/dist/kernel/grant-env.d.ts.map +1 -1
- package/dist/kernel/grant-env.js +1 -0
- package/dist/kernel/grant-env.js.map +1 -1
- package/dist/kernel/propagation.d.ts +2 -2
- package/dist/kernel/propagation.d.ts.map +1 -1
- package/dist/kernel/propagation.js +7 -2
- package/dist/kernel/propagation.js.map +1 -1
- package/dist/products/dashboard-cli.d.ts +4 -0
- package/dist/products/dashboard-cli.d.ts.map +1 -1
- package/dist/products/dashboard-cli.js +32 -4
- package/dist/products/dashboard-cli.js.map +1 -1
- package/dist/products/dashboard-handshake.d.ts +8 -0
- package/dist/products/dashboard-handshake.d.ts.map +1 -1
- package/dist/products/dashboard-handshake.js +3 -0
- package/dist/products/dashboard-handshake.js.map +1 -1
- package/dist/products/dashboard-herdr.d.ts +4 -0
- package/dist/products/dashboard-herdr.d.ts.map +1 -1
- package/dist/products/dashboard-herdr.js +10 -2
- package/dist/products/dashboard-herdr.js.map +1 -1
- package/dist/products/dashboard-projection.d.ts.map +1 -1
- package/dist/products/dashboard-projection.js +13 -6
- package/dist/products/dashboard-projection.js.map +1 -1
- package/dist/products/dashboard-render.d.ts +10 -0
- package/dist/products/dashboard-render.d.ts.map +1 -1
- package/dist/products/dashboard-render.js +9 -0
- package/dist/products/dashboard-render.js.map +1 -1
- package/dist/products/dashboard-session-client.d.ts +17 -0
- package/dist/products/dashboard-session-client.d.ts.map +1 -0
- package/dist/products/dashboard-session-client.js +28 -0
- package/dist/products/dashboard-session-client.js.map +1 -0
- package/dist/products/episode-outcomes.d.ts +32 -0
- package/dist/products/episode-outcomes.d.ts.map +1 -0
- package/dist/products/episode-outcomes.js +280 -0
- package/dist/products/episode-outcomes.js.map +1 -0
- package/dist/products/episode-report.d.ts +59 -0
- package/dist/products/episode-report.d.ts.map +1 -0
- package/dist/products/episode-report.js +311 -0
- package/dist/products/episode-report.js.map +1 -0
- package/extensions/chain-ledger.ts +2 -0
- package/extensions/chain-plan.ts +4 -2
- package/extensions/dashboard-session-server.ts +86 -0
- package/extensions/definition-runtime.ts +93 -0
- package/extensions/delegate-chain.ts +31 -2
- package/extensions/delegation-ledger.ts +2 -0
- package/extensions/delegation.ts +24 -0
- package/extensions/effort-advice.ts +12 -0
- package/extensions/execute-child.ts +148 -9
- package/extensions/grants-command.ts +32 -2
- package/extensions/grants.ts +23 -1
- package/extensions/pruning-advice.ts +3 -1
- package/extensions/run-delegation.ts +54 -6
- package/extensions/session-model-prompt.ts +179 -0
- package/extensions/session.ts +104 -3
- package/package.json +1 -1
- package/src/advisors/advisor.ts +45 -2
- package/src/advisors/decider.ts +8 -0
- package/src/advisors/jev.ts +34 -1
- package/src/cli.ts +90 -2
- package/src/executors/activity-session.ts +56 -2
- package/src/governance/episode-cost-gate.ts +128 -0
- package/src/governance/ledger-events.ts +173 -1
- package/src/governance/ledger-v3-validation.ts +146 -0
- package/src/governance/ledger.ts +29 -1
- package/src/kernel/definitions.ts +32 -1
- package/src/kernel/delegate-types.ts +4 -0
- package/src/kernel/delegate.ts +22 -5
- package/src/kernel/env-names.ts +10 -0
- package/src/kernel/grant-env.ts +3 -0
- package/src/kernel/propagation.ts +7 -0
- package/src/products/dashboard-cli.ts +32 -4
- package/src/products/dashboard-handshake.ts +5 -0
- package/src/products/dashboard-herdr.ts +15 -1
- package/src/products/dashboard-projection.ts +13 -6
- package/src/products/dashboard-render.ts +11 -0
- package/src/products/dashboard-session-client.ts +35 -0
- package/src/products/episode-outcomes.ts +317 -0
- package/src/products/episode-report.ts +372 -0
package/extensions/delegation.ts
CHANGED
|
@@ -37,6 +37,7 @@ import { newDelegationOccurrence } from "./execution-occurrence.ts";
|
|
|
37
37
|
import { correlationShape as buildCorrelationShape } from "./correlation-shape.ts";
|
|
38
38
|
import { assertDelegationAuthority } from "./delegation-authority.ts";
|
|
39
39
|
import { contextShape } from "./context-shape.ts";
|
|
40
|
+
import { ensureSessionModelPrompt } from "./session-model-prompt.ts";
|
|
40
41
|
|
|
41
42
|
/**
|
|
42
43
|
* Wire a set of children to pi's partial-result channel — ADR-0032.
|
|
@@ -201,6 +202,9 @@ export function registerDelegationTools(pi: ExtensionAPI, session: GrantsSession
|
|
|
201
202
|
maxItems: MAX_CHILDREN_PER_CALL,
|
|
202
203
|
description: "The sub-agents to run concurrently. Each is independent and unaware of the others.",
|
|
203
204
|
}),
|
|
205
|
+
episodeCostCeiling: Type.Optional(
|
|
206
|
+
Type.Number({ exclusiveMinimum: 0, description: "Override the USD ceiling for this whole episode." }),
|
|
207
|
+
),
|
|
204
208
|
});
|
|
205
209
|
|
|
206
210
|
const delegateParams = Type.Object({
|
|
@@ -223,6 +227,9 @@ export function registerDelegationTools(pi: ExtensionAPI, session: GrantsSession
|
|
|
223
227
|
}),
|
|
224
228
|
),
|
|
225
229
|
thinking: thinkingShape,
|
|
230
|
+
episodeCostCeiling: Type.Optional(
|
|
231
|
+
Type.Number({ exclusiveMinimum: 0, description: "Override the USD ceiling for this whole episode." }),
|
|
232
|
+
),
|
|
226
233
|
context: Type.Optional(contextShape()),
|
|
227
234
|
correlation: Type.Optional(correlationShape),
|
|
228
235
|
workspace: Type.Optional(workspaceShape),
|
|
@@ -239,6 +246,11 @@ export function registerDelegationTools(pi: ExtensionAPI, session: GrantsSession
|
|
|
239
246
|
parameters: delegateParams,
|
|
240
247
|
async execute(_toolCallId, params, signal, onUpdate, ctx) {
|
|
241
248
|
assertDelegationAuthority(session);
|
|
249
|
+
await ensureSessionModelPrompt(session, params.agent ? [params.agent] : [], {
|
|
250
|
+
hasUI: ctx.hasUI && typeof ctx.ui.input === "function",
|
|
251
|
+
input: (title, placeholder) => ctx.ui.input(title, placeholder),
|
|
252
|
+
notify: (message, type) => ctx.ui.notify(message, type),
|
|
253
|
+
});
|
|
242
254
|
// ADR-0032: one child, same block. `_onUpdate` was discarded here, so a delegation showed the bare word
|
|
243
255
|
// `delegate` for up to DEFAULT_TIMEOUT_MS — sixty minutes by default.
|
|
244
256
|
const progress = progressReporter(session, [params.agent ?? "delegate"], onUpdate as never);
|
|
@@ -250,6 +262,7 @@ export function registerDelegationTools(pi: ExtensionAPI, session: GrantsSession
|
|
|
250
262
|
tools: params.tools,
|
|
251
263
|
model: params.model,
|
|
252
264
|
thinking: params.thinking,
|
|
265
|
+
episodeCostCeiling: params.episodeCostCeiling,
|
|
253
266
|
context: params.context,
|
|
254
267
|
correlation: params.correlation,
|
|
255
268
|
workspace: params.workspace,
|
|
@@ -318,6 +331,15 @@ export function registerDelegationTools(pi: ExtensionAPI, session: GrantsSession
|
|
|
318
331
|
async execute(_toolCallId, params, signal, onUpdate, ctx) {
|
|
319
332
|
assertDelegationAuthority(session);
|
|
320
333
|
const children = params.children ?? [];
|
|
334
|
+
await ensureSessionModelPrompt(
|
|
335
|
+
session,
|
|
336
|
+
children.flatMap((child) => (child.agent ? [child.agent] : [])),
|
|
337
|
+
{
|
|
338
|
+
hasUI: ctx.hasUI && typeof ctx.ui.input === "function",
|
|
339
|
+
input: (title, placeholder) => ctx.ui.input(title, placeholder),
|
|
340
|
+
notify: (message, type) => ctx.ui.notify(message, type),
|
|
341
|
+
},
|
|
342
|
+
);
|
|
321
343
|
const split = splitBudget(session.fanoutBudget, children.length);
|
|
322
344
|
if (!split.ok) {
|
|
323
345
|
// Thrown, not returned: a returned `isError` is discarded by pi, so a refusal that came back as a
|
|
@@ -325,6 +347,8 @@ export function registerDelegationTools(pi: ExtensionAPI, session: GrantsSession
|
|
|
325
347
|
throw new GovernanceRefusal(refusal("FANOUT_EXCEEDED", `fan-out refused: ${split.reason}`));
|
|
326
348
|
}
|
|
327
349
|
|
|
350
|
+
if (params.episodeCostCeiling !== undefined) session.episodeCostGate.setCeiling(params.episodeCostCeiling);
|
|
351
|
+
|
|
328
352
|
// ADR-0032: ONE status block covering every child. `onUpdate` replaces the tool's rendered result, so a
|
|
329
353
|
// painter per child would have each overwriting the others.
|
|
330
354
|
const progress = progressReporter(
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import { supportedModelEfforts } from "../src/kernel/model-preflight.ts";
|
|
16
16
|
import type { Advisor } from "../src/advisors/advisor.ts";
|
|
17
|
+
import { resolvedModelOf, type ResolvedDefinitionRuntime } from "./definition-runtime.ts";
|
|
17
18
|
|
|
18
19
|
/** What pi's resolved catalogue entry carries that decides which efforts exist. */
|
|
19
20
|
interface ResolvedModel {
|
|
@@ -40,6 +41,7 @@ export async function adviseEffort(input: {
|
|
|
40
41
|
task: string;
|
|
41
42
|
executionId?: string;
|
|
42
43
|
agent?: string;
|
|
44
|
+
fallback?: ResolvedDefinitionRuntime;
|
|
43
45
|
signal?: AbortSignal;
|
|
44
46
|
}): Promise<string | undefined> {
|
|
45
47
|
const advisor = input.session.advisorSession.advisor;
|
|
@@ -71,6 +73,16 @@ export async function adviseEffort(input: {
|
|
|
71
73
|
},
|
|
72
74
|
input.signal,
|
|
73
75
|
input.executionId,
|
|
76
|
+
(result) => {
|
|
77
|
+
const answer = result?.answers.effort;
|
|
78
|
+
const advised = answer?.kind === "choice" && (levels as readonly string[]).includes(answer.value);
|
|
79
|
+
return {
|
|
80
|
+
resolvedModel: resolvedModelOf(input.fallback?.model ?? input.model),
|
|
81
|
+
modelSource: input.fallback?.modelSource ?? "pi",
|
|
82
|
+
thinkingLevel: advised ? answer.value : (input.fallback?.thinking ?? null),
|
|
83
|
+
thinkingSource: advised ? "advisor" : (input.fallback?.thinkingSource ?? "pi"),
|
|
84
|
+
};
|
|
85
|
+
},
|
|
74
86
|
);
|
|
75
87
|
const chosen = advice?.answers.effort;
|
|
76
88
|
// Belt and braces: `parseAnswer` already refuses a choice outside the options it was given, so this can only
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
retentionConfigurationDigest,
|
|
5
5
|
type RetentionStatus,
|
|
6
6
|
} from "../src/governance/execution-retention.ts";
|
|
7
|
-
import { appendLedgerEvent, buildChildLifecycleEvent } from "../src/governance/ledger.ts";
|
|
7
|
+
import { appendLedgerEvent, buildChildLifecycleEvent, buildEpisodeCostGateEvent } from "../src/governance/ledger.ts";
|
|
8
8
|
import { hasFinalizerError } from "../src/governance/finalization.ts";
|
|
9
9
|
import { mergeChildEnv } from "../src/kernel/propagation.ts";
|
|
10
10
|
import type { Capability } from "../src/kernel/resolve.ts";
|
|
@@ -22,9 +22,11 @@ import { resolveWorkspace } from "../src/executors/herdr-cli.ts";
|
|
|
22
22
|
import { HerdrWriterCloseError, runHerdrPane } from "../src/executors/run-herdr.ts";
|
|
23
23
|
import { GovernanceRefusal, refusal, type StructuredRefusal } from "../src/kernel/refusals.ts";
|
|
24
24
|
import { ENV_HERDR_KEEP_PANE, type GrantsSession } from "./session.ts";
|
|
25
|
-
import { ENV_EPISODE_ID } from "../src/kernel/env-names.ts";
|
|
25
|
+
import { CHILD_ATTRIBUTION_ENV_KEYS, ENV_EPISODE_ID } from "../src/kernel/env-names.ts";
|
|
26
26
|
import { releaseDelegationWorkspace, type PreparedWorkspace } from "./workspace-runtime.ts";
|
|
27
27
|
import { ActivityTimelineRecorder, ENV_ACTIVITY_PARENT_TASK } from "../src/products/activity-timeline.ts";
|
|
28
|
+
import { loadEpisodeCosts } from "../src/governance/episode-cost-gate.ts";
|
|
29
|
+
import { resolvedModelOf, type ResolvedDefinitionRuntime } from "./definition-runtime.ts";
|
|
28
30
|
export interface DelegationOutcome {
|
|
29
31
|
ok: boolean;
|
|
30
32
|
text: string;
|
|
@@ -105,11 +107,26 @@ export async function executePlannedChild(input: {
|
|
|
105
107
|
toolCallId?: string;
|
|
106
108
|
cwd: string;
|
|
107
109
|
preparedWorkspace?: PreparedWorkspace;
|
|
110
|
+
resolvedRuntime?: ResolvedDefinitionRuntime;
|
|
108
111
|
signal?: AbortSignal;
|
|
109
112
|
onProgress?: (update: ChildProgressUpdate) => void;
|
|
113
|
+
costGateUI?: {
|
|
114
|
+
hasUI: boolean;
|
|
115
|
+
input(title: string, placeholder: string, signal?: AbortSignal): Promise<string | undefined>;
|
|
116
|
+
notify(message: string): void;
|
|
117
|
+
};
|
|
110
118
|
}): Promise<DelegationOutcome> {
|
|
111
119
|
const { session, plan, childId, executionId, parentExecutionId, preparedWorkspace, signal, onProgress } = input;
|
|
112
120
|
const ledgerPath = session.ledgerPath;
|
|
121
|
+
const runtimeAttribution = (observed?: {
|
|
122
|
+
resolvedModel?: { provider: string; modelId: string };
|
|
123
|
+
thinking?: string;
|
|
124
|
+
}) => ({
|
|
125
|
+
resolvedModel: observed?.resolvedModel ?? resolvedModelOf(input.resolvedRuntime?.model) ?? undefined,
|
|
126
|
+
modelSource: input.resolvedRuntime?.modelSource ?? ("pi" as const),
|
|
127
|
+
effectiveThinkingLevel: observed?.thinking ?? input.resolvedRuntime?.thinking,
|
|
128
|
+
thinkingSource: input.resolvedRuntime?.thinkingSource ?? ("pi" as const),
|
|
129
|
+
});
|
|
113
130
|
let activityParent: string | undefined,
|
|
114
131
|
activity = new ActivityTimelineRecorder(input.cwd),
|
|
115
132
|
activityStarted = false,
|
|
@@ -165,11 +182,90 @@ export async function executePlannedChild(input: {
|
|
|
165
182
|
// The process executor adds a second signal that is live during a tool call: the child tree's CPU time and
|
|
166
183
|
// descendants (`process-activity.ts`). Herdr children are started by the daemon, so their pid is not known here.
|
|
167
184
|
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
|
+
};
|
|
168
262
|
const probe = async () => {
|
|
169
|
-
const [file, tree] = await Promise.all([
|
|
263
|
+
const [file, tree, usage] = await Promise.all([
|
|
170
264
|
activitySession.probe(),
|
|
171
265
|
childPid === undefined ? undefined : processTreeActivity(childPid),
|
|
266
|
+
activitySession.usage(),
|
|
172
267
|
]);
|
|
268
|
+
await observeCost(usage.usage?.cost.total, false);
|
|
173
269
|
return file === undefined && tree === undefined ? undefined : `${file ?? "-"}|${tree ?? "-"}`;
|
|
174
270
|
};
|
|
175
271
|
const startedAt = new Date();
|
|
@@ -187,6 +283,10 @@ export async function executePlannedChild(input: {
|
|
|
187
283
|
executor: session.executor.kind,
|
|
188
284
|
deadlineAt,
|
|
189
285
|
idleTimeoutMs: configuredIdleMs,
|
|
286
|
+
exportedEnvironment: CHILD_ATTRIBUTION_ENV_KEYS,
|
|
287
|
+
...runtimeAttribution(),
|
|
288
|
+
definitionHash: plan.definitionHash,
|
|
289
|
+
definitionPackageVersion: plan.definitionPackageVersion,
|
|
190
290
|
correlation: plan.correlation,
|
|
191
291
|
now: startedAt,
|
|
192
292
|
}),
|
|
@@ -219,11 +319,12 @@ export async function executePlannedChild(input: {
|
|
|
219
319
|
leaseLost = true;
|
|
220
320
|
leaseAbort.abort();
|
|
221
321
|
});
|
|
222
|
-
const
|
|
223
|
-
|
|
224
|
-
:
|
|
225
|
-
|
|
226
|
-
|
|
322
|
+
const executionSignals = [
|
|
323
|
+
costAbort.signal,
|
|
324
|
+
...(signal ? [signal] : []),
|
|
325
|
+
...(writerLease ? [leaseAbort.signal] : []),
|
|
326
|
+
];
|
|
327
|
+
const executionSignal = AbortSignal.any(executionSignals);
|
|
227
328
|
const retention = beginExecutionRetention({
|
|
228
329
|
executionId,
|
|
229
330
|
parentExecutionId,
|
|
@@ -265,6 +366,10 @@ export async function executePlannedChild(input: {
|
|
|
265
366
|
deadlineAt,
|
|
266
367
|
idleTimeoutMs: configuredIdleMs,
|
|
267
368
|
...(pane ? { herdrPaneId: pane.id, herdrAgentName: pane.agentName } : {}),
|
|
369
|
+
exportedEnvironment: CHILD_ATTRIBUTION_ENV_KEYS,
|
|
370
|
+
...runtimeAttribution(),
|
|
371
|
+
definitionHash: plan.definitionHash,
|
|
372
|
+
definitionPackageVersion: plan.definitionPackageVersion,
|
|
268
373
|
correlation: plan.correlation,
|
|
269
374
|
now: new Date(),
|
|
270
375
|
}),
|
|
@@ -343,6 +448,7 @@ export async function executePlannedChild(input: {
|
|
|
343
448
|
failed: childFailed,
|
|
344
449
|
});
|
|
345
450
|
const usageObservation = await activitySession.usage();
|
|
451
|
+
await observeCost(usageObservation.usage?.cost.total, usageObservation.usage === undefined);
|
|
346
452
|
releaseReason = output.timedOut ? "timeout" : output.aborted ? "cancelled" : childFailed ? "failed" : "completed";
|
|
347
453
|
if (activityStarted)
|
|
348
454
|
try {
|
|
@@ -386,9 +492,22 @@ export async function executePlannedChild(input: {
|
|
|
386
492
|
idleTimeoutMs: configuredIdleMs,
|
|
387
493
|
reason:
|
|
388
494
|
output.spawnError ?? (output.timedOut ? (output.idle ? "idle-timeout" : "wall-clock") : undefined),
|
|
495
|
+
exportedEnvironment: CHILD_ATTRIBUTION_ENV_KEYS,
|
|
496
|
+
definitionHash: plan.definitionHash,
|
|
497
|
+
definitionPackageVersion: plan.definitionPackageVersion,
|
|
498
|
+
...runtimeAttribution({
|
|
499
|
+
...(usageObservation.resolvedModel ? { resolvedModel: usageObservation.resolvedModel } : {}),
|
|
500
|
+
...(usageObservation.effectiveThinkingLevel
|
|
501
|
+
? { thinking: usageObservation.effectiveThinkingLevel }
|
|
502
|
+
: {}),
|
|
503
|
+
}),
|
|
504
|
+
...(usageObservation.tokenDetail ? { tokenDetail: usageObservation.tokenDetail } : {}),
|
|
389
505
|
...(usageObservation.usage
|
|
390
506
|
? { usage: usageObservation.usage }
|
|
391
507
|
: { usageUnavailable: usageObservation.unavailable! }),
|
|
508
|
+
...(usageObservation.compactionCount !== undefined
|
|
509
|
+
? { compactionCount: usageObservation.compactionCount }
|
|
510
|
+
: {}),
|
|
392
511
|
correlation: plan.correlation,
|
|
393
512
|
now: new Date(),
|
|
394
513
|
}),
|
|
@@ -403,7 +522,9 @@ export async function executePlannedChild(input: {
|
|
|
403
522
|
: output.aborted
|
|
404
523
|
? leaseWasLost
|
|
405
524
|
? "lost the exclusive writer lease protecting its workspace and was stopped"
|
|
406
|
-
:
|
|
525
|
+
: costStopped
|
|
526
|
+
? "crossed the episode cost ceiling and was stopped"
|
|
527
|
+
: "was cancelled"
|
|
407
528
|
: output.timedOut
|
|
408
529
|
? output.idle
|
|
409
530
|
? `showed no activity for ${describeBound(configuredIdleMs)} and was killed ` +
|
|
@@ -473,6 +594,11 @@ export async function executePlannedChild(input: {
|
|
|
473
594
|
});
|
|
474
595
|
retainWriterLease = Boolean(writerLease && isHerdrWriterCloseFailure(error));
|
|
475
596
|
const usageObservation = await activitySession.usage();
|
|
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
|
+
}
|
|
476
602
|
if (ledgerPath && !terminalAttempted) {
|
|
477
603
|
// Best-effort: this records the failure, so it must not REPLACE the failure. A strict append that
|
|
478
604
|
// throws here would discard the original error — including HerdrWriterCloseError, whose whole
|
|
@@ -498,9 +624,22 @@ export async function executePlannedChild(input: {
|
|
|
498
624
|
: error instanceof Error
|
|
499
625
|
? error.name
|
|
500
626
|
: "unknown executor error",
|
|
627
|
+
exportedEnvironment: CHILD_ATTRIBUTION_ENV_KEYS,
|
|
628
|
+
definitionHash: plan.definitionHash,
|
|
629
|
+
definitionPackageVersion: plan.definitionPackageVersion,
|
|
630
|
+
...runtimeAttribution({
|
|
631
|
+
...(usageObservation.resolvedModel ? { resolvedModel: usageObservation.resolvedModel } : {}),
|
|
632
|
+
...(usageObservation.effectiveThinkingLevel
|
|
633
|
+
? { thinking: usageObservation.effectiveThinkingLevel }
|
|
634
|
+
: {}),
|
|
635
|
+
}),
|
|
636
|
+
...(usageObservation.tokenDetail ? { tokenDetail: usageObservation.tokenDetail } : {}),
|
|
501
637
|
...(usageObservation.usage
|
|
502
638
|
? { usage: usageObservation.usage }
|
|
503
639
|
: { usageUnavailable: usageObservation.unavailable! }),
|
|
640
|
+
...(usageObservation.compactionCount !== undefined
|
|
641
|
+
? { compactionCount: usageObservation.compactionCount }
|
|
642
|
+
: {}),
|
|
504
643
|
correlation: plan.correlation,
|
|
505
644
|
now: new Date(),
|
|
506
645
|
}),
|
|
@@ -20,6 +20,7 @@ import type { GatedPlan } from "./run-delegation.ts";
|
|
|
20
20
|
import { handleConnectedCommand } from "./grants-connected-command.ts";
|
|
21
21
|
import { loadApprovals, revokeAll, revokeApproval, type SubjectLookup } from "../src/governance/approval-store.ts";
|
|
22
22
|
import { verifyLedger } from "../src/governance/ledger.ts";
|
|
23
|
+
import type { ResolvedDefinitionRuntime } from "./definition-runtime.ts";
|
|
23
24
|
|
|
24
25
|
export interface GrantsCommandContext {
|
|
25
26
|
cwd: string;
|
|
@@ -60,6 +61,13 @@ export interface GrantsCommandContext {
|
|
|
60
61
|
* R-28 discipline where it belongs: this command asks what would happen instead of working it out.
|
|
61
62
|
*/
|
|
62
63
|
previewDelegation: (name: string) => Promise<GatedPlan>;
|
|
64
|
+
runtimeFor: (name: string) => ResolvedDefinitionRuntime;
|
|
65
|
+
changeModels: (ui: {
|
|
66
|
+
input: GrantsCommandContext["modelInput"];
|
|
67
|
+
notify: GrantsCommandContext["modelNotify"];
|
|
68
|
+
}) => Promise<string>;
|
|
69
|
+
modelInput(title: string, placeholder?: string): Promise<string | undefined>;
|
|
70
|
+
modelNotify(message: string, type?: "info" | "warning" | "error"): void;
|
|
63
71
|
/**
|
|
64
72
|
* Run `/grants init`: scaffold definitions, ask about withheld capabilities, atomically store grant plus
|
|
65
73
|
* project-ledger consent outside the workspace, and apply both now (ADR-0030, ADR-0037).
|
|
@@ -83,7 +91,15 @@ export interface GrantsCommandContext {
|
|
|
83
91
|
const PREVIEW_LIMIT = 12;
|
|
84
92
|
|
|
85
93
|
/** The verbs `/grants` answers to. Anything else is refused rather than silently treated as no verb. */
|
|
86
|
-
const KNOWN_SUBCOMMANDS: readonly string[] = [
|
|
94
|
+
const KNOWN_SUBCOMMANDS: readonly string[] = [
|
|
95
|
+
"init",
|
|
96
|
+
"dashboard",
|
|
97
|
+
"ledger",
|
|
98
|
+
"approvals",
|
|
99
|
+
"revoke",
|
|
100
|
+
"workspaces",
|
|
101
|
+
"models",
|
|
102
|
+
];
|
|
87
103
|
|
|
88
104
|
export const grantsCommand = {
|
|
89
105
|
description:
|
|
@@ -110,6 +126,7 @@ export const grantsCommand = {
|
|
|
110
126
|
inheritedApprovals,
|
|
111
127
|
snapshotOf,
|
|
112
128
|
previewDelegation,
|
|
129
|
+
runtimeFor,
|
|
113
130
|
} = ctx.grants as GrantsCommandContext;
|
|
114
131
|
|
|
115
132
|
const [sub, target] = args.trim().split(/\s+/).filter(Boolean);
|
|
@@ -127,6 +144,15 @@ export const grantsCommand = {
|
|
|
127
144
|
)
|
|
128
145
|
return;
|
|
129
146
|
|
|
147
|
+
if (sub === "models") {
|
|
148
|
+
const rendered = await ctx.grants.changeModels({
|
|
149
|
+
input: ctx.grants.modelInput,
|
|
150
|
+
notify: ctx.grants.modelNotify,
|
|
151
|
+
});
|
|
152
|
+
ctx.ui.notify(`grants: current session model overrides\n${rendered}`, "info");
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
|
|
130
156
|
if (sub === "workspaces") {
|
|
131
157
|
// The one verb that WRITES, and it takes its ability to do so from the caller — the same shape
|
|
132
158
|
// `/grants init` uses, so this screen cannot become a governance path by accident.
|
|
@@ -445,6 +471,10 @@ export const grantsCommand = {
|
|
|
445
471
|
const shown = [...definitions].slice(0, PREVIEW_LIMIT);
|
|
446
472
|
for (const [name] of shown) {
|
|
447
473
|
const { plan, approval } = await previewDelegation(name);
|
|
474
|
+
const runtime = runtimeFor(name);
|
|
475
|
+
const runtimeText =
|
|
476
|
+
`model ${runtime.model ?? "pi default"} (${runtime.modelSource}); ` +
|
|
477
|
+
`thinking ${runtime.thinking ?? "pi default"} (${runtime.thinkingSource})`;
|
|
448
478
|
// Why it is allowed, when a standing approval is the reason. An `allow` that silently depends on a
|
|
449
479
|
// 30-day entry in a file elsewhere is precisely what an operator runs this command to discover, and
|
|
450
480
|
// R-38 was the version of this listing that could not have told them (it said BLOCK instead).
|
|
@@ -455,7 +485,7 @@ export const grantsCommand = {
|
|
|
455
485
|
? ` (${approval.approved.map((c) => `${c} approved: ${approval.sources[c] ?? "?"}`).join("; ")})`
|
|
456
486
|
: "";
|
|
457
487
|
lines.push(
|
|
458
|
-
` ${plan.ok ? "allow" : "BLOCK"} ${name}` +
|
|
488
|
+
` ${plan.ok ? "allow" : "BLOCK"} ${name} [${runtimeText}]` +
|
|
459
489
|
(plan.ok ? ` ${plan.effective.join(", ")}${because}` : ` — ${plan.reason}`),
|
|
460
490
|
);
|
|
461
491
|
}
|
package/extensions/grants.ts
CHANGED
|
@@ -55,6 +55,9 @@ import { legacyEnvironmentWarning } from "../src/kernel/env-names.ts";
|
|
|
55
55
|
import { agentDir } from "../src/kernel/project-paths.ts";
|
|
56
56
|
import { importLegacyLedger } from "../src/governance/ledger.ts";
|
|
57
57
|
import { legacyProjectLedgerPath, projectLedgerPath } from "../src/kernel/project-paths.ts";
|
|
58
|
+
import { resolveDefinitionRuntime } from "./definition-runtime.ts";
|
|
59
|
+
import { changeSessionModels, renderSessionModels } from "./session-model-prompt.ts";
|
|
60
|
+
import { ensureDashboardSessionServer } from "./dashboard-session-server.ts";
|
|
58
61
|
export default function (pi: ExtensionAPI) {
|
|
59
62
|
// The path pi loads as the extension, so a child granted `tool:delegate` can be started with `-e <this>`.
|
|
60
63
|
const extensionPath = (() => {
|
|
@@ -227,6 +230,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
227
230
|
pluginRoot: dashboardPluginRoot,
|
|
228
231
|
preferencePath: dashboardPaths.preferencePath,
|
|
229
232
|
paneStatePath: dashboardPaths.paneStatePath,
|
|
233
|
+
sessionEndpoint: await ensureDashboardSessionServer(session),
|
|
230
234
|
ui: ctx.ui,
|
|
231
235
|
});
|
|
232
236
|
} catch (error) {
|
|
@@ -413,7 +417,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
413
417
|
await loadProjectDefinitions(session, ctx.cwd);
|
|
414
418
|
delegation.refreshSpawnable();
|
|
415
419
|
}),
|
|
416
|
-
openDashboard: () =>
|
|
420
|
+
openDashboard: async () =>
|
|
417
421
|
openDashboardCommand({
|
|
418
422
|
env: process.env,
|
|
419
423
|
pid: process.pid,
|
|
@@ -421,8 +425,26 @@ export default function (pi: ExtensionAPI) {
|
|
|
421
425
|
ledgerPath: session.ledgerPath ?? defaultActivityTimelinePath(ctx.cwd),
|
|
422
426
|
pluginRoot: dashboardPluginRoot,
|
|
423
427
|
paneStatePath: dashboardPaths.paneStatePath,
|
|
428
|
+
sessionEndpoint: await ensureDashboardSessionServer(session),
|
|
424
429
|
}),
|
|
425
430
|
previewDelegation: (name: string) => planWithApprovals(session, { task: "(preview)", agent: name }, {}, null),
|
|
431
|
+
modelInput: (title: string, placeholder?: string) => ctx.ui.input(title, placeholder),
|
|
432
|
+
modelNotify: (message: string, type?: "info" | "warning" | "error") => ctx.ui.notify(message, type),
|
|
433
|
+
changeModels: async (ui: {
|
|
434
|
+
input(title: string, placeholder?: string): Promise<string | undefined>;
|
|
435
|
+
notify(message: string, type?: "info" | "warning" | "error"): void;
|
|
436
|
+
}) => {
|
|
437
|
+
await changeSessionModels(session, { hasUI: ctx.hasUI, ...ui });
|
|
438
|
+
return renderSessionModels(session);
|
|
439
|
+
},
|
|
440
|
+
runtimeFor: (name: string) =>
|
|
441
|
+
resolveDefinitionRuntime({
|
|
442
|
+
definition: name,
|
|
443
|
+
explicit: {},
|
|
444
|
+
session: session.definitionRuntimeOverrides,
|
|
445
|
+
settings: session.definitionRuntimeSettings,
|
|
446
|
+
piModel: ctx.model ? `${ctx.model.provider}/${ctx.model.id}` : undefined,
|
|
447
|
+
}),
|
|
426
448
|
},
|
|
427
449
|
}),
|
|
428
450
|
});
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* selection stands — the same "identical to today" property the effort decision point has.
|
|
16
16
|
*/
|
|
17
17
|
import { selectPrunedTurns, type ContextRequest } from "../src/kernel/context-handoff.ts";
|
|
18
|
-
import type { Advisor } from "../src/advisors/advisor.ts";
|
|
18
|
+
import type { AdviceAttribution, Advisor } from "../src/advisors/advisor.ts";
|
|
19
19
|
import type { Question } from "../src/advisors/decider.ts";
|
|
20
20
|
|
|
21
21
|
export const PRUNING_PURPOSE = "handoff-pruning";
|
|
@@ -39,6 +39,7 @@ export async function advisePruning(input: {
|
|
|
39
39
|
executionId?: string;
|
|
40
40
|
/** The current delegation call, excluded from advisor input because its arguments contain the raw task. */
|
|
41
41
|
toolCallId?: string;
|
|
42
|
+
attribution?: AdviceAttribution;
|
|
42
43
|
signal?: AbortSignal;
|
|
43
44
|
}): Promise<string[] | undefined> {
|
|
44
45
|
if (input.granted.mode !== "pruned" || !input.session.parentSession) return undefined;
|
|
@@ -85,6 +86,7 @@ export async function advisePruning(input: {
|
|
|
85
86
|
{ state: {}, questions },
|
|
86
87
|
input.signal,
|
|
87
88
|
input.executionId,
|
|
89
|
+
input.attribution,
|
|
88
90
|
);
|
|
89
91
|
if (!advice) return undefined;
|
|
90
92
|
// Every candidate or none. A response missing eleven of twelve answers would otherwise read as "drop eleven",
|
|
@@ -32,6 +32,8 @@ import { GovernanceRefusal, refusal as structuredRefusal } from "../src/kernel/r
|
|
|
32
32
|
import { executePlannedChild, type DelegationOutcome } from "./execute-child.ts";
|
|
33
33
|
import { recordDelegationDecision, type ApprovalLedgerFacts } from "./delegation-ledger.ts";
|
|
34
34
|
import type { ExecutionOccurrenceIds } from "./execution-occurrence.ts";
|
|
35
|
+
import { ENV_EPISODE_COST_CEILING } from "../src/kernel/env-names.ts";
|
|
36
|
+
import { resolvedModelOf, resolveDefinitionRuntime } from "./definition-runtime.ts";
|
|
35
37
|
import {
|
|
36
38
|
governedWorkspaceAccess,
|
|
37
39
|
prepareDelegationWorkspace,
|
|
@@ -47,6 +49,8 @@ interface ChildSpec {
|
|
|
47
49
|
tools?: string[];
|
|
48
50
|
model?: string;
|
|
49
51
|
thinking?: string;
|
|
52
|
+
/** Positive USD override for the whole episode, applied only when this delegation will run. */
|
|
53
|
+
episodeCostCeiling?: number;
|
|
50
54
|
/** ADR-0078: what of the parent's session crosses. Validated in the kernel, never here. */
|
|
51
55
|
context?: unknown;
|
|
52
56
|
correlation?: CorrelationMetadata;
|
|
@@ -246,14 +250,25 @@ export async function runOneDelegation(
|
|
|
246
250
|
// pi resolves a BARE model id to an unauthenticated provider and the child dies at startup — the id
|
|
247
251
|
// alone is not enough, it must be qualified with its provider (`Model<Api>` carries both).
|
|
248
252
|
const defaultModel = ctx.model ? `${ctx.model.provider}/${ctx.model.id}` : undefined;
|
|
253
|
+
const configured = resolveDefinitionRuntime({
|
|
254
|
+
definition: spec.agent,
|
|
255
|
+
explicit: { model: spec.model, thinking: spec.thinking },
|
|
256
|
+
session: session.definitionRuntimeOverrides,
|
|
257
|
+
settings: session.definitionRuntimeSettings,
|
|
258
|
+
piModel: defaultModel,
|
|
259
|
+
});
|
|
260
|
+
let resolvedRuntime = configured;
|
|
249
261
|
const request = {
|
|
250
262
|
task: spec.task,
|
|
251
263
|
agent: spec.agent,
|
|
252
264
|
tools: spec.tools,
|
|
253
|
-
model:
|
|
265
|
+
model: configured.model,
|
|
254
266
|
// Filled below, once the refusals that doom a delegation are known: asking first shipped the task text for a
|
|
255
267
|
// child that never starts, which is the ordering this module already fixed for the approval dialog.
|
|
256
|
-
thinking:
|
|
268
|
+
thinking:
|
|
269
|
+
configured.thinkingSource === "explicit" || configured.thinkingSource === "session"
|
|
270
|
+
? configured.thinking
|
|
271
|
+
: undefined,
|
|
257
272
|
context: spec.context,
|
|
258
273
|
correlation: spec.workspace
|
|
259
274
|
? { ...(spec.correlation ?? {}), workspace_id: spec.workspace.workspace_id }
|
|
@@ -296,17 +311,28 @@ export async function runOneDelegation(
|
|
|
296
311
|
|
|
297
312
|
// ADR-0077's first decision point, after the refusal checks for the reason above. Fills a blank from the levels
|
|
298
313
|
// the CHILD's model reports; never overrules a caller, and yields today's behaviour whenever there is no answer.
|
|
299
|
-
if (!executorRefusal && !modelRefusal)
|
|
300
|
-
|
|
314
|
+
if (!executorRefusal && !modelRefusal) {
|
|
315
|
+
const advisedThinking = await adviseEffort({
|
|
301
316
|
session,
|
|
302
|
-
requested:
|
|
303
|
-
model:
|
|
317
|
+
requested: request.thinking,
|
|
318
|
+
model: configured.model,
|
|
304
319
|
registry: ctx.modelRegistry,
|
|
305
320
|
task: spec.task,
|
|
306
321
|
executionId: ids.executionId,
|
|
307
322
|
agent: spec.agent,
|
|
323
|
+
fallback: configured,
|
|
308
324
|
signal,
|
|
309
325
|
});
|
|
326
|
+
resolvedRuntime = resolveDefinitionRuntime({
|
|
327
|
+
definition: spec.agent,
|
|
328
|
+
explicit: { model: spec.model, thinking: spec.thinking },
|
|
329
|
+
session: session.definitionRuntimeOverrides,
|
|
330
|
+
settings: session.definitionRuntimeSettings,
|
|
331
|
+
piModel: defaultModel,
|
|
332
|
+
advisorThinking: advisedThinking,
|
|
333
|
+
});
|
|
334
|
+
request.thinking = resolvedRuntime.thinking;
|
|
335
|
+
}
|
|
310
336
|
|
|
311
337
|
const planContext = await handoffPlanContext({
|
|
312
338
|
session,
|
|
@@ -316,6 +342,12 @@ export async function runOneDelegation(
|
|
|
316
342
|
toolCallId,
|
|
317
343
|
blocked: Boolean(executorRefusal || modelRefusal),
|
|
318
344
|
preview: () => planWithApprovals(session, request, extra, null, signal, preApproved).then((r) => r.plan),
|
|
345
|
+
attribution: {
|
|
346
|
+
resolvedModel: resolvedModelOf(resolvedRuntime.model),
|
|
347
|
+
modelSource: resolvedRuntime.modelSource,
|
|
348
|
+
thinkingLevel: resolvedRuntime.thinking ?? null,
|
|
349
|
+
thinkingSource: resolvedRuntime.thinkingSource,
|
|
350
|
+
},
|
|
319
351
|
...(signal ? { signal } : {}),
|
|
320
352
|
});
|
|
321
353
|
let preparedWorkspace: PreparedWorkspace | undefined;
|
|
@@ -427,6 +459,14 @@ export async function runOneDelegation(
|
|
|
427
459
|
};
|
|
428
460
|
}
|
|
429
461
|
|
|
462
|
+
if (spec.episodeCostCeiling !== undefined) session.episodeCostGate.setCeiling(spec.episodeCostCeiling);
|
|
463
|
+
plan = {
|
|
464
|
+
...plan,
|
|
465
|
+
env: { ...plan.env, [ENV_EPISODE_COST_CEILING]: String(session.episodeCostGate.ceiling) },
|
|
466
|
+
};
|
|
467
|
+
const costUI = ctx.ui as typeof ctx.ui & {
|
|
468
|
+
input(title: string, placeholder?: string, opts?: { signal?: AbortSignal }): Promise<string | undefined>;
|
|
469
|
+
};
|
|
430
470
|
return executePlannedChild({
|
|
431
471
|
session,
|
|
432
472
|
plan,
|
|
@@ -437,8 +477,14 @@ export async function runOneDelegation(
|
|
|
437
477
|
toolCallId: options.toolCallId,
|
|
438
478
|
cwd: ctx.cwd,
|
|
439
479
|
preparedWorkspace,
|
|
480
|
+
resolvedRuntime,
|
|
440
481
|
signal,
|
|
441
482
|
onProgress,
|
|
483
|
+
costGateUI: {
|
|
484
|
+
hasUI: ctx.hasUI,
|
|
485
|
+
input: (title, placeholder, gateSignal) => costUI.input(title, placeholder, { signal: gateSignal }),
|
|
486
|
+
notify: (message) => ctx.ui.notify(message, "warning"),
|
|
487
|
+
},
|
|
442
488
|
});
|
|
443
489
|
}
|
|
444
490
|
|
|
@@ -462,6 +508,7 @@ export async function handoffPlanContext(input: {
|
|
|
462
508
|
blocked: boolean;
|
|
463
509
|
/** Plans with no human in the loop; its result decides whether an advisor is consulted at all. */
|
|
464
510
|
preview: () => Promise<{ handoff?: { mode: string } }>;
|
|
511
|
+
attribution?: Parameters<typeof advisePruning>[0]["attribution"];
|
|
465
512
|
signal?: AbortSignal;
|
|
466
513
|
}): Promise<Record<string, unknown>> {
|
|
467
514
|
if (input.blocked) return { ...input.base };
|
|
@@ -473,6 +520,7 @@ export async function handoffPlanContext(input: {
|
|
|
473
520
|
task: input.task,
|
|
474
521
|
...(input.executionId ? { executionId: input.executionId } : {}),
|
|
475
522
|
...(input.toolCallId ? { toolCallId: input.toolCallId } : {}),
|
|
523
|
+
...(input.attribution ? { attribution: input.attribution } : {}),
|
|
476
524
|
...(input.signal ? { signal: input.signal } : {}),
|
|
477
525
|
});
|
|
478
526
|
return ids ? { ...input.base, handoffTurnIds: ids } : { ...input.base };
|