pi-daddy 0.43.0 → 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.
Files changed (222) hide show
  1. package/CHANGELOG.md +104 -33
  2. package/README.md +27 -5
  3. package/THIRD_PARTY_LICENSES/musl-COPYRIGHT.txt +193 -0
  4. package/THIRD_PARTY_LICENSES/zig-MIT.txt +21 -0
  5. package/THIRD_PARTY_NOTICES.md +9 -0
  6. package/contracts/ledger-record/v1/README.md +40 -25
  7. package/dist/cli.d.ts +2 -1
  8. package/dist/cli.d.ts.map +1 -1
  9. package/dist/cli.js +10 -5
  10. package/dist/cli.js.map +1 -1
  11. package/dist/executors/activity-session.js +116 -65
  12. package/dist/executors/activity-session.js.map +1 -1
  13. package/dist/executors/captured-execution.d.ts +13 -0
  14. package/dist/executors/captured-execution.d.ts.map +1 -0
  15. package/dist/executors/captured-execution.js +53 -0
  16. package/dist/executors/captured-execution.js.map +1 -0
  17. package/dist/executors/child-final.d.ts +31 -0
  18. package/dist/executors/child-final.d.ts.map +1 -0
  19. package/dist/executors/child-final.js +197 -0
  20. package/dist/executors/child-final.js.map +1 -0
  21. package/dist/executors/executor.d.ts +2 -1
  22. package/dist/executors/executor.d.ts.map +1 -1
  23. package/dist/executors/executor.js +12 -0
  24. package/dist/executors/executor.js.map +1 -1
  25. package/dist/executors/owned-worker.d.ts +18 -0
  26. package/dist/executors/owned-worker.d.ts.map +1 -0
  27. package/dist/executors/owned-worker.js +295 -0
  28. package/dist/executors/owned-worker.js.map +1 -0
  29. package/dist/executors/worker-artifact.d.ts +3 -0
  30. package/dist/executors/worker-artifact.d.ts.map +1 -0
  31. package/dist/executors/worker-artifact.js +3 -0
  32. package/dist/executors/worker-artifact.js.map +1 -0
  33. package/dist/governance/approval-prompt.d.ts +4 -3
  34. package/dist/governance/approval-prompt.d.ts.map +1 -1
  35. package/dist/governance/approval-prompt.js +13 -6
  36. package/dist/governance/approval-prompt.js.map +1 -1
  37. package/dist/governance/captured-worker-record.d.ts +9 -0
  38. package/dist/governance/captured-worker-record.d.ts.map +1 -0
  39. package/dist/governance/captured-worker-record.js +123 -0
  40. package/dist/governance/captured-worker-record.js.map +1 -0
  41. package/dist/governance/lease-record.d.ts +4 -0
  42. package/dist/governance/lease-record.d.ts.map +1 -1
  43. package/dist/governance/lease-record.js +19 -2
  44. package/dist/governance/lease-record.js.map +1 -1
  45. package/dist/governance/ledger-events.d.ts +5 -5
  46. package/dist/governance/ledger-events.d.ts.map +1 -1
  47. package/dist/governance/ledger-v3-validation.js +1 -1
  48. package/dist/governance/ledger-v3-validation.js.map +1 -1
  49. package/dist/governance/workspace-acceptance.d.ts +3 -1
  50. package/dist/governance/workspace-acceptance.d.ts.map +1 -1
  51. package/dist/governance/workspace-acceptance.js +21 -5
  52. package/dist/governance/workspace-acceptance.js.map +1 -1
  53. package/dist/governance/workspace-lease.d.ts.map +1 -1
  54. package/dist/governance/workspace-lease.js +44 -1
  55. package/dist/governance/workspace-lease.js.map +1 -1
  56. package/dist/kernel/authority-text.d.ts +3 -0
  57. package/dist/kernel/authority-text.d.ts.map +1 -0
  58. package/dist/kernel/authority-text.js +22 -0
  59. package/dist/kernel/authority-text.js.map +1 -0
  60. package/dist/kernel/bounded-read-failures.d.ts +9 -0
  61. package/dist/kernel/bounded-read-failures.d.ts.map +1 -0
  62. package/dist/kernel/bounded-read-failures.js +33 -0
  63. package/dist/kernel/bounded-read-failures.js.map +1 -0
  64. package/dist/kernel/bounded-read-sync.d.ts +24 -0
  65. package/dist/kernel/bounded-read-sync.d.ts.map +1 -0
  66. package/dist/kernel/bounded-read-sync.js +75 -0
  67. package/dist/kernel/bounded-read-sync.js.map +1 -0
  68. package/dist/kernel/bounded-read.d.ts +29 -2
  69. package/dist/kernel/bounded-read.d.ts.map +1 -1
  70. package/dist/kernel/bounded-read.js +98 -9
  71. package/dist/kernel/bounded-read.js.map +1 -1
  72. package/dist/kernel/capacity.d.ts +36 -0
  73. package/dist/kernel/capacity.d.ts.map +1 -0
  74. package/dist/kernel/capacity.js +94 -0
  75. package/dist/kernel/capacity.js.map +1 -0
  76. package/dist/kernel/captured-worker-contract.d.ts +38 -0
  77. package/dist/kernel/captured-worker-contract.d.ts.map +1 -0
  78. package/dist/kernel/captured-worker-contract.js +2 -0
  79. package/dist/kernel/captured-worker-contract.js.map +1 -0
  80. package/dist/kernel/catalog.d.ts +12 -3
  81. package/dist/kernel/catalog.d.ts.map +1 -1
  82. package/dist/kernel/catalog.js +28 -8
  83. package/dist/kernel/catalog.js.map +1 -1
  84. package/dist/kernel/chain.d.ts +1 -0
  85. package/dist/kernel/chain.d.ts.map +1 -1
  86. package/dist/kernel/chain.js.map +1 -1
  87. package/dist/kernel/definitions.d.ts +11 -1
  88. package/dist/kernel/definitions.d.ts.map +1 -1
  89. package/dist/kernel/definitions.js +6 -3
  90. package/dist/kernel/definitions.js.map +1 -1
  91. package/dist/kernel/fanout.d.ts +5 -25
  92. package/dist/kernel/fanout.d.ts.map +1 -1
  93. package/dist/kernel/fanout.js +15 -30
  94. package/dist/kernel/fanout.js.map +1 -1
  95. package/dist/kernel/grant-env.d.ts +0 -2
  96. package/dist/kernel/grant-env.d.ts.map +1 -1
  97. package/dist/kernel/grant-env.js +0 -1
  98. package/dist/kernel/grant-env.js.map +1 -1
  99. package/dist/kernel/model-preflight.d.ts +8 -0
  100. package/dist/kernel/model-preflight.d.ts.map +1 -1
  101. package/dist/kernel/model-preflight.js +23 -0
  102. package/dist/kernel/model-preflight.js.map +1 -1
  103. package/dist/kernel/selected-definitions.d.ts +15 -0
  104. package/dist/kernel/selected-definitions.d.ts.map +1 -0
  105. package/dist/kernel/selected-definitions.js +146 -0
  106. package/dist/kernel/selected-definitions.js.map +1 -0
  107. package/dist/kernel/skill-packages.d.ts +4 -2
  108. package/dist/kernel/skill-packages.d.ts.map +1 -1
  109. package/dist/kernel/skill-packages.js +34 -41
  110. package/dist/kernel/skill-packages.js.map +1 -1
  111. package/dist/kernel/skill-resources.d.ts.map +1 -1
  112. package/dist/kernel/skill-resources.js +8 -11
  113. package/dist/kernel/skill-resources.js.map +1 -1
  114. package/dist/kernel/workspace.d.ts +6 -3
  115. package/dist/kernel/workspace.d.ts.map +1 -1
  116. package/dist/kernel/workspace.js +10 -6
  117. package/dist/kernel/workspace.js.map +1 -1
  118. package/dist/products/dashboard-cli.js +1 -1
  119. package/dist/products/dashboard-cli.js.map +1 -1
  120. package/dist/products/dashboard-render.d.ts +1 -2
  121. package/dist/products/dashboard-render.d.ts.map +1 -1
  122. package/dist/products/dashboard-render.js +3 -1
  123. package/dist/products/dashboard-render.js.map +1 -1
  124. package/dist/products/dashboard-session-client.d.ts +1 -2
  125. package/dist/products/dashboard-session-client.d.ts.map +1 -1
  126. package/dist/products/dashboard-session-client.js.map +1 -1
  127. package/dist/products/episode-report.d.ts +12 -8
  128. package/dist/products/episode-report.d.ts.map +1 -1
  129. package/dist/products/episode-report.js +61 -12
  130. package/dist/products/episode-report.js.map +1 -1
  131. package/extensions/approvals.ts +1 -1
  132. package/extensions/chain-plan.ts +9 -3
  133. package/extensions/context-staging.ts +30 -35
  134. package/extensions/dashboard-session-server.ts +1 -2
  135. package/extensions/definition-describe.ts +44 -0
  136. package/extensions/definition-runtime.ts +89 -26
  137. package/extensions/delegate-chain.ts +273 -256
  138. package/extensions/delegation-activation.ts +1 -1
  139. package/extensions/delegation.ts +41 -53
  140. package/extensions/execute-child.ts +167 -134
  141. package/extensions/fanout-outcome.ts +1 -9
  142. package/extensions/grants-command.ts +2 -9
  143. package/extensions/grants.ts +106 -38
  144. package/extensions/init-command.ts +52 -12
  145. package/extensions/reload-environment.ts +21 -2
  146. package/extensions/retired-inputs.ts +7 -0
  147. package/extensions/run-delegation.ts +212 -265
  148. package/extensions/session-capacity.ts +53 -0
  149. package/extensions/session-environment.ts +5 -2
  150. package/extensions/session-model-prompt.ts +33 -32
  151. package/extensions/session-report.ts +28 -11
  152. package/extensions/session.ts +200 -157
  153. package/extensions/workspace-runtime.ts +2 -1
  154. package/native/README.md +25 -0
  155. package/native/linux-x64/worker +0 -0
  156. package/native/linux-x64/worker.sha256 +1 -0
  157. package/native/provenance.json +42 -0
  158. package/native/toolchain.json +18 -0
  159. package/native/worker.c +209 -0
  160. package/package.json +15 -7
  161. package/src/cli.ts +16 -6
  162. package/src/executors/activity-session.ts +120 -66
  163. package/src/executors/captured-execution.ts +61 -0
  164. package/src/executors/child-final.ts +190 -0
  165. package/src/executors/executor.ts +14 -1
  166. package/src/executors/owned-worker.ts +322 -0
  167. package/src/executors/worker-artifact.ts +2 -0
  168. package/src/governance/approval-prompt.ts +18 -8
  169. package/src/governance/captured-worker-record.ts +138 -0
  170. package/src/governance/lease-record.ts +21 -2
  171. package/src/governance/ledger-events.ts +5 -5
  172. package/src/governance/ledger-v3-validation.ts +1 -1
  173. package/src/governance/workspace-acceptance.ts +20 -4
  174. package/src/governance/workspace-lease.ts +59 -1
  175. package/src/kernel/authority-text.ts +17 -0
  176. package/src/kernel/bounded-read-failures.ts +42 -0
  177. package/src/kernel/bounded-read-sync.ts +87 -0
  178. package/src/kernel/bounded-read.ts +128 -11
  179. package/src/kernel/capacity.ts +123 -0
  180. package/src/kernel/captured-worker-contract.ts +29 -0
  181. package/src/kernel/catalog.ts +45 -16
  182. package/src/kernel/chain.ts +1 -0
  183. package/src/kernel/definitions.ts +11 -2
  184. package/src/kernel/fanout.ts +17 -32
  185. package/src/kernel/grant-env.ts +0 -3
  186. package/src/kernel/model-preflight.ts +29 -0
  187. package/src/kernel/selected-definitions.ts +156 -0
  188. package/src/kernel/skill-packages.ts +40 -38
  189. package/src/kernel/skill-resources.ts +8 -9
  190. package/src/kernel/workspace.ts +12 -5
  191. package/src/products/dashboard-cli.ts +1 -1
  192. package/src/products/dashboard-render.ts +6 -2
  193. package/src/products/dashboard-session-client.ts +1 -2
  194. package/src/products/episode-report.ts +80 -17
  195. package/dist/advisors/advisor.d.ts +0 -90
  196. package/dist/advisors/advisor.d.ts.map +0 -1
  197. package/dist/advisors/advisor.js +0 -154
  198. package/dist/advisors/advisor.js.map +0 -1
  199. package/dist/advisors/decider.d.ts +0 -88
  200. package/dist/advisors/decider.d.ts.map +0 -1
  201. package/dist/advisors/decider.js +0 -29
  202. package/dist/advisors/decider.js.map +0 -1
  203. package/dist/advisors/jev.d.ts +0 -41
  204. package/dist/advisors/jev.d.ts.map +0 -1
  205. package/dist/advisors/jev.js +0 -139
  206. package/dist/advisors/jev.js.map +0 -1
  207. package/dist/advisors/settings.d.ts +0 -49
  208. package/dist/advisors/settings.d.ts.map +0 -1
  209. package/dist/advisors/settings.js +0 -99
  210. package/dist/advisors/settings.js.map +0 -1
  211. package/dist/governance/episode-cost-gate.d.ts +0 -31
  212. package/dist/governance/episode-cost-gate.d.ts.map +0 -1
  213. package/dist/governance/episode-cost-gate.js +0 -112
  214. package/dist/governance/episode-cost-gate.js.map +0 -1
  215. package/extensions/advisor-session.ts +0 -67
  216. package/extensions/effort-advice.ts +0 -112
  217. package/extensions/pruning-advice.ts +0 -133
  218. package/src/advisors/advisor.ts +0 -246
  219. package/src/advisors/decider.ts +0 -74
  220. package/src/advisors/jev.ts +0 -163
  221. package/src/advisors/settings.ts +0 -115
  222. package/src/governance/episode-cost-gate.ts +0 -128
@@ -1,99 +0,0 @@
1
- /**
2
- * Whether an advisor is on, and which one (ADR-0077).
3
- *
4
- * **Default off, and only the environment can turn it on.** An advisor sends a description of the caller's
5
- * situation to a third party, so enabling one is `PI_DADDY_ADVISOR=jev` plus a key — both outside the workspace,
6
- * both stripped from every child.
7
- *
8
- * 0.34.0 read the enable from `.pi/pi-daddy/settings.json` and that was wrong for the reason `grant-store.ts`
9
- * states about the same file: it is writable by any child holding `tool:write`, so it is "the reviewable record of
10
- * the decision, not the thing the enforcer reads". A grant lives outside the workspace precisely so a child cannot
11
- * widen the next session's ceiling; an advisor switch a child could flip would make the operator's next session
12
- * ship its own description to a third party, which is the same self-defeating shape. The settings block may still
13
- * NARROW — a shorter timeout, or `enabled: false` to turn an advisor off for one project — and can never turn one
14
- * on, choose its model, or lengthen its bound. A model is a destination rather than a narrowing, so `model` in the
15
- * block is refused with a message naming `PI_DADDY_ADVISOR_MODEL`, and a timeout is clamped to the default rather
16
- * than trusted. Malformed configuration disables the advisor and says so: a typo must not be a way to enable anything.
17
- *
18
- * **Not a dashboard toggle**, which is what the programme originally sketched. The dashboard is a read-only
19
- * renderer in a separate process that "never affects enforcement" (ADR-0036), and a control there that wrote to
20
- * settings would be the first thing it ever wrote. Turning an advisor on is an operator decision that belongs in
21
- * the reviewable file; `/grants` reports what is in force. That is a deliberate departure from the roadmap line.
22
- *
23
- * The key is never in the settings file either, because that file is committed and an API key must not be.
24
- */
25
- // Spelled once, in the kernel's table, so this layer cannot drift from the list `childEnv` refuses to write.
26
- export { ENV_ADVISOR_KEY as ADVISOR_KEY_ENV } from "../kernel/env-names.js";
27
- import { ENV_ADVISOR, ENV_ADVISOR_KEY, ENV_ADVISOR_MODEL, ENV_ADVISOR_TASK_EGRESS } from "../kernel/env-names.js";
28
- import { DEFAULT_ADVICE_TIMEOUT_MS } from "./advisor.js";
29
- export { ENV_ADVISOR_MODEL, ENV_ADVISOR_TASK_EGRESS } from "../kernel/env-names.js";
30
- export { ENV_ADVISOR } from "../kernel/env-names.js";
31
- export const ADVISOR_OFF = Object.freeze({ enabled: false, decider: "none", taskEgress: "digest" });
32
- /**
33
- * Read the `advisor` block of a project settings file. Absent is off; malformed is off WITH a reason.
34
- *
35
- * The reason matters more than it looks: an operator who wrote `"enabeld": true` and got silence would conclude the
36
- * feature does not work, and an operator who wrote it and got an advisor anyway would have a third party reading
37
- * their session without having successfully asked for it. Both are worse than a sentence naming the field.
38
- */
39
- export function advisorSettingsFrom(raw, env = process.env) {
40
- // The environment decides WHETHER, before the workspace is consulted at all. A settings file that asks for an
41
- // advisor nobody enabled is not a configuration error; it is simply a project that would use one if the operator
42
- // turned it on, so it is reported rather than refused.
43
- const requested = env[ENV_ADVISOR]?.trim();
44
- if (!requested)
45
- return ADVISOR_OFF;
46
- if (requested !== "jev")
47
- return { ...ADVISOR_OFF, refusal: `${ENV_ADVISOR}=${requested} names no advisor this release knows` };
48
- const block = raw === undefined || raw === null ? {} : raw;
49
- if (typeof block !== "object" || Array.isArray(block))
50
- return { ...ADVISOR_OFF, refusal: "settings.advisor must be an object; no advisor is enabled" };
51
- const fields = block;
52
- // `model` stays KNOWN so the refusal below can say why it is refused, rather than reporting it as a typo.
53
- const unknownKeys = Object.keys(fields).filter((key) => !["enabled", "decider", "model", "timeoutMs"].includes(key));
54
- // Key names are echoed back, and this file is workspace-writable: a key containing an escape sequence or a
55
- // newline would otherwise forge lines in the `/grants` panel, which is a trust surface. Review measured a forged
56
- // `grant tool:*` line. Sanitised and truncated before it reaches any renderer.
57
- if (unknownKeys.length > 0)
58
- return {
59
- ...ADVISOR_OFF,
60
- refusal: `settings.advisor has unknown field(s) ${unknownKeys
61
- .map((key) => key.replace(/[^\w.-]/g, "?").slice(0, 40))
62
- .join(", ")}`,
63
- };
64
- // A project may switch it OFF; it may never switch it on, which is why `true` is not read. Anything that is not
65
- // exactly `true` disables: `"false"`, `0` and `null` used to leave the advisor ON with no word said, which is
66
- // rule 8 inverted — the one control this file retains failing open.
67
- if (fields.enabled !== undefined && fields.enabled !== true)
68
- return { ...ADVISOR_OFF, refusal: "settings.advisor.enabled is not true for this project" };
69
- if (fields.decider !== undefined && fields.decider !== "jev")
70
- return { ...ADVISOR_OFF, refusal: `settings.advisor.decider must be "jev"; no advisor is enabled` };
71
- // **A model is a DESTINATION, not a narrowing.** Letting this workspace-writable file choose it would let a child
72
- // holding `tool:write` point the operator's next session at a generative model of its choosing, billed to the
73
- // operator's key — the same self-defeating shape this release moved the enable switch to close, one step
74
- // sideways. Review measured it. The model comes from the environment or not at all.
75
- if (fields.model !== undefined)
76
- return { ...ADVISOR_OFF, refusal: `settings.advisor.model is not a narrowing; set ${ENV_ADVISOR_MODEL} instead` };
77
- if (fields.timeoutMs !== undefined &&
78
- (!Number.isInteger(fields.timeoutMs) || fields.timeoutMs < 1 || fields.timeoutMs > 30_000))
79
- return { ...ADVISOR_OFF, refusal: "settings.advisor.timeoutMs must be an integer between 1 and 30000" };
80
- const model = env[ENV_ADVISOR_MODEL]?.trim();
81
- const key = env[ENV_ADVISOR_KEY]?.trim();
82
- if (!key)
83
- return {
84
- ...ADVISOR_OFF,
85
- refusal: `settings.advisor is enabled but ${ENV_ADVISOR_KEY} is not set; no advisor is enabled`,
86
- };
87
- return {
88
- enabled: true,
89
- decider: "jev",
90
- taskEgress: env[ENV_ADVISOR_TASK_EGRESS]?.trim() === "raw" ? "raw" : "digest",
91
- ...(model ? { model } : {}),
92
- // Clamped, never raised: a longer bound is not a narrowing either, and a child-writable 30s would be a stall on
93
- // every delegation.
94
- ...(fields.timeoutMs !== undefined
95
- ? { timeoutMs: Math.min(fields.timeoutMs, DEFAULT_ADVICE_TIMEOUT_MS) }
96
- : {}),
97
- };
98
- }
99
- //# sourceMappingURL=settings.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"settings.js","sourceRoot":"","sources":["../../src/advisors/settings.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,6GAA6G;AAC7G,OAAO,EAAE,eAAe,IAAI,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAC5E,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,iBAAiB,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AAClH,OAAO,EAAE,yBAAyB,EAAuB,MAAM,cAAc,CAAC;AAC9E,OAAO,EAAE,iBAAiB,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AACpF,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAcrD,MAAM,CAAC,MAAM,WAAW,GAAoB,MAAM,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,CAAC,CAAC;AAErH;;;;;;GAMG;AACH,MAAM,UAAU,mBAAmB,CAAC,GAAY,EAAE,MAAyB,OAAO,CAAC,GAAG;IACpF,8GAA8G;IAC9G,iHAAiH;IACjH,uDAAuD;IACvD,MAAM,SAAS,GAAG,GAAG,CAAC,WAAW,CAAC,EAAE,IAAI,EAAE,CAAC;IAC3C,IAAI,CAAC,SAAS;QAAE,OAAO,WAAW,CAAC;IACnC,IAAI,SAAS,KAAK,KAAK;QACrB,OAAO,EAAE,GAAG,WAAW,EAAE,OAAO,EAAE,GAAG,WAAW,IAAI,SAAS,sCAAsC,EAAE,CAAC;IAExG,MAAM,KAAK,GAAG,GAAG,KAAK,SAAS,IAAI,GAAG,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC;IAC3D,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QACnD,OAAO,EAAE,GAAG,WAAW,EAAE,OAAO,EAAE,2DAA2D,EAAE,CAAC;IAClG,MAAM,MAAM,GAAG,KAAgC,CAAC;IAChD,0GAA0G;IAC1G,MAAM,WAAW,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,EAAE,SAAS,EAAE,OAAO,EAAE,WAAW,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC;IACrH,2GAA2G;IAC3G,iHAAiH;IACjH,+EAA+E;IAC/E,IAAI,WAAW,CAAC,MAAM,GAAG,CAAC;QACxB,OAAO;YACL,GAAG,WAAW;YACd,OAAO,EAAE,yCAAyC,WAAW;iBAC1D,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;iBACvD,IAAI,CAAC,IAAI,CAAC,EAAE;SAChB,CAAC;IACJ,gHAAgH;IAChH,8GAA8G;IAC9G,oEAAoE;IACpE,IAAI,MAAM,CAAC,OAAO,KAAK,SAAS,IAAI,MAAM,CAAC,OAAO,KAAK,IAAI;QACzD,OAAO,EAAE,GAAG,WAAW,EAAE,OAAO,EAAE,uDAAuD,EAAE,CAAC;IAC9F,IAAI,MAAM,CAAC,OAAO,KAAK,SAAS,IAAI,MAAM,CAAC,OAAO,KAAK,KAAK;QAC1D,OAAO,EAAE,GAAG,WAAW,EAAE,OAAO,EAAE,+DAA+D,EAAE,CAAC;IACtG,kHAAkH;IAClH,8GAA8G;IAC9G,yGAAyG;IACzG,oFAAoF;IACpF,IAAK,MAAkC,CAAC,KAAK,KAAK,SAAS;QACzD,OAAO,EAAE,GAAG,WAAW,EAAE,OAAO,EAAE,kDAAkD,iBAAiB,UAAU,EAAE,CAAC;IACpH,IACE,MAAM,CAAC,SAAS,KAAK,SAAS;QAC9B,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,SAAS,CAAC,IAAK,MAAM,CAAC,SAAoB,GAAG,CAAC,IAAK,MAAM,CAAC,SAAoB,GAAG,MAAM,CAAC;QAElH,OAAO,EAAE,GAAG,WAAW,EAAE,OAAO,EAAE,mEAAmE,EAAE,CAAC;IAC1G,MAAM,KAAK,GAAG,GAAG,CAAC,iBAAiB,CAAC,EAAE,IAAI,EAAE,CAAC;IAC7C,MAAM,GAAG,GAAG,GAAG,CAAC,eAAe,CAAC,EAAE,IAAI,EAAE,CAAC;IACzC,IAAI,CAAC,GAAG;QACN,OAAO;YACL,GAAG,WAAW;YACd,OAAO,EAAE,mCAAmC,eAAe,oCAAoC;SAChG,CAAC;IACJ,OAAO;QACL,OAAO,EAAE,IAAI;QACb,OAAO,EAAE,KAAK;QACd,UAAU,EAAE,GAAG,CAAC,uBAAuB,CAAC,EAAE,IAAI,EAAE,KAAK,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ;QAC7E,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC3B,gHAAgH;QAChH,oBAAoB;QACpB,GAAG,CAAC,MAAM,CAAC,SAAS,KAAK,SAAS;YAChC,CAAC,CAAC,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,SAAmB,EAAE,yBAAyB,CAAC,EAAE;YAChF,CAAC,CAAC,EAAE,CAAC;KACR,CAAC;AACJ,CAAC"}
@@ -1,31 +0,0 @@
1
- export declare const DEFAULT_EPISODE_COST_CEILING = 5;
2
- export type EpisodeCostGateAnswer = {
3
- cost: number;
4
- ceiling: number;
5
- outcome: "continued";
6
- newCeiling: number;
7
- } | {
8
- cost: number;
9
- ceiling: number;
10
- outcome: "stopped";
11
- };
12
- export interface EpisodeCostGateHooks {
13
- warn(message: string): void;
14
- pause?(): Promise<unknown> | unknown;
15
- ask?(): Promise<number | null>;
16
- resume?(): Promise<unknown> | unknown;
17
- stop?(): Promise<unknown> | unknown;
18
- gate(event: EpisodeCostGateAnswer): Promise<unknown> | unknown;
19
- }
20
- export declare function episodeCostCeilingFromSettings(value: unknown): number;
21
- export declare class EpisodeCostGate {
22
- #private;
23
- constructor(ceiling: number);
24
- get ceiling(): number;
25
- get cost(): number;
26
- setCeiling(ceiling: number): void;
27
- seedCost(executionId: string, cost: number): void;
28
- observe(executionId: string, cost: number | undefined, terminal: boolean, hooks: EpisodeCostGateHooks): Promise<"continue" | "stop">;
29
- }
30
- export declare function loadEpisodeCosts(path: string, episodeId: string): Promise<Map<string, number>>;
31
- //# sourceMappingURL=episode-cost-gate.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"episode-cost-gate.d.ts","sourceRoot":"","sources":["../../src/governance/episode-cost-gate.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,4BAA4B,IAAI,CAAC;AAE9C,MAAM,MAAM,qBAAqB,GAC7B;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,WAAW,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GAC3E;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,SAAS,CAAA;CAAE,CAAC;AAE1D,MAAM,WAAW,oBAAoB;IACnC,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,KAAK,CAAC,IAAI,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;IACrC,GAAG,CAAC,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC/B,MAAM,CAAC,IAAI,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;IACtC,IAAI,CAAC,IAAI,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;IACpC,IAAI,CAAC,KAAK,EAAE,qBAAqB,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;CAChE;AAED,wBAAgB,8BAA8B,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAMrE;AAED,qBAAa,eAAe;;gBAOd,OAAO,EAAE,MAAM;IAI3B,IAAI,OAAO,IAAI,MAAM,CAEpB;IAED,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAIjC,QAAQ,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAI3C,OAAO,CACX,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,MAAM,GAAG,SAAS,EACxB,QAAQ,EAAE,OAAO,EACjB,KAAK,EAAE,oBAAoB,GAC1B,OAAO,CAAC,UAAU,GAAG,MAAM,CAAC;CA8ChC;AAED,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAoBpG"}
@@ -1,112 +0,0 @@
1
- /** Per-episode cost threshold state shared by every child started by one session. */
2
- import { readFile } from "node:fs/promises";
3
- import { readRecords } from "./record.js";
4
- export const DEFAULT_EPISODE_COST_CEILING = 5;
5
- export function episodeCostCeilingFromSettings(value) {
6
- if (!value || typeof value !== "object" || Array.isArray(value))
7
- return DEFAULT_EPISODE_COST_CEILING;
8
- const ceiling = value.episodeCostCeiling;
9
- return typeof ceiling === "number" && Number.isFinite(ceiling) && ceiling > 0
10
- ? ceiling
11
- : DEFAULT_EPISODE_COST_CEILING;
12
- }
13
- export class EpisodeCostGate {
14
- #ceiling;
15
- #costs = new Map();
16
- #warned = false;
17
- #usageWarning = false;
18
- #decision;
19
- constructor(ceiling) {
20
- this.#ceiling = ceiling;
21
- }
22
- get ceiling() {
23
- return this.#ceiling;
24
- }
25
- get cost() {
26
- return [...this.#costs.values()].reduce((sum, value) => sum + value, 0);
27
- }
28
- setCeiling(ceiling) {
29
- if (Number.isFinite(ceiling) && ceiling > 0 && !this.#decision)
30
- this.#ceiling = ceiling;
31
- }
32
- seedCost(executionId, cost) {
33
- if (Number.isFinite(cost) && cost >= 0)
34
- this.#costs.set(executionId, cost);
35
- }
36
- async observe(executionId, cost, terminal, hooks) {
37
- if (cost === undefined) {
38
- if (terminal && !this.#usageWarning) {
39
- this.#usageWarning = true;
40
- hooks.warn("pi-daddy: provider reported no token usage; the episode cost gate cannot fire");
41
- }
42
- return "continue";
43
- }
44
- this.#costs.set(executionId, cost);
45
- const total = [...this.#costs.values()].reduce((sum, value) => sum + value, 0);
46
- if (!this.#warned && total >= this.#ceiling / 2) {
47
- this.#warned = true;
48
- hooks.warn(`pi-daddy: episode cost reached 50% of the $${this.#ceiling} ceiling`);
49
- }
50
- if (total <= this.#ceiling)
51
- return "continue";
52
- if (this.#decision) {
53
- const decision = await this.#decision;
54
- if (decision === "stop")
55
- await hooks.stop?.();
56
- return decision;
57
- }
58
- this.#decision = this.#decide(total, hooks);
59
- return this.#decision;
60
- }
61
- async #decide(cost, hooks) {
62
- await hooks.pause?.();
63
- const answer = await hooks.ask?.();
64
- if (answer !== undefined && answer !== null && Number.isFinite(answer) && answer > cost) {
65
- const prior = this.#ceiling;
66
- this.#ceiling = answer;
67
- try {
68
- await hooks.gate({ cost, ceiling: prior, outcome: "continued", newCeiling: answer });
69
- }
70
- catch (error) {
71
- await hooks.stop?.();
72
- throw error;
73
- }
74
- await hooks.resume?.();
75
- return "continue";
76
- }
77
- try {
78
- await hooks.gate({ cost, ceiling: this.#ceiling, outcome: "stopped" });
79
- }
80
- finally {
81
- await hooks.stop?.();
82
- }
83
- return "stop";
84
- }
85
- }
86
- export async function loadEpisodeCosts(path, episodeId) {
87
- try {
88
- const parsed = readRecords(await readFile(path, "utf8"));
89
- if (parsed.damage)
90
- return new Map();
91
- const costs = new Map();
92
- for (const record of parsed.records) {
93
- const body = record.body;
94
- if (body?.event !== "child_lifecycle" || body.episodeId !== episodeId)
95
- continue;
96
- if (body.state !== "completed" && body.state !== "failed")
97
- continue;
98
- const usage = body.usage;
99
- const cost = usage?.cost?.total;
100
- if (typeof body.executionId === "string" && typeof cost === "number" && Number.isFinite(cost) && cost >= 0) {
101
- costs.set(body.executionId, cost);
102
- }
103
- }
104
- return costs;
105
- }
106
- catch (error) {
107
- if (error.code === "ENOENT")
108
- return new Map();
109
- throw error;
110
- }
111
- }
112
- //# sourceMappingURL=episode-cost-gate.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"episode-cost-gate.js","sourceRoot":"","sources":["../../src/governance/episode-cost-gate.ts"],"names":[],"mappings":"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"}
@@ -1,67 +0,0 @@
1
- /**
2
- * Build the session's advisor from its committed settings (ADR-0077).
3
- *
4
- * Composition, because everything here is I/O or policy the layers must not hold: reading the settings file,
5
- * reaching for the key in the environment, and writing the `advice` record to the ledger. The advisors layer does
6
- * none of that — it is handed a decider and a recorder.
7
- *
8
- * An advisor with no ledger records nothing and still answers. That is deliberate and worth saying: the ledger is
9
- * opt-in (ADR-0037), and making advice conditional on it would mean an ungoverned session behaved differently from
10
- * a governed one for reasons that have nothing to do with the advisor.
11
- */
12
- import { createAdvisor, type AdviceRecord, type Advisor } from "../src/advisors/advisor.ts";
13
- import { nullDecider } from "../src/advisors/decider.ts";
14
- import { jevDecider } from "../src/advisors/jev.ts";
15
- import { ADVISOR_KEY_ENV, advisorSettingsFrom, type AdvisorSettings } from "../src/advisors/settings.ts";
16
- import { appendRecord } from "../src/governance/record.ts";
17
-
18
- export interface AdvisorSession {
19
- readonly settings: AdvisorSettings;
20
- readonly advisor: Advisor;
21
- /**
22
- * Which decider was actually constructed — `"none"` or `"jev"`.
23
- *
24
- * Exposed because a test asserting only `settings` could not tell: review measured that removing the key check
25
- * here left every test green, since the disabled wrapper returns before touching the decider. A named breaking
26
- * change that does not break the test is decoration (AGENTS.md).
27
- */
28
- readonly deciderName: string;
29
- }
30
-
31
- export function createAdvisorSession(input: {
32
- /** The `advisor` block of `.pi/pi-daddy/settings.json`, or undefined when there is none. */
33
- block: unknown;
34
- ledgerPath?: string;
35
- env?: NodeJS.ProcessEnv;
36
- episodeId?: string;
37
- }): AdvisorSession {
38
- const env = input.env ?? process.env;
39
- const settings = advisorSettingsFrom(input.block, env);
40
- const record = async (entry: AdviceRecord): Promise<void> => {
41
- if (!input.ledgerPath) return;
42
- // Never strict: advice is an observation beside a decision that has already been taken, so a ledger failure
43
- // must not change what the caller does — `execute-child`'s rule for a terminal lifecycle append.
44
- await appendRecord(input.ledgerPath, "advice", entry).catch(() => undefined);
45
- };
46
- const decider =
47
- settings.enabled && settings.decider === "jev"
48
- ? jevDecider({
49
- // Trimmed for the reason `settings` trims when validating: a key exported with a trailing newline
50
- // passed validation and was then sent verbatim.
51
- apiKey: env[ADVISOR_KEY_ENV]?.trim() ?? "",
52
- ...(settings.model ? { model: settings.model } : {}),
53
- })
54
- : nullDecider;
55
- return {
56
- settings,
57
- deciderName: decider.name,
58
- advisor: createAdvisor({
59
- decider,
60
- record,
61
- enabled: settings.enabled,
62
- taskEgress: settings.taskEgress,
63
- ...(input.episodeId ? { episodeId: input.episodeId } : {}),
64
- ...(settings.timeoutMs ? { timeoutMs: settings.timeoutMs } : {}),
65
- }),
66
- };
67
- }
@@ -1,112 +0,0 @@
1
- /**
2
- * The first decision point that consults an advisor (ADR-0077, roadmap PR 8): how hard a child should think.
3
- *
4
- * **Why this one.** It is the shape the boundary was designed for. The options are not invented by the advisor —
5
- * they are the thinking levels this session's own model reports it supports (`supportedModelEfforts`), so the
6
- * advisor picks among things the caller already had, which is the entire permitted verb list. It touches no
7
- * capability, no gate and no grant: the worst an advisor can do here is make a child think harder or less hard
8
- * than a human would have chosen, and the ledger says it did.
9
- *
10
- * **Only when the caller said nothing.** An explicit `thinking` on the call is the operator's or the model's own
11
- * choice and is never second-guessed; advice fills a blank, it does not overrule. With no advisor, no key, no
12
- * answer, a timeout or an unrecognised response, the blank stays blank and the child is spawned exactly as it is
13
- * today — which is the property that keeps advisors optional rather than load-bearing.
14
- */
15
- import { supportedModelEfforts } from "../src/kernel/model-preflight.ts";
16
- import type { Advisor } from "../src/advisors/advisor.ts";
17
- import { resolvedModelOf, type ResolvedDefinitionRuntime } from "./definition-runtime.ts";
18
-
19
- /** What pi's resolved catalogue entry carries that decides which efforts exist. */
20
- interface ResolvedModel {
21
- reasoning: boolean;
22
- thinkingLevelMap?: Partial<Record<string, string | null>>;
23
- }
24
-
25
- export const EFFORT_PURPOSE = "child-effort";
26
-
27
- export async function adviseEffort(input: {
28
- /** The session, read here rather than passed as an advisor: an argument can be severed and nothing notices. */
29
- session: { advisorSession: { advisor: Advisor } };
30
- /** Absent means the caller chose one; nothing is asked and nothing is recorded. */
31
- requested?: string;
32
- /**
33
- * The model the CHILD will run on, `provider/id`, not the session's.
34
- *
35
- * Review measured the first version reading the parent session's model while the child was spawned on
36
- * `spec.model`: the levels offered then came from a model the child would never use, and pi clamps rather than
37
- * refuses, so the effect was a silently shifted effort rather than a loud failure.
38
- */
39
- model?: string;
40
- registry: { find(provider: string, modelId: string): unknown };
41
- task: string;
42
- executionId?: string;
43
- agent?: string;
44
- fallback?: ResolvedDefinitionRuntime;
45
- signal?: AbortSignal;
46
- }): Promise<string | undefined> {
47
- const advisor = input.session.advisorSession.advisor;
48
- const slash = input.model === undefined ? -1 : input.model.indexOf("/");
49
- if (input.requested !== undefined || slash <= 0) return input.requested;
50
- const resolved = input.registry.find(input.model!.slice(0, slash), input.model!.slice(slash + 1)) as
51
- ResolvedModel | undefined;
52
- if (!resolved) return undefined;
53
- const levels = supportedModelEfforts(resolved);
54
- // One option is not a choice, and a model with no reasoning has exactly one. Asking would spend a call and a
55
- // ledger line to be told the only thing that could be said.
56
- if (levels.length < 2) return undefined;
57
-
58
- const task = advisor.task(input.task);
59
- const advice = await advisor.ask(
60
- EFFORT_PURPOSE,
61
- {
62
- // Raw text is a separate opt-in from enabling the advisor. By default `advisor.task` returns only structural
63
- // facts; raw mode preserves the prior request, including the definition name.
64
- state: { task, ...(typeof task === "string" && input.agent ? { definition: input.agent } : {}) },
65
- questions: {
66
- effort: {
67
- kind: "choice",
68
- instructions:
69
- "How much reasoning effort does this task need? Choose the cheapest level that would still do it well.",
70
- options: Object.fromEntries(levels.map((level) => [level, effortDescription(level)])),
71
- },
72
- },
73
- },
74
- input.signal,
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
- },
86
- );
87
- const chosen = advice?.answers.effort;
88
- // Belt and braces: `parseAnswer` already refuses a choice outside the options it was given, so this can only
89
- // fire if a future decider is written that does not. An effort the model does not support would be refused by
90
- // pi in the child, after the spawn, which is a worse place to find out.
91
- if (!chosen || chosen.kind !== "choice" || !(levels as readonly string[]).includes(chosen.value)) return undefined;
92
- return chosen.value;
93
- }
94
-
95
- function effortDescription(level: string): string {
96
- switch (level) {
97
- case "off":
98
- return "No reasoning. Mechanical work: a rename, a formatting pass, reading one file back.";
99
- case "minimal":
100
- return "Almost none. A single obvious step with no choice in it.";
101
- case "low":
102
- return "A little. One decision, or a change confined to one file.";
103
- case "medium":
104
- return "Ordinary. Several steps, or a change that has to fit existing code.";
105
- case "high":
106
- return "Substantial. Design choices, or work across several files that must stay consistent.";
107
- case "xhigh":
108
- return "Very high. A subtle problem where the obvious approach is likely to be wrong.";
109
- default:
110
- return "The most this model can do. Reserve it for work that has defeated a lesser effort.";
111
- }
112
- }
@@ -1,133 +0,0 @@
1
- /**
2
- * The second decision point: which of the parent's turns a `pruned` handoff actually carries (ADR-0077).
3
- *
4
- * The deterministic rule keeps the last few turns plus older ones naming the given files. That rule is honest and
5
- * its recall is unmeasured — it is a proxy for "what matters here", not an answer. An advisor can judge the turns
6
- * against the task the child is about to be given, which the rule cannot see.
7
- *
8
- * **It can only take turns away.** The candidates are exactly what `selectPrunedTurns` produced; the advisor is
9
- * asked one boolean per candidate and the ids it kept are handed back. Staging then intersects those ids with the
10
- * candidates again, so even a selector that invented an id, returned one from another session, or returned every
11
- * id in existence cannot put a turn in front of a child that the rule did not already offer. Narrowing is the only
12
- * verb available, which is what makes this advice rather than authority.
13
- *
14
- * With no advisor, no answer, a timeout or an unrecognised response, nothing is returned and the rule's own
15
- * selection stands — the same "identical to today" property the effort decision point has.
16
- */
17
- import { selectPrunedTurns, type ContextRequest } from "../src/kernel/context-handoff.ts";
18
- import type { AdviceAttribution, Advisor } from "../src/advisors/advisor.ts";
19
- import type { Question } from "../src/advisors/decider.ts";
20
-
21
- export const PRUNING_PURPOSE = "handoff-pruning";
22
-
23
- /**
24
- * How many candidates may be judged. One question per turn, and a decision a human is waiting on should not carry
25
- * an unbounded number of them. Beyond this the OLDEST candidates go unjudged and are kept, which is the safe
26
- * direction: an unjudged turn is never dropped, so the advisor can still only narrow.
27
- */
28
- export const MAX_JUDGED_TURNS = 12;
29
-
30
- /** Just enough of the parent's session for the rule; the same shape `context-staging` reduces entries to. */
31
- export interface ParentTurnSource {
32
- getEntries(): Array<{ id: string; type: string }>;
33
- }
34
-
35
- export async function advisePruning(input: {
36
- session: { advisorSession: { advisor: Advisor }; parentSession?: ParentTurnSource };
37
- granted: ContextRequest;
38
- task: string;
39
- executionId?: string;
40
- /** The current delegation call, excluded from advisor input because its arguments contain the raw task. */
41
- toolCallId?: string;
42
- attribution?: AdviceAttribution;
43
- signal?: AbortSignal;
44
- }): Promise<string[] | undefined> {
45
- if (input.granted.mode !== "pruned" || !input.session.parentSession) return undefined;
46
- const all = turnsOf(input.session.parentSession);
47
- const candidates = selectPrunedTurns(all, {
48
- ...(input.granted.turns !== undefined ? { turns: input.granted.turns } : {}),
49
- ...(input.granted.files !== undefined ? { files: input.granted.files } : {}),
50
- }).kept;
51
- const privateIds = new Set(
52
- input.toolCallId ? all.filter((turn) => turn.toolCallIds.includes(input.toolCallId!)).map((turn) => turn.id) : [],
53
- );
54
- const privateCandidates = candidates.filter((turn) => privateIds.has(turn.id));
55
- const candidatesForAdvice = candidates.filter((turn) => !privateCandidates.includes(turn));
56
- if (candidatesForAdvice.length < 2) return undefined;
57
- // **Judge the MOST RECENT candidates, and keep the rest unjudged.** This was `> MAX_JUDGED_TURNS` → give up
58
- // entirely, which was fine while the rule offered six turns and became a silent feature death the moment
59
- // `DEFAULT_CONTEXT_TURNS` rose to 20: every default request would have exceeded the bound, so the pruning
60
- // decision point shipped in 0.35.0 would never have fired again. Found while raising the default, which is
61
- // the only reason it was found at all.
62
- //
63
- // Judging a SUBSET is still only narrowing — an unjudged turn is kept, never dropped — so the attenuation
64
- // property is untouched. The recent end is judged because that is where the rule's own recall is
65
- // concentrated, and because a turn adjacent to the task is the one a wrong answer costs most.
66
- const unjudged = candidatesForAdvice.slice(0, Math.max(0, candidatesForAdvice.length - MAX_JUDGED_TURNS));
67
- const judged = candidatesForAdvice.slice(unjudged.length);
68
-
69
- const task = input.session.advisorSession.advisor.task(input.task);
70
- const taskDescription = typeof task === "string" ? task.slice(0, 2000) : JSON.stringify(task);
71
- const questions: Record<string, Question> = {};
72
- for (const [index, turn] of judged.entries())
73
- questions[`turn${index}`] = {
74
- kind: "noul",
75
- // Both bounded: the task is embedded once per candidate, so an unbounded task became a request twelve times
76
- // its size, on a two-second budget and the operator's key.
77
- instructions: `Would a sub-agent doing this task be helped by seeing this part of the parent's session?\n\nTASK: ${taskDescription}\n\nPART:\n${turn.text.slice(0, 2000)}`,
78
- whenTrue: "It bears on the task: a decision, a constraint, a fact the task depends on.",
79
- whenFalse: "It does not: unrelated work, chatter, or something the task already states.",
80
- };
81
-
82
- const advice = await input.session.advisorSession.advisor.ask(
83
- PRUNING_PURPOSE,
84
- // The state is empty: everything the advisor needs is already in the questions, and a question carries the
85
- // task and one turn rather than the whole session. Nothing here is recorded — `createAdvisor` writes keys.
86
- { state: {}, questions },
87
- input.signal,
88
- input.executionId,
89
- input.attribution,
90
- );
91
- if (!advice) return undefined;
92
- // Every candidate or none. A response missing eleven of twelve answers would otherwise read as "drop eleven",
93
- // which is a narrowing nobody asked for rather than the "unrecognised response means no advice" contract.
94
- if (judged.some((_, index) => advice.answers[`turn${index}`]?.kind !== "noul")) return undefined;
95
- const kept = [
96
- ...privateCandidates.map((turn) => turn.id),
97
- ...unjudged.map((turn) => turn.id),
98
- ...judged.filter((_, index) => (advice.answers[`turn${index}`] as { value: boolean }).value).map((turn) => turn.id),
99
- ];
100
- // An advisor that drops everything is answering a different question from the one that was asked; the rule's
101
- // selection stands rather than handing a child a handoff with nothing in it.
102
- return kept.length === 0 ? undefined : kept;
103
- }
104
-
105
- function turnsOf(session: ParentTurnSource): Array<{ id: string; text: string; toolCallIds: string[] }> {
106
- try {
107
- return session
108
- .getEntries()
109
- .filter((entry) => entry.type === "message")
110
- .map((entry) => {
111
- const message = (entry as { message?: unknown }).message ?? entry;
112
- const content =
113
- typeof message === "object" && message !== null && Array.isArray((message as { content?: unknown }).content)
114
- ? (message as { content: unknown[] }).content
115
- : [];
116
- return {
117
- id: entry.id,
118
- text: JSON.stringify(message),
119
- toolCallIds: content
120
- .filter(
121
- (part): part is { type: "toolCall"; id: string } =>
122
- typeof part === "object" &&
123
- part !== null &&
124
- (part as { type?: unknown }).type === "toolCall" &&
125
- typeof (part as { id?: unknown }).id === "string",
126
- )
127
- .map((part) => part.id),
128
- };
129
- });
130
- } catch {
131
- return [];
132
- }
133
- }