@codyswann/lisa 4.80.1 → 4.81.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 (335) hide show
  1. package/README.md +6 -3
  2. package/all/copy-contents/gitignore +5 -0
  3. package/cdk/package-lisa/package.lisa.json +0 -2
  4. package/dist/cli/doctor-cdk-preset-adoption.js +2 -2
  5. package/dist/cli/doctor-cdk-preset-adoption.js.map +1 -1
  6. package/dist/cli/doctor-session-manager-trust.d.ts +62 -0
  7. package/dist/cli/doctor-session-manager-trust.d.ts.map +1 -0
  8. package/dist/cli/doctor-session-manager-trust.js +174 -0
  9. package/dist/cli/doctor-session-manager-trust.js.map +1 -0
  10. package/dist/cli/doctor-session-manager.d.ts +35 -0
  11. package/dist/cli/doctor-session-manager.d.ts.map +1 -0
  12. package/dist/cli/doctor-session-manager.js +209 -0
  13. package/dist/cli/doctor-session-manager.js.map +1 -0
  14. package/dist/cli/doctor.d.ts.map +1 -1
  15. package/dist/cli/doctor.js +3 -0
  16. package/dist/cli/doctor.js.map +1 -1
  17. package/dist/cli/lisa-test-run-bootstrap.js +28 -0
  18. package/dist/cli/lisa-test-run-bootstrap.js.map +1 -1
  19. package/dist/cli/session-manager-config-merge.d.ts +25 -0
  20. package/dist/cli/session-manager-config-merge.d.ts.map +1 -0
  21. package/dist/cli/session-manager-config-merge.js +54 -0
  22. package/dist/cli/session-manager-config-merge.js.map +1 -0
  23. package/dist/core/cdk-preset-adoption.d.ts +7 -2
  24. package/dist/core/cdk-preset-adoption.d.ts.map +1 -1
  25. package/dist/core/cdk-preset-adoption.js +10 -2
  26. package/dist/core/cdk-preset-adoption.js.map +1 -1
  27. package/dist/core/nightly-e2e-guard-behavior-certificate.js +2 -2
  28. package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
  29. package/dist/core/upstream-evidence-manifest.js +258 -13
  30. package/dist/core/upstream-evidence-manifest.js.map +1 -1
  31. package/package.json +4 -4
  32. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  33. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  34. package/plugins/lisa/.codex-plugin/skills/lisa-automation-status/SKILL.md +10 -0
  35. package/plugins/lisa/.codex-plugin/skills/lisa-implement/SKILL.md +17 -2
  36. package/plugins/lisa/.codex-plugin/skills/lisa-secrets-access/scripts/validate-config.mjs +377 -5
  37. package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/SKILL.md +287 -0
  38. package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/agents/openai.yaml +4 -0
  39. package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/references/runbook-template.md +66 -0
  40. package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/scripts/capacity.mjs +186 -0
  41. package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/scripts/cli-cycle.mjs +262 -0
  42. package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/scripts/cli-manager.mjs +308 -0
  43. package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/scripts/cli-queue.mjs +148 -0
  44. package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/scripts/cli-runtime.mjs +326 -0
  45. package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/scripts/cli-usage.mjs +114 -0
  46. package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/scripts/cli-workers.mjs +295 -0
  47. package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/scripts/config.mjs +265 -0
  48. package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/scripts/isolation.mjs +130 -0
  49. package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/scripts/launch.mjs +234 -0
  50. package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/scripts/ledger.mjs +445 -0
  51. package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/scripts/reconcile.mjs +134 -0
  52. package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/scripts/session-manager.mjs +274 -0
  53. package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/scripts/state-dir.mjs +112 -0
  54. package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/scripts/telegram-route.mjs +210 -0
  55. package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/scripts/token.mjs +191 -0
  56. package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/scripts/worker-launch.mjs +92 -0
  57. package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/scripts/workers.mjs +406 -0
  58. package/plugins/lisa/.codex-plugin/skills/lisa-setup-session-manager/SKILL.md +158 -0
  59. package/plugins/lisa/.codex-plugin/skills/lisa-setup-session-manager/agents/openai.yaml +4 -0
  60. package/plugins/lisa/.codex-plugin/skills/lisa-tear-down-automations/SKILL.md +10 -0
  61. package/plugins/lisa/commands/implement.md +2 -2
  62. package/plugins/lisa/commands/session-manager.md +7 -0
  63. package/plugins/lisa/commands/setup/session-manager.md +6 -0
  64. package/plugins/lisa/rules/reference/automation-runbook-contract.md +14 -0
  65. package/plugins/lisa/rules/reference/config-resolution.md +58 -0
  66. package/plugins/lisa/scripts/automation-status-claude-adapter.mjs +4 -1
  67. package/plugins/lisa/scripts/automation-status-codex-adapter.mjs +4 -1
  68. package/plugins/lisa/scripts/automation-status-expected-fleet.mjs +98 -0
  69. package/plugins/lisa/scripts/automation-status-run-history.mjs +29 -7
  70. package/plugins/lisa/scripts/ready-queue-order.mjs +352 -0
  71. package/plugins/lisa/skills/lisa-automation-status/SKILL.md +10 -0
  72. package/plugins/lisa/skills/lisa-implement/SKILL.md +17 -2
  73. package/plugins/lisa/skills/lisa-secrets-access/scripts/validate-config.mjs +377 -5
  74. package/plugins/lisa/skills/lisa-session-manager/SKILL.md +285 -0
  75. package/plugins/lisa/skills/lisa-session-manager/agents/openai.yaml +4 -0
  76. package/plugins/lisa/skills/lisa-session-manager/references/runbook-template.md +66 -0
  77. package/plugins/lisa/skills/lisa-session-manager/scripts/capacity.mjs +186 -0
  78. package/plugins/lisa/skills/lisa-session-manager/scripts/cli-cycle.mjs +262 -0
  79. package/plugins/lisa/skills/lisa-session-manager/scripts/cli-manager.mjs +308 -0
  80. package/plugins/lisa/skills/lisa-session-manager/scripts/cli-queue.mjs +148 -0
  81. package/plugins/lisa/skills/lisa-session-manager/scripts/cli-runtime.mjs +326 -0
  82. package/plugins/lisa/skills/lisa-session-manager/scripts/cli-usage.mjs +114 -0
  83. package/plugins/lisa/skills/lisa-session-manager/scripts/cli-workers.mjs +295 -0
  84. package/plugins/lisa/skills/lisa-session-manager/scripts/config.mjs +265 -0
  85. package/plugins/lisa/skills/lisa-session-manager/scripts/isolation.mjs +130 -0
  86. package/plugins/lisa/skills/lisa-session-manager/scripts/launch.mjs +234 -0
  87. package/plugins/lisa/skills/lisa-session-manager/scripts/ledger.mjs +445 -0
  88. package/plugins/lisa/skills/lisa-session-manager/scripts/reconcile.mjs +134 -0
  89. package/plugins/lisa/skills/lisa-session-manager/scripts/session-manager.mjs +274 -0
  90. package/plugins/lisa/skills/lisa-session-manager/scripts/state-dir.mjs +112 -0
  91. package/plugins/lisa/skills/lisa-session-manager/scripts/telegram-route.mjs +210 -0
  92. package/plugins/lisa/skills/lisa-session-manager/scripts/token.mjs +191 -0
  93. package/plugins/lisa/skills/lisa-session-manager/scripts/worker-launch.mjs +92 -0
  94. package/plugins/lisa/skills/lisa-session-manager/scripts/workers.mjs +406 -0
  95. package/plugins/lisa/skills/lisa-setup-session-manager/SKILL.md +156 -0
  96. package/plugins/lisa/skills/lisa-setup-session-manager/agents/openai.yaml +4 -0
  97. package/plugins/lisa/skills/lisa-tear-down-automations/SKILL.md +10 -0
  98. package/plugins/lisa-agy/commands/lisa/implement.md +2 -2
  99. package/plugins/lisa-agy/commands/lisa/session-manager.md +7 -0
  100. package/plugins/lisa-agy/commands/lisa/setup/session-manager.md +6 -0
  101. package/plugins/lisa-agy/plugin.json +1 -1
  102. package/plugins/lisa-agy/scripts/automation-status-claude-adapter.mjs +4 -1
  103. package/plugins/lisa-agy/scripts/automation-status-codex-adapter.mjs +4 -1
  104. package/plugins/lisa-agy/scripts/automation-status-expected-fleet.mjs +98 -0
  105. package/plugins/lisa-agy/scripts/automation-status-run-history.mjs +29 -7
  106. package/plugins/lisa-agy/scripts/ready-queue-order.mjs +352 -0
  107. package/plugins/lisa-agy/skills/lisa-automation-status/SKILL.md +10 -0
  108. package/plugins/lisa-agy/skills/lisa-implement/SKILL.md +17 -2
  109. package/plugins/lisa-agy/skills/lisa-secrets-access/scripts/validate-config.mjs +377 -5
  110. package/plugins/lisa-agy/skills/lisa-session-manager/SKILL.md +285 -0
  111. package/plugins/lisa-agy/skills/lisa-session-manager/references/runbook-template.md +66 -0
  112. package/plugins/lisa-agy/skills/lisa-session-manager/scripts/capacity.mjs +186 -0
  113. package/plugins/lisa-agy/skills/lisa-session-manager/scripts/cli-cycle.mjs +262 -0
  114. package/plugins/lisa-agy/skills/lisa-session-manager/scripts/cli-manager.mjs +308 -0
  115. package/plugins/lisa-agy/skills/lisa-session-manager/scripts/cli-queue.mjs +148 -0
  116. package/plugins/lisa-agy/skills/lisa-session-manager/scripts/cli-runtime.mjs +326 -0
  117. package/plugins/lisa-agy/skills/lisa-session-manager/scripts/cli-usage.mjs +114 -0
  118. package/plugins/lisa-agy/skills/lisa-session-manager/scripts/cli-workers.mjs +295 -0
  119. package/plugins/lisa-agy/skills/lisa-session-manager/scripts/config.mjs +265 -0
  120. package/plugins/lisa-agy/skills/lisa-session-manager/scripts/isolation.mjs +130 -0
  121. package/plugins/lisa-agy/skills/lisa-session-manager/scripts/launch.mjs +234 -0
  122. package/plugins/lisa-agy/skills/lisa-session-manager/scripts/ledger.mjs +445 -0
  123. package/plugins/lisa-agy/skills/lisa-session-manager/scripts/reconcile.mjs +134 -0
  124. package/plugins/lisa-agy/skills/lisa-session-manager/scripts/session-manager.mjs +274 -0
  125. package/plugins/lisa-agy/skills/lisa-session-manager/scripts/state-dir.mjs +112 -0
  126. package/plugins/lisa-agy/skills/lisa-session-manager/scripts/telegram-route.mjs +210 -0
  127. package/plugins/lisa-agy/skills/lisa-session-manager/scripts/token.mjs +191 -0
  128. package/plugins/lisa-agy/skills/lisa-session-manager/scripts/worker-launch.mjs +92 -0
  129. package/plugins/lisa-agy/skills/lisa-session-manager/scripts/workers.mjs +406 -0
  130. package/plugins/lisa-agy/skills/lisa-setup-session-manager/SKILL.md +156 -0
  131. package/plugins/lisa-agy/skills/lisa-tear-down-automations/SKILL.md +10 -0
  132. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  133. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  134. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  135. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  136. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  137. package/plugins/lisa-cdk-devin/.claude-plugin/plugin.json +1 -1
  138. package/plugins/lisa-cdk-devin/.devin-plugin/plugin.json +1 -1
  139. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  140. package/plugins/lisa-copilot/commands/lisa/implement.md +2 -2
  141. package/plugins/lisa-copilot/commands/lisa/session-manager.md +7 -0
  142. package/plugins/lisa-copilot/commands/lisa/setup/session-manager.md +6 -0
  143. package/plugins/lisa-copilot/rules/reference/automation-runbook-contract.md +14 -0
  144. package/plugins/lisa-copilot/rules/reference/config-resolution.md +58 -0
  145. package/plugins/lisa-copilot/scripts/automation-status-claude-adapter.mjs +4 -1
  146. package/plugins/lisa-copilot/scripts/automation-status-codex-adapter.mjs +4 -1
  147. package/plugins/lisa-copilot/scripts/automation-status-expected-fleet.mjs +98 -0
  148. package/plugins/lisa-copilot/scripts/automation-status-run-history.mjs +29 -7
  149. package/plugins/lisa-copilot/scripts/ready-queue-order.mjs +352 -0
  150. package/plugins/lisa-copilot/skills/lisa-automation-status/SKILL.md +10 -0
  151. package/plugins/lisa-copilot/skills/lisa-implement/SKILL.md +17 -2
  152. package/plugins/lisa-copilot/skills/lisa-secrets-access/scripts/validate-config.mjs +377 -5
  153. package/plugins/lisa-copilot/skills/lisa-session-manager/SKILL.md +285 -0
  154. package/plugins/lisa-copilot/skills/lisa-session-manager/references/runbook-template.md +66 -0
  155. package/plugins/lisa-copilot/skills/lisa-session-manager/scripts/capacity.mjs +186 -0
  156. package/plugins/lisa-copilot/skills/lisa-session-manager/scripts/cli-cycle.mjs +262 -0
  157. package/plugins/lisa-copilot/skills/lisa-session-manager/scripts/cli-manager.mjs +308 -0
  158. package/plugins/lisa-copilot/skills/lisa-session-manager/scripts/cli-queue.mjs +148 -0
  159. package/plugins/lisa-copilot/skills/lisa-session-manager/scripts/cli-runtime.mjs +326 -0
  160. package/plugins/lisa-copilot/skills/lisa-session-manager/scripts/cli-usage.mjs +114 -0
  161. package/plugins/lisa-copilot/skills/lisa-session-manager/scripts/cli-workers.mjs +295 -0
  162. package/plugins/lisa-copilot/skills/lisa-session-manager/scripts/config.mjs +265 -0
  163. package/plugins/lisa-copilot/skills/lisa-session-manager/scripts/isolation.mjs +130 -0
  164. package/plugins/lisa-copilot/skills/lisa-session-manager/scripts/launch.mjs +234 -0
  165. package/plugins/lisa-copilot/skills/lisa-session-manager/scripts/ledger.mjs +445 -0
  166. package/plugins/lisa-copilot/skills/lisa-session-manager/scripts/reconcile.mjs +134 -0
  167. package/plugins/lisa-copilot/skills/lisa-session-manager/scripts/session-manager.mjs +274 -0
  168. package/plugins/lisa-copilot/skills/lisa-session-manager/scripts/state-dir.mjs +112 -0
  169. package/plugins/lisa-copilot/skills/lisa-session-manager/scripts/telegram-route.mjs +210 -0
  170. package/plugins/lisa-copilot/skills/lisa-session-manager/scripts/token.mjs +191 -0
  171. package/plugins/lisa-copilot/skills/lisa-session-manager/scripts/worker-launch.mjs +92 -0
  172. package/plugins/lisa-copilot/skills/lisa-session-manager/scripts/workers.mjs +406 -0
  173. package/plugins/lisa-copilot/skills/lisa-setup-session-manager/SKILL.md +156 -0
  174. package/plugins/lisa-copilot/skills/lisa-tear-down-automations/SKILL.md +10 -0
  175. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  176. package/plugins/lisa-cursor/commands/lisa/implement.md +2 -2
  177. package/plugins/lisa-cursor/commands/lisa/session-manager.md +7 -0
  178. package/plugins/lisa-cursor/commands/lisa/setup/session-manager.md +6 -0
  179. package/plugins/lisa-cursor/rules/automation-runbook-contract-reference.mdc +14 -0
  180. package/plugins/lisa-cursor/rules/config-resolution-reference.mdc +58 -0
  181. package/plugins/lisa-cursor/scripts/automation-status-claude-adapter.mjs +4 -1
  182. package/plugins/lisa-cursor/scripts/automation-status-codex-adapter.mjs +4 -1
  183. package/plugins/lisa-cursor/scripts/automation-status-expected-fleet.mjs +98 -0
  184. package/plugins/lisa-cursor/scripts/automation-status-run-history.mjs +29 -7
  185. package/plugins/lisa-cursor/scripts/ready-queue-order.mjs +352 -0
  186. package/plugins/lisa-cursor/skills/lisa-automation-status/SKILL.md +10 -0
  187. package/plugins/lisa-cursor/skills/lisa-implement/SKILL.md +17 -2
  188. package/plugins/lisa-cursor/skills/lisa-secrets-access/scripts/validate-config.mjs +377 -5
  189. package/plugins/lisa-cursor/skills/lisa-session-manager/SKILL.md +285 -0
  190. package/plugins/lisa-cursor/skills/lisa-session-manager/references/runbook-template.md +66 -0
  191. package/plugins/lisa-cursor/skills/lisa-session-manager/scripts/capacity.mjs +186 -0
  192. package/plugins/lisa-cursor/skills/lisa-session-manager/scripts/cli-cycle.mjs +262 -0
  193. package/plugins/lisa-cursor/skills/lisa-session-manager/scripts/cli-manager.mjs +308 -0
  194. package/plugins/lisa-cursor/skills/lisa-session-manager/scripts/cli-queue.mjs +148 -0
  195. package/plugins/lisa-cursor/skills/lisa-session-manager/scripts/cli-runtime.mjs +326 -0
  196. package/plugins/lisa-cursor/skills/lisa-session-manager/scripts/cli-usage.mjs +114 -0
  197. package/plugins/lisa-cursor/skills/lisa-session-manager/scripts/cli-workers.mjs +295 -0
  198. package/plugins/lisa-cursor/skills/lisa-session-manager/scripts/config.mjs +265 -0
  199. package/plugins/lisa-cursor/skills/lisa-session-manager/scripts/isolation.mjs +130 -0
  200. package/plugins/lisa-cursor/skills/lisa-session-manager/scripts/launch.mjs +234 -0
  201. package/plugins/lisa-cursor/skills/lisa-session-manager/scripts/ledger.mjs +445 -0
  202. package/plugins/lisa-cursor/skills/lisa-session-manager/scripts/reconcile.mjs +134 -0
  203. package/plugins/lisa-cursor/skills/lisa-session-manager/scripts/session-manager.mjs +274 -0
  204. package/plugins/lisa-cursor/skills/lisa-session-manager/scripts/state-dir.mjs +112 -0
  205. package/plugins/lisa-cursor/skills/lisa-session-manager/scripts/telegram-route.mjs +210 -0
  206. package/plugins/lisa-cursor/skills/lisa-session-manager/scripts/token.mjs +191 -0
  207. package/plugins/lisa-cursor/skills/lisa-session-manager/scripts/worker-launch.mjs +92 -0
  208. package/plugins/lisa-cursor/skills/lisa-session-manager/scripts/workers.mjs +406 -0
  209. package/plugins/lisa-cursor/skills/lisa-setup-session-manager/SKILL.md +156 -0
  210. package/plugins/lisa-cursor/skills/lisa-tear-down-automations/SKILL.md +10 -0
  211. package/plugins/lisa-devin/.claude-plugin/plugin.json +1 -1
  212. package/plugins/lisa-devin/.devin-plugin/plugin.json +1 -1
  213. package/plugins/lisa-devin/rules/reference/automation-runbook-contract.md +14 -0
  214. package/plugins/lisa-devin/rules/reference/config-resolution.md +58 -0
  215. package/plugins/lisa-devin/scripts/automation-status-claude-adapter.mjs +4 -1
  216. package/plugins/lisa-devin/scripts/automation-status-codex-adapter.mjs +4 -1
  217. package/plugins/lisa-devin/scripts/automation-status-expected-fleet.mjs +98 -0
  218. package/plugins/lisa-devin/scripts/automation-status-run-history.mjs +29 -7
  219. package/plugins/lisa-devin/scripts/ready-queue-order.mjs +352 -0
  220. package/plugins/lisa-devin/skills/lisa-automation-status/SKILL.md +10 -0
  221. package/plugins/lisa-devin/skills/lisa-implement/SKILL.md +17 -2
  222. package/plugins/lisa-devin/skills/lisa-secrets-access/scripts/validate-config.mjs +377 -5
  223. package/plugins/lisa-devin/skills/lisa-session-manager/SKILL.md +285 -0
  224. package/plugins/lisa-devin/skills/lisa-session-manager/references/runbook-template.md +66 -0
  225. package/plugins/lisa-devin/skills/lisa-session-manager/scripts/capacity.mjs +186 -0
  226. package/plugins/lisa-devin/skills/lisa-session-manager/scripts/cli-cycle.mjs +262 -0
  227. package/plugins/lisa-devin/skills/lisa-session-manager/scripts/cli-manager.mjs +308 -0
  228. package/plugins/lisa-devin/skills/lisa-session-manager/scripts/cli-queue.mjs +148 -0
  229. package/plugins/lisa-devin/skills/lisa-session-manager/scripts/cli-runtime.mjs +326 -0
  230. package/plugins/lisa-devin/skills/lisa-session-manager/scripts/cli-usage.mjs +114 -0
  231. package/plugins/lisa-devin/skills/lisa-session-manager/scripts/cli-workers.mjs +295 -0
  232. package/plugins/lisa-devin/skills/lisa-session-manager/scripts/config.mjs +265 -0
  233. package/plugins/lisa-devin/skills/lisa-session-manager/scripts/isolation.mjs +130 -0
  234. package/plugins/lisa-devin/skills/lisa-session-manager/scripts/launch.mjs +234 -0
  235. package/plugins/lisa-devin/skills/lisa-session-manager/scripts/ledger.mjs +445 -0
  236. package/plugins/lisa-devin/skills/lisa-session-manager/scripts/reconcile.mjs +134 -0
  237. package/plugins/lisa-devin/skills/lisa-session-manager/scripts/session-manager.mjs +274 -0
  238. package/plugins/lisa-devin/skills/lisa-session-manager/scripts/state-dir.mjs +112 -0
  239. package/plugins/lisa-devin/skills/lisa-session-manager/scripts/telegram-route.mjs +210 -0
  240. package/plugins/lisa-devin/skills/lisa-session-manager/scripts/token.mjs +191 -0
  241. package/plugins/lisa-devin/skills/lisa-session-manager/scripts/worker-launch.mjs +92 -0
  242. package/plugins/lisa-devin/skills/lisa-session-manager/scripts/workers.mjs +406 -0
  243. package/plugins/lisa-devin/skills/lisa-setup-session-manager/SKILL.md +156 -0
  244. package/plugins/lisa-devin/skills/lisa-tear-down-automations/SKILL.md +10 -0
  245. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  246. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  247. package/plugins/lisa-expo-agy/plugin.json +1 -1
  248. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  249. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  250. package/plugins/lisa-expo-devin/.claude-plugin/plugin.json +1 -1
  251. package/plugins/lisa-expo-devin/.devin-plugin/plugin.json +1 -1
  252. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  253. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  254. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  255. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  256. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  257. package/plugins/lisa-harper-fabric-devin/.claude-plugin/plugin.json +1 -1
  258. package/plugins/lisa-harper-fabric-devin/.devin-plugin/plugin.json +1 -1
  259. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  260. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  261. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  262. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  263. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  264. package/plugins/lisa-nestjs-devin/.claude-plugin/plugin.json +1 -1
  265. package/plugins/lisa-nestjs-devin/.devin-plugin/plugin.json +1 -1
  266. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  267. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  268. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  269. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  270. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  271. package/plugins/lisa-openclaw-devin/.claude-plugin/plugin.json +1 -1
  272. package/plugins/lisa-openclaw-devin/.devin-plugin/plugin.json +1 -1
  273. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  274. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  275. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  276. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  277. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  278. package/plugins/lisa-phaser-devin/.claude-plugin/plugin.json +1 -1
  279. package/plugins/lisa-phaser-devin/.devin-plugin/plugin.json +1 -1
  280. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  281. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  282. package/plugins/lisa-rails-agy/plugin.json +1 -1
  283. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  284. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  285. package/plugins/lisa-rails-devin/.claude-plugin/plugin.json +1 -1
  286. package/plugins/lisa-rails-devin/.devin-plugin/plugin.json +1 -1
  287. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  288. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  289. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  290. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  291. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  292. package/plugins/lisa-typescript-devin/.claude-plugin/plugin.json +1 -1
  293. package/plugins/lisa-typescript-devin/.devin-plugin/plugin.json +1 -1
  294. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  295. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  296. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  297. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  298. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  299. package/plugins/lisa-wiki-devin/.claude-plugin/plugin.json +1 -1
  300. package/plugins/lisa-wiki-devin/.devin-plugin/plugin.json +1 -1
  301. package/plugins/src/base/commands/implement.md +2 -2
  302. package/plugins/src/base/commands/session-manager.md +7 -0
  303. package/plugins/src/base/commands/setup/session-manager.md +6 -0
  304. package/plugins/src/base/rules/reference/automation-runbook-contract.md +14 -0
  305. package/plugins/src/base/rules/reference/config-resolution.md +58 -0
  306. package/plugins/src/base/scripts/automation-status-claude-adapter.mjs +4 -1
  307. package/plugins/src/base/scripts/automation-status-codex-adapter.mjs +4 -1
  308. package/plugins/src/base/scripts/automation-status-expected-fleet.mjs +98 -0
  309. package/plugins/src/base/scripts/automation-status-run-history.mjs +29 -7
  310. package/plugins/src/base/scripts/ready-queue-order.mjs +352 -0
  311. package/plugins/src/base/skills/lisa-automation-status/SKILL.md +10 -0
  312. package/plugins/src/base/skills/lisa-implement/SKILL.md +17 -2
  313. package/plugins/src/base/skills/lisa-secrets-access/scripts/validate-config.mjs +377 -5
  314. package/plugins/src/base/skills/lisa-session-manager/SKILL.md +285 -0
  315. package/plugins/src/base/skills/lisa-session-manager/references/runbook-template.md +66 -0
  316. package/plugins/src/base/skills/lisa-session-manager/scripts/capacity.mjs +186 -0
  317. package/plugins/src/base/skills/lisa-session-manager/scripts/cli-cycle.mjs +262 -0
  318. package/plugins/src/base/skills/lisa-session-manager/scripts/cli-manager.mjs +308 -0
  319. package/plugins/src/base/skills/lisa-session-manager/scripts/cli-queue.mjs +148 -0
  320. package/plugins/src/base/skills/lisa-session-manager/scripts/cli-runtime.mjs +326 -0
  321. package/plugins/src/base/skills/lisa-session-manager/scripts/cli-usage.mjs +114 -0
  322. package/plugins/src/base/skills/lisa-session-manager/scripts/cli-workers.mjs +295 -0
  323. package/plugins/src/base/skills/lisa-session-manager/scripts/config.mjs +265 -0
  324. package/plugins/src/base/skills/lisa-session-manager/scripts/isolation.mjs +130 -0
  325. package/plugins/src/base/skills/lisa-session-manager/scripts/launch.mjs +234 -0
  326. package/plugins/src/base/skills/lisa-session-manager/scripts/ledger.mjs +445 -0
  327. package/plugins/src/base/skills/lisa-session-manager/scripts/reconcile.mjs +134 -0
  328. package/plugins/src/base/skills/lisa-session-manager/scripts/session-manager.mjs +274 -0
  329. package/plugins/src/base/skills/lisa-session-manager/scripts/state-dir.mjs +112 -0
  330. package/plugins/src/base/skills/lisa-session-manager/scripts/telegram-route.mjs +210 -0
  331. package/plugins/src/base/skills/lisa-session-manager/scripts/token.mjs +191 -0
  332. package/plugins/src/base/skills/lisa-session-manager/scripts/worker-launch.mjs +92 -0
  333. package/plugins/src/base/skills/lisa-session-manager/scripts/workers.mjs +406 -0
  334. package/plugins/src/base/skills/lisa-setup-session-manager/SKILL.md +156 -0
  335. package/plugins/src/base/skills/lisa-tear-down-automations/SKILL.md +10 -0
@@ -0,0 +1,234 @@
1
+ /**
2
+ * Starting and stopping the session manager, and the Telegram bot token it
3
+ * needs.
4
+ *
5
+ * The official Telegram plugin keeps its token, pairing list, inbox and
6
+ * `bot.pid` in one state dir (`TELEGRAM_STATE_DIR`, else
7
+ * `<CLAUDE_CONFIG_DIR>/channels/telegram`). It long-polls from EVERY session
8
+ * where it is enabled, and on start it SIGTERMs whichever poller holds the
9
+ * `bot.pid` in the same dir. So isolation is the whole design here:
10
+ * - the manager gets its own per-project state dir, set only in its own tmux
11
+ * env, and the shared default dir is refused outright;
12
+ * - the plugin is enabled only through the manager's own `--settings`, never
13
+ * at user scope, so worker sessions never start a poller;
14
+ * - the token lives in `<stateDir>/.env` (dir 0700, file 0600), is read from
15
+ * the operator's terminal with echo off, and never appears in argv, a
16
+ * return value, an error message or a log line.
17
+ *
18
+ * The tmux argv is returned as an array and handed straight to `spawnSync`.
19
+ * With more than one command argument tmux runs the command directly rather
20
+ * than through `sh -c`, so the settings JSON and the loop prompt are never
21
+ * re-parsed by a shell.
22
+ *
23
+ * The state dir lives in state-dir.mjs and the token in token.mjs; both are
24
+ * re-exported here so callers keep one import.
25
+ *
26
+ * Everything here is pure or takes injected seams. This module never reads
27
+ * the environment and never runs tmux or claude itself: `session-manager.mjs`
28
+ * passes the real env in and executes the argv.
29
+ * @module launch
30
+ */
31
+ import path from "node:path";
32
+
33
+ import { parseLoopInterval } from "./config.mjs";
34
+ import { LaunchError } from "./state-dir.mjs";
35
+
36
+ export {
37
+ LaunchError,
38
+ realpathExisting,
39
+ resolveStateDir,
40
+ } from "./state-dir.mjs";
41
+ export {
42
+ assertTokenPresent,
43
+ buildStoreTokenCommand,
44
+ loadPromptSecret,
45
+ storeToken,
46
+ validateBotToken,
47
+ writeTokenFile,
48
+ } from "./token.mjs";
49
+
50
+ /** The plugin the manager, and only the manager, enables. */
51
+ export const TELEGRAM_PLUGIN_ID = "telegram@claude-plugins-official";
52
+
53
+ /** Same slug rule the config validator applies to `sessionManager.name`. */
54
+ const SESSION_NAME_PATTERN = /^[a-z0-9][a-z0-9_-]{0,63}$/;
55
+
56
+ /** A `'` inside a single-quoted shell word: close, escaped quote, reopen. */
57
+ const ESCAPED_QUOTE = "'\\''";
58
+
59
+ /** An argument a POSIX shell reads back unchanged without quotes. */
60
+ const SHELL_SAFE = /^[A-Za-z0-9_@%+=:,./-]+$/;
61
+
62
+ /** The skill command each manager loop iteration runs. */
63
+ const CYCLE_COMMAND = "/lisa:session-manager cycle";
64
+
65
+ /**
66
+ * Settings blob for the manager session only: the Telegram plugin on, and
67
+ * workers' messages accepted.
68
+ * @returns {{ crossSessionInbound: "accept",
69
+ * enabledPlugins: Record<string, boolean> }} Fresh settings object
70
+ */
71
+ export function buildManagerSettings() {
72
+ return {
73
+ crossSessionInbound: "accept",
74
+ enabledPlugins: { [TELEGRAM_PLUGIN_ID]: true },
75
+ };
76
+ }
77
+
78
+ /**
79
+ * Refuse a tmux session name that is not a plain slug; `:` and `.` would be
80
+ * read by tmux as a window or pane target.
81
+ * @param {unknown} name - Session name
82
+ * @returns {string} The name
83
+ */
84
+ function assertSessionName(name) {
85
+ if (typeof name !== "string" || !SESSION_NAME_PATTERN.test(name)) {
86
+ throw new LaunchError(
87
+ `Manager name "${String(name)}" must be a slug: lowercase letters, ` +
88
+ `digits, "-" or "_", at most 64 characters.`
89
+ );
90
+ }
91
+ return name;
92
+ }
93
+
94
+ /**
95
+ * Refuse a path that is not absolute.
96
+ * @param {string} label - Field name for the message
97
+ * @param {unknown} value - Path to check
98
+ * @returns {string} The path
99
+ */
100
+ function assertAbsolute(label, value) {
101
+ if (typeof value !== "string" || !path.isAbsolute(value)) {
102
+ throw new LaunchError(`${label} must be an absolute path.`);
103
+ }
104
+ return value;
105
+ }
106
+
107
+ /**
108
+ * tmux argv (without `tmux`) that starts the manager detached.
109
+ *
110
+ * `TELEGRAM_STATE_DIR` goes only into this session's env so no other session
111
+ * ever sees it. `SM_SCRIPTS_DIR` lets every cycle's Bash find these scripts
112
+ * without re-resolving the plugin root.
113
+ * @param {{ name: string, root: string, stateDir: string, scriptsDir: string,
114
+ * interval: string }} input - Session name, project root, resolved state
115
+ * dir, this scripts dir, and the loop interval (e.g. "5m")
116
+ * @returns {string[]} argv for `spawnSync("tmux", argv)`
117
+ */
118
+ export function buildManagerLaunch({
119
+ name,
120
+ root,
121
+ stateDir,
122
+ scriptsDir,
123
+ interval,
124
+ }) {
125
+ assertSessionName(name);
126
+ assertAbsolute("root", root);
127
+ assertAbsolute("stateDir", stateDir);
128
+ assertAbsolute("scriptsDir", scriptsDir);
129
+ if (parseLoopInterval(interval) === null) {
130
+ throw new LaunchError(
131
+ `interval "${String(interval)}" must be a positive duration like 5m.`
132
+ );
133
+ }
134
+ return [
135
+ "new-session",
136
+ "-d",
137
+ "-s",
138
+ name,
139
+ "-c",
140
+ root,
141
+ "-e",
142
+ `TELEGRAM_STATE_DIR=${stateDir}`,
143
+ "-e",
144
+ `SM_SCRIPTS_DIR=${scriptsDir}`,
145
+ "claude",
146
+ "-n",
147
+ name,
148
+ "--dangerously-skip-permissions",
149
+ "--settings",
150
+ JSON.stringify(buildManagerSettings()),
151
+ "--channels",
152
+ `plugin:${TELEGRAM_PLUGIN_ID}`,
153
+ // `--channels` is variadic (claude 2.1.296): without `--` it takes the
154
+ // prompt as a second channel entry and claude exits 1 ("--channels
155
+ // entries must be tagged") before the session starts.
156
+ "--",
157
+ `/loop ${interval} ${CYCLE_COMMAND}`,
158
+ ];
159
+ }
160
+
161
+ /**
162
+ * Quote one argument for a POSIX shell, for display only.
163
+ * @param {string} value - Argument
164
+ * @returns {string} Single-quoted argument
165
+ */
166
+ function shellQuote(value) {
167
+ return `'${value.replaceAll("'", ESCAPED_QUOTE)}'`;
168
+ }
169
+
170
+ /**
171
+ * The shell line an operator runs to start the manager in the foreground,
172
+ * from the tmux argv {@link buildManagerLaunch} returned: same cwd, the same
173
+ * two session variables, the same claude argv. `start` prints it when the
174
+ * tmux session dies at once, because tmux closes the window and claude's own
175
+ * error goes with it. It is display text only and is never executed here.
176
+ * The token is not in the argv, so it is not in this line either.
177
+ * @param {readonly string[]} argv - Result of {@link buildManagerLaunch}
178
+ * @returns {string} One shell line
179
+ */
180
+ export function buildForegroundCommand(argv) {
181
+ const claudeAt = argv.indexOf("claude");
182
+ const head = argv.slice(0, claudeAt);
183
+ const cwd = head[head.indexOf("-c") + 1];
184
+ const vars = head
185
+ .filter((_, index) => head[index - 1] === "-e")
186
+ .map(entry => {
187
+ const at = entry.indexOf("=");
188
+ return `${entry.slice(0, at)}=${shellQuote(entry.slice(at + 1))}`;
189
+ });
190
+ const command = argv
191
+ .slice(claudeAt)
192
+ .map(arg => (SHELL_SAFE.test(arg) ? arg : shellQuote(arg)));
193
+ return `cd ${shellQuote(cwd)} && ${[...vars, ...command].join(" ")}`;
194
+ }
195
+
196
+ /**
197
+ * tmux argv that reports (by exit status) whether the manager is running.
198
+ * `=` asks for an exact name: tmux otherwise falls back to a prefix match, so
199
+ * `demo` would find a running `demo-manager` that belongs to someone else.
200
+ * @param {string} name - Manager session name
201
+ * @returns {string[]} argv for `spawnSync("tmux", argv)`
202
+ */
203
+ export function buildHasSession(name) {
204
+ return ["has-session", "-t", `=${assertSessionName(name)}`];
205
+ }
206
+
207
+ /**
208
+ * tmux argv that prints the directory the manager session was started in.
209
+ * This is a target-pane command, where `=name` alone does not resolve to a
210
+ * session; `=name:` does. tmux 3.6 prints an empty line and exits 0 for a
211
+ * session that does not exist, so callers treat empty output as unknown.
212
+ * @param {string} name - Manager session name
213
+ * @returns {string[]} argv for `spawnSync("tmux", argv)`
214
+ */
215
+ export function buildSessionPath(name) {
216
+ return [
217
+ "display-message",
218
+ "-p",
219
+ "-t",
220
+ `=${assertSessionName(name)}:`,
221
+ "#{session_path}",
222
+ ];
223
+ }
224
+
225
+ /**
226
+ * tmux argv that stops the manager session and nothing else (exact name, see
227
+ * {@link buildHasSession}). Workers are separate background sessions and keep
228
+ * running.
229
+ * @param {string} name - Manager session name
230
+ * @returns {string[]} argv for `spawnSync("tmux", argv)`
231
+ */
232
+ export function buildStopManager(name) {
233
+ return ["kill-session", "-t", `=${assertSessionName(name)}`];
234
+ }
@@ -0,0 +1,445 @@
1
+ /**
2
+ * The session manager's ledger: one JSON file of everything the manager
3
+ * dispatched, plus an append-only log of session events.
4
+ *
5
+ * Attempts are counted here and **never** in tracker comments. A counter kept
6
+ * in append-only comments cannot be reset when an item recovers, so a later
7
+ * cycle re-reads the old count and blocks the item again. The cost of keeping
8
+ * it local is stated plainly: a lost ledger resets attempts to zero.
9
+ *
10
+ * Every write goes through a temp file and a rename, so a crash mid-write
11
+ * leaves the previous ledger intact rather than half a file. A corrupt ledger
12
+ * is refused, not replaced, because replacing it would silently reset every
13
+ * attempt count.
14
+ *
15
+ * File I/O and the clock are injected (`{fs, now}`) so tests can observe the
16
+ * rename and pin timestamps. Nothing here reads `process.env` or a tracker.
17
+ * @module ledger
18
+ */
19
+
20
+ import { randomUUID } from "node:crypto";
21
+ import * as nodeFs from "node:fs/promises";
22
+ import { dirname, join } from "node:path";
23
+
24
+ /** Shape version, bumped only on an incompatible change. */
25
+ const LEDGER_VERSION = 1;
26
+
27
+ /** Upper bound for `control.maxWorkers`, matching the Telegram `cap N` range. */
28
+ const MAX_WORKERS_LIMIT = 32;
29
+
30
+ /** Outcomes a worker can report about itself. */
31
+ const FINISH_STATUSES = new Set(["DONE", "FAILED"]);
32
+
33
+ /**
34
+ * @typedef {object} LedgerSeams
35
+ * @property {Pick<typeof nodeFs, "readFile"|"writeFile"|"rename"|"mkdir"|"appendFile">} [fs] - File system
36
+ * @property {() => Date} [now] - Clock
37
+ */
38
+
39
+ /**
40
+ * The question an item is waiting on, and when it was last re-sent.
41
+ * @typedef {object} PendingQuestion
42
+ * @property {string} text - Question text
43
+ * @property {string} askedAt - When it was first asked (ISO)
44
+ * @property {string} lastPingAt - When it was last (re-)sent (ISO)
45
+ * @property {number|null} telegramMessageId - Telegram message carrying it
46
+ */
47
+
48
+ /**
49
+ * One row of an item's history. `event` names what happened; the remaining
50
+ * fields depend on the event (a spawn carries the worker, a finish its status).
51
+ * @typedef {object} LedgerHistoryRow
52
+ * @property {string} event - spawn | question | answered | finish
53
+ * @property {string} at - When it happened (ISO)
54
+ * @property {string} [worker] - Spawned worker name
55
+ * @property {string|null} [sessionId] - Spawned session id
56
+ * @property {number} [attempt] - Attempt number of a spawn
57
+ * @property {string} [status] - Reported finish status
58
+ * @property {string} [reason] - Reported finish reason
59
+ */
60
+
61
+ /**
62
+ * Everything the manager remembers about one dispatched (or asked-about) ref.
63
+ * @typedef {object} LedgerItem
64
+ * @property {string|null} repo - Repository the worker runs in
65
+ * @property {string|null} worker - Worker name
66
+ * @property {string|null} sessionId - Agent session id
67
+ * @property {string} status - running | question | DONE | FAILED
68
+ * @property {string|null} startedAt - Last spawn time (ISO)
69
+ * @property {string|null} lastProgressAt - Last time the logs hash moved (ISO)
70
+ * @property {string|null} logsHash - Hash of the worker's logs last cycle
71
+ * @property {PendingQuestion|null} pendingQuestion - Open question, if any
72
+ * @property {number} attempts - Spawns so far for this ref
73
+ * @property {LedgerHistoryRow[]} history - Everything that happened, in order
74
+ * @property {string|null} [reason] - Reason given with a finish
75
+ * @property {string|null} [finishedAt] - When the worker finished (ISO)
76
+ */
77
+
78
+ /**
79
+ * The whole ledger file.
80
+ * @typedef {object} Ledger
81
+ * @property {number} version - Shape version
82
+ * @property {Record<string, LedgerItem>} items - Items keyed by ref
83
+ * @property {{paused: boolean, maxWorkers: number|null, me: string|null}} control - Operator controls
84
+ */
85
+
86
+ /**
87
+ * Directory holding all session-manager state.
88
+ * @param {string} projectRoot - Absolute project root
89
+ * @returns {string} State directory
90
+ */
91
+ function stateDir(projectRoot) {
92
+ return join(projectRoot, ".lisa", "session-manager");
93
+ }
94
+
95
+ /**
96
+ * Path of the ledger file.
97
+ * @param {string} projectRoot - Absolute project root
98
+ * @returns {string} Ledger path
99
+ */
100
+ export function ledgerPath(projectRoot) {
101
+ return join(stateDir(projectRoot), "ledger.json");
102
+ }
103
+
104
+ /**
105
+ * Path of the session event log.
106
+ * @param {string} projectRoot - Absolute project root
107
+ * @returns {string} JSONL path
108
+ */
109
+ export function sessionsLogPath(projectRoot) {
110
+ return join(stateDir(projectRoot), "sessions.jsonl");
111
+ }
112
+
113
+ /**
114
+ * A ledger with nothing in it.
115
+ * @returns {Ledger} Empty ledger
116
+ */
117
+ function emptyLedger() {
118
+ return {
119
+ version: LEDGER_VERSION,
120
+ items: {},
121
+ control: { paused: false, maxWorkers: null, me: null },
122
+ };
123
+ }
124
+
125
+ /**
126
+ * Resolve injected seams against the real defaults.
127
+ * @param {LedgerSeams} [seams] - Injected seams
128
+ * @returns {{fs: NonNullable<LedgerSeams["fs"]>, now: () => Date}} Resolved seams
129
+ */
130
+ function resolveSeams(seams = {}) {
131
+ return { fs: seams.fs ?? nodeFs, now: seams.now ?? (() => new Date()) };
132
+ }
133
+
134
+ /**
135
+ * Read the ledger. A missing file is an empty ledger; a corrupt one throws.
136
+ * @param {string} projectRoot - Absolute project root
137
+ * @param {LedgerSeams} [seams] - Injected seams
138
+ * @returns {Promise<Ledger>} Ledger
139
+ */
140
+ export async function readLedger(projectRoot, seams) {
141
+ const { fs } = resolveSeams(seams);
142
+ const file = ledgerPath(projectRoot);
143
+ const text = await fs.readFile(file, "utf8").catch(error => {
144
+ if (error?.code === "ENOENT") return null;
145
+ throw error;
146
+ });
147
+ if (text === null) return emptyLedger();
148
+ const parsed = (() => {
149
+ try {
150
+ return JSON.parse(text);
151
+ } catch (error) {
152
+ throw new Error(
153
+ `session-manager ledger at ${file} is not valid JSON (${error.message}); refusing to reset attempts — repair or remove it`
154
+ );
155
+ }
156
+ })();
157
+ const empty = emptyLedger();
158
+ return {
159
+ version: parsed.version ?? empty.version,
160
+ items: parsed.items ?? {},
161
+ control: { ...empty.control, ...parsed.control },
162
+ };
163
+ }
164
+
165
+ /**
166
+ * Write the whole ledger atomically (temp file, then rename).
167
+ * @param {string} projectRoot - Absolute project root
168
+ * @param {Ledger} ledger - Ledger to persist
169
+ * @param {LedgerSeams} [seams] - Injected seams
170
+ * @returns {Promise<void>}
171
+ */
172
+ export async function writeLedger(projectRoot, ledger, seams) {
173
+ const { fs } = resolveSeams(seams);
174
+ const file = ledgerPath(projectRoot);
175
+ await fs.mkdir(dirname(file), { recursive: true });
176
+ const tempPath = `${file}.${process.pid}.${randomUUID()}.tmp`;
177
+ await fs.writeFile(tempPath, `${JSON.stringify(ledger, null, 2)}\n`, "utf8");
178
+ await fs.rename(tempPath, file);
179
+ }
180
+
181
+ /**
182
+ * Read, transform and write the ledger in one step.
183
+ * @template T
184
+ * @param {string} projectRoot - Absolute project root
185
+ * @param {LedgerSeams | undefined} seams - Injected seams
186
+ * @param {(ledger: Ledger, at: string) => {ledger: Ledger, result: T}} transform - Pure update
187
+ * @returns {Promise<T>} The transform's result
188
+ */
189
+ async function updateLedger(projectRoot, seams, transform) {
190
+ const { now } = resolveSeams(seams);
191
+ const current = await readLedger(projectRoot, seams);
192
+ const { ledger, result } = transform(current, now().toISOString());
193
+ await writeLedger(projectRoot, ledger, seams);
194
+ return result;
195
+ }
196
+
197
+ /**
198
+ * Replace one item in a ledger, returning the new ledger and the new item.
199
+ * @param {Ledger} ledger - Current ledger
200
+ * @param {string} ref - Item key
201
+ * @param {LedgerItem} entry - New item
202
+ * @returns {{ledger: Ledger, result: LedgerItem}} Update
203
+ */
204
+ function withItem(ledger, ref, entry) {
205
+ return {
206
+ ledger: { ...ledger, items: { ...ledger.items, [ref]: entry } },
207
+ result: entry,
208
+ };
209
+ }
210
+
211
+ /**
212
+ * Look up an item that must already exist.
213
+ * @param {Ledger} ledger - Current ledger
214
+ * @param {string} ref - Item key
215
+ * @returns {LedgerItem} The item
216
+ */
217
+ function requireItem(ledger, ref) {
218
+ const entry = ledger.items[ref];
219
+ if (!entry)
220
+ throw new Error(
221
+ `session-manager ledger has no item ${JSON.stringify(ref)}`
222
+ );
223
+ return entry;
224
+ }
225
+
226
+ /**
227
+ * A ledger item for a ref the manager has asked about but not dispatched.
228
+ * @returns {LedgerItem} Placeholder item
229
+ */
230
+ function placeholderItem() {
231
+ return {
232
+ repo: null,
233
+ worker: null,
234
+ sessionId: null,
235
+ status: "question",
236
+ startedAt: null,
237
+ lastProgressAt: null,
238
+ logsHash: null,
239
+ pendingQuestion: null,
240
+ attempts: 0,
241
+ history: [],
242
+ };
243
+ }
244
+
245
+ /**
246
+ * Record a worker spawn. Each spawn for the same ref is one more attempt.
247
+ * @param {string} projectRoot - Absolute project root
248
+ * @param {{ref: string, worker: string, sessionId: string|null, repo: string}} spawn - What was spawned
249
+ * @param {LedgerSeams} [seams] - Injected seams
250
+ * @returns {Promise<LedgerItem>} The updated item
251
+ */
252
+ export function recordSpawn(
253
+ projectRoot,
254
+ { ref, worker, sessionId, repo },
255
+ seams
256
+ ) {
257
+ return updateLedger(projectRoot, seams, (ledger, at) => {
258
+ const previous = ledger.items[ref] ?? placeholderItem();
259
+ const attempts = (previous.attempts ?? 0) + 1;
260
+ return withItem(ledger, ref, {
261
+ ...previous,
262
+ repo,
263
+ worker,
264
+ sessionId,
265
+ status: "running",
266
+ startedAt: at,
267
+ lastProgressAt: at,
268
+ logsHash: null,
269
+ pendingQuestion: null,
270
+ reason: null,
271
+ finishedAt: null,
272
+ attempts,
273
+ history: [
274
+ ...(previous.history ?? []),
275
+ { event: "spawn", at, worker, sessionId, attempt: attempts },
276
+ ],
277
+ });
278
+ });
279
+ }
280
+
281
+ /**
282
+ * Record (or re-ping) the question an item is waiting on.
283
+ *
284
+ * The same text again is a re-ping: `askedAt` is kept and `lastPingAt` moves,
285
+ * so the requestion clock restarts without forgetting how old the question
286
+ * is. A ref with no item yet (a repo-routing question asked before dispatch)
287
+ * gets a placeholder item with zero attempts.
288
+ * @param {string} projectRoot - Absolute project root
289
+ * @param {{ref: string, text: string, telegramMessageId: number|null}} question - Question
290
+ * @param {LedgerSeams} [seams] - Injected seams
291
+ * @returns {Promise<LedgerItem>} The updated item
292
+ */
293
+ export function setPendingQuestion(
294
+ projectRoot,
295
+ { ref, text, telegramMessageId },
296
+ seams
297
+ ) {
298
+ return updateLedger(projectRoot, seams, (ledger, at) => {
299
+ const previous = ledger.items[ref] ?? placeholderItem();
300
+ const existing = previous.pendingQuestion;
301
+ const askedAt = existing?.text === text ? existing.askedAt : at;
302
+ return withItem(ledger, ref, {
303
+ ...previous,
304
+ pendingQuestion: {
305
+ text,
306
+ askedAt,
307
+ lastPingAt: at,
308
+ telegramMessageId: telegramMessageId ?? null,
309
+ },
310
+ history: [...(previous.history ?? []), { event: "question", at }],
311
+ });
312
+ });
313
+ }
314
+
315
+ /**
316
+ * Clear an item's pending question, once its answer has been delivered.
317
+ * @param {string} projectRoot - Absolute project root
318
+ * @param {{ref: string}} target - Item
319
+ * @param {LedgerSeams} [seams] - Injected seams
320
+ * @returns {Promise<LedgerItem>} The updated item
321
+ */
322
+ export function clearPendingQuestion(projectRoot, { ref }, seams) {
323
+ return updateLedger(projectRoot, seams, (ledger, at) => {
324
+ const previous = requireItem(ledger, ref);
325
+ return withItem(ledger, ref, {
326
+ ...previous,
327
+ pendingQuestion: null,
328
+ history: [...(previous.history ?? []), { event: "answered", at }],
329
+ });
330
+ });
331
+ }
332
+
333
+ /**
334
+ * Record this cycle's hash of the worker's logs. `lastProgressAt` moves only
335
+ * when the hash changed, which is what "no output for N minutes" measures.
336
+ * @param {string} projectRoot - Absolute project root
337
+ * @param {{ref: string, logsHash: string}} progress - Observed hash
338
+ * @param {LedgerSeams} [seams] - Injected seams
339
+ * @returns {Promise<{changed: boolean, entry: LedgerItem}>} Whether it moved, and the item
340
+ */
341
+ export function recordProgress(projectRoot, { ref, logsHash }, seams) {
342
+ return updateLedger(projectRoot, seams, (ledger, at) => {
343
+ const previous = requireItem(ledger, ref);
344
+ if (previous.logsHash === logsHash) {
345
+ return { ledger, result: { changed: false, entry: previous } };
346
+ }
347
+ const { ledger: next, result: entry } = withItem(ledger, ref, {
348
+ ...previous,
349
+ logsHash,
350
+ lastProgressAt: at,
351
+ });
352
+ return { ledger: next, result: { changed: true, entry } };
353
+ });
354
+ }
355
+
356
+ /**
357
+ * Record the worker's own terminal report.
358
+ * @param {string} projectRoot - Absolute project root
359
+ * @param {{ref: string, status: "DONE"|"FAILED", reason: string}} finish - Report
360
+ * @param {LedgerSeams} [seams] - Injected seams
361
+ * @returns {Promise<LedgerItem>} The updated item
362
+ */
363
+ export function recordFinish(projectRoot, { ref, status, reason }, seams) {
364
+ if (!FINISH_STATUSES.has(status)) {
365
+ return Promise.reject(
366
+ new Error(
367
+ `finish status must be DONE or FAILED, got ${JSON.stringify(status)}`
368
+ )
369
+ );
370
+ }
371
+ return updateLedger(projectRoot, seams, (ledger, at) => {
372
+ const previous = requireItem(ledger, ref);
373
+ return withItem(ledger, ref, {
374
+ ...previous,
375
+ status,
376
+ reason,
377
+ finishedAt: at,
378
+ pendingQuestion: null,
379
+ history: [
380
+ ...(previous.history ?? []),
381
+ { event: "finish", at, status, reason },
382
+ ],
383
+ });
384
+ });
385
+ }
386
+
387
+ /**
388
+ * Check a control patch before it is written.
389
+ * @param {{paused?: unknown, maxWorkers?: unknown, me?: unknown}} patch - Patch
390
+ */
391
+ function assertControlPatch(patch) {
392
+ if ("paused" in patch && typeof patch.paused !== "boolean") {
393
+ throw new Error("control.paused must be a boolean");
394
+ }
395
+ if (
396
+ "maxWorkers" in patch &&
397
+ patch.maxWorkers !== null &&
398
+ !(
399
+ Number.isInteger(patch.maxWorkers) &&
400
+ patch.maxWorkers >= 0 &&
401
+ patch.maxWorkers <= MAX_WORKERS_LIMIT
402
+ )
403
+ ) {
404
+ throw new Error(
405
+ `control.maxWorkers must be null or an integer 0..${MAX_WORKERS_LIMIT}`
406
+ );
407
+ }
408
+ if ("me" in patch && patch.me !== null && typeof patch.me !== "string") {
409
+ throw new Error("control.me must be a string or null");
410
+ }
411
+ }
412
+
413
+ /**
414
+ * Merge operator controls into the ledger: `paused`, the `maxWorkers`
415
+ * override (null clears it) and `me`, the resolved tracker identity.
416
+ * @param {string} projectRoot - Absolute project root
417
+ * @param {{paused?: boolean, maxWorkers?: number|null, me?: string|null}} patch - Fields to set
418
+ * @param {LedgerSeams} [seams] - Injected seams
419
+ * @returns {Promise<{paused: boolean, maxWorkers: number|null, me: string|null}>} New control block
420
+ */
421
+ export async function setControl(projectRoot, patch, seams) {
422
+ assertControlPatch(patch);
423
+ return updateLedger(projectRoot, seams, ledger => {
424
+ const control = { ...ledger.control, ...patch };
425
+ return { ledger: { ...ledger, control }, result: control };
426
+ });
427
+ }
428
+
429
+ /**
430
+ * Append one event to `sessions.jsonl`, stamped with the time.
431
+ * @param {string} projectRoot - Absolute project root
432
+ * @param {Record<string, unknown>} event - Event fields
433
+ * @param {LedgerSeams} [seams] - Injected seams
434
+ * @returns {Promise<void>}
435
+ */
436
+ export async function appendSessionEvent(projectRoot, event, seams) {
437
+ const { fs, now } = resolveSeams(seams);
438
+ const file = sessionsLogPath(projectRoot);
439
+ await fs.mkdir(dirname(file), { recursive: true });
440
+ await fs.appendFile(
441
+ file,
442
+ `${JSON.stringify({ at: now().toISOString(), ...event })}\n`,
443
+ "utf8"
444
+ );
445
+ }