@caupulican/pi-adaptative 0.81.41 → 0.81.42

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 (237) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/bundled-resources/runtimes/pi-shell-engine/commands/text.py +5 -1
  3. package/dist/bundled-resources/runtimes/pi-shell-engine/context.py +3 -3
  4. package/dist/bundled-resources/runtimes/pi-shell-engine/errors.py +8 -0
  5. package/dist/bundled-resources/runtimes/pi-shell-engine/exec.py +26 -3
  6. package/dist/bundled-resources/runtimes/pi-shell-engine/expand.py +3 -0
  7. package/dist/bundled-resources/runtimes/pi-shell-engine/main.py +4 -1
  8. package/dist/bundled-resources/runtimes/pi-shell-engine/state.py +2 -1
  9. package/dist/bundled-resources/runtimes/pi-shell-engine/tokens.py +2 -0
  10. package/dist/core/agent-paths.d.ts +32 -0
  11. package/dist/core/agent-paths.d.ts.map +1 -1
  12. package/dist/core/agent-paths.js +65 -0
  13. package/dist/core/agent-paths.js.map +1 -1
  14. package/dist/core/agent-session-contracts.d.ts +19 -1
  15. package/dist/core/agent-session-contracts.d.ts.map +1 -1
  16. package/dist/core/agent-session-contracts.js.map +1 -1
  17. package/dist/core/agent-session.d.ts.map +1 -1
  18. package/dist/core/agent-session.js +4 -9
  19. package/dist/core/agent-session.js.map +1 -1
  20. package/dist/core/autonomy/contracts.d.ts +2 -0
  21. package/dist/core/autonomy/contracts.d.ts.map +1 -1
  22. package/dist/core/autonomy/contracts.js.map +1 -1
  23. package/dist/core/autonomy/envelope-enforcement.d.ts.map +1 -1
  24. package/dist/core/autonomy/envelope-enforcement.js +4 -9
  25. package/dist/core/autonomy/envelope-enforcement.js.map +1 -1
  26. package/dist/core/autonomy/lane-tool-surface.d.ts +3 -1
  27. package/dist/core/autonomy/lane-tool-surface.d.ts.map +1 -1
  28. package/dist/core/autonomy/lane-tool-surface.js +17 -4
  29. package/dist/core/autonomy/lane-tool-surface.js.map +1 -1
  30. package/dist/core/autonomy/path-scope.d.ts +14 -0
  31. package/dist/core/autonomy/path-scope.d.ts.map +1 -1
  32. package/dist/core/autonomy/path-scope.js +37 -6
  33. package/dist/core/autonomy/path-scope.js.map +1 -1
  34. package/dist/core/background-lane-controller.d.ts +27 -1
  35. package/dist/core/background-lane-controller.d.ts.map +1 -1
  36. package/dist/core/background-lane-controller.js +32 -1
  37. package/dist/core/background-lane-controller.js.map +1 -1
  38. package/dist/core/delegation/managed-lane-controller.d.ts +11 -1
  39. package/dist/core/delegation/managed-lane-controller.d.ts.map +1 -1
  40. package/dist/core/delegation/managed-lane-controller.js +84 -41
  41. package/dist/core/delegation/managed-lane-controller.js.map +1 -1
  42. package/dist/core/delegation/session-worker-claim.d.ts.map +1 -1
  43. package/dist/core/delegation/session-worker-claim.js +4 -3
  44. package/dist/core/delegation/session-worker-claim.js.map +1 -1
  45. package/dist/core/delegation/worker-action-journal.d.ts +73 -0
  46. package/dist/core/delegation/worker-action-journal.d.ts.map +1 -0
  47. package/dist/core/delegation/worker-action-journal.js +297 -0
  48. package/dist/core/delegation/worker-action-journal.js.map +1 -0
  49. package/dist/core/delegation/worker-actions.d.ts +36 -1
  50. package/dist/core/delegation/worker-actions.d.ts.map +1 -1
  51. package/dist/core/delegation/worker-actions.js +171 -46
  52. package/dist/core/delegation/worker-actions.js.map +1 -1
  53. package/dist/core/delegation/worker-agent-control-coordinator.d.ts +75 -0
  54. package/dist/core/delegation/worker-agent-control-coordinator.d.ts.map +1 -0
  55. package/dist/core/delegation/worker-agent-control-coordinator.js +218 -0
  56. package/dist/core/delegation/worker-agent-control-coordinator.js.map +1 -0
  57. package/dist/core/delegation/worker-agent-control.d.ts +66 -0
  58. package/dist/core/delegation/worker-agent-control.d.ts.map +1 -0
  59. package/dist/core/delegation/worker-agent-control.js +163 -0
  60. package/dist/core/delegation/worker-agent-control.js.map +1 -0
  61. package/dist/core/delegation/worker-attempt-executor.d.ts +80 -0
  62. package/dist/core/delegation/worker-attempt-executor.d.ts.map +1 -0
  63. package/dist/core/delegation/worker-attempt-executor.js +343 -0
  64. package/dist/core/delegation/worker-attempt-executor.js.map +1 -0
  65. package/dist/core/delegation/worker-claim.d.ts +30 -0
  66. package/dist/core/delegation/worker-claim.d.ts.map +1 -1
  67. package/dist/core/delegation/worker-claim.js +205 -53
  68. package/dist/core/delegation/worker-claim.js.map +1 -1
  69. package/dist/core/delegation/worker-conversation-store.d.ts +116 -0
  70. package/dist/core/delegation/worker-conversation-store.d.ts.map +1 -0
  71. package/dist/core/delegation/worker-conversation-store.js +442 -0
  72. package/dist/core/delegation/worker-conversation-store.js.map +1 -0
  73. package/dist/core/delegation/worker-delegation-controller.d.ts +21 -5
  74. package/dist/core/delegation/worker-delegation-controller.d.ts.map +1 -1
  75. package/dist/core/delegation/worker-delegation-controller.js +386 -251
  76. package/dist/core/delegation/worker-delegation-controller.js.map +1 -1
  77. package/dist/core/delegation/worker-delegation-request.d.ts +14 -0
  78. package/dist/core/delegation/worker-delegation-request.d.ts.map +1 -1
  79. package/dist/core/delegation/worker-delegation-request.js.map +1 -1
  80. package/dist/core/delegation/worker-dispatch-scheduler.d.ts +4 -2
  81. package/dist/core/delegation/worker-dispatch-scheduler.d.ts.map +1 -1
  82. package/dist/core/delegation/worker-dispatch-scheduler.js +16 -6
  83. package/dist/core/delegation/worker-dispatch-scheduler.js.map +1 -1
  84. package/dist/core/delegation/worker-execution-policy.d.ts +2 -1
  85. package/dist/core/delegation/worker-execution-policy.d.ts.map +1 -1
  86. package/dist/core/delegation/worker-execution-policy.js +5 -7
  87. package/dist/core/delegation/worker-execution-policy.js.map +1 -1
  88. package/dist/core/delegation/worker-lifecycle.d.ts +44 -1
  89. package/dist/core/delegation/worker-lifecycle.d.ts.map +1 -1
  90. package/dist/core/delegation/worker-lifecycle.js +120 -0
  91. package/dist/core/delegation/worker-lifecycle.js.map +1 -1
  92. package/dist/core/delegation/worker-process-owner.d.ts +18 -0
  93. package/dist/core/delegation/worker-process-owner.d.ts.map +1 -0
  94. package/dist/core/delegation/worker-process-owner.js +54 -0
  95. package/dist/core/delegation/worker-process-owner.js.map +1 -0
  96. package/dist/core/delegation/worker-profile-resolver.d.ts +5 -1
  97. package/dist/core/delegation/worker-profile-resolver.d.ts.map +1 -1
  98. package/dist/core/delegation/worker-profile-resolver.js +8 -0
  99. package/dist/core/delegation/worker-profile-resolver.js.map +1 -1
  100. package/dist/core/delegation/worker-recovery-coordinator.d.ts +54 -0
  101. package/dist/core/delegation/worker-recovery-coordinator.d.ts.map +1 -0
  102. package/dist/core/delegation/worker-recovery-coordinator.js +181 -0
  103. package/dist/core/delegation/worker-recovery-coordinator.js.map +1 -0
  104. package/dist/core/delegation/worker-resource-catalog.d.ts +32 -0
  105. package/dist/core/delegation/worker-resource-catalog.d.ts.map +1 -0
  106. package/dist/core/delegation/worker-resource-catalog.js +116 -0
  107. package/dist/core/delegation/worker-resource-catalog.js.map +1 -0
  108. package/dist/core/delegation/worker-resource-materializer.d.ts +47 -0
  109. package/dist/core/delegation/worker-resource-materializer.d.ts.map +1 -0
  110. package/dist/core/delegation/worker-resource-materializer.js +137 -0
  111. package/dist/core/delegation/worker-resource-materializer.js.map +1 -0
  112. package/dist/core/delegation/worker-runner.d.ts +3 -1
  113. package/dist/core/delegation/worker-runner.d.ts.map +1 -1
  114. package/dist/core/delegation/worker-runner.js +189 -54
  115. package/dist/core/delegation/worker-runner.js.map +1 -1
  116. package/dist/core/delegation/worker-terminal-finalizer.d.ts +23 -0
  117. package/dist/core/delegation/worker-terminal-finalizer.d.ts.map +1 -0
  118. package/dist/core/delegation/worker-terminal-finalizer.js +12 -0
  119. package/dist/core/delegation/worker-terminal-finalizer.js.map +1 -0
  120. package/dist/core/delegation/worker-write-reservation-coordinator.d.ts +38 -0
  121. package/dist/core/delegation/worker-write-reservation-coordinator.d.ts.map +1 -0
  122. package/dist/core/delegation/worker-write-reservation-coordinator.js +107 -0
  123. package/dist/core/delegation/worker-write-reservation-coordinator.js.map +1 -0
  124. package/dist/core/delegation/worker-write-reservation.d.ts +90 -0
  125. package/dist/core/delegation/worker-write-reservation.d.ts.map +1 -0
  126. package/dist/core/delegation/worker-write-reservation.js +348 -0
  127. package/dist/core/delegation/worker-write-reservation.js.map +1 -0
  128. package/dist/core/models/model-endpoint.d.ts +3 -0
  129. package/dist/core/models/model-endpoint.d.ts.map +1 -0
  130. package/dist/core/models/model-endpoint.js +11 -0
  131. package/dist/core/models/model-endpoint.js.map +1 -0
  132. package/dist/core/orchestration/agent-resume.d.ts +2 -0
  133. package/dist/core/orchestration/agent-resume.d.ts.map +1 -1
  134. package/dist/core/orchestration/agent-resume.js +7 -0
  135. package/dist/core/orchestration/agent-resume.js.map +1 -1
  136. package/dist/core/orchestration/attempt-usage.d.ts +28 -0
  137. package/dist/core/orchestration/attempt-usage.d.ts.map +1 -0
  138. package/dist/core/orchestration/attempt-usage.js +168 -0
  139. package/dist/core/orchestration/attempt-usage.js.map +1 -0
  140. package/dist/core/orchestration/capability-gateway.d.ts +19 -1
  141. package/dist/core/orchestration/capability-gateway.d.ts.map +1 -1
  142. package/dist/core/orchestration/capability-gateway.js +128 -28
  143. package/dist/core/orchestration/capability-gateway.js.map +1 -1
  144. package/dist/core/orchestration/contracts.d.ts +35 -0
  145. package/dist/core/orchestration/contracts.d.ts.map +1 -1
  146. package/dist/core/orchestration/contracts.js +17 -0
  147. package/dist/core/orchestration/contracts.js.map +1 -1
  148. package/dist/core/orchestration/delegation-ledger.d.ts +17 -2
  149. package/dist/core/orchestration/delegation-ledger.d.ts.map +1 -1
  150. package/dist/core/orchestration/delegation-ledger.js +78 -3
  151. package/dist/core/orchestration/delegation-ledger.js.map +1 -1
  152. package/dist/core/orchestration/event-store.d.ts +1 -0
  153. package/dist/core/orchestration/event-store.d.ts.map +1 -1
  154. package/dist/core/orchestration/event-store.js +78 -38
  155. package/dist/core/orchestration/event-store.js.map +1 -1
  156. package/dist/core/orchestration/profile-registry.d.ts.map +1 -1
  157. package/dist/core/orchestration/profile-registry.js +81 -41
  158. package/dist/core/orchestration/profile-registry.js.map +1 -1
  159. package/dist/core/orchestration/profile-store.d.ts.map +1 -1
  160. package/dist/core/orchestration/profile-store.js +18 -4
  161. package/dist/core/orchestration/profile-store.js.map +1 -1
  162. package/dist/core/orchestration/risk-budget.d.ts +2 -0
  163. package/dist/core/orchestration/risk-budget.d.ts.map +1 -1
  164. package/dist/core/orchestration/risk-budget.js +26 -0
  165. package/dist/core/orchestration/risk-budget.js.map +1 -1
  166. package/dist/core/orchestration/task-runtime.d.ts +13 -1
  167. package/dist/core/orchestration/task-runtime.d.ts.map +1 -1
  168. package/dist/core/orchestration/task-runtime.js +127 -16
  169. package/dist/core/orchestration/task-runtime.js.map +1 -1
  170. package/dist/core/orchestration/worker-execution-contract.d.ts +3 -1
  171. package/dist/core/orchestration/worker-execution-contract.d.ts.map +1 -1
  172. package/dist/core/orchestration/worker-execution-contract.js +75 -23
  173. package/dist/core/orchestration/worker-execution-contract.js.map +1 -1
  174. package/dist/core/orchestration/worker-result-adapter.d.ts +9 -3
  175. package/dist/core/orchestration/worker-result-adapter.d.ts.map +1 -1
  176. package/dist/core/orchestration/worker-result-adapter.js +167 -14
  177. package/dist/core/orchestration/worker-result-adapter.js.map +1 -1
  178. package/dist/core/reflection-controller.d.ts +1 -1
  179. package/dist/core/reflection-controller.d.ts.map +1 -1
  180. package/dist/core/reflection-controller.js +90 -54
  181. package/dist/core/reflection-controller.js.map +1 -1
  182. package/dist/core/research/evidence-bundle.d.ts +20 -0
  183. package/dist/core/research/evidence-bundle.d.ts.map +1 -1
  184. package/dist/core/research/evidence-bundle.js +173 -69
  185. package/dist/core/research/evidence-bundle.js.map +1 -1
  186. package/dist/core/runtime-builder.d.ts +7 -0
  187. package/dist/core/runtime-builder.d.ts.map +1 -1
  188. package/dist/core/runtime-builder.js +15 -1
  189. package/dist/core/runtime-builder.js.map +1 -1
  190. package/dist/core/session-artifact-bundle.d.ts +35 -0
  191. package/dist/core/session-artifact-bundle.d.ts.map +1 -0
  192. package/dist/core/session-artifact-bundle.js +61 -0
  193. package/dist/core/session-artifact-bundle.js.map +1 -0
  194. package/dist/core/tools/delegate-status.d.ts +6 -1
  195. package/dist/core/tools/delegate-status.d.ts.map +1 -1
  196. package/dist/core/tools/delegate-status.js +100 -18
  197. package/dist/core/tools/delegate-status.js.map +1 -1
  198. package/dist/core/tools/delegate.d.ts +9 -1
  199. package/dist/core/tools/delegate.d.ts.map +1 -1
  200. package/dist/core/tools/delegate.js +323 -61
  201. package/dist/core/tools/delegate.js.map +1 -1
  202. package/dist/core/tools/run-process.d.ts +1 -1
  203. package/dist/core/tools/run-process.d.ts.map +1 -1
  204. package/dist/core/tools/run-process.js +69 -9
  205. package/dist/core/tools/run-process.js.map +1 -1
  206. package/dist/core/tools/shell-contract-router.d.ts.map +1 -1
  207. package/dist/core/tools/shell-contract-router.js +12 -5
  208. package/dist/core/tools/shell-contract-router.js.map +1 -1
  209. package/dist/core/util/bounded-file.d.ts +15 -0
  210. package/dist/core/util/bounded-file.d.ts.map +1 -0
  211. package/dist/core/util/bounded-file.js +120 -0
  212. package/dist/core/util/bounded-file.js.map +1 -0
  213. package/dist/core/util/bounded-jsonl.d.ts.map +1 -1
  214. package/dist/core/util/bounded-jsonl.js +119 -9
  215. package/dist/core/util/bounded-jsonl.js.map +1 -1
  216. package/dist/core/util/bounded-value.d.ts +3 -0
  217. package/dist/core/util/bounded-value.d.ts.map +1 -0
  218. package/dist/core/util/bounded-value.js +12 -0
  219. package/dist/core/util/bounded-value.js.map +1 -0
  220. package/dist/core/util/value-guards.d.ts +2 -0
  221. package/dist/core/util/value-guards.d.ts.map +1 -1
  222. package/dist/core/util/value-guards.js +5 -0
  223. package/dist/core/util/value-guards.js.map +1 -1
  224. package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
  225. package/dist/modes/interactive/components/session-selector.js +27 -44
  226. package/dist/modes/interactive/components/session-selector.js.map +1 -1
  227. package/docs/tool-repair.md +3 -0
  228. package/docs/windows.md +4 -2
  229. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  230. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  231. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  232. package/examples/extensions/sandbox/package-lock.json +2 -2
  233. package/examples/extensions/sandbox/package.json +1 -1
  234. package/examples/extensions/with-deps/package-lock.json +2 -2
  235. package/examples/extensions/with-deps/package.json +1 -1
  236. package/npm-shrinkwrap.json +12 -12
  237. package/package.json +4 -4
@@ -0,0 +1,38 @@
1
+ import type { AttemptRuntimeState } from "../orchestration/task-runtime.ts";
2
+ import type { WorkerExecutionPlan } from "./worker-execution-policy.ts";
3
+ export type WorkerWriteReservationAdmission = {
4
+ kind: "granted";
5
+ } | {
6
+ kind: "blocked";
7
+ } | {
8
+ kind: "denied";
9
+ reasonCode: "write_reservation_scope_invalid" | "write_reservation_unavailable";
10
+ };
11
+ export interface WorkerWriteReservationCoordinatorOptions {
12
+ agentDir: string;
13
+ getCwd(): string;
14
+ getParentSessionId(): string;
15
+ ownerId: string;
16
+ drainQueuedWorkers(): void;
17
+ warn(message: string): void;
18
+ isProcessAlive?(pid: number): boolean;
19
+ }
20
+ /**
21
+ * Owns one controller process's write-reservation lifecycle: admission, exact-fence release,
22
+ * event-driven queue wakeup, and fail-closed recovery of positively dead owners.
23
+ */
24
+ export declare class WorkerWriteReservationCoordinator {
25
+ private readonly options;
26
+ private readonly store;
27
+ private readonly leases;
28
+ private watchDispose;
29
+ constructor(options: WorkerWriteReservationCoordinatorOptions);
30
+ acquire(laneId: string, attempt: Pick<AttemptRuntimeState, "attemptId" | "lease">, plan: Pick<WorkerExecutionPlan, "writeEnabled" | "writePaths">): WorkerWriteReservationAdmission;
31
+ hasFenceMismatch(laneId: string, attemptId: string, fencingToken: number): boolean;
32
+ release(laneId: string, expectedAttemptId?: string, expectedFencingToken?: number): void;
33
+ recoverProvenStale(): void;
34
+ dispose(): void;
35
+ private workspace;
36
+ private ensureWatch;
37
+ }
38
+ //# sourceMappingURL=worker-write-reservation-coordinator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"worker-write-reservation-coordinator.d.ts","sourceRoot":"","sources":["../../../src/core/delegation/worker-write-reservation-coordinator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AAC5E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AAIxE,MAAM,MAAM,+BAA+B,GACxC;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,GACnB;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,GACnB;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,UAAU,EAAE,iCAAiC,GAAG,+BAA+B,CAAA;CAAE,CAAC;AAEvG,MAAM,WAAW,wCAAwC;IACxD,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,IAAI,MAAM,CAAC;IACjB,kBAAkB,IAAI,MAAM,CAAC;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,kBAAkB,IAAI,IAAI,CAAC;IAC3B,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,cAAc,CAAC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACtC;AAED;;;GAGG;AACH,qBAAa,iCAAiC;IAC7C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA2C;IACnE,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA8B;IACpD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkD;IACzE,OAAO,CAAC,YAAY,CAA2B;IAE/C,YAAY,OAAO,EAAE,wCAAwC,EAG5D;IAED,OAAO,CACN,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,IAAI,CAAC,mBAAmB,EAAE,WAAW,GAAG,OAAO,CAAC,EACzD,IAAI,EAAE,IAAI,CAAC,mBAAmB,EAAE,cAAc,GAAG,YAAY,CAAC,GAC5D,+BAA+B,CAkCjC;IAED,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAGjF;IAED,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,iBAAiB,CAAC,EAAE,MAAM,EAAE,oBAAoB,CAAC,EAAE,MAAM,GAAG,IAAI,CAcvF;IAED,kBAAkB,IAAI,IAAI,CAqBzB;IAED,OAAO,IAAI,IAAI,CAGd;IAED,OAAO,CAAC,SAAS;IAKjB,OAAO,CAAC,WAAW;CAInB","sourcesContent":["import type { AttemptRuntimeState } from \"../orchestration/task-runtime.ts\";\nimport type { WorkerExecutionPlan } from \"./worker-execution-policy.ts\";\nimport { isLocalProcessAlive, localWorkerProcessOwnerLiveness } from \"./worker-process-owner.ts\";\nimport { type WorkerWriteReservationLease, WorkerWriteReservationStore } from \"./worker-write-reservation.ts\";\n\nexport type WorkerWriteReservationAdmission =\n\t| { kind: \"granted\" }\n\t| { kind: \"blocked\" }\n\t| { kind: \"denied\"; reasonCode: \"write_reservation_scope_invalid\" | \"write_reservation_unavailable\" };\n\nexport interface WorkerWriteReservationCoordinatorOptions {\n\tagentDir: string;\n\tgetCwd(): string;\n\tgetParentSessionId(): string;\n\townerId: string;\n\tdrainQueuedWorkers(): void;\n\twarn(message: string): void;\n\tisProcessAlive?(pid: number): boolean;\n}\n\n/**\n * Owns one controller process's write-reservation lifecycle: admission, exact-fence release,\n * event-driven queue wakeup, and fail-closed recovery of positively dead owners.\n */\nexport class WorkerWriteReservationCoordinator {\n\tprivate readonly options: WorkerWriteReservationCoordinatorOptions;\n\tprivate readonly store: WorkerWriteReservationStore;\n\tprivate readonly leases = new Map<string, WorkerWriteReservationLease>();\n\tprivate watchDispose: (() => void) | undefined;\n\n\tconstructor(options: WorkerWriteReservationCoordinatorOptions) {\n\t\tthis.options = options;\n\t\tthis.store = new WorkerWriteReservationStore({ agentDir: options.agentDir });\n\t}\n\n\tacquire(\n\t\tlaneId: string,\n\t\tattempt: Pick<AttemptRuntimeState, \"attemptId\" | \"lease\">,\n\t\tplan: Pick<WorkerExecutionPlan, \"writeEnabled\" | \"writePaths\">,\n\t): WorkerWriteReservationAdmission {\n\t\tif (!plan.writeEnabled || plan.writePaths.length === 0) return { kind: \"granted\" };\n\t\tconst fencingToken = (attempt.lease?.fencingToken ?? 0) + 1;\n\t\tconst current = this.leases.get(laneId);\n\t\tif (current && current.attemptId === attempt.attemptId && current.fencingToken === fencingToken) {\n\t\t\treturn { kind: \"granted\" };\n\t\t}\n\t\ttry {\n\t\t\tconst result = this.store.acquire({\n\t\t\t\tparentSessionId: this.options.getParentSessionId(),\n\t\t\t\townerId: this.options.ownerId,\n\t\t\t\ttaskId: laneId,\n\t\t\t\tattemptId: attempt.attemptId,\n\t\t\t\tfencingToken,\n\t\t\t\taccess: \"write\",\n\t\t\t\tworkspace: this.workspace(),\n\t\t\t\twriteScopes: plan.writePaths,\n\t\t\t});\n\t\t\tif (result.kind !== \"granted\" || !result.lease) {\n\t\t\t\tthis.ensureWatch();\n\t\t\t\treturn { kind: \"blocked\" };\n\t\t\t}\n\t\t\tthis.leases.set(laneId, result.lease);\n\t\t\treturn { kind: \"granted\" };\n\t\t} catch (error) {\n\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\tthis.options.warn(`Worker write reservation denied: ${message}`);\n\t\t\treturn {\n\t\t\t\tkind: \"denied\",\n\t\t\t\treasonCode: message.includes(\"within the execution root\")\n\t\t\t\t\t? \"write_reservation_scope_invalid\"\n\t\t\t\t\t: \"write_reservation_unavailable\",\n\t\t\t};\n\t\t}\n\t}\n\n\thasFenceMismatch(laneId: string, attemptId: string, fencingToken: number): boolean {\n\t\tconst lease = this.leases.get(laneId);\n\t\treturn lease !== undefined && (lease.attemptId !== attemptId || lease.fencingToken !== fencingToken);\n\t}\n\n\trelease(laneId: string, expectedAttemptId?: string, expectedFencingToken?: number): void {\n\t\tconst held = this.leases.get(laneId);\n\t\tif (!held) return;\n\t\tif (expectedAttemptId !== undefined && held.attemptId !== expectedAttemptId) return;\n\t\tif (expectedFencingToken !== undefined && held.fencingToken !== expectedFencingToken) return;\n\t\ttry {\n\t\t\tthis.store.release(held);\n\t\t} catch (error) {\n\t\t\tthis.options.warn(\n\t\t\t\t`Failed to release worker write reservation ${laneId}: ${error instanceof Error ? error.message : String(error)}`,\n\t\t\t);\n\t\t} finally {\n\t\t\tif (this.leases.get(laneId) === held) this.leases.delete(laneId);\n\t\t}\n\t}\n\n\trecoverProvenStale(): void {\n\t\tconst discovered = this.store.recover({ workspace: this.workspace(), evidence: [] });\n\t\tconst evidence = discovered.outcomes.map((outcome) => ({\n\t\t\treservationId: outcome.reservationId,\n\t\t\tstate:\n\t\t\t\tlocalWorkerProcessOwnerLiveness(\n\t\t\t\t\toutcome.lease.ownerId,\n\t\t\t\t\tthis.options.isProcessAlive ?? isLocalProcessAlive,\n\t\t\t\t) === \"dead\"\n\t\t\t\t\t? (\"not_live\" as const)\n\t\t\t\t\t: (\"unknown\" as const),\n\t\t}));\n\t\tfor (const outcome of this.store.recover({ workspace: this.workspace(), evidence }).outcomes) {\n\t\t\tif (outcome.kind !== \"stale\") continue;\n\t\t\tconst released = this.store.release(outcome.lease);\n\t\t\tif (released.kind !== \"released\" && released.kind !== \"not_found\") {\n\t\t\t\tthis.options.warn(\n\t\t\t\t\t`Stale worker write reservation ${outcome.reservationId} was not released (${released.kind}).`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n\n\tdispose(): void {\n\t\tthis.watchDispose?.();\n\t\tthis.watchDispose = undefined;\n\t}\n\n\tprivate workspace() {\n\t\tconst cwd = this.options.getCwd();\n\t\treturn { repositoryRoot: cwd, executionRoot: cwd };\n\t}\n\n\tprivate ensureWatch(): void {\n\t\tif (this.watchDispose) return;\n\t\tthis.watchDispose = this.store.watchAvailability(this.workspace(), () => this.options.drainQueuedWorkers());\n\t}\n}\n"]}
@@ -0,0 +1,107 @@
1
+ import { isLocalProcessAlive, localWorkerProcessOwnerLiveness } from "./worker-process-owner.js";
2
+ import { WorkerWriteReservationStore } from "./worker-write-reservation.js";
3
+ /**
4
+ * Owns one controller process's write-reservation lifecycle: admission, exact-fence release,
5
+ * event-driven queue wakeup, and fail-closed recovery of positively dead owners.
6
+ */
7
+ export class WorkerWriteReservationCoordinator {
8
+ options;
9
+ store;
10
+ leases = new Map();
11
+ watchDispose;
12
+ constructor(options) {
13
+ this.options = options;
14
+ this.store = new WorkerWriteReservationStore({ agentDir: options.agentDir });
15
+ }
16
+ acquire(laneId, attempt, plan) {
17
+ if (!plan.writeEnabled || plan.writePaths.length === 0)
18
+ return { kind: "granted" };
19
+ const fencingToken = (attempt.lease?.fencingToken ?? 0) + 1;
20
+ const current = this.leases.get(laneId);
21
+ if (current && current.attemptId === attempt.attemptId && current.fencingToken === fencingToken) {
22
+ return { kind: "granted" };
23
+ }
24
+ try {
25
+ const result = this.store.acquire({
26
+ parentSessionId: this.options.getParentSessionId(),
27
+ ownerId: this.options.ownerId,
28
+ taskId: laneId,
29
+ attemptId: attempt.attemptId,
30
+ fencingToken,
31
+ access: "write",
32
+ workspace: this.workspace(),
33
+ writeScopes: plan.writePaths,
34
+ });
35
+ if (result.kind !== "granted" || !result.lease) {
36
+ this.ensureWatch();
37
+ return { kind: "blocked" };
38
+ }
39
+ this.leases.set(laneId, result.lease);
40
+ return { kind: "granted" };
41
+ }
42
+ catch (error) {
43
+ const message = error instanceof Error ? error.message : String(error);
44
+ this.options.warn(`Worker write reservation denied: ${message}`);
45
+ return {
46
+ kind: "denied",
47
+ reasonCode: message.includes("within the execution root")
48
+ ? "write_reservation_scope_invalid"
49
+ : "write_reservation_unavailable",
50
+ };
51
+ }
52
+ }
53
+ hasFenceMismatch(laneId, attemptId, fencingToken) {
54
+ const lease = this.leases.get(laneId);
55
+ return lease !== undefined && (lease.attemptId !== attemptId || lease.fencingToken !== fencingToken);
56
+ }
57
+ release(laneId, expectedAttemptId, expectedFencingToken) {
58
+ const held = this.leases.get(laneId);
59
+ if (!held)
60
+ return;
61
+ if (expectedAttemptId !== undefined && held.attemptId !== expectedAttemptId)
62
+ return;
63
+ if (expectedFencingToken !== undefined && held.fencingToken !== expectedFencingToken)
64
+ return;
65
+ try {
66
+ this.store.release(held);
67
+ }
68
+ catch (error) {
69
+ this.options.warn(`Failed to release worker write reservation ${laneId}: ${error instanceof Error ? error.message : String(error)}`);
70
+ }
71
+ finally {
72
+ if (this.leases.get(laneId) === held)
73
+ this.leases.delete(laneId);
74
+ }
75
+ }
76
+ recoverProvenStale() {
77
+ const discovered = this.store.recover({ workspace: this.workspace(), evidence: [] });
78
+ const evidence = discovered.outcomes.map((outcome) => ({
79
+ reservationId: outcome.reservationId,
80
+ state: localWorkerProcessOwnerLiveness(outcome.lease.ownerId, this.options.isProcessAlive ?? isLocalProcessAlive) === "dead"
81
+ ? "not_live"
82
+ : "unknown",
83
+ }));
84
+ for (const outcome of this.store.recover({ workspace: this.workspace(), evidence }).outcomes) {
85
+ if (outcome.kind !== "stale")
86
+ continue;
87
+ const released = this.store.release(outcome.lease);
88
+ if (released.kind !== "released" && released.kind !== "not_found") {
89
+ this.options.warn(`Stale worker write reservation ${outcome.reservationId} was not released (${released.kind}).`);
90
+ }
91
+ }
92
+ }
93
+ dispose() {
94
+ this.watchDispose?.();
95
+ this.watchDispose = undefined;
96
+ }
97
+ workspace() {
98
+ const cwd = this.options.getCwd();
99
+ return { repositoryRoot: cwd, executionRoot: cwd };
100
+ }
101
+ ensureWatch() {
102
+ if (this.watchDispose)
103
+ return;
104
+ this.watchDispose = this.store.watchAvailability(this.workspace(), () => this.options.drainQueuedWorkers());
105
+ }
106
+ }
107
+ //# sourceMappingURL=worker-write-reservation-coordinator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"worker-write-reservation-coordinator.js","sourceRoot":"","sources":["../../../src/core/delegation/worker-write-reservation-coordinator.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AttemptRuntimeState } from \"../orchestration/task-runtime.ts\";\nimport type { WorkerExecutionPlan } from \"./worker-execution-policy.ts\";\nimport { isLocalProcessAlive, localWorkerProcessOwnerLiveness } from \"./worker-process-owner.ts\";\nimport { type WorkerWriteReservationLease, WorkerWriteReservationStore } from \"./worker-write-reservation.ts\";\n\nexport type WorkerWriteReservationAdmission =\n\t| { kind: \"granted\" }\n\t| { kind: \"blocked\" }\n\t| { kind: \"denied\"; reasonCode: \"write_reservation_scope_invalid\" | \"write_reservation_unavailable\" };\n\nexport interface WorkerWriteReservationCoordinatorOptions {\n\tagentDir: string;\n\tgetCwd(): string;\n\tgetParentSessionId(): string;\n\townerId: string;\n\tdrainQueuedWorkers(): void;\n\twarn(message: string): void;\n\tisProcessAlive?(pid: number): boolean;\n}\n\n/**\n * Owns one controller process's write-reservation lifecycle: admission, exact-fence release,\n * event-driven queue wakeup, and fail-closed recovery of positively dead owners.\n */\nexport class WorkerWriteReservationCoordinator {\n\tprivate readonly options: WorkerWriteReservationCoordinatorOptions;\n\tprivate readonly store: WorkerWriteReservationStore;\n\tprivate readonly leases = new Map<string, WorkerWriteReservationLease>();\n\tprivate watchDispose: (() => void) | undefined;\n\n\tconstructor(options: WorkerWriteReservationCoordinatorOptions) {\n\t\tthis.options = options;\n\t\tthis.store = new WorkerWriteReservationStore({ agentDir: options.agentDir });\n\t}\n\n\tacquire(\n\t\tlaneId: string,\n\t\tattempt: Pick<AttemptRuntimeState, \"attemptId\" | \"lease\">,\n\t\tplan: Pick<WorkerExecutionPlan, \"writeEnabled\" | \"writePaths\">,\n\t): WorkerWriteReservationAdmission {\n\t\tif (!plan.writeEnabled || plan.writePaths.length === 0) return { kind: \"granted\" };\n\t\tconst fencingToken = (attempt.lease?.fencingToken ?? 0) + 1;\n\t\tconst current = this.leases.get(laneId);\n\t\tif (current && current.attemptId === attempt.attemptId && current.fencingToken === fencingToken) {\n\t\t\treturn { kind: \"granted\" };\n\t\t}\n\t\ttry {\n\t\t\tconst result = this.store.acquire({\n\t\t\t\tparentSessionId: this.options.getParentSessionId(),\n\t\t\t\townerId: this.options.ownerId,\n\t\t\t\ttaskId: laneId,\n\t\t\t\tattemptId: attempt.attemptId,\n\t\t\t\tfencingToken,\n\t\t\t\taccess: \"write\",\n\t\t\t\tworkspace: this.workspace(),\n\t\t\t\twriteScopes: plan.writePaths,\n\t\t\t});\n\t\t\tif (result.kind !== \"granted\" || !result.lease) {\n\t\t\t\tthis.ensureWatch();\n\t\t\t\treturn { kind: \"blocked\" };\n\t\t\t}\n\t\t\tthis.leases.set(laneId, result.lease);\n\t\t\treturn { kind: \"granted\" };\n\t\t} catch (error) {\n\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\tthis.options.warn(`Worker write reservation denied: ${message}`);\n\t\t\treturn {\n\t\t\t\tkind: \"denied\",\n\t\t\t\treasonCode: message.includes(\"within the execution root\")\n\t\t\t\t\t? \"write_reservation_scope_invalid\"\n\t\t\t\t\t: \"write_reservation_unavailable\",\n\t\t\t};\n\t\t}\n\t}\n\n\thasFenceMismatch(laneId: string, attemptId: string, fencingToken: number): boolean {\n\t\tconst lease = this.leases.get(laneId);\n\t\treturn lease !== undefined && (lease.attemptId !== attemptId || lease.fencingToken !== fencingToken);\n\t}\n\n\trelease(laneId: string, expectedAttemptId?: string, expectedFencingToken?: number): void {\n\t\tconst held = this.leases.get(laneId);\n\t\tif (!held) return;\n\t\tif (expectedAttemptId !== undefined && held.attemptId !== expectedAttemptId) return;\n\t\tif (expectedFencingToken !== undefined && held.fencingToken !== expectedFencingToken) return;\n\t\ttry {\n\t\t\tthis.store.release(held);\n\t\t} catch (error) {\n\t\t\tthis.options.warn(\n\t\t\t\t`Failed to release worker write reservation ${laneId}: ${error instanceof Error ? error.message : String(error)}`,\n\t\t\t);\n\t\t} finally {\n\t\t\tif (this.leases.get(laneId) === held) this.leases.delete(laneId);\n\t\t}\n\t}\n\n\trecoverProvenStale(): void {\n\t\tconst discovered = this.store.recover({ workspace: this.workspace(), evidence: [] });\n\t\tconst evidence = discovered.outcomes.map((outcome) => ({\n\t\t\treservationId: outcome.reservationId,\n\t\t\tstate:\n\t\t\t\tlocalWorkerProcessOwnerLiveness(\n\t\t\t\t\toutcome.lease.ownerId,\n\t\t\t\t\tthis.options.isProcessAlive ?? isLocalProcessAlive,\n\t\t\t\t) === \"dead\"\n\t\t\t\t\t? (\"not_live\" as const)\n\t\t\t\t\t: (\"unknown\" as const),\n\t\t}));\n\t\tfor (const outcome of this.store.recover({ workspace: this.workspace(), evidence }).outcomes) {\n\t\t\tif (outcome.kind !== \"stale\") continue;\n\t\t\tconst released = this.store.release(outcome.lease);\n\t\t\tif (released.kind !== \"released\" && released.kind !== \"not_found\") {\n\t\t\t\tthis.options.warn(\n\t\t\t\t\t`Stale worker write reservation ${outcome.reservationId} was not released (${released.kind}).`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n\n\tdispose(): void {\n\t\tthis.watchDispose?.();\n\t\tthis.watchDispose = undefined;\n\t}\n\n\tprivate workspace() {\n\t\tconst cwd = this.options.getCwd();\n\t\treturn { repositoryRoot: cwd, executionRoot: cwd };\n\t}\n\n\tprivate ensureWatch(): void {\n\t\tif (this.watchDispose) return;\n\t\tthis.watchDispose = this.store.watchAvailability(this.workspace(), () => this.options.drainQueuedWorkers());\n\t}\n}\n"]}
@@ -0,0 +1,90 @@
1
+ import { type FSWatcher } from "node:fs";
2
+ type WatchDirectory = (directory: string, options: {
3
+ persistent: boolean;
4
+ }, listener: (eventType: string, fileName: string | Buffer | null) => void) => FSWatcher;
5
+ export interface WorkerWriteReservationWorkspace {
6
+ /** Canonical repository/workspace root shared by primary and isolated worktrees. */
7
+ repositoryRoot: string;
8
+ /** Actual checkout used by this attempt. A non-primary checkout requires an isolation identity. */
9
+ executionRoot: string;
10
+ /** Durable worktree identity supplied by the worktree owner, never a model-provided label. */
11
+ isolatedWorktreeId?: string;
12
+ }
13
+ export interface WorkerWriteReservationRequest {
14
+ parentSessionId: string;
15
+ /** Exact pi-owned process instance that acquired this reservation. */
16
+ ownerId: string;
17
+ taskId: string;
18
+ attemptId: string;
19
+ fencingToken: number;
20
+ access: "read" | "write";
21
+ workspace: WorkerWriteReservationWorkspace;
22
+ /** Absolute authorized write roots. They are resolved before persistence. */
23
+ writeScopes: readonly string[];
24
+ }
25
+ export interface WorkerWriteReservationLease {
26
+ filePath: string;
27
+ reservationId: string;
28
+ parentSessionId: string;
29
+ ownerId: string;
30
+ taskId: string;
31
+ attemptId: string;
32
+ fencingToken: number;
33
+ repositoryRoot: string;
34
+ executionRoot: string;
35
+ executionIdentity: string;
36
+ isolatedWorktreeId?: string;
37
+ writeScopes: readonly string[];
38
+ }
39
+ export type WorkerWriteReservationAcquireResult = {
40
+ kind: "granted";
41
+ lease?: WorkerWriteReservationLease;
42
+ } | {
43
+ kind: "blocked";
44
+ reasonCode: "overlapping_write_scope" | "attempt_reservation_conflict" | "reservation_capacity_reached";
45
+ conflictingReservationIds?: readonly string[];
46
+ };
47
+ export type WorkerWriteReservationReleaseResult = {
48
+ kind: "released";
49
+ } | {
50
+ kind: "stale_fence";
51
+ } | {
52
+ kind: "not_found";
53
+ };
54
+ export type WorkerWriteReservationEvidenceState = "live" | "not_live" | "unknown";
55
+ export interface WorkerWriteReservationRecoveryEvidence {
56
+ reservationId: string;
57
+ state: WorkerWriteReservationEvidenceState;
58
+ }
59
+ export interface WorkerWriteReservationRecoveryRequest {
60
+ workspace: WorkerWriteReservationWorkspace;
61
+ /** Caller-owned lease observations. This store never polls process state or worker output. */
62
+ evidence: readonly WorkerWriteReservationRecoveryEvidence[];
63
+ }
64
+ export interface WorkerWriteReservationRecoveryOutcome {
65
+ kind: "active" | "stale" | "inspection_required";
66
+ reservationId: string;
67
+ lease: WorkerWriteReservationLease;
68
+ }
69
+ /**
70
+ * Cross-process, agent-owned admission seam for concurrent worker mutations. It owns only
71
+ * reservations; lifecycle, process liveness, and terminal status remain controller concerns.
72
+ */
73
+ export declare class WorkerWriteReservationStore {
74
+ private readonly agentDir;
75
+ private readonly watchDirectory;
76
+ constructor(options: {
77
+ agentDir: string;
78
+ watchDirectory?: WatchDirectory;
79
+ });
80
+ acquire(request: WorkerWriteReservationRequest): WorkerWriteReservationAcquireResult;
81
+ release(lease: WorkerWriteReservationLease): WorkerWriteReservationReleaseResult;
82
+ /** Event-driven cross-process wakeup for queued write admission; caller owns disposal. */
83
+ watchAvailability(workspace: WorkerWriteReservationWorkspace, listener: () => void): () => void;
84
+ recover(request: WorkerWriteReservationRecoveryRequest): {
85
+ outcomes: WorkerWriteReservationRecoveryOutcome[];
86
+ };
87
+ private readLocked;
88
+ }
89
+ export {};
90
+ //# sourceMappingURL=worker-write-reservation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"worker-write-reservation.d.ts","sourceRoot":"","sources":["../../../src/core/delegation/worker-write-reservation.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash, randomUUID } from \"node:crypto\";\nimport { existsSync, type FSWatcher, mkdirSync, unlinkSync, watch } from \"node:fs\";\nimport { basename, dirname } from \"node:path\";\nimport { stateFile } from \"../agent-paths.ts\";\nimport {\n\tcanonicalPathScopeIdentity,\n\tisPathWithinScope,\n\tpathScopesOverlap,\n\tsafeRealpathSync,\n} from \"../autonomy/path-scope.ts\";\nimport { withFileLockSync, writeFileAtomicSync } from \"../util/atomic-file.ts\";\nimport { readBoundedTextFileSync } from \"../util/bounded-file.ts\";\nimport { requireBoundedTrimmedText } from \"../util/bounded-value.ts\";\nimport { parseLocalWorkerProcessOwnerId } from \"./worker-process-owner.ts\";\n\nconst SCHEMA_VERSION = 1;\nconst MAX_IDENTIFIER_CHARS = 256;\nconst MAX_PATH_CHARS = 4096;\nconst MAX_SCOPES = 32;\nconst MAX_RESERVATIONS = 64;\nconst MAX_FILE_BYTES = 128 * 1024;\nconst availabilityListeners = new Map<string, Set<() => void>>();\n\ntype WatchDirectory = (\n\tdirectory: string,\n\toptions: { persistent: boolean },\n\tlistener: (eventType: string, fileName: string | Buffer | null) => void,\n) => FSWatcher;\n\nexport interface WorkerWriteReservationWorkspace {\n\t/** Canonical repository/workspace root shared by primary and isolated worktrees. */\n\trepositoryRoot: string;\n\t/** Actual checkout used by this attempt. A non-primary checkout requires an isolation identity. */\n\texecutionRoot: string;\n\t/** Durable worktree identity supplied by the worktree owner, never a model-provided label. */\n\tisolatedWorktreeId?: string;\n}\n\nexport interface WorkerWriteReservationRequest {\n\tparentSessionId: string;\n\t/** Exact pi-owned process instance that acquired this reservation. */\n\townerId: string;\n\ttaskId: string;\n\tattemptId: string;\n\tfencingToken: number;\n\taccess: \"read\" | \"write\";\n\tworkspace: WorkerWriteReservationWorkspace;\n\t/** Absolute authorized write roots. They are resolved before persistence. */\n\twriteScopes: readonly string[];\n}\n\nexport interface WorkerWriteReservationLease {\n\tfilePath: string;\n\treservationId: string;\n\tparentSessionId: string;\n\townerId: string;\n\ttaskId: string;\n\tattemptId: string;\n\tfencingToken: number;\n\trepositoryRoot: string;\n\texecutionRoot: string;\n\texecutionIdentity: string;\n\tisolatedWorktreeId?: string;\n\twriteScopes: readonly string[];\n}\n\nexport type WorkerWriteReservationAcquireResult =\n\t| { kind: \"granted\"; lease?: WorkerWriteReservationLease }\n\t| {\n\t\t\tkind: \"blocked\";\n\t\t\treasonCode: \"overlapping_write_scope\" | \"attempt_reservation_conflict\" | \"reservation_capacity_reached\";\n\t\t\tconflictingReservationIds?: readonly string[];\n\t };\n\nexport type WorkerWriteReservationReleaseResult =\n\t| { kind: \"released\" }\n\t| { kind: \"stale_fence\" }\n\t| { kind: \"not_found\" };\n\nexport type WorkerWriteReservationEvidenceState = \"live\" | \"not_live\" | \"unknown\";\n\nexport interface WorkerWriteReservationRecoveryEvidence {\n\treservationId: string;\n\tstate: WorkerWriteReservationEvidenceState;\n}\n\nexport interface WorkerWriteReservationRecoveryRequest {\n\tworkspace: WorkerWriteReservationWorkspace;\n\t/** Caller-owned lease observations. This store never polls process state or worker output. */\n\tevidence: readonly WorkerWriteReservationRecoveryEvidence[];\n}\n\nexport interface WorkerWriteReservationRecoveryOutcome {\n\tkind: \"active\" | \"stale\" | \"inspection_required\";\n\treservationId: string;\n\tlease: WorkerWriteReservationLease;\n}\n\ninterface PersistedReservation {\n\treservationId: string;\n\tparentSessionId: string;\n\townerId: string;\n\ttaskId: string;\n\tattemptId: string;\n\tfencingToken: number;\n\trepositoryRoot: string;\n\texecutionRoot: string;\n\texecutionIdentity: string;\n\tisolatedWorktreeId?: string;\n\twriteScopes: string[];\n\tcreatedAt: string;\n}\n\ninterface PersistedReservationStore {\n\tschemaVersion: number;\n\trepositoryRoot: string;\n\treservations: PersistedReservation[];\n}\n\ninterface CanonicalWorkspace {\n\trepositoryRoot: string;\n\texecutionRoot: string;\n\texecutionIdentity: string;\n\tisolatedWorktreeId?: string;\n}\n\nfunction positiveFencingToken(value: number): number {\n\tif (!Number.isSafeInteger(value) || value < 1) throw new TypeError(\"Fencing token must be a positive safe integer.\");\n\treturn value;\n}\n\n/**\n * Prefer a real path for filesystem-backed scopes, but preserve a foreign lexical path family\n * (for example a Windows drive path inspected from WSL) so shared path-scope helpers retain\n * their documented Windows drive/UNC comparison semantics.\n */\nfunction canonicalRealOrLexicalPath(value: string, label: string): string {\n\tconst lexical = canonicalPathScopeIdentity(requireBoundedTrimmedText(value, MAX_PATH_CHARS, label));\n\tconst resolved = safeRealpathSync(lexical);\n\treturn pathScopesOverlap(lexical, resolved) ? canonicalPathScopeIdentity(resolved) : lexical;\n}\n\nfunction canonicalWorkspace(workspace: WorkerWriteReservationWorkspace): CanonicalWorkspace {\n\tconst repositoryRoot = canonicalRealOrLexicalPath(workspace.repositoryRoot, \"Repository root\");\n\tconst executionRoot = canonicalRealOrLexicalPath(workspace.executionRoot, \"Execution root\");\n\tconst isolatedWorktreeId = workspace.isolatedWorktreeId\n\t\t? requireBoundedTrimmedText(workspace.isolatedWorktreeId, MAX_IDENTIFIER_CHARS, \"Isolated worktree id\")\n\t\t: undefined;\n\tconst isPrimaryExecutionRoot =\n\t\tisPathWithinScope(executionRoot, repositoryRoot) && isPathWithinScope(repositoryRoot, executionRoot);\n\tif (isPrimaryExecutionRoot && isolatedWorktreeId) {\n\t\tthrow new TypeError(\"An isolated worktree execution root must differ from the shared repository root.\");\n\t}\n\tif (!isPrimaryExecutionRoot && !isolatedWorktreeId) {\n\t\tthrow new TypeError(\"A non-primary execution root requires an explicit isolated worktree identity.\");\n\t}\n\treturn {\n\t\trepositoryRoot,\n\t\texecutionRoot,\n\t\texecutionIdentity: isolatedWorktreeId ? `isolated:${executionRoot}` : `shared:${repositoryRoot}`,\n\t\t...(isolatedWorktreeId ? { isolatedWorktreeId } : {}),\n\t};\n}\n\nfunction reservationFile(agentDir: string, repositoryRoot: string): string {\n\tconst digest = createHash(\"sha256\")\n\t\t.update(\"pi-worker-write-reservations-v1\")\n\t\t.update(\"\\0\")\n\t\t.update(repositoryRoot)\n\t\t.digest(\"hex\");\n\treturn stateFile(agentDir, \"orchestration\", \"worker-write-reservations\", `${digest}.json`);\n}\n\nfunction notifyAvailability(filePath: string): void {\n\tfor (const listener of availabilityListeners.get(filePath) ?? []) queueMicrotask(listener);\n}\n\nfunction leaseFromRecord(filePath: string, record: PersistedReservation): WorkerWriteReservationLease {\n\treturn {\n\t\tfilePath,\n\t\treservationId: record.reservationId,\n\t\tparentSessionId: record.parentSessionId,\n\t\townerId: record.ownerId,\n\t\ttaskId: record.taskId,\n\t\tattemptId: record.attemptId,\n\t\tfencingToken: record.fencingToken,\n\t\trepositoryRoot: record.repositoryRoot,\n\t\texecutionRoot: record.executionRoot,\n\t\texecutionIdentity: record.executionIdentity,\n\t\t...(record.isolatedWorktreeId ? { isolatedWorktreeId: record.isolatedWorktreeId } : {}),\n\t\twriteScopes: [...record.writeScopes],\n\t};\n}\n\nfunction stringField(value: unknown, label: string, maximum: number): string {\n\tif (typeof value !== \"string\") throw new Error(`Worker write reservation ${label} is invalid.`);\n\treturn requireBoundedTrimmedText(value, maximum, `Worker write reservation ${label}`);\n}\n\nfunction localOwnerId(value: unknown): string {\n\tconst ownerId = stringField(value, \"owner id\", MAX_IDENTIFIER_CHARS);\n\tif (!parseLocalWorkerProcessOwnerId(ownerId)) throw new Error(\"Worker write reservation owner id is invalid.\");\n\treturn ownerId;\n}\n\nfunction parseReservation(value: unknown, repositoryRoot: string): PersistedReservation {\n\tif (!value || typeof value !== \"object\" || Array.isArray(value))\n\t\tthrow new Error(\"Worker write reservation entry is invalid.\");\n\tconst entry = value as Record<string, unknown>;\n\tconst fencingToken = entry.fencingToken;\n\tif (typeof fencingToken !== \"number\") throw new Error(\"Worker write reservation fencing token is invalid.\");\n\tconst parsedRepositoryRoot = stringField(entry.repositoryRoot, \"repository root\", MAX_PATH_CHARS);\n\tif (parsedRepositoryRoot !== repositoryRoot)\n\t\tthrow new Error(\"Worker write reservation repository root conflicts with its store.\");\n\tif (!Array.isArray(entry.writeScopes) || entry.writeScopes.length === 0 || entry.writeScopes.length > MAX_SCOPES) {\n\t\tthrow new Error(\"Worker write reservation scopes are invalid.\");\n\t}\n\tconst writeScopes = entry.writeScopes.map((scope) => stringField(scope, \"write scope\", MAX_PATH_CHARS));\n\tconst isolatedWorktreeId =\n\t\tentry.isolatedWorktreeId === undefined\n\t\t\t? undefined\n\t\t\t: stringField(entry.isolatedWorktreeId, \"isolated worktree id\", MAX_IDENTIFIER_CHARS);\n\treturn {\n\t\treservationId: stringField(entry.reservationId, \"id\", MAX_IDENTIFIER_CHARS),\n\t\tparentSessionId: stringField(entry.parentSessionId, \"parent session\", MAX_IDENTIFIER_CHARS),\n\t\townerId: localOwnerId(entry.ownerId),\n\t\ttaskId: stringField(entry.taskId, \"task\", MAX_IDENTIFIER_CHARS),\n\t\tattemptId: stringField(entry.attemptId, \"attempt\", MAX_IDENTIFIER_CHARS),\n\t\tfencingToken: positiveFencingToken(fencingToken),\n\t\trepositoryRoot: parsedRepositoryRoot,\n\t\texecutionRoot: stringField(entry.executionRoot, \"execution root\", MAX_PATH_CHARS),\n\t\texecutionIdentity: stringField(entry.executionIdentity, \"execution identity\", MAX_PATH_CHARS),\n\t\t...(isolatedWorktreeId ? { isolatedWorktreeId } : {}),\n\t\twriteScopes,\n\t\tcreatedAt: stringField(entry.createdAt, \"created time\", MAX_IDENTIFIER_CHARS),\n\t};\n}\n\nfunction parseStore(raw: string, repositoryRoot: string): PersistedReservationStore {\n\tif (Buffer.byteLength(raw, \"utf-8\") > MAX_FILE_BYTES)\n\t\tthrow new Error(\"Worker write reservation store exceeds its byte limit.\");\n\tlet value: unknown;\n\ttry {\n\t\tvalue = JSON.parse(raw);\n\t} catch {\n\t\tthrow new Error(\"Worker write reservation store is unreadable; refusing concurrent write admission.\");\n\t}\n\tif (!value || typeof value !== \"object\" || Array.isArray(value))\n\t\tthrow new Error(\"Worker write reservation store is invalid.\");\n\tconst store = value as Record<string, unknown>;\n\tif (store.schemaVersion !== SCHEMA_VERSION) throw new Error(\"Worker write reservation store schema is unsupported.\");\n\tif (stringField(store.repositoryRoot, \"store repository root\", MAX_PATH_CHARS) !== repositoryRoot) {\n\t\tthrow new Error(\"Worker write reservation store root conflicts with the requested workspace.\");\n\t}\n\tif (!Array.isArray(store.reservations) || store.reservations.length > MAX_RESERVATIONS) {\n\t\tthrow new Error(\"Worker write reservation store entries are invalid.\");\n\t}\n\tconst reservations = store.reservations.map((entry) => parseReservation(entry, repositoryRoot));\n\tif (new Set(reservations.map((entry) => entry.reservationId)).size !== reservations.length) {\n\t\tthrow new Error(\"Worker write reservation store contains duplicate reservation identities.\");\n\t}\n\treturn { schemaVersion: SCHEMA_VERSION, repositoryRoot, reservations };\n}\n\nfunction writeStore(filePath: string, store: PersistedReservationStore): void {\n\tconst serialized = `${JSON.stringify(store)}\\n`;\n\tif (Buffer.byteLength(serialized, \"utf-8\") > MAX_FILE_BYTES) {\n\t\tthrow new Error(\"Worker write reservation store would exceed its byte limit.\");\n\t}\n\twriteFileAtomicSync(filePath, serialized, { mode: 0o600 });\n}\n\nfunction overlappingReservationIds(\n\treservations: readonly PersistedReservation[],\n\texecutionIdentity: string,\n\twriteScopes: readonly string[],\n): string[] {\n\tconst matches: string[] = [];\n\tfor (const reservation of reservations) {\n\t\tif (reservation.executionIdentity !== executionIdentity) continue;\n\t\tif (\n\t\t\treservation.writeScopes.some((reservedScope) =>\n\t\t\t\twriteScopes.some((requestedScope) => pathScopesOverlap(reservedScope, requestedScope)),\n\t\t\t)\n\t\t) {\n\t\t\tmatches.push(reservation.reservationId);\n\t\t}\n\t}\n\treturn matches;\n}\n\n/**\n * Cross-process, agent-owned admission seam for concurrent worker mutations. It owns only\n * reservations; lifecycle, process liveness, and terminal status remain controller concerns.\n */\nexport class WorkerWriteReservationStore {\n\tprivate readonly agentDir: string;\n\tprivate readonly watchDirectory: WatchDirectory;\n\n\tconstructor(options: { agentDir: string; watchDirectory?: WatchDirectory }) {\n\t\tthis.agentDir = requireBoundedTrimmedText(options.agentDir, MAX_PATH_CHARS, \"Agent directory\");\n\t\tthis.watchDirectory = options.watchDirectory ?? watch;\n\t}\n\n\tacquire(request: WorkerWriteReservationRequest): WorkerWriteReservationAcquireResult {\n\t\tif (request.access === \"read\") return { kind: \"granted\" };\n\t\tif (request.access !== \"write\") throw new TypeError(\"Worker write reservation access must be read or write.\");\n\t\tif (request.writeScopes.length === 0 || request.writeScopes.length > MAX_SCOPES) {\n\t\t\tthrow new TypeError(`Write reservations require from 1 through ${MAX_SCOPES} write scopes.`);\n\t\t}\n\t\tconst workspace = canonicalWorkspace(request.workspace);\n\t\tconst writeScopes = [\n\t\t\t...new Set(request.writeScopes.map((scope) => canonicalRealOrLexicalPath(scope, \"Write scope\"))),\n\t\t].sort();\n\t\tif (writeScopes.some((scope) => !isPathWithinScope(scope, workspace.executionRoot))) {\n\t\t\tthrow new TypeError(\"Write reservation scopes must be within the execution root.\");\n\t\t}\n\t\tconst parentSessionId = requireBoundedTrimmedText(\n\t\t\trequest.parentSessionId,\n\t\t\tMAX_IDENTIFIER_CHARS,\n\t\t\t\"Parent session id\",\n\t\t);\n\t\tconst ownerId = localOwnerId(request.ownerId);\n\t\tconst taskId = requireBoundedTrimmedText(request.taskId, MAX_IDENTIFIER_CHARS, \"Task id\");\n\t\tconst attemptId = requireBoundedTrimmedText(request.attemptId, MAX_IDENTIFIER_CHARS, \"Attempt id\");\n\t\tconst fencingToken = positiveFencingToken(request.fencingToken);\n\t\tconst filePath = reservationFile(this.agentDir, workspace.repositoryRoot);\n\n\t\treturn withFileLockSync(filePath, () => {\n\t\t\tconst store = this.readLocked(filePath, workspace.repositoryRoot);\n\t\t\tconst matchingAttempt = store.reservations.find(\n\t\t\t\t(reservation) =>\n\t\t\t\t\treservation.parentSessionId === parentSessionId &&\n\t\t\t\t\treservation.ownerId === ownerId &&\n\t\t\t\t\treservation.taskId === taskId &&\n\t\t\t\t\treservation.attemptId === attemptId &&\n\t\t\t\t\treservation.fencingToken === fencingToken,\n\t\t\t);\n\t\t\tif (matchingAttempt) {\n\t\t\t\tif (\n\t\t\t\t\tmatchingAttempt.executionIdentity !== workspace.executionIdentity ||\n\t\t\t\t\tmatchingAttempt.isolatedWorktreeId !== workspace.isolatedWorktreeId ||\n\t\t\t\t\tmatchingAttempt.writeScopes.length !== writeScopes.length ||\n\t\t\t\t\tmatchingAttempt.writeScopes.some((scope, index) => scope !== writeScopes[index])\n\t\t\t\t) {\n\t\t\t\t\treturn { kind: \"blocked\", reasonCode: \"attempt_reservation_conflict\" };\n\t\t\t\t}\n\t\t\t\treturn { kind: \"granted\", lease: leaseFromRecord(filePath, matchingAttempt) };\n\t\t\t}\n\t\t\tconst conflicts = overlappingReservationIds(store.reservations, workspace.executionIdentity, writeScopes);\n\t\t\tif (conflicts.length > 0) {\n\t\t\t\treturn { kind: \"blocked\", reasonCode: \"overlapping_write_scope\", conflictingReservationIds: conflicts };\n\t\t\t}\n\t\t\tif (store.reservations.length >= MAX_RESERVATIONS)\n\t\t\t\treturn { kind: \"blocked\", reasonCode: \"reservation_capacity_reached\" };\n\t\t\tconst record: PersistedReservation = {\n\t\t\t\treservationId: randomUUID(),\n\t\t\t\tparentSessionId,\n\t\t\t\townerId,\n\t\t\t\ttaskId,\n\t\t\t\tattemptId,\n\t\t\t\tfencingToken,\n\t\t\t\trepositoryRoot: workspace.repositoryRoot,\n\t\t\t\texecutionRoot: workspace.executionRoot,\n\t\t\t\texecutionIdentity: workspace.executionIdentity,\n\t\t\t\t...(workspace.isolatedWorktreeId ? { isolatedWorktreeId: workspace.isolatedWorktreeId } : {}),\n\t\t\t\twriteScopes,\n\t\t\t\tcreatedAt: new Date().toISOString(),\n\t\t\t};\n\t\t\tstore.reservations.push(record);\n\t\t\twriteStore(filePath, store);\n\t\t\treturn { kind: \"granted\", lease: leaseFromRecord(filePath, record) };\n\t\t});\n\t}\n\n\trelease(lease: WorkerWriteReservationLease): WorkerWriteReservationReleaseResult {\n\t\tconst repositoryRoot = requireBoundedTrimmedText(lease.repositoryRoot, MAX_PATH_CHARS, \"Lease repository root\");\n\t\tconst ownerId = localOwnerId(lease.ownerId);\n\t\tconst expectedFilePath = reservationFile(this.agentDir, repositoryRoot);\n\t\tif (lease.filePath !== expectedFilePath)\n\t\t\tthrow new TypeError(\"Worker write reservation lease does not belong to this agent state.\");\n\t\treturn withFileLockSync(expectedFilePath, () => {\n\t\t\tconst store = this.readLocked(expectedFilePath, repositoryRoot);\n\t\t\tconst index = store.reservations.findIndex((reservation) => reservation.reservationId === lease.reservationId);\n\t\t\tif (index < 0) {\n\t\t\t\tconst supersedingReservation = store.reservations.some(\n\t\t\t\t\t(reservation) =>\n\t\t\t\t\t\treservation.parentSessionId === lease.parentSessionId &&\n\t\t\t\t\t\treservation.ownerId === ownerId &&\n\t\t\t\t\t\treservation.taskId === lease.taskId &&\n\t\t\t\t\t\treservation.fencingToken > lease.fencingToken,\n\t\t\t\t);\n\t\t\t\treturn { kind: supersedingReservation ? \"stale_fence\" : \"not_found\" };\n\t\t\t}\n\t\t\tconst record = store.reservations[index];\n\t\t\tif (\n\t\t\t\trecord.parentSessionId !== lease.parentSessionId ||\n\t\t\t\trecord.ownerId !== ownerId ||\n\t\t\t\trecord.taskId !== lease.taskId ||\n\t\t\t\trecord.attemptId !== lease.attemptId ||\n\t\t\t\trecord.fencingToken !== lease.fencingToken ||\n\t\t\t\trecord.executionIdentity !== lease.executionIdentity ||\n\t\t\t\trecord.isolatedWorktreeId !== lease.isolatedWorktreeId\n\t\t\t) {\n\t\t\t\treturn { kind: \"stale_fence\" };\n\t\t\t}\n\t\t\tstore.reservations.splice(index, 1);\n\t\t\tif (store.reservations.length === 0) unlinkSync(expectedFilePath);\n\t\t\telse writeStore(expectedFilePath, store);\n\t\t\tnotifyAvailability(expectedFilePath);\n\t\t\treturn { kind: \"released\" };\n\t\t});\n\t}\n\n\t/** Event-driven cross-process wakeup for queued write admission; caller owns disposal. */\n\twatchAvailability(workspace: WorkerWriteReservationWorkspace, listener: () => void): () => void {\n\t\tconst canonical = canonicalWorkspace(workspace);\n\t\tconst filePath = reservationFile(this.agentDir, canonical.repositoryRoot);\n\t\tconst listeners = availabilityListeners.get(filePath) ?? new Set<() => void>();\n\t\tavailabilityListeners.set(filePath, listeners);\n\t\tlisteners.add(listener);\n\t\tconst directory = dirname(filePath);\n\t\tmkdirSync(directory, { recursive: true });\n\t\tlet scheduled = false;\n\t\tconst schedule = () => {\n\t\t\tif (scheduled) return;\n\t\t\tscheduled = true;\n\t\t\tqueueMicrotask(() => {\n\t\t\t\tscheduled = false;\n\t\t\t\tlistener();\n\t\t\t});\n\t\t};\n\t\tconst watcher = this.watchDirectory(directory, { persistent: false }, (_eventType, fileName) => {\n\t\t\t// Node may omit a filename (notably on Windows), or provide a Buffer when the watcher is\n\t\t\t// configured that way. An unknown filename is conservatively treated as a wake signal: the\n\t\t\t// scheduler re-evaluates durable admission, so this cannot grant access by itself.\n\t\t\tif (fileName === null) {\n\t\t\t\tschedule();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst normalizedName = Buffer.isBuffer(fileName) ? fileName.toString(\"utf-8\") : fileName;\n\t\t\tif (normalizedName === basename(filePath)) schedule();\n\t\t});\n\t\treturn () => {\n\t\t\tlisteners.delete(listener);\n\t\t\tif (listeners.size === 0) availabilityListeners.delete(filePath);\n\t\t\twatcher.close();\n\t\t};\n\t}\n\n\trecover(request: WorkerWriteReservationRecoveryRequest): { outcomes: WorkerWriteReservationRecoveryOutcome[] } {\n\t\tconst workspace = canonicalWorkspace(request.workspace);\n\t\tconst filePath = reservationFile(this.agentDir, workspace.repositoryRoot);\n\t\tconst evidence = new Map<string, WorkerWriteReservationEvidenceState>();\n\t\tfor (const observation of request.evidence) {\n\t\t\tconst reservationId = requireBoundedTrimmedText(\n\t\t\t\tobservation.reservationId,\n\t\t\t\tMAX_IDENTIFIER_CHARS,\n\t\t\t\t\"Reservation evidence id\",\n\t\t\t);\n\t\t\tif (observation.state !== \"live\" && observation.state !== \"not_live\" && observation.state !== \"unknown\") {\n\t\t\t\tthrow new TypeError(\"Reservation evidence state must be live, not_live, or unknown.\");\n\t\t\t}\n\t\t\tif (evidence.has(reservationId)) throw new TypeError(\"Reservation evidence must not repeat an identity.\");\n\t\t\tevidence.set(reservationId, observation.state);\n\t\t}\n\t\treturn withFileLockSync(filePath, () => {\n\t\t\tconst store = this.readLocked(filePath, workspace.repositoryRoot);\n\t\t\tconst outcomes = store.reservations\n\t\t\t\t.filter((reservation) => reservation.executionIdentity === workspace.executionIdentity)\n\t\t\t\t.map((reservation): WorkerWriteReservationRecoveryOutcome => {\n\t\t\t\t\tconst state = evidence.get(reservation.reservationId);\n\t\t\t\t\treturn {\n\t\t\t\t\t\tkind: state === \"live\" ? \"active\" : state === \"not_live\" ? \"stale\" : \"inspection_required\",\n\t\t\t\t\t\treservationId: reservation.reservationId,\n\t\t\t\t\t\tlease: leaseFromRecord(filePath, reservation),\n\t\t\t\t\t};\n\t\t\t\t});\n\t\t\treturn { outcomes };\n\t\t});\n\t}\n\n\tprivate readLocked(filePath: string, repositoryRoot: string): PersistedReservationStore {\n\t\tif (!existsSync(filePath)) return { schemaVersion: SCHEMA_VERSION, repositoryRoot, reservations: [] };\n\t\treturn parseStore(\n\t\t\treadBoundedTextFileSync(filePath, MAX_FILE_BYTES, \"Worker write reservation store\"),\n\t\t\trepositoryRoot,\n\t\t);\n\t}\n}\n"]}