@agents-ensemble/core 0.21.0 → 0.22.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 (266) hide show
  1. package/assets/pi/pi-cli-env-wrapper.cmd +28 -0
  2. package/assets/pi/pi-cli-env-wrapper.sh +19 -0
  3. package/assets/pi/settings.json +4 -0
  4. package/dist/acp/acp-preset-bins.d.ts +3 -3
  5. package/dist/acp/acp-preset-bins.d.ts.map +1 -1
  6. package/dist/acp/acp-preset-bins.js +3 -3
  7. package/dist/acp/acp-preset-bins.js.map +1 -1
  8. package/dist/acp/index.d.ts +3 -1
  9. package/dist/acp/index.d.ts.map +1 -1
  10. package/dist/acp/index.js +2 -1
  11. package/dist/acp/index.js.map +1 -1
  12. package/dist/acp/pi-acp-wrapper.d.ts +3 -0
  13. package/dist/acp/pi-acp-wrapper.d.ts.map +1 -0
  14. package/dist/acp/pi-acp-wrapper.js +41 -0
  15. package/dist/acp/pi-acp-wrapper.js.map +1 -0
  16. package/dist/acp/pi-builtin-config.d.ts +10 -0
  17. package/dist/acp/pi-builtin-config.d.ts.map +1 -0
  18. package/dist/acp/pi-builtin-config.js +36 -0
  19. package/dist/acp/pi-builtin-config.js.map +1 -0
  20. package/dist/acp/pi-runtime.d.ts +23 -0
  21. package/dist/acp/pi-runtime.d.ts.map +1 -0
  22. package/dist/acp/pi-runtime.js +160 -0
  23. package/dist/acp/pi-runtime.js.map +1 -0
  24. package/dist/acp/pi-session-map.d.ts +36 -0
  25. package/dist/acp/pi-session-map.d.ts.map +1 -0
  26. package/dist/acp/pi-session-map.js +266 -0
  27. package/dist/acp/pi-session-map.js.map +1 -0
  28. package/dist/acp/resolve-acp-spawn.d.ts +9 -3
  29. package/dist/acp/resolve-acp-spawn.d.ts.map +1 -1
  30. package/dist/acp/resolve-acp-spawn.js +41 -9
  31. package/dist/acp/resolve-acp-spawn.js.map +1 -1
  32. package/dist/acp/resolve-bundled-acp-bin.d.ts +2 -2
  33. package/dist/acp/resolve-bundled-acp-bin.d.ts.map +1 -1
  34. package/dist/acp/resolve-bundled-acp-bin.js +4 -4
  35. package/dist/acp/resolve-bundled-acp-bin.js.map +1 -1
  36. package/dist/acp/validate-acp-preset-prerequisites.d.ts +1 -1
  37. package/dist/acp/validate-acp-preset-prerequisites.d.ts.map +1 -1
  38. package/dist/acp/validate-acp-preset-prerequisites.js +30 -10
  39. package/dist/acp/validate-acp-preset-prerequisites.js.map +1 -1
  40. package/dist/conductor/conductor-agent.d.ts +15 -3
  41. package/dist/conductor/conductor-agent.d.ts.map +1 -1
  42. package/dist/conductor/conductor-auth.d.ts +2 -3
  43. package/dist/conductor/conductor-auth.d.ts.map +1 -1
  44. package/dist/conductor/conductor-auth.js +5 -2
  45. package/dist/conductor/conductor-auth.js.map +1 -1
  46. package/dist/conductor/conductor-dispatch-lifecycle.d.ts +85 -0
  47. package/dist/conductor/conductor-dispatch-lifecycle.d.ts.map +1 -0
  48. package/dist/conductor/conductor-dispatch-lifecycle.js +190 -0
  49. package/dist/conductor/conductor-dispatch-lifecycle.js.map +1 -0
  50. package/dist/conductor/conductor-history.d.ts +6 -0
  51. package/dist/conductor/conductor-history.d.ts.map +1 -1
  52. package/dist/conductor/conductor-history.js +11 -6
  53. package/dist/conductor/conductor-history.js.map +1 -1
  54. package/dist/conductor/conductor-path-safety.d.ts +23 -0
  55. package/dist/conductor/conductor-path-safety.d.ts.map +1 -0
  56. package/dist/conductor/conductor-path-safety.js +319 -0
  57. package/dist/conductor/conductor-path-safety.js.map +1 -0
  58. package/dist/conductor/conductor-pi-auth.d.ts +57 -2
  59. package/dist/conductor/conductor-pi-auth.d.ts.map +1 -1
  60. package/dist/conductor/conductor-pi-auth.js +718 -40
  61. package/dist/conductor/conductor-pi-auth.js.map +1 -1
  62. package/dist/conductor/conductor-recovery-inbox.d.ts +73 -0
  63. package/dist/conductor/conductor-recovery-inbox.d.ts.map +1 -0
  64. package/dist/conductor/conductor-recovery-inbox.js +181 -0
  65. package/dist/conductor/conductor-recovery-inbox.js.map +1 -0
  66. package/dist/conductor/conductor-send-reconnect.d.ts +27 -5
  67. package/dist/conductor/conductor-send-reconnect.d.ts.map +1 -1
  68. package/dist/conductor/conductor-send-reconnect.js +88 -39
  69. package/dist/conductor/conductor-send-reconnect.js.map +1 -1
  70. package/dist/conductor/conductor-session-driver.d.ts +32 -7
  71. package/dist/conductor/conductor-session-driver.d.ts.map +1 -1
  72. package/dist/conductor/conductor-session-driver.js +474 -233
  73. package/dist/conductor/conductor-session-driver.js.map +1 -1
  74. package/dist/conductor/conductor-session.d.ts +2 -0
  75. package/dist/conductor/conductor-session.d.ts.map +1 -1
  76. package/dist/conductor/conductor-session.js +404 -156
  77. package/dist/conductor/conductor-session.js.map +1 -1
  78. package/dist/conductor/conductor-setup.d.ts +30 -0
  79. package/dist/conductor/conductor-setup.d.ts.map +1 -0
  80. package/dist/conductor/conductor-setup.js +243 -0
  81. package/dist/conductor/conductor-setup.js.map +1 -0
  82. package/dist/conductor/cursor-bootstrap-state.d.ts +9 -0
  83. package/dist/conductor/cursor-bootstrap-state.d.ts.map +1 -0
  84. package/dist/conductor/cursor-bootstrap-state.js +11 -0
  85. package/dist/conductor/cursor-bootstrap-state.js.map +1 -0
  86. package/dist/conductor/cursor-run-snapshots.d.ts +30 -0
  87. package/dist/conductor/cursor-run-snapshots.d.ts.map +1 -0
  88. package/dist/conductor/cursor-run-snapshots.js +120 -0
  89. package/dist/conductor/cursor-run-snapshots.js.map +1 -0
  90. package/dist/conductor/index.d.ts +15 -6
  91. package/dist/conductor/index.d.ts.map +1 -1
  92. package/dist/conductor/index.js +8 -4
  93. package/dist/conductor/index.js.map +1 -1
  94. package/dist/conductor/operator-command.d.ts +1 -1
  95. package/dist/conductor/operator-command.js +1 -1
  96. package/dist/conductor/operator-command.js.map +1 -1
  97. package/dist/conductor/operator-input-binding.d.ts +12 -0
  98. package/dist/conductor/operator-input-binding.d.ts.map +1 -1
  99. package/dist/conductor/pi-auth-recovery-snapshot-store.d.ts +4 -1
  100. package/dist/conductor/pi-auth-recovery-snapshot-store.d.ts.map +1 -1
  101. package/dist/conductor/pi-auth-recovery-snapshot-store.js +4 -1
  102. package/dist/conductor/pi-auth-recovery-snapshot-store.js.map +1 -1
  103. package/dist/conductor/pi-conductor-agent.d.ts +6 -3
  104. package/dist/conductor/pi-conductor-agent.d.ts.map +1 -1
  105. package/dist/conductor/pi-conductor-agent.js +272 -53
  106. package/dist/conductor/pi-conductor-agent.js.map +1 -1
  107. package/dist/conductor/pi-conductor-session-file.d.ts +13 -0
  108. package/dist/conductor/pi-conductor-session-file.d.ts.map +1 -0
  109. package/dist/conductor/pi-conductor-session-file.js +21 -0
  110. package/dist/conductor/pi-conductor-session-file.js.map +1 -0
  111. package/dist/conductor/pi-resource-loader.d.ts +6 -0
  112. package/dist/conductor/pi-resource-loader.d.ts.map +1 -1
  113. package/dist/conductor/pi-resource-loader.js +26 -12
  114. package/dist/conductor/pi-resource-loader.js.map +1 -1
  115. package/dist/conductor/pi-user-root-lock.d.ts +8 -0
  116. package/dist/conductor/pi-user-root-lock.d.ts.map +1 -0
  117. package/dist/conductor/pi-user-root-lock.js +53 -0
  118. package/dist/conductor/pi-user-root-lock.js.map +1 -0
  119. package/dist/conductor/recovery-inbox-command.d.ts +29 -0
  120. package/dist/conductor/recovery-inbox-command.d.ts.map +1 -0
  121. package/dist/conductor/recovery-inbox-command.js +248 -0
  122. package/dist/conductor/recovery-inbox-command.js.map +1 -0
  123. package/dist/conductor/session/events/session-event.d.ts +15 -2
  124. package/dist/conductor/session/events/session-event.d.ts.map +1 -1
  125. package/dist/conductor/session/events/session-log-event-groups.d.ts +1 -1
  126. package/dist/conductor/session/events/session-log-event-groups.d.ts.map +1 -1
  127. package/dist/conductor/session/events/session-log-event-groups.js +1 -0
  128. package/dist/conductor/session/events/session-log-event-groups.js.map +1 -1
  129. package/dist/conductor/session/events/session-log-event.d.ts +4 -0
  130. package/dist/conductor/session/events/session-log-event.d.ts.map +1 -1
  131. package/dist/conductor/session/format-session-event.js +25 -4
  132. package/dist/conductor/session/format-session-event.js.map +1 -1
  133. package/dist/conductor/session/select-dispatch-batch.d.ts +2 -0
  134. package/dist/conductor/session/select-dispatch-batch.d.ts.map +1 -1
  135. package/dist/conductor/session/select-dispatch-batch.js +31 -2
  136. package/dist/conductor/session/select-dispatch-batch.js.map +1 -1
  137. package/dist/conductor/session/session-event-queue.d.ts +5 -0
  138. package/dist/conductor/session/session-event-queue.d.ts.map +1 -1
  139. package/dist/conductor/session/session-event-queue.js +41 -4
  140. package/dist/conductor/session/session-event-queue.js.map +1 -1
  141. package/dist/conductor/session/session-event.d.ts +1 -1
  142. package/dist/conductor/session/session-event.d.ts.map +1 -1
  143. package/dist/conductor/session-policy.d.ts.map +1 -1
  144. package/dist/conductor/session-policy.js +4 -2
  145. package/dist/conductor/session-policy.js.map +1 -1
  146. package/dist/conductor/submit-operator-input.d.ts +3 -0
  147. package/dist/conductor/submit-operator-input.d.ts.map +1 -1
  148. package/dist/conductor/submit-operator-input.js +21 -0
  149. package/dist/conductor/submit-operator-input.js.map +1 -1
  150. package/dist/config/deep-merge.d.ts.map +1 -1
  151. package/dist/config/deep-merge.js +13 -3
  152. package/dist/config/deep-merge.js.map +1 -1
  153. package/dist/config/defaults.d.ts.map +1 -1
  154. package/dist/config/defaults.js +1 -1
  155. package/dist/config/defaults.js.map +1 -1
  156. package/dist/config/doctor-settings.d.ts +59 -0
  157. package/dist/config/doctor-settings.d.ts.map +1 -0
  158. package/dist/config/doctor-settings.js +343 -0
  159. package/dist/config/doctor-settings.js.map +1 -0
  160. package/dist/config/ensemble-config-digest.d.ts +2 -4
  161. package/dist/config/ensemble-config-digest.d.ts.map +1 -1
  162. package/dist/config/ensemble-config-digest.js +2 -4
  163. package/dist/config/ensemble-config-digest.js.map +1 -1
  164. package/dist/config/index.d.ts +3 -1
  165. package/dist/config/index.d.ts.map +1 -1
  166. package/dist/config/index.js +1 -0
  167. package/dist/config/index.js.map +1 -1
  168. package/dist/config/load-ensemble-config.d.ts.map +1 -1
  169. package/dist/config/load-ensemble-config.js +8 -1
  170. package/dist/config/load-ensemble-config.js.map +1 -1
  171. package/dist/config/parse-config.d.ts.map +1 -1
  172. package/dist/config/parse-config.js +34 -14
  173. package/dist/config/parse-config.js.map +1 -1
  174. package/dist/config/resolve-settings.d.ts +3 -1
  175. package/dist/config/resolve-settings.d.ts.map +1 -1
  176. package/dist/config/resolve-settings.js +7 -4
  177. package/dist/config/resolve-settings.js.map +1 -1
  178. package/dist/config/types.d.ts +14 -5
  179. package/dist/config/types.d.ts.map +1 -1
  180. package/dist/dispatch/attach-worker.js +1 -1
  181. package/dist/dispatch/attach-worker.js.map +1 -1
  182. package/dist/dispatch/worker-acp-session.d.ts +21 -3
  183. package/dist/dispatch/worker-acp-session.d.ts.map +1 -1
  184. package/dist/dispatch/worker-acp-session.js +50 -13
  185. package/dist/dispatch/worker-acp-session.js.map +1 -1
  186. package/dist/ensemble-paths.d.ts +19 -19
  187. package/dist/ensemble-paths.d.ts.map +1 -1
  188. package/dist/ensemble-paths.js +40 -22
  189. package/dist/ensemble-paths.js.map +1 -1
  190. package/dist/issue/resolve-issue-url.d.ts +4 -0
  191. package/dist/issue/resolve-issue-url.d.ts.map +1 -1
  192. package/dist/issue/resolve-issue-url.js +2 -2
  193. package/dist/issue/resolve-issue-url.js.map +1 -1
  194. package/dist/operator-runtime.d.ts +17 -4
  195. package/dist/operator-runtime.d.ts.map +1 -1
  196. package/dist/operator-runtime.js +12 -3
  197. package/dist/operator-runtime.js.map +1 -1
  198. package/dist/profile/types.d.ts +4 -0
  199. package/dist/profile/types.d.ts.map +1 -1
  200. package/dist/profile/types.js +29 -12
  201. package/dist/profile/types.js.map +1 -1
  202. package/dist/representation/escape-terminal-control-characters.d.ts +9 -0
  203. package/dist/representation/escape-terminal-control-characters.d.ts.map +1 -0
  204. package/dist/representation/escape-terminal-control-characters.js +34 -0
  205. package/dist/representation/escape-terminal-control-characters.js.map +1 -0
  206. package/dist/representation/index.d.ts +1 -0
  207. package/dist/representation/index.d.ts.map +1 -1
  208. package/dist/representation/index.js +1 -0
  209. package/dist/representation/index.js.map +1 -1
  210. package/dist/runtime/types.d.ts +9 -0
  211. package/dist/runtime/types.d.ts.map +1 -1
  212. package/dist/runtime/worker-runtime.d.ts +14 -1
  213. package/dist/runtime/worker-runtime.d.ts.map +1 -1
  214. package/dist/runtime/worker-runtime.js +197 -50
  215. package/dist/runtime/worker-runtime.js.map +1 -1
  216. package/dist/runtime/worker-session.d.ts +2 -1
  217. package/dist/runtime/worker-session.d.ts.map +1 -1
  218. package/dist/runtime/worker-session.js +1 -0
  219. package/dist/runtime/worker-session.js.map +1 -1
  220. package/dist/session/export-session-bundle.d.ts +77 -0
  221. package/dist/session/export-session-bundle.d.ts.map +1 -0
  222. package/dist/session/export-session-bundle.js +640 -0
  223. package/dist/session/export-session-bundle.js.map +1 -0
  224. package/dist/session/index.d.ts +4 -3
  225. package/dist/session/index.d.ts.map +1 -1
  226. package/dist/session/index.js +2 -1
  227. package/dist/session/index.js.map +1 -1
  228. package/dist/session/open-question-control.d.ts +9 -0
  229. package/dist/session/open-question-control.d.ts.map +1 -1
  230. package/dist/session/open-question-control.js +26 -1
  231. package/dist/session/open-question-control.js.map +1 -1
  232. package/dist/session/open-questions-store.d.ts +1 -1
  233. package/dist/session/open-questions-store.d.ts.map +1 -1
  234. package/dist/session/open-questions-store.js +3 -6
  235. package/dist/session/open-questions-store.js.map +1 -1
  236. package/dist/session/persisted-file.d.ts +5 -0
  237. package/dist/session/persisted-file.d.ts.map +1 -0
  238. package/dist/session/persisted-file.js +56 -0
  239. package/dist/session/persisted-file.js.map +1 -0
  240. package/dist/session/session-resume-store.d.ts +77 -10
  241. package/dist/session/session-resume-store.d.ts.map +1 -1
  242. package/dist/session/session-resume-store.js +346 -83
  243. package/dist/session/session-resume-store.js.map +1 -1
  244. package/dist/session/session-snapshot-paths.d.ts +7 -1
  245. package/dist/session/session-snapshot-paths.d.ts.map +1 -1
  246. package/dist/session/session-snapshot-paths.js +29 -26
  247. package/dist/session/session-snapshot-paths.js.map +1 -1
  248. package/dist/session/session-state-store.d.ts +2 -2
  249. package/dist/session/session-state-store.d.ts.map +1 -1
  250. package/dist/session/session-state-store.js +5 -16
  251. package/dist/session/session-state-store.js.map +1 -1
  252. package/dist/session/tool-call-snapshot-history.d.ts +12 -1
  253. package/dist/session/tool-call-snapshot-history.d.ts.map +1 -1
  254. package/dist/session/tool-call-snapshot-history.js +75 -70
  255. package/dist/session/tool-call-snapshot-history.js.map +1 -1
  256. package/dist/session/worker-session-history.d.ts +13 -1
  257. package/dist/session/worker-session-history.d.ts.map +1 -1
  258. package/dist/session/worker-session-history.js +171 -50
  259. package/dist/session/worker-session-history.js.map +1 -1
  260. package/dist/workspace/index.d.ts +1 -1
  261. package/dist/workspace/index.d.ts.map +1 -1
  262. package/dist/worktree/worktree.d.ts +6 -1
  263. package/dist/worktree/worktree.d.ts.map +1 -1
  264. package/dist/worktree/worktree.js +123 -2
  265. package/dist/worktree/worktree.js.map +1 -1
  266. package/package.json +9 -4
@@ -1,7 +1,9 @@
1
1
  import { chmod, mkdir, readdir, readFile, rename, writeFile } from 'node:fs/promises';
2
- import { dirname, join, resolve } from 'node:path';
2
+ import { dirname, join } from 'node:path';
3
3
  import { computeAcpSpawnEnvDigest, } from '../acp/resolve-acp-spawn.js';
4
+ import { parseIssueUrl } from '../issue/issue-ref.js';
4
5
  import { resolveEnsemblePaths } from '../ensemble-paths.js';
6
+ import { resolveUserEnsembleRoot } from '../resolve-user-ensemble-root.js';
5
7
  export const SESSION_RESUME_STORE_VERSION = 1;
6
8
  /** @deprecated Use SESSION_RESUME_STORE_VERSION; supported for one release only. */
7
9
  export const SESSION_SIDECAR_VERSION = SESSION_RESUME_STORE_VERSION;
@@ -28,6 +30,13 @@ export class SessionResumeStoreNotFoundError extends Error {
28
30
  this.name = 'SessionResumeStoreNotFoundError';
29
31
  }
30
32
  }
33
+ class InvalidSessionResumeStoreDataError extends Error {
34
+ constructor(path, reason, cause) {
35
+ const detail = cause instanceof Error ? `: ${cause.message}` : '';
36
+ super(`Invalid session resume store at ${path} (${reason})${detail}`);
37
+ this.name = 'InvalidSessionResumeStoreDataError';
38
+ }
39
+ }
31
40
  /** @deprecated Use SessionResumeStoreNotFoundError; supported for one release only. */
32
41
  export class SessionSidecarNotFoundError extends SessionResumeStoreNotFoundError {
33
42
  code = 'SESSION_SIDECAR_NOT_FOUND';
@@ -36,8 +45,12 @@ export class SessionSidecarNotFoundError extends SessionResumeStoreNotFoundError
36
45
  this.name = 'SessionSidecarNotFoundError';
37
46
  }
38
47
  }
39
- export function sessionResumeStoreDir(repoRoot) {
40
- return resolveEnsemblePaths({ repoRoot }).sessionsDir;
48
+ const SESSION_RESUME_STORE_PAGE_MAX_LIMIT = 500;
49
+ const SESSION_RESUME_STORE_PAGE_CURSOR_VERSION = 1;
50
+ /** Return the canonical user-store directory for an Issue repository. */
51
+ export function sessionResumeStoreDir(input) {
52
+ return resolveEnsemblePaths({ repoRoot: input.repoRoot })
53
+ .canonicalRepoSessionsDir(input.issueUrl);
41
54
  }
42
55
  export function legacySessionResumeStoreDir(repoRoot) {
43
56
  return resolveEnsemblePaths({ repoRoot }).legacySessionsDir;
@@ -47,30 +60,47 @@ export function legacySessionResumeStorePath(options) {
47
60
  repoRoot: options.repoRoot,
48
61
  }).legacySessionResumeStorePath(options.conductorAgentId);
49
62
  }
63
+ /** Return the canonical user-store path; an Issue URL is required for identity. */
50
64
  export function sessionResumeStorePath(options) {
51
- return resolveEnsemblePaths({
52
- repoRoot: options.repoRoot,
53
- }).sessionResumeStorePath(options.conductorAgentId);
65
+ return canonicalSessionResumeStorePath(options);
66
+ }
67
+ export function canonicalSessionResumeStorePath(input) {
68
+ return resolveEnsemblePaths({ repoRoot: input.repoRoot })
69
+ .canonicalSessionResumeStorePath(input.issueUrl, input.conductorAgentId);
54
70
  }
55
71
  export async function loadSessionResumeStore(path) {
56
72
  const loaded = await readSessionResumeStore(path);
57
73
  return loaded?.resumeStore;
58
74
  }
59
75
  async function readSessionResumeStore(path) {
76
+ let raw;
60
77
  try {
61
- const raw = await readFile(path, 'utf8');
62
- const value = JSON.parse(raw);
63
- return {
64
- resumeStore: parseSessionResumeStore(value),
65
- hasLegacyAcpSpawnEnv: containsLegacyAcpSpawnEnv(value),
66
- rawValue: value,
67
- };
78
+ raw = await readFile(path, 'utf8');
68
79
  }
69
80
  catch (error) {
70
81
  if (isEnoent(error))
71
82
  return undefined;
72
83
  throw error;
73
84
  }
85
+ let value;
86
+ try {
87
+ value = JSON.parse(raw);
88
+ }
89
+ catch (error) {
90
+ throw new InvalidSessionResumeStoreDataError(path, 'invalid JSON', error);
91
+ }
92
+ let resumeStore;
93
+ try {
94
+ resumeStore = parseSessionResumeStore(value);
95
+ }
96
+ catch (error) {
97
+ throw new InvalidSessionResumeStoreDataError(path, 'invalid schema', error);
98
+ }
99
+ return {
100
+ resumeStore,
101
+ hasLegacyAcpSpawnEnv: containsLegacyAcpSpawnEnv(value),
102
+ rawValue: value,
103
+ };
74
104
  }
75
105
  /** `resumeAgentId` 指定時に SessionResumeStore が無い場合は起動失敗とする。 */
76
106
  export async function requireSessionResumeStoreForResume(input) {
@@ -78,33 +108,36 @@ export async function requireSessionResumeStoreForResume(input) {
78
108
  return located.resumeStore;
79
109
  }
80
110
  export async function requireLocatedSessionResumeStoreForResume(input) {
81
- const locations = await listSessionResumeStoreLocations(input.repoRoot, {
82
- onDuplicate: input.onDuplicate,
83
- });
84
- const located = locations.find((candidate) => candidate.resumeStore.conductorAgentId === input.conductorAgentId);
111
+ if (typeof input.issueUrl !== 'string' ||
112
+ !input.issueUrl.trim()) {
113
+ throw new Error('issueUrl is required to resume a session.');
114
+ }
115
+ const locations = await listLocatedSessionResumeStoresPage(input.repoRoot, {
116
+ issueUrl: input.issueUrl,
117
+ conductorAgentId: input.conductorAgentId,
118
+ }, { onDuplicate: input.onDuplicate });
119
+ const located = locations.sessions[0];
85
120
  if (!located) {
86
- const path = sessionResumeStorePath(input);
121
+ const path = canonicalSessionResumeStorePath(input);
87
122
  throw new SessionResumeStoreNotFoundError(input.conductorAgentId, path);
88
123
  }
89
124
  const loaded = await readSessionResumeStore(located.path);
90
125
  if (!loaded) {
91
126
  throw new SessionResumeStoreNotFoundError(input.conductorAgentId, located.path);
92
127
  }
128
+ const persistedValue = loaded.hasLegacyAcpSpawnEnv
129
+ ? redactLegacyAcpSpawnEnv(loaded.rawValue, loaded.resumeStore)
130
+ : loaded.resumeStore;
131
+ const resumeStore = parseSessionResumeStore(persistedValue);
93
132
  if (loaded.hasLegacyAcpSpawnEnv) {
94
- // Scrub pre-digest env values before resume validation can reject the
95
- // session and bypass its normal first write.
96
- await writeSessionResumeStoreFile(located.path, redactLegacyAcpSpawnEnv(loaded.rawValue, loaded.resumeStore));
133
+ // Scrub canonical data before resume validation can reject the session
134
+ // and bypass its normal first write.
135
+ await writeSessionResumeStoreFile(located.path, persistedValue);
97
136
  }
98
- if (located.location === 'project-legacy') {
99
- const migratedPath = sessionResumeStorePath(input);
100
- await writeSessionResumeStoreFile(migratedPath, loaded.resumeStore);
101
- return {
102
- resumeStore: loaded.resumeStore,
103
- path: migratedPath,
104
- location: 'user-path-hash',
105
- };
106
- }
107
- return { ...located, resumeStore: loaded.resumeStore };
137
+ return {
138
+ ...located,
139
+ resumeStore,
140
+ };
108
141
  }
109
142
  /** @deprecated Use requireSessionResumeStoreForResume; supported for one release only. */
110
143
  export async function requireSessionSidecarForResume(input) {
@@ -153,13 +186,142 @@ export async function listLocatedSessionResumeStores(repoRoot, options = {}) {
153
186
  return listSessionResumeStoreLocations(repoRoot, options);
154
187
  }
155
188
  export async function findSessionResumeStore(repoRoot, conductorAgentId, options = {}) {
156
- return (await listLocatedSessionResumeStores(repoRoot, options)).find((candidate) => candidate.resumeStore.conductorAgentId === conductorAgentId);
189
+ const matches = (await listLocatedSessionResumeStores(repoRoot, options)).filter((candidate) => candidate.resumeStore.conductorAgentId === conductorAgentId);
190
+ if (matches.length > 1) {
191
+ throw new Error(`Session ${conductorAgentId} is ambiguous across saved Issues; use findSessionResumeStoreBySelector with githubRepo or issueUrl.`);
192
+ }
193
+ return matches[0];
194
+ }
195
+ /**
196
+ * List local sessions using canonical GitHub repository identity.
197
+ *
198
+ * With no limit this returns every matching local store. Supplying a limit
199
+ * enables keyset pagination; no GitHub API repository discovery is performed.
200
+ */
201
+ export async function listLocatedSessionResumeStoresPage(repoRoot, query = {}, options = {}) {
202
+ const scope = resolveSessionResumeStoreScope(query);
203
+ const stores = (await listSessionResumeStoreLocations(repoRoot, options))
204
+ .filter(({ resumeStore }) => {
205
+ const identity = sessionIssueIdentity(resumeStore.issueUrl);
206
+ return ((!scope.githubRepo || identity.repository === scope.githubRepo) &&
207
+ (!scope.issueKey || identity.key === scope.issueKey) &&
208
+ (!query.conductorAgentId ||
209
+ resumeStore.conductorAgentId === query.conductorAgentId));
210
+ })
211
+ .sort(compareLocatedSessionResumeStores);
212
+ const limit = query.limit === undefined
213
+ ? query.cursor === undefined
214
+ ? undefined
215
+ : SESSION_RESUME_STORE_PAGE_MAX_LIMIT
216
+ : validateSessionResumeStorePageLimit(query.limit);
217
+ if (limit === undefined) {
218
+ return { sessions: stores, total: stores.length };
219
+ }
220
+ const cursor = query.cursor === undefined
221
+ ? undefined
222
+ : decodeSessionResumeStoreCursor(query.cursor);
223
+ const remaining = cursor
224
+ ? stores.filter((store) => compareLocatedSessionResumeStoreToCursor(store, cursor) > 0)
225
+ : stores;
226
+ const sessions = remaining.slice(0, limit);
227
+ const next = remaining.length > sessions.length ? sessions.at(-1) : undefined;
228
+ return {
229
+ sessions,
230
+ total: stores.length,
231
+ ...(next ? { nextCursor: encodeSessionResumeStoreCursor(next) } : {}),
232
+ };
233
+ }
234
+ /**
235
+ * List user-scoped canonical slug stores.
236
+ *
237
+ * This is intended for repository-independent operator tools. It does not
238
+ * inspect path-hash or checkout-local session data, or call GitHub.
239
+ */
240
+ export async function listUserLocatedSessionResumeStoresPage(query = {}, options = {}) {
241
+ const userRoot = options.userEnsembleRoot ?? resolveUserEnsembleRoot();
242
+ const scope = resolveSessionResumeStoreScope(query);
243
+ const stores = (await listSessionResumeStoreLocations(userRoot, {
244
+ onDuplicate: options.onDuplicate,
245
+ userEnsembleRoot: userRoot,
246
+ }))
247
+ .filter(({ resumeStore }) => {
248
+ const identity = sessionIssueIdentity(resumeStore.issueUrl);
249
+ return ((!scope.githubRepo || identity.repository === scope.githubRepo) &&
250
+ (!scope.issueKey || identity.key === scope.issueKey) &&
251
+ (!query.conductorAgentId ||
252
+ resumeStore.conductorAgentId === query.conductorAgentId));
253
+ })
254
+ .sort(compareLocatedSessionResumeStores);
255
+ return paginateLocatedSessionResumeStores(stores, query);
256
+ }
257
+ export async function findUserSessionResumeStore(conductorAgentId, options = {}) {
258
+ const id = conductorAgentId.trim();
259
+ if (!id) {
260
+ throw new Error('conductorAgentId must be a non-empty string.');
261
+ }
262
+ const page = await listUserLocatedSessionResumeStoresPage({ conductorAgentId: id }, options);
263
+ if (page.sessions.length > 1) {
264
+ throw new Error(`Session ${id} is ambiguous across saved Issues; use a repository or Issue selector.`);
265
+ }
266
+ return page.sessions[0];
267
+ }
268
+ function paginateLocatedSessionResumeStores(stores, query) {
269
+ const limit = query.limit === undefined
270
+ ? query.cursor === undefined
271
+ ? undefined
272
+ : SESSION_RESUME_STORE_PAGE_MAX_LIMIT
273
+ : validateSessionResumeStorePageLimit(query.limit);
274
+ if (limit === undefined) {
275
+ return { sessions: stores, total: stores.length };
276
+ }
277
+ const cursor = query.cursor === undefined
278
+ ? undefined
279
+ : decodeSessionResumeStoreCursor(query.cursor);
280
+ const remaining = cursor
281
+ ? stores.filter((store) => compareLocatedSessionResumeStoreToCursor(store, cursor) > 0)
282
+ : stores;
283
+ const sessions = remaining.slice(0, limit);
284
+ const next = remaining.length > sessions.length ? sessions.at(-1) : undefined;
285
+ return {
286
+ sessions,
287
+ total: stores.length,
288
+ ...(next ? { nextCursor: encodeSessionResumeStoreCursor(next) } : {}),
289
+ };
290
+ }
291
+ export async function listSessionResumeStoresPage(repoRoot, query = {}, options = {}) {
292
+ const page = await listLocatedSessionResumeStoresPage(repoRoot, query, options);
293
+ return {
294
+ sessions: page.sessions.map(({ resumeStore }) => resumeStore),
295
+ total: page.total,
296
+ ...(page.nextCursor ? { nextCursor: page.nextCursor } : {}),
297
+ };
298
+ }
299
+ /** Read a session only inside an explicit GitHub repository / Issue scope. */
300
+ export async function findSessionResumeStoreBySelector(repoRoot, selector, options = {}) {
301
+ const conductorAgentId = selector.conductorAgentId.trim();
302
+ if (!conductorAgentId) {
303
+ throw new Error('conductorAgentId must be a non-empty string.');
304
+ }
305
+ const scope = resolveSessionResumeStoreScope(selector);
306
+ if (!scope.githubRepo && !scope.issueKey) {
307
+ throw new Error('Repository scope is required to read a session; pass --github-repo or --issue-url.');
308
+ }
309
+ const page = await listLocatedSessionResumeStoresPage(repoRoot, { ...selector, conductorAgentId }, options);
310
+ if (page.sessions.length > 1) {
311
+ throw new Error(`Session ${conductorAgentId} is ambiguous in this repository; specify --issue-url.`);
312
+ }
313
+ return page.sessions[0];
157
314
  }
158
315
  async function listSessionResumeStoreLocations(repoRoot, options = {}) {
159
- const paths = resolveEnsemblePaths({ repoRoot });
316
+ const paths = resolveEnsemblePaths({
317
+ repoRoot,
318
+ ...(options.userEnsembleRoot
319
+ ? { userEnsembleRoot: options.userEnsembleRoot }
320
+ : {}),
321
+ });
160
322
  const candidates = [];
161
323
  const visited = new Set();
162
- const readDirectory = async (dir, location, slugRoot) => {
324
+ const readDirectory = async (dir, location, { slugRoot, skipMalformedStoreData = false, } = {}) => {
163
325
  let entries;
164
326
  try {
165
327
  entries = await readdir(dir, { withFileTypes: true });
@@ -176,23 +338,29 @@ async function listSessionResumeStoreLocations(repoRoot, options = {}) {
176
338
  if (visited.has(path))
177
339
  continue;
178
340
  visited.add(path);
179
- const resumeStore = await loadSessionResumeStore(path);
341
+ let resumeStore;
342
+ try {
343
+ resumeStore = await loadSessionResumeStore(path);
344
+ }
345
+ catch (error) {
346
+ if (skipMalformedStoreData &&
347
+ error instanceof InvalidSessionResumeStoreDataError) {
348
+ continue;
349
+ }
350
+ throw error;
351
+ }
180
352
  if (!resumeStore)
181
353
  continue;
182
354
  if (slugRoot &&
183
- (resolve(resumeStore.repoRoot) !== paths.repoRoot ||
184
- canonicalSessionDirectory(paths, resumeStore.issueUrl) !== slugRoot)) {
355
+ canonicalSessionDirectory(paths, resumeStore.issueUrl) !== slugRoot) {
185
356
  continue;
186
357
  }
187
358
  candidates.push({ resumeStore, path, location });
188
359
  }
189
360
  };
190
- // The current path-hash and project-local directories are bounded and remain
191
- // the discovery roots for legacy data.
192
- await readDirectory(paths.sessionsDir, 'user-path-hash');
193
- await readDirectory(paths.legacySessionsDir, 'project-legacy');
194
361
  // Slug stores are keyed by GitHub repository identity. Search only the
195
- // host/owner/repository depth and validate repoRoot before accepting a file.
362
+ // host/owner/repository depth; repoRoot is execution context, not a lookup
363
+ // filter for canonical records.
196
364
  let hosts;
197
365
  try {
198
366
  hosts = await readdir(paths.sessionsRoot, { withFileTypes: true });
@@ -235,30 +403,29 @@ async function listSessionResumeStoreLocations(repoRoot, options = {}) {
235
403
  if (!repository.isDirectory())
236
404
  continue;
237
405
  const slugRoot = join(paths.sessionsRoot, host.name, owner.name, repository.name);
238
- await readDirectory(slugRoot, 'canonical-slug', slugRoot);
406
+ await readDirectory(slugRoot, 'canonical-slug', { slugRoot });
239
407
  }
240
408
  }
241
409
  }
242
- const byAgentId = new Map();
410
+ const bySession = new Map();
243
411
  for (const candidate of candidates) {
244
- const matching = byAgentId.get(candidate.resumeStore.conductorAgentId) ?? [];
412
+ const sessionKey = JSON.stringify([
413
+ sessionIssueIdentity(candidate.resumeStore.issueUrl).key,
414
+ candidate.resumeStore.conductorAgentId,
415
+ ]);
416
+ const matching = bySession.get(sessionKey) ?? [];
245
417
  matching.push(candidate);
246
- byAgentId.set(candidate.resumeStore.conductorAgentId, matching);
418
+ bySession.set(sessionKey, matching);
247
419
  }
248
- const locationPriority = {
249
- 'canonical-slug': 0,
250
- 'user-path-hash': 1,
251
- 'project-legacy': 2,
252
- };
253
420
  const selected = [];
254
- for (const [conductorAgentId, matches] of byAgentId) {
255
- matches.sort((left, right) => locationPriority[left.location] - locationPriority[right.location] ||
256
- right.resumeStore.updatedAt - left.resumeStore.updatedAt);
421
+ for (const matches of bySession.values()) {
422
+ matches.sort((left, right) => right.resumeStore.updatedAt - left.resumeStore.updatedAt);
257
423
  const chosen = matches[0];
258
424
  selected.push(chosen);
259
425
  if (matches.length > 1) {
260
426
  options.onDuplicate?.({
261
- conductorAgentId,
427
+ conductorAgentId: chosen.resumeStore.conductorAgentId,
428
+ issueUrl: chosen.resumeStore.issueUrl,
262
429
  selectedPath: chosen.path,
263
430
  selectedLocation: chosen.location,
264
431
  candidates: matches.map((candidate) => ({
@@ -279,6 +446,102 @@ function canonicalSessionDirectory(paths, issueUrl) {
279
446
  return undefined;
280
447
  }
281
448
  }
449
+ function resolveSessionResumeStoreScope(input) {
450
+ const githubRepo = input.githubRepo === undefined
451
+ ? undefined
452
+ : normalizeGitHubRepo(input.githubRepo);
453
+ let issue;
454
+ if (input.issueUrl !== undefined) {
455
+ parseIssueUrl(input.issueUrl);
456
+ issue = sessionIssueIdentity(input.issueUrl);
457
+ }
458
+ if (githubRepo !== undefined &&
459
+ issue !== undefined &&
460
+ githubRepo !== issue.repository) {
461
+ throw new Error('--github-repo and --issue-url refer to different GitHub repositories.');
462
+ }
463
+ return {
464
+ ...(githubRepo ?? issue?.repository
465
+ ? { githubRepo: githubRepo ?? issue?.repository }
466
+ : {}),
467
+ ...(issue ? { issueKey: issue.key } : {}),
468
+ };
469
+ }
470
+ function normalizeGitHubRepo(value) {
471
+ const segments = value.trim().split('/');
472
+ if (segments.length !== 2 ||
473
+ segments.some((segment) => !segment ||
474
+ segment === '.' ||
475
+ segment === '..' ||
476
+ !/^[A-Za-z0-9_.-]+$/u.test(segment))) {
477
+ throw new Error(`Invalid GitHub repository "${value}" (expected <owner>/<repo>).`);
478
+ }
479
+ return `${segments[0].toLowerCase()}/${segments[1].replace(/\.git$/iu, '').toLowerCase()}`;
480
+ }
481
+ function sessionIssueIdentity(issueUrl) {
482
+ try {
483
+ const issue = parseIssueUrl(issueUrl);
484
+ const repository = `${issue.owner.toLowerCase()}/${issue.repo.toLowerCase()}`;
485
+ return {
486
+ repository,
487
+ key: `${repository}#${issue.number}`,
488
+ };
489
+ }
490
+ catch {
491
+ return { repository: '', key: `invalid:${issueUrl.toLowerCase()}` };
492
+ }
493
+ }
494
+ function compareLocatedSessionResumeStores(left, right) {
495
+ return (right.resumeStore.updatedAt - left.resumeStore.updatedAt ||
496
+ sessionIssueIdentity(left.resumeStore.issueUrl).key.localeCompare(sessionIssueIdentity(right.resumeStore.issueUrl).key) ||
497
+ left.resumeStore.conductorAgentId.localeCompare(right.resumeStore.conductorAgentId));
498
+ }
499
+ function compareLocatedSessionResumeStoreToCursor(store, cursor) {
500
+ const resumeStore = store.resumeStore;
501
+ return (cursor.updatedAt - resumeStore.updatedAt ||
502
+ sessionIssueIdentity(resumeStore.issueUrl).key.localeCompare(cursor.issueKey) ||
503
+ resumeStore.conductorAgentId.localeCompare(cursor.conductorAgentId));
504
+ }
505
+ function validateSessionResumeStorePageLimit(limit) {
506
+ if (!Number.isSafeInteger(limit) ||
507
+ limit < 1 ||
508
+ limit > SESSION_RESUME_STORE_PAGE_MAX_LIMIT) {
509
+ throw new Error(`limit must be an integer between 1 and ${SESSION_RESUME_STORE_PAGE_MAX_LIMIT}.`);
510
+ }
511
+ return limit;
512
+ }
513
+ function encodeSessionResumeStoreCursor(store) {
514
+ const cursor = {
515
+ version: SESSION_RESUME_STORE_PAGE_CURSOR_VERSION,
516
+ updatedAt: store.resumeStore.updatedAt,
517
+ issueKey: sessionIssueIdentity(store.resumeStore.issueUrl).key,
518
+ conductorAgentId: store.resumeStore.conductorAgentId,
519
+ };
520
+ return Buffer.from(JSON.stringify(cursor), 'utf8').toString('base64url');
521
+ }
522
+ function decodeSessionResumeStoreCursor(value) {
523
+ let cursor;
524
+ try {
525
+ cursor = JSON.parse(Buffer.from(value, 'base64url').toString('utf8'));
526
+ }
527
+ catch {
528
+ throw new Error('Invalid session list cursor.');
529
+ }
530
+ if (!isRecord(cursor) ||
531
+ cursor.version !== SESSION_RESUME_STORE_PAGE_CURSOR_VERSION ||
532
+ typeof cursor.updatedAt !== 'number' ||
533
+ !Number.isFinite(cursor.updatedAt) ||
534
+ typeof cursor.issueKey !== 'string' ||
535
+ typeof cursor.conductorAgentId !== 'string') {
536
+ throw new Error('Invalid session list cursor.');
537
+ }
538
+ return {
539
+ version: cursor.version,
540
+ updatedAt: cursor.updatedAt,
541
+ issueKey: cursor.issueKey,
542
+ conductorAgentId: cursor.conductorAgentId,
543
+ };
544
+ }
282
545
  /** @deprecated Use listSessionResumeStores; supported for one release only. */
283
546
  export const listSessionSidecars = listSessionResumeStores;
284
547
  /**
@@ -286,8 +549,12 @@ export const listSessionSidecars = listSessionResumeStores;
286
549
  * updatedAt が最も新しい SessionResumeStore を返す。
287
550
  */
288
551
  export async function findLatestSessionResumeStoreForIssue(input) {
289
- const matches = (await listSessionResumeStores(input.repoRoot)).filter((resumeStore) => resumeStore.issueUrl === input.issueUrl &&
290
- resumeStore.repoRoot === input.repoRoot &&
552
+ const issueKey = sessionIssueIdentity(input.issueUrl).key;
553
+ const matches = (await listLocatedSessionResumeStoresPage(input.repoRoot, {
554
+ issueUrl: input.issueUrl,
555
+ })).sessions
556
+ .map(({ resumeStore }) => resumeStore)
557
+ .filter((resumeStore) => sessionIssueIdentity(resumeStore.issueUrl).key === issueKey &&
291
558
  (resumeStore.harnessPhase ??
292
559
  harnessPhaseFromLegacyLifecycle(resumeStore.lifecycle)) !== 'exit');
293
560
  if (matches.length === 0)
@@ -311,31 +578,10 @@ export function assertSessionResumeStoreMatches(resumeStore, input) {
311
578
  if (resumeStore.conductorAgentId !== input.conductorAgentId) {
312
579
  throw new Error(`Session resume store conductorAgentId mismatch: ${resumeStore.conductorAgentId} !== ${input.conductorAgentId}`);
313
580
  }
314
- if (resumeStore.issueUrl !== input.issueUrl) {
581
+ if (sessionIssueIdentity(resumeStore.issueUrl).key !==
582
+ sessionIssueIdentity(input.issueUrl).key) {
315
583
  throw new Error(`Session resume store issueUrl mismatch: ${resumeStore.issueUrl} !== ${input.issueUrl}`);
316
584
  }
317
- if (resumeStore.repoRoot !== input.repoRoot) {
318
- throw new Error(`Session resume store repoRoot mismatch: ${resumeStore.repoRoot} !== ${input.repoRoot}`);
319
- }
320
- if (input.conductorBackend !== undefined &&
321
- (resumeStore.conductorBackend ?? 'cursor') !== input.conductorBackend) {
322
- throw new Error(`Session resume store conductorBackend mismatch: ${resumeStore.conductorBackend ?? 'cursor'} !== ${input.conductorBackend}`);
323
- }
324
- if (input.mcpConfigDigest !== undefined) {
325
- if (resumeStore.mcpConfigDigest === undefined) {
326
- if (input.mcpConfigConfigured) {
327
- throw new Error('Session resume store MCP config digest missing; cannot safely resume with MCP configuration.');
328
- }
329
- }
330
- else if (resumeStore.mcpConfigDigest !== input.mcpConfigDigest) {
331
- throw new Error(`Session resume store MCP config digest mismatch: ${resumeStore.mcpConfigDigest} !== ${input.mcpConfigDigest}`);
332
- }
333
- }
334
- if (input.ensembleConfigDigest !== undefined &&
335
- resumeStore.ensembleConfigDigest !== undefined &&
336
- resumeStore.ensembleConfigDigest !== input.ensembleConfigDigest) {
337
- throw new Error(`Session resume store ensemble config digest mismatch: ${resumeStore.ensembleConfigDigest} !== ${input.ensembleConfigDigest}`);
338
- }
339
585
  }
340
586
  /** @deprecated Use assertSessionResumeStoreMatches; supported for one release only. */
341
587
  export const assertSessionSidecarMatches = assertSessionResumeStoreMatches;
@@ -416,6 +662,10 @@ function parseSessionResumeStore(value) {
416
662
  record.cursorBootstrapRunId.trim() === '')) {
417
663
  throw new Error('Invalid session resume store: cursorBootstrapRunId');
418
664
  }
665
+ if (record.cursorBootstrapComplete !== undefined &&
666
+ typeof record.cursorBootstrapComplete !== 'boolean') {
667
+ throw new Error('Invalid session resume store: cursorBootstrapComplete');
668
+ }
419
669
  if (record.lifecycle !== undefined &&
420
670
  record.lifecycle !== 'closed' &&
421
671
  record.lifecycle !== 'exited' &&
@@ -471,6 +721,9 @@ function parseSessionResumeStore(value) {
471
721
  ...(typeof record.cursorBootstrapRunId === 'string'
472
722
  ? { cursorBootstrapRunId: record.cursorBootstrapRunId }
473
723
  : {}),
724
+ ...(typeof record.cursorBootstrapComplete === 'boolean'
725
+ ? { cursorBootstrapComplete: record.cursorBootstrapComplete }
726
+ : {}),
474
727
  ...(typeof record.lazyWorkers === 'boolean'
475
728
  ? { lazyWorkers: record.lazyWorkers }
476
729
  : {}),
@@ -528,6 +781,9 @@ function parseConductorBackend(value) {
528
781
  }
529
782
  throw new Error('Invalid session resume store: conductorBackend');
530
783
  }
784
+ function isRecord(value) {
785
+ return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
786
+ }
531
787
  function isEnoent(error) {
532
788
  return (typeof error === 'object' &&
533
789
  error !== null &&
@@ -588,11 +844,17 @@ function parseAcpSpawnFingerprint(raw, workerName) {
588
844
  if (!Array.isArray(record.args) || record.args.some((arg) => typeof arg !== 'string')) {
589
845
  throw new Error(`Invalid session resume store worker acpSpawn args: ${workerName}`);
590
846
  }
591
- const validModelTags = ['high', 'default', 'low', 'fast'];
847
+ let piRuntime = undefined;
848
+ if (record.piRuntime === 'builtin') {
849
+ piRuntime = 'builtin';
850
+ }
851
+ else if (record.piRuntime !== undefined &&
852
+ record.piRuntime !== 'byo') {
853
+ throw new Error(`Invalid session resume store worker acpSpawn piRuntime: ${workerName}`);
854
+ }
592
855
  let modelTag;
593
856
  if (record.modelTag !== undefined) {
594
- if (typeof record.modelTag !== 'string' ||
595
- !validModelTags.includes(record.modelTag)) {
857
+ if (typeof record.modelTag !== 'string') {
596
858
  throw new Error(`Invalid session resume store worker acpSpawn modelTag: ${workerName}`);
597
859
  }
598
860
  modelTag = record.modelTag;
@@ -657,6 +919,7 @@ function parseAcpSpawnFingerprint(raw, workerName) {
657
919
  preset: record.preset,
658
920
  command: record.command,
659
921
  args: record.args,
922
+ ...(piRuntime !== undefined ? { piRuntime } : {}),
660
923
  ...(modelTag !== undefined ? { modelTag } : {}),
661
924
  ...(model !== undefined ? { model } : {}),
662
925
  ...(reasoning !== undefined ? { reasoning } : {}),