pi-daddy 0.44.4 → 0.46.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +383 -25
  3. package/dist/executors/captured-execution.d.ts +2 -2
  4. package/dist/executors/captured-execution.d.ts.map +1 -1
  5. package/dist/executors/captured-execution.js +2 -2
  6. package/dist/executors/captured-execution.js.map +1 -1
  7. package/dist/executors/executor.d.ts.map +1 -1
  8. package/dist/executors/executor.js +4 -2
  9. package/dist/executors/executor.js.map +1 -1
  10. package/dist/executors/herdr-cli.d.ts +2 -0
  11. package/dist/executors/herdr-cli.d.ts.map +1 -1
  12. package/dist/executors/herdr-cli.js +2 -2
  13. package/dist/executors/herdr-cli.js.map +1 -1
  14. package/dist/executors/herdr-launcher.d.ts +2 -0
  15. package/dist/executors/herdr-launcher.d.ts.map +1 -0
  16. package/dist/executors/herdr-launcher.js +123 -0
  17. package/dist/executors/herdr-launcher.js.map +1 -0
  18. package/dist/executors/herdr-owned.d.ts +13 -0
  19. package/dist/executors/herdr-owned.d.ts.map +1 -0
  20. package/dist/executors/herdr-owned.js +229 -0
  21. package/dist/executors/herdr-owned.js.map +1 -0
  22. package/dist/executors/herdr-qualification.d.ts +6 -0
  23. package/dist/executors/herdr-qualification.d.ts.map +1 -0
  24. package/dist/executors/herdr-qualification.js +33 -0
  25. package/dist/executors/herdr-qualification.js.map +1 -0
  26. package/dist/executors/herdr-wire.d.ts +5 -0
  27. package/dist/executors/herdr-wire.d.ts.map +1 -0
  28. package/dist/executors/herdr-wire.js +47 -0
  29. package/dist/executors/herdr-wire.js.map +1 -0
  30. package/dist/governance/runtime-settlement-lock.d.ts +5 -0
  31. package/dist/governance/runtime-settlement-lock.d.ts.map +1 -0
  32. package/dist/governance/runtime-settlement-lock.js +72 -0
  33. package/dist/governance/runtime-settlement-lock.js.map +1 -0
  34. package/dist/governance/runtime-settlement.d.ts +22 -0
  35. package/dist/governance/runtime-settlement.d.ts.map +1 -0
  36. package/dist/governance/runtime-settlement.js +236 -0
  37. package/dist/governance/runtime-settlement.js.map +1 -0
  38. package/dist/kernel/definition-sources.d.ts +13 -0
  39. package/dist/kernel/definition-sources.d.ts.map +1 -0
  40. package/dist/kernel/definition-sources.js +2 -0
  41. package/dist/kernel/definition-sources.js.map +1 -0
  42. package/dist/kernel/definitions.d.ts +2 -0
  43. package/dist/kernel/definitions.d.ts.map +1 -1
  44. package/dist/kernel/definitions.js.map +1 -1
  45. package/dist/kernel/env-names.d.ts +2 -0
  46. package/dist/kernel/env-names.d.ts.map +1 -1
  47. package/dist/kernel/env-names.js +3 -0
  48. package/dist/kernel/env-names.js.map +1 -1
  49. package/dist/kernel/propagation.d.ts +1 -1
  50. package/dist/kernel/propagation.d.ts.map +1 -1
  51. package/dist/kernel/propagation.js +2 -0
  52. package/dist/kernel/propagation.js.map +1 -1
  53. package/dist/kernel/selected-definitions.d.ts +1 -1
  54. package/dist/kernel/selected-definitions.d.ts.map +1 -1
  55. package/dist/kernel/selected-definitions.js +19 -10
  56. package/dist/kernel/selected-definitions.js.map +1 -1
  57. package/dist/products/public-evidence.d.ts +62 -0
  58. package/dist/products/public-evidence.d.ts.map +1 -0
  59. package/dist/products/public-evidence.js +116 -0
  60. package/dist/products/public-evidence.js.map +1 -0
  61. package/extensions/definition-describe.ts +4 -1
  62. package/extensions/delegate-chain.ts +21 -15
  63. package/extensions/delegation.ts +57 -27
  64. package/extensions/execute-child.ts +98 -74
  65. package/extensions/execution-evidence.ts +10 -1
  66. package/extensions/executor-session.ts +2 -1
  67. package/extensions/grants.ts +3 -0
  68. package/extensions/public-evidence.ts +90 -0
  69. package/extensions/reload-environment.ts +8 -0
  70. package/extensions/run-delegation.ts +20 -4
  71. package/extensions/runtime-snapshot.ts +123 -0
  72. package/extensions/session.ts +11 -2
  73. package/package.json +1 -1
  74. package/src/executors/captured-execution.ts +2 -1
  75. package/src/executors/executor.ts +4 -2
  76. package/src/executors/herdr-cli.ts +19 -12
  77. package/src/executors/herdr-launcher.ts +124 -0
  78. package/src/executors/herdr-owned.ts +260 -0
  79. package/src/executors/herdr-qualification.ts +33 -0
  80. package/src/executors/herdr-wire.ts +43 -0
  81. package/src/governance/runtime-settlement-lock.ts +76 -0
  82. package/src/governance/runtime-settlement.ts +271 -0
  83. package/src/kernel/definition-sources.ts +12 -0
  84. package/src/kernel/definitions.ts +2 -0
  85. package/src/kernel/env-names.ts +3 -0
  86. package/src/kernel/propagation.ts +2 -0
  87. package/src/kernel/selected-definitions.ts +31 -10
  88. package/src/products/public-evidence.ts +181 -0
@@ -0,0 +1,62 @@
1
+ import type { DefinitionSourceSnapshot } from "../kernel/definition-sources.ts";
2
+ export declare const PUBLIC_EVIDENCE_SCHEMA = "pi-daddy-public-evidence-v1";
3
+ export declare const MAX_PUBLIC_EVIDENCE_BYTES: number;
4
+ export declare const MAX_PUBLIC_EVIDENCE_FILES = 64;
5
+ export interface PublicEvidenceRef {
6
+ path: string;
7
+ sha256: string;
8
+ bytes: number;
9
+ }
10
+ export interface PublicEvidenceOwner {
11
+ readonly directory: string;
12
+ readonly ownerId: string;
13
+ }
14
+ export interface PublicDefinitionEvidence {
15
+ agent: string;
16
+ definitionId: string;
17
+ sourceHash: string | null;
18
+ bodySha256: string;
19
+ binding: Readonly<{
20
+ package: string;
21
+ phase: string;
22
+ }> | null;
23
+ snapshot?: DefinitionSourceSnapshot;
24
+ }
25
+ export interface PublicEvidenceRequest {
26
+ ordinal: number;
27
+ agent: string | null;
28
+ requestedDefinitionId: string | null;
29
+ definitionId: string | null;
30
+ executionId: string | null;
31
+ }
32
+ export interface PublicFinalEvidence {
33
+ ordinal: number;
34
+ executionId: string | null;
35
+ final: {
36
+ state: "complete";
37
+ text: string;
38
+ sessionId: string;
39
+ messageId: string;
40
+ leafId: string;
41
+ sha256: string;
42
+ } | null;
43
+ }
44
+ export interface PublicEvidenceInput {
45
+ toolCallId: string;
46
+ tool: "delegate_describe" | "delegate" | "delegate_all" | "delegate_chain";
47
+ response: {
48
+ isError: boolean;
49
+ content: readonly {
50
+ type: "text";
51
+ text: string;
52
+ }[];
53
+ };
54
+ requested: readonly PublicEvidenceRequest[];
55
+ runtimeEvidence: unknown;
56
+ definitions: readonly PublicDefinitionEvidence[];
57
+ finals: readonly PublicFinalEvidence[];
58
+ }
59
+ export declare function createPublicEvidenceOwner(directory: string | undefined): PublicEvidenceOwner | undefined;
60
+ /** No schema accepts arbitrary filenames, output paths, raw arguments, environment, or native details. */
61
+ export declare function capturePublicEvidence(owner: PublicEvidenceOwner, input: PublicEvidenceInput): PublicEvidenceRef;
62
+ //# sourceMappingURL=public-evidence.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"public-evidence.d.ts","sourceRoot":"","sources":["../../src/products/public-evidence.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,iCAAiC,CAAC;AAChF,eAAO,MAAM,sBAAsB,gCAAgC,CAAC;AACpE,eAAO,MAAM,yBAAyB,QAAmB,CAAC;AAC1D,eAAO,MAAM,yBAAyB,KAAK,CAAC;AAC5C,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;CACf;AACD,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AACD,MAAM,WAAW,wBAAwB;IACvC,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,QAAQ,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,GAAG,IAAI,CAAC;IAC7D,QAAQ,CAAC,EAAE,wBAAwB,CAAC;CACrC;AACD,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,qBAAqB,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AACD,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,KAAK,EAAE;QACL,KAAK,EAAE,UAAU,CAAC;QAClB,IAAI,EAAE,MAAM,CAAC;QACb,SAAS,EAAE,MAAM,CAAC;QAClB,SAAS,EAAE,MAAM,CAAC;QAClB,MAAM,EAAE,MAAM,CAAC;QACf,MAAM,EAAE,MAAM,CAAC;KAChB,GAAG,IAAI,CAAC;CACV;AACD,MAAM,WAAW,mBAAmB;IAClC,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,mBAAmB,GAAG,UAAU,GAAG,cAAc,GAAG,gBAAgB,CAAC;IAC3E,QAAQ,EAAE;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,OAAO,EAAE,SAAS;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE,MAAM,CAAA;SAAE,EAAE,CAAA;KAAE,CAAC;IACnF,SAAS,EAAE,SAAS,qBAAqB,EAAE,CAAC;IAC5C,eAAe,EAAE,OAAO,CAAC;IACzB,WAAW,EAAE,SAAS,wBAAwB,EAAE,CAAC;IACjD,MAAM,EAAE,SAAS,mBAAmB,EAAE,CAAC;CACxC;AACD,wBAAgB,yBAAyB,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG,mBAAmB,GAAG,SAAS,CAIxG;AAGD,0GAA0G;AAC1G,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,mBAAmB,EAAE,KAAK,EAAE,mBAAmB,GAAG,iBAAiB,CAyG/G"}
@@ -0,0 +1,116 @@
1
+ /** Exact public extension-return capture. Opt-in local observation, never an authority/acceptance source.
2
+ * Existing operator-created root must be canonical, private and owned by this uid. Held directory
3
+ * descriptors anchor every exclusive write; successful refs are returned only after fsync and close.
4
+ * Hashes detect accidental edits; this does not authenticate against a hostile process with the same uid.
5
+ */
6
+ import { randomUUID, createHash } from "node:crypto";
7
+ import { constants, openSync, closeSync, fstatSync, fsyncSync, writeFileSync, mkdirSync, realpathSync, statSync, } from "node:fs";
8
+ import { isAbsolute, join, resolve } from "node:path";
9
+ export const PUBLIC_EVIDENCE_SCHEMA = "pi-daddy-public-evidence-v1";
10
+ export const MAX_PUBLIC_EVIDENCE_BYTES = 64 * 1024 * 1024;
11
+ export const MAX_PUBLIC_EVIDENCE_FILES = 64;
12
+ export function createPublicEvidenceOwner(directory) {
13
+ return directory === undefined || directory === "off"
14
+ ? undefined
15
+ : Object.freeze({ directory, ownerId: randomUUID() });
16
+ }
17
+ const sha256 = (bytes) => createHash("sha256").update(bytes).digest("hex");
18
+ const json = (value) => Buffer.from(JSON.stringify(value) + "\n", "utf8");
19
+ /** No schema accepts arbitrary filenames, output paths, raw arguments, environment, or native details. */
20
+ export function capturePublicEvidence(owner, input) {
21
+ const directory = owner.directory;
22
+ if (process.platform !== "linux" ||
23
+ !isAbsolute(directory) ||
24
+ resolve(directory) !== directory ||
25
+ realpathSync(directory) !== directory)
26
+ throw Error("public evidence root must be an existing canonical absolute Linux directory");
27
+ const rootFd = openSync(directory, constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
28
+ try {
29
+ const root = fstatSync(rootFd);
30
+ if (!root.isDirectory() || root.uid !== process.getuid?.() || (root.mode & 0o077) !== 0)
31
+ throw Error("public evidence root must be owned by this user and private (mode 0700)");
32
+ const captureId = randomUUID(), target = join(directory, captureId);
33
+ const rootPath = `/proc/self/fd/${rootFd}`;
34
+ mkdirSync(join(rootPath, captureId), { mode: 0o700 });
35
+ const captureFd = openSync(join(rootPath, captureId), constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW);
36
+ let manifest;
37
+ try {
38
+ let bytes = 0, files = 0;
39
+ const put = (name, content) => {
40
+ bytes += content.byteLength;
41
+ files += 1;
42
+ if (bytes > MAX_PUBLIC_EVIDENCE_BYTES || files > MAX_PUBLIC_EVIDENCE_FILES)
43
+ throw Error("public evidence capture exceeds its byte/file limit; no complete manifest published");
44
+ const fd = openSync(join(`/proc/self/fd/${captureFd}`, name), constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW | constants.O_NONBLOCK, 0o600);
45
+ try {
46
+ if (!fstatSync(fd).isFile())
47
+ throw Error("public evidence target is not an ordinary file");
48
+ writeFileSync(fd, content);
49
+ fsyncSync(fd);
50
+ }
51
+ finally {
52
+ closeSync(fd);
53
+ }
54
+ return { path: join(target, name), sha256: sha256(content), bytes: content.byteLength };
55
+ };
56
+ const response = put("response.json", json(input.response));
57
+ const finals = input.finals.map((outcome) => {
58
+ if (!outcome.final)
59
+ return { ordinal: outcome.ordinal, executionId: outcome.executionId, final: null };
60
+ const { text, ...identity } = outcome.final;
61
+ const bytes = Buffer.from(text, "utf8");
62
+ if (sha256(bytes) !== identity.sha256)
63
+ throw Error("attributed final bytes do not match the native final hash");
64
+ if (!text || !input.response.content.some((block) => block.text.includes(text)))
65
+ throw Error("attributed final is not present in the public returned content");
66
+ return {
67
+ ordinal: outcome.ordinal,
68
+ executionId: outcome.executionId,
69
+ final: { ...identity, content: put(`outcome-${outcome.ordinal}-final.txt`, bytes) },
70
+ };
71
+ });
72
+ const definitions = input.definitions.map((definition, index) => {
73
+ if (!definition.snapshot)
74
+ throw Error(`selected source snapshot unavailable for ${definition.agent}`);
75
+ const resources = definition.snapshot.resources.map((source, resource) => ({
76
+ kind: source.kind,
77
+ path: source.path,
78
+ copy: put(`definition-${index}-source-${resource}.bin`, Buffer.from(source.base64, "base64")),
79
+ }));
80
+ const { snapshot: _snapshot, ...identity } = definition;
81
+ return {
82
+ ...identity,
83
+ resources,
84
+ body: put(`definition-${index}-body.txt`, Buffer.from(definition.snapshot.body, "utf8")),
85
+ };
86
+ });
87
+ manifest = put("manifest.json", json({
88
+ schema: PUBLIC_EVIDENCE_SCHEMA,
89
+ version: 1,
90
+ captureId,
91
+ ownerId: owner.ownerId,
92
+ toolCallId: input.toolCallId,
93
+ tool: input.tool,
94
+ state: "returned",
95
+ response,
96
+ requested: input.requested,
97
+ runtimeEvidence: input.runtimeEvidence,
98
+ finals,
99
+ definitions,
100
+ }));
101
+ fsyncSync(captureFd);
102
+ }
103
+ finally {
104
+ closeSync(captureFd);
105
+ }
106
+ fsyncSync(rootFd);
107
+ const current = statSync(directory);
108
+ if (realpathSync(directory) !== directory || current.dev !== root.dev || current.ino !== root.ino)
109
+ throw Error("public evidence root changed before reference publication");
110
+ return manifest;
111
+ }
112
+ finally {
113
+ closeSync(rootFd);
114
+ }
115
+ }
116
+ //# sourceMappingURL=public-evidence.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"public-evidence.js","sourceRoot":"","sources":["../../src/products/public-evidence.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,5 @@
1
1
  /** Read-only handshake with the exact immutable definition dispatched by this extension instance. */
2
+ import { publicEvidenceCall } from "./public-evidence.ts";
2
3
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
3
4
  import { Type } from "typebox";
4
5
  import { digestDefinition } from "../src/kernel/definitions.ts";
@@ -38,7 +39,9 @@ export function registerDefinitionDescribe(pi: ExtensionAPI, session: GrantsSess
38
39
  binding: definition.binding ?? null,
39
40
  definitionId: definition.definitionId,
40
41
  };
41
- return { content: [{ type: "text" as const, text: JSON.stringify(details) }], details };
42
+ const capture = publicEvidenceCall(session.publicEvidence, _id, "delegate_describe", [{ agent: params.agent }]);
43
+ capture.selected(0, definition);
44
+ return capture.finish({ content: [{ type: "text" as const, text: JSON.stringify(details) }], details });
42
45
  },
43
46
  });
44
47
  }
@@ -1,3 +1,4 @@
1
+ import { publicEvidenceCall } from "./public-evidence.ts";
1
2
  import { executionEvidenceContent } from "./execution-evidence.ts";
2
3
  import { assertDefinitionIdentity } from "./definition-describe.ts";
3
4
  /**
@@ -142,6 +143,7 @@ export function registerChainTool(pi: ExtensionAPI, session: GrantsSession): voi
142
143
  // Plan every step first. A step that can never run refuses the chain HERE, before anyone is asked — see
143
144
  // `planChain`.
144
145
  const executionIds = steps.map(() => newExecutionId());
146
+ const capture = publicEvidenceCall(session.publicEvidence, _toolCallId, "delegate_chain", steps, executionIds);
145
147
  await reconcileDelegationCapacity(session);
146
148
  const firstReservation = reserveDelegationCapacity(session, executionIds[0]);
147
149
  try {
@@ -326,6 +328,7 @@ export function registerChainTool(pi: ExtensionAPI, session: GrantsSession): voi
326
328
  {
327
329
  ...(index === 0 ? { capacityReservation: firstReservation } : {}),
328
330
  resolvedRuntime: runtimeChoices.get(step),
331
+ onDefinition: (definition) => capture.selected(index, definition),
329
332
  preApproved: availableForStep,
330
333
  toolCallId: _toolCallId,
331
334
  // Only approvals actually offered to this step are attributed or consumed here.
@@ -407,22 +410,25 @@ export function registerChainTool(pi: ExtensionAPI, session: GrantsSession): voi
407
410
  if (outcomes[0].refusal) throw new GovernanceRefusal({ ...outcomes[0].refusal, message });
408
411
  throw new Error(message);
409
412
  }
410
- return {
411
- isError: aborted || outcomes.some((o) => o.control === "failed"),
412
- content: [
413
- { type: "text", text: `${report}${tail}` },
414
- executionEvidenceContent("delegate_chain", outcomes, steps.length),
415
- ],
416
- details: {
417
- outcomes,
418
- steps: steps.length,
419
- completed: outcomes.filter((o) => o.ok).length,
420
- aborted,
421
- budgetPerStep: firstReservation.childAllowance,
422
- ...(blockedReason ? { blockedReason } : {}),
423
- refusals: outcomes.map((outcome) => outcome.refusal ?? null),
413
+ return capture.finish(
414
+ {
415
+ isError: aborted || outcomes.some((o) => o.control === "failed"),
416
+ content: [
417
+ { type: "text", text: `${report}${tail}` },
418
+ executionEvidenceContent("delegate_chain", outcomes, steps.length),
419
+ ],
420
+ details: {
421
+ outcomes,
422
+ steps: steps.length,
423
+ completed: outcomes.filter((o) => o.ok).length,
424
+ aborted,
425
+ budgetPerStep: firstReservation.childAllowance,
426
+ ...(blockedReason ? { blockedReason } : {}),
427
+ refusals: outcomes.map((outcome) => outcome.refusal ?? null),
428
+ },
424
429
  },
425
- };
430
+ outcomes,
431
+ );
426
432
  } finally {
427
433
  firstReservation.finalize({ state: "not-started", reason: "chain ended before its first execution" });
428
434
  }
@@ -1,3 +1,4 @@
1
+ import { publicEvidenceCall } from "./public-evidence.ts";
1
2
  import { executionEvidenceContent } from "./execution-evidence.ts";
2
3
  import { reconcileDelegationCapacity, reserveDelegationCapacity } from "./session-capacity.ts";
3
4
  import { registerDefinitionDescribe } from "./definition-describe.ts";
@@ -253,6 +254,14 @@ export function registerDelegationTools(pi: ExtensionAPI, session: GrantsSession
253
254
  // ADR-0032: one child, same block. `_onUpdate` was discarded here, so a delegation showed the bare word
254
255
  // `delegate` for up to DEFAULT_TIMEOUT_MS — sixty minutes by default.
255
256
  const progress = progressReporter(session, [params.agent ?? "delegate"], onUpdate as never);
257
+ const occurrence = newDelegationOccurrence(session, 0);
258
+ const capture = publicEvidenceCall(
259
+ session.publicEvidence,
260
+ _toolCallId,
261
+ "delegate",
262
+ [params],
263
+ [occurrence.executionId],
264
+ );
256
265
  const outcome = await runOneDelegation(
257
266
  session,
258
267
  {
@@ -266,12 +275,16 @@ export function registerDelegationTools(pi: ExtensionAPI, session: GrantsSession
266
275
  correlation: params.correlation,
267
276
  workspace: params.workspace,
268
277
  },
269
- newDelegationOccurrence(session, 0),
278
+ occurrence,
270
279
  // Reserve one actual child plus the currently available disjoint subtree allowance.
271
280
  undefined,
272
281
  ctx,
273
282
  signal,
274
- { onProgress: progress.sink(0), toolCallId: _toolCallId },
283
+ {
284
+ onProgress: progress.sink(0),
285
+ toolCallId: _toolCallId,
286
+ onDefinition: (definition) => capture.selected(0, definition),
287
+ },
275
288
  );
276
289
  progress.settle([outcome]);
277
290
 
@@ -284,19 +297,22 @@ export function registerDelegationTools(pi: ExtensionAPI, session: GrantsSession
284
297
  }
285
298
  // Pi 1.0.4 preserves returned isError. Keep every outcome dimension and useful output together.
286
299
  const failed = !outcome.ok || outcome.control === "failed";
287
- return {
288
- isError: failed,
289
- content: [
290
- {
291
- type: "text",
292
- text: failed
293
- ? `delegation failed: ${outcome.reason ?? "required control failed"}${outcome.text ? `\n\n${outcome.text}` : ""}`
294
- : outcome.text || "(no output)",
295
- },
296
- executionEvidenceContent("delegate", [outcome], 1),
297
- ],
298
- details: outcome,
299
- };
300
+ return capture.finish(
301
+ {
302
+ isError: failed,
303
+ content: [
304
+ {
305
+ type: "text",
306
+ text: failed
307
+ ? `delegation failed: ${outcome.reason ?? "required control failed"}${outcome.text ? `\n\n${outcome.text}` : ""}`
308
+ : outcome.text || "(no output)",
309
+ },
310
+ executionEvidenceContent("delegate", [outcome], 1),
311
+ ],
312
+ details: outcome,
313
+ },
314
+ [outcome],
315
+ );
300
316
  },
301
317
  });
302
318
 
@@ -356,11 +372,19 @@ export function registerDelegationTools(pi: ExtensionAPI, session: GrantsSession
356
372
  // (R-116).
357
373
  const infrastructureErrors: unknown[] = [],
358
374
  occurrences = children.map((_, index) => newDelegationOccurrence(session, index));
375
+ const capture = publicEvidenceCall(
376
+ session.publicEvidence,
377
+ _toolCallId,
378
+ "delegate_all",
379
+ children,
380
+ occurrences.map((row) => row.executionId),
381
+ );
359
382
  const pending = children.map(async (child, index): Promise<DelegationOutcome> => {
360
383
  try {
361
384
  return await runOneDelegation(session, child, occurrences[index], split.perChild, ctx, signal, {
362
385
  // Reserve each batch share synchronously here, before another dispatch can interleave.
363
386
  capacityReservation: reserveDelegationCapacity(session, occurrences[index].executionId, split.perChild),
387
+ onDefinition: (definition) => capture.selected(index, definition),
364
388
  onProgress: progress.sink(index),
365
389
  toolCallId: _toolCallId,
366
390
  });
@@ -385,19 +409,25 @@ export function registerDelegationTools(pi: ExtensionAPI, session: GrantsSession
385
409
  if (failed.length === children.length && outcomes.every((o) => !o.work && !o.final && !o.cleanup))
386
410
  throw totalFanoutFailure(failed, `fan-out failed: every child was refused or failed.\n\n${report}`);
387
411
 
388
- return {
389
- isError: outcomes.some((o) => !o.ok || o.control === "failed"),
390
- content: [{ type: "text", text: report }, executionEvidenceContent("delegate_all", outcomes, children.length)],
391
- details: {
392
- outcomes,
393
- children: outcomes.length,
394
- failed: failed.length,
395
- budgetPerChild: split.perChild,
396
- granted: outcomes.map((o) => o.granted),
397
- refusals: outcomes.map((o) => o.refusal ?? null),
398
- retention: outcomes.map((o) => o.retention ?? null),
412
+ return capture.finish(
413
+ {
414
+ isError: outcomes.some((o) => !o.ok || o.control === "failed"),
415
+ content: [
416
+ { type: "text", text: report },
417
+ executionEvidenceContent("delegate_all", outcomes, children.length),
418
+ ],
419
+ details: {
420
+ outcomes,
421
+ children: outcomes.length,
422
+ failed: failed.length,
423
+ budgetPerChild: split.perChild,
424
+ granted: outcomes.map((o) => o.granted),
425
+ refusals: outcomes.map((o) => o.refusal ?? null),
426
+ retention: outcomes.map((o) => o.retention ?? null),
427
+ },
399
428
  },
400
- };
429
+ outcomes,
430
+ );
401
431
  },
402
432
  });
403
433
 
@@ -1,7 +1,7 @@
1
1
  import { BoundedReadCleanupError } from "../src/kernel/bounded-read.ts";
2
2
  import { retainDiscoveryCleanupFailure } from "./session.ts";
3
3
  import type { CapacityReservation } from "../src/kernel/capacity.ts";
4
- import { HERDR_UNQUALIFIED_REASON } from "../src/executors/executor.ts";
4
+ import { runHerdrOwned } from "../src/executors/herdr-owned.ts";
5
5
  import { runCapturedExecution } from "../src/executors/captured-execution.ts";
6
6
  import type { ChildFinal } from "../src/executors/child-final.ts";
7
7
  import type { CapturedWorkerCleanup } from "../src/kernel/captured-worker-contract.ts";
@@ -20,13 +20,12 @@ import {
20
20
  ENV_CHILD_IDLE_TIMEOUT,
21
21
  ENV_CHILD_TIMEOUT,
22
22
  idleTimeoutFromEnv,
23
- runChild,
24
23
  timeoutFromEnv,
25
24
  } from "../src/kernel/run-child.ts";
26
25
  import { activitySessionFor } from "../src/executors/activity-session.ts";
27
26
  import { processTreeActivity } from "../src/executors/process-activity.ts";
28
27
  import { resolveWorkspace } from "../src/executors/herdr-cli.ts";
29
- import { HerdrWriterCloseError, runHerdrPane } from "../src/executors/run-herdr.ts";
28
+ import { HerdrWriterCloseError } from "../src/executors/run-herdr.ts";
30
29
  import { GovernanceRefusal, refusal, type StructuredRefusal } from "../src/kernel/refusals.ts";
31
30
  import { ENV_HERDR_KEEP_PANE, type GrantsSession } from "./session.ts";
32
31
  import { CHILD_ATTRIBUTION_ENV_KEYS, ENV_EPISODE_ID } from "../src/kernel/env-names.ts";
@@ -138,15 +137,49 @@ export interface ChildExecutionInput {
138
137
  }
139
138
  export async function executePlannedChild(input: ChildExecutionInput): Promise<DelegationOutcome> {
140
139
  let executorEntered = false;
140
+ const runtime = input.session.reloadLifecycle?.runtimeSettlement;
141
+ let registered = false;
141
142
  try {
142
- return await executePreparedChild(input, () => {
143
- executorEntered = true;
144
- });
143
+ runtime?.begin(input.executionId);
144
+ registered = Boolean(runtime);
145
+ const result = await executePreparedChild(
146
+ input,
147
+ () => {
148
+ executorEntered = true;
149
+ },
150
+ runtime,
151
+ );
152
+ try {
153
+ await runtime?.finish(
154
+ input.executionId,
155
+ result.cleanup ?? { state: "unknown", reason: "executor omitted cleanup" },
156
+ result.control === "failed" ? (result.reason ?? "runtime control failed") : undefined,
157
+ );
158
+ } catch (error) {
159
+ return {
160
+ ...result,
161
+ control: "failed",
162
+ reason: [result.reason, `runtime settlement record failed: ${String(error)}`].filter(Boolean).join("; "),
163
+ };
164
+ }
165
+ return result;
145
166
  } 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
167
  const failures: unknown[] = [error];
168
+ if (registered)
169
+ try {
170
+ await runtime?.finish(input.executionId, {
171
+ state: executorEntered ? "unknown" : "not-started",
172
+ reason: String(error),
173
+ });
174
+ } catch (recordError) {
175
+ failures.push(recordError);
176
+ }
177
+ if (executorEntered) {
178
+ if (failures.length > 1) throw new AggregateError(failures, failures.map(String).join("; "));
179
+ throw error;
180
+ }
181
+ // Setup failures prove no worker started, even if recording that fact also failed.
182
+ input.capacityReservation?.finalize({ state: "not-started", reason: "child setup failed before executor entry" });
150
183
  try {
151
184
  const released = await releaseDelegationWorkspace({
152
185
  prepared: input.preparedWorkspace,
@@ -174,6 +207,7 @@ export async function executePlannedChild(input: ChildExecutionInput): Promise<D
174
207
  async function executePreparedChild(
175
208
  input: ChildExecutionInput,
176
209
  onExecutorEntry: () => void,
210
+ runtime?: import("../src/governance/runtime-settlement.ts").RuntimeSettlement,
177
211
  ): Promise<DelegationOutcome> {
178
212
  const { session, plan, childId, executionId, parentExecutionId, preparedWorkspace, signal, onProgress } = input;
179
213
  const ledgerPath = session.ledgerPath;
@@ -229,7 +263,7 @@ async function executePreparedChild(
229
263
  const activitySession = await activitySessionFor(plan.args, executionId);
230
264
  // Disposed on every path (review finding: it leaked on every throw), except when the operator keeps a Herdr pane,
231
265
  // where the interactive pi in that pane is still alive and still appending to this file.
232
- const keepPaneRequested = session.executor.kind === "herdr" && process.env[ENV_HERDR_KEEP_PANE] === "1";
266
+ const keepPaneRequested = false; // The qualified pane launcher exits; retained panes contain no live Pi writer.
233
267
  try {
234
268
  return await executeWithActivitySession();
235
269
  } finally {
@@ -368,73 +402,63 @@ async function executePreparedChild(
368
402
  }
369
403
  };
370
404
  try {
371
- if (String(session.executor.kind) === "herdr")
372
- throw new GovernanceRefusal(refusal("EXECUTOR_UNAVAILABLE", HERDR_UNQUALIFIED_REASON));
405
+ if (session.executor.refusal)
406
+ throw new GovernanceRefusal(refusal("EXECUTOR_UNAVAILABLE", session.executor.refusal));
373
407
  executorEntered = true;
374
408
  onExecutorEntry();
375
- const output =
376
- session.executor.kind === "herdr"
377
- ? await runHerdrPane({
378
- args: args.slice(0, -1),
379
- prompt: args[args.length - 1].trimStart(),
380
- env: plan.env,
381
- cwd,
382
- name: `${input.agent ?? "delegate"}-${childId}`,
383
- workspace: resolveWorkspace(process.env),
384
- signal: executionSignal,
385
- timeoutMs: remainingTimeoutMs,
386
- idleTimeoutMs: configuredIdleMs,
387
- activityProbe: activitySession.probe, // Herdr: session file plus pane text when a display is attached
388
- keepPane: writerLease ? false : process.env[ENV_HERDR_KEEP_PANE] === "1",
389
- closeOnSettle: Boolean(writerLease),
390
- onPane: (paneId, agentName) => {
391
- retention.native({ paneId, agentName });
392
- onProgress?.({ paneId, agentName, state: "starting" });
393
- },
394
- onObservation: (bytes) => retention.capture("paneSnapshot", bytes, true),
395
- onSessionReference: (reference) => retention.observeSession(reference),
396
- onRunning: (paneId, agentName) => {
397
- // Record first: runHerdrPane isolates this display callback, so a renderer exception after the
398
- // observation cannot suppress the authoritative running event.
399
- recordRunning("herdr", { id: paneId, agentName });
400
- onProgress?.({ paneId, agentName, state: "running" });
401
- },
402
- onTab: preparedWorkspace ? (tabId) => preparedWorkspace.lease.attachHerdrTab(tabId) : undefined,
403
- onNativeTab: (tabId) => retention.native({ tabId }),
404
- onSnapshot: onProgress ? (snapshot) => onProgress({ snapshot }) : undefined,
405
- })
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
- env: mergeChildEnv(process.env, plan.env),
417
- cwd,
418
- signal: executionSignal,
419
- // SIGTERM gets only grace that fits INSIDE the recorded deadline. The independent hard timer
420
- // prevents a delayed soft-timeout callback from starting a fresh grace period beyond that bound.
421
- timeoutMs: Math.max(1, remainingTimeoutMs - terminationGraceMs),
422
- hardDeadlineAt: Date.parse(deadlineAt),
423
- idleTimeoutMs: configuredIdleMs,
424
- activityProbe: probe,
425
- onOutput: onProgress ? (chunk) => onProgress({ chunk }) : undefined,
426
- onObservation: (stream, bytes) => retention.capture(stream, bytes),
427
- onSpawn: (pid) => {
428
- childPid = pid;
429
- // Lease attachment is a security hook and may fail the spawn. The shared reporters isolate
430
- // display exceptions before they reach this callback; runChild deliberately kills on any error
431
- // here, so presentation must never be added directly without that reporter boundary.
409
+ let displayHerdr: ((text: string) => void) | undefined;
410
+ const output = await runCapturedExecution(
411
+ {
412
+ executionId,
413
+ sessionPath: activitySession.path,
414
+ onReadCleanup,
415
+ onOwnership: async (identity) => {
416
+ runtime?.bind(identity);
417
+ input.capacityReservation?.bindOwnership(identity);
418
+ await preparedWorkspace?.lease.attachCapturedWorker(identity);
419
+ },
420
+ command: "pi",
421
+ args,
422
+ env: mergeChildEnv(process.env, plan.env),
423
+ cwd,
424
+ signal: executionSignal,
425
+ // SIGTERM gets only grace that fits INSIDE the recorded deadline. The independent hard timer
426
+ // prevents a delayed soft-timeout callback from starting a fresh grace period beyond that bound.
427
+ timeoutMs: Math.max(1, remainingTimeoutMs - terminationGraceMs),
428
+ hardDeadlineAt: Date.parse(deadlineAt),
429
+ idleTimeoutMs: configuredIdleMs,
430
+ activityProbe: probe,
431
+ onOutput: (chunk) => {
432
+ displayHerdr?.(chunk);
433
+ onProgress?.({ chunk });
434
+ },
435
+ onObservation: (stream, bytes) => retention.capture(stream, bytes),
436
+ onSpawn: (pid) => {
437
+ childPid = pid;
438
+ // Lease attachment is a security hook and may fail the spawn. The shared reporters isolate
439
+ // display exceptions before they reach this callback; runChild deliberately kills on any error
440
+ // here, so presentation must never be added directly without that reporter boundary.
432
441
 
433
- retention.native({ pid });
434
- recordRunning("process");
435
- onProgress?.({ state: "running" });
436
- },
437
- });
442
+ retention.native({ pid });
443
+ recordRunning(session.executor.kind);
444
+ onProgress?.({ state: "running" });
445
+ },
446
+ },
447
+ session.executor.kind === "herdr"
448
+ ? (request) =>
449
+ runHerdrOwned(request, {
450
+ onDisplay: (display) => {
451
+ displayHerdr = display;
452
+ },
453
+ workspace: resolveWorkspace(process.env),
454
+ keepPane: process.env[ENV_HERDR_KEEP_PANE] === "1",
455
+ onPane: (paneId, tabId) => {
456
+ retention.native({ paneId, tabId });
457
+ onProgress?.({ paneId, state: "starting" });
458
+ },
459
+ })
460
+ : undefined,
461
+ );
438
462
 
439
463
  if (sessionFlag >= 0) retention.observeSession({ source: "pi-session-file", value: plan.args[sessionFlag + 1] });
440
464
  retention.capture("result", Buffer.from(output.text), true);