@codyswann/lisa 4.80.2 → 4.81.1

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 (334) hide show
  1. package/all/copy-contents/gitignore +5 -0
  2. package/dist/cli/doctor-session-manager-trust.d.ts +62 -0
  3. package/dist/cli/doctor-session-manager-trust.d.ts.map +1 -0
  4. package/dist/cli/doctor-session-manager-trust.js +174 -0
  5. package/dist/cli/doctor-session-manager-trust.js.map +1 -0
  6. package/dist/cli/doctor-session-manager.d.ts +35 -0
  7. package/dist/cli/doctor-session-manager.d.ts.map +1 -0
  8. package/dist/cli/doctor-session-manager.js +209 -0
  9. package/dist/cli/doctor-session-manager.js.map +1 -0
  10. package/dist/cli/doctor.d.ts.map +1 -1
  11. package/dist/cli/doctor.js +3 -0
  12. package/dist/cli/doctor.js.map +1 -1
  13. package/dist/cli/lisa-test-run-bootstrap.js +28 -0
  14. package/dist/cli/lisa-test-run-bootstrap.js.map +1 -1
  15. package/dist/cli/session-manager-config-merge.d.ts +25 -0
  16. package/dist/cli/session-manager-config-merge.d.ts.map +1 -0
  17. package/dist/cli/session-manager-config-merge.js +54 -0
  18. package/dist/cli/session-manager-config-merge.js.map +1 -0
  19. package/dist/core/nightly-e2e-guard-behavior-certificate.js +2 -2
  20. package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
  21. package/dist/core/upstream-evidence-manifest.js +258 -12
  22. package/dist/core/upstream-evidence-manifest.js.map +1 -1
  23. package/dist/migrations/deploy-outcome-guard-edit.d.ts.map +1 -1
  24. package/dist/migrations/deploy-outcome-guard-edit.js +35 -14
  25. package/dist/migrations/deploy-outcome-guard-edit.js.map +1 -1
  26. package/dist/migrations/deploy-outcome-guard-yaml.d.ts +30 -1
  27. package/dist/migrations/deploy-outcome-guard-yaml.d.ts.map +1 -1
  28. package/dist/migrations/deploy-outcome-guard-yaml.js +88 -7
  29. package/dist/migrations/deploy-outcome-guard-yaml.js.map +1 -1
  30. package/package.json +4 -4
  31. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  32. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  33. package/plugins/lisa/.codex-plugin/skills/lisa-automation-status/SKILL.md +10 -0
  34. package/plugins/lisa/.codex-plugin/skills/lisa-implement/SKILL.md +17 -2
  35. package/plugins/lisa/.codex-plugin/skills/lisa-secrets-access/scripts/validate-config.mjs +377 -5
  36. package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/SKILL.md +287 -0
  37. package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/agents/openai.yaml +4 -0
  38. package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/references/runbook-template.md +66 -0
  39. package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/scripts/capacity.mjs +186 -0
  40. package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/scripts/cli-cycle.mjs +262 -0
  41. package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/scripts/cli-manager.mjs +308 -0
  42. package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/scripts/cli-queue.mjs +148 -0
  43. package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/scripts/cli-runtime.mjs +326 -0
  44. package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/scripts/cli-usage.mjs +114 -0
  45. package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/scripts/cli-workers.mjs +295 -0
  46. package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/scripts/config.mjs +265 -0
  47. package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/scripts/isolation.mjs +130 -0
  48. package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/scripts/launch.mjs +234 -0
  49. package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/scripts/ledger.mjs +445 -0
  50. package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/scripts/reconcile.mjs +134 -0
  51. package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/scripts/session-manager.mjs +274 -0
  52. package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/scripts/state-dir.mjs +112 -0
  53. package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/scripts/telegram-route.mjs +210 -0
  54. package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/scripts/token.mjs +191 -0
  55. package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/scripts/worker-launch.mjs +92 -0
  56. package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/scripts/workers.mjs +406 -0
  57. package/plugins/lisa/.codex-plugin/skills/lisa-setup-session-manager/SKILL.md +158 -0
  58. package/plugins/lisa/.codex-plugin/skills/lisa-setup-session-manager/agents/openai.yaml +4 -0
  59. package/plugins/lisa/.codex-plugin/skills/lisa-tear-down-automations/SKILL.md +10 -0
  60. package/plugins/lisa/commands/implement.md +2 -2
  61. package/plugins/lisa/commands/session-manager.md +7 -0
  62. package/plugins/lisa/commands/setup/session-manager.md +6 -0
  63. package/plugins/lisa/rules/reference/automation-runbook-contract.md +14 -0
  64. package/plugins/lisa/rules/reference/config-resolution.md +58 -0
  65. package/plugins/lisa/scripts/automation-status-claude-adapter.mjs +4 -1
  66. package/plugins/lisa/scripts/automation-status-codex-adapter.mjs +4 -1
  67. package/plugins/lisa/scripts/automation-status-expected-fleet.mjs +98 -0
  68. package/plugins/lisa/scripts/automation-status-run-history.mjs +29 -7
  69. package/plugins/lisa/scripts/ready-queue-order.mjs +352 -0
  70. package/plugins/lisa/skills/lisa-automation-status/SKILL.md +10 -0
  71. package/plugins/lisa/skills/lisa-implement/SKILL.md +17 -2
  72. package/plugins/lisa/skills/lisa-secrets-access/scripts/validate-config.mjs +377 -5
  73. package/plugins/lisa/skills/lisa-session-manager/SKILL.md +285 -0
  74. package/plugins/lisa/skills/lisa-session-manager/agents/openai.yaml +4 -0
  75. package/plugins/lisa/skills/lisa-session-manager/references/runbook-template.md +66 -0
  76. package/plugins/lisa/skills/lisa-session-manager/scripts/capacity.mjs +186 -0
  77. package/plugins/lisa/skills/lisa-session-manager/scripts/cli-cycle.mjs +262 -0
  78. package/plugins/lisa/skills/lisa-session-manager/scripts/cli-manager.mjs +308 -0
  79. package/plugins/lisa/skills/lisa-session-manager/scripts/cli-queue.mjs +148 -0
  80. package/plugins/lisa/skills/lisa-session-manager/scripts/cli-runtime.mjs +326 -0
  81. package/plugins/lisa/skills/lisa-session-manager/scripts/cli-usage.mjs +114 -0
  82. package/plugins/lisa/skills/lisa-session-manager/scripts/cli-workers.mjs +295 -0
  83. package/plugins/lisa/skills/lisa-session-manager/scripts/config.mjs +265 -0
  84. package/plugins/lisa/skills/lisa-session-manager/scripts/isolation.mjs +130 -0
  85. package/plugins/lisa/skills/lisa-session-manager/scripts/launch.mjs +234 -0
  86. package/plugins/lisa/skills/lisa-session-manager/scripts/ledger.mjs +445 -0
  87. package/plugins/lisa/skills/lisa-session-manager/scripts/reconcile.mjs +134 -0
  88. package/plugins/lisa/skills/lisa-session-manager/scripts/session-manager.mjs +274 -0
  89. package/plugins/lisa/skills/lisa-session-manager/scripts/state-dir.mjs +112 -0
  90. package/plugins/lisa/skills/lisa-session-manager/scripts/telegram-route.mjs +210 -0
  91. package/plugins/lisa/skills/lisa-session-manager/scripts/token.mjs +191 -0
  92. package/plugins/lisa/skills/lisa-session-manager/scripts/worker-launch.mjs +92 -0
  93. package/plugins/lisa/skills/lisa-session-manager/scripts/workers.mjs +406 -0
  94. package/plugins/lisa/skills/lisa-setup-session-manager/SKILL.md +156 -0
  95. package/plugins/lisa/skills/lisa-setup-session-manager/agents/openai.yaml +4 -0
  96. package/plugins/lisa/skills/lisa-tear-down-automations/SKILL.md +10 -0
  97. package/plugins/lisa-agy/commands/lisa/implement.md +2 -2
  98. package/plugins/lisa-agy/commands/lisa/session-manager.md +7 -0
  99. package/plugins/lisa-agy/commands/lisa/setup/session-manager.md +6 -0
  100. package/plugins/lisa-agy/plugin.json +1 -1
  101. package/plugins/lisa-agy/scripts/automation-status-claude-adapter.mjs +4 -1
  102. package/plugins/lisa-agy/scripts/automation-status-codex-adapter.mjs +4 -1
  103. package/plugins/lisa-agy/scripts/automation-status-expected-fleet.mjs +98 -0
  104. package/plugins/lisa-agy/scripts/automation-status-run-history.mjs +29 -7
  105. package/plugins/lisa-agy/scripts/ready-queue-order.mjs +352 -0
  106. package/plugins/lisa-agy/skills/lisa-automation-status/SKILL.md +10 -0
  107. package/plugins/lisa-agy/skills/lisa-implement/SKILL.md +17 -2
  108. package/plugins/lisa-agy/skills/lisa-secrets-access/scripts/validate-config.mjs +377 -5
  109. package/plugins/lisa-agy/skills/lisa-session-manager/SKILL.md +285 -0
  110. package/plugins/lisa-agy/skills/lisa-session-manager/references/runbook-template.md +66 -0
  111. package/plugins/lisa-agy/skills/lisa-session-manager/scripts/capacity.mjs +186 -0
  112. package/plugins/lisa-agy/skills/lisa-session-manager/scripts/cli-cycle.mjs +262 -0
  113. package/plugins/lisa-agy/skills/lisa-session-manager/scripts/cli-manager.mjs +308 -0
  114. package/plugins/lisa-agy/skills/lisa-session-manager/scripts/cli-queue.mjs +148 -0
  115. package/plugins/lisa-agy/skills/lisa-session-manager/scripts/cli-runtime.mjs +326 -0
  116. package/plugins/lisa-agy/skills/lisa-session-manager/scripts/cli-usage.mjs +114 -0
  117. package/plugins/lisa-agy/skills/lisa-session-manager/scripts/cli-workers.mjs +295 -0
  118. package/plugins/lisa-agy/skills/lisa-session-manager/scripts/config.mjs +265 -0
  119. package/plugins/lisa-agy/skills/lisa-session-manager/scripts/isolation.mjs +130 -0
  120. package/plugins/lisa-agy/skills/lisa-session-manager/scripts/launch.mjs +234 -0
  121. package/plugins/lisa-agy/skills/lisa-session-manager/scripts/ledger.mjs +445 -0
  122. package/plugins/lisa-agy/skills/lisa-session-manager/scripts/reconcile.mjs +134 -0
  123. package/plugins/lisa-agy/skills/lisa-session-manager/scripts/session-manager.mjs +274 -0
  124. package/plugins/lisa-agy/skills/lisa-session-manager/scripts/state-dir.mjs +112 -0
  125. package/plugins/lisa-agy/skills/lisa-session-manager/scripts/telegram-route.mjs +210 -0
  126. package/plugins/lisa-agy/skills/lisa-session-manager/scripts/token.mjs +191 -0
  127. package/plugins/lisa-agy/skills/lisa-session-manager/scripts/worker-launch.mjs +92 -0
  128. package/plugins/lisa-agy/skills/lisa-session-manager/scripts/workers.mjs +406 -0
  129. package/plugins/lisa-agy/skills/lisa-setup-session-manager/SKILL.md +156 -0
  130. package/plugins/lisa-agy/skills/lisa-tear-down-automations/SKILL.md +10 -0
  131. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  132. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  133. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  134. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  135. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  136. package/plugins/lisa-cdk-devin/.claude-plugin/plugin.json +1 -1
  137. package/plugins/lisa-cdk-devin/.devin-plugin/plugin.json +1 -1
  138. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  139. package/plugins/lisa-copilot/commands/lisa/implement.md +2 -2
  140. package/plugins/lisa-copilot/commands/lisa/session-manager.md +7 -0
  141. package/plugins/lisa-copilot/commands/lisa/setup/session-manager.md +6 -0
  142. package/plugins/lisa-copilot/rules/reference/automation-runbook-contract.md +14 -0
  143. package/plugins/lisa-copilot/rules/reference/config-resolution.md +58 -0
  144. package/plugins/lisa-copilot/scripts/automation-status-claude-adapter.mjs +4 -1
  145. package/plugins/lisa-copilot/scripts/automation-status-codex-adapter.mjs +4 -1
  146. package/plugins/lisa-copilot/scripts/automation-status-expected-fleet.mjs +98 -0
  147. package/plugins/lisa-copilot/scripts/automation-status-run-history.mjs +29 -7
  148. package/plugins/lisa-copilot/scripts/ready-queue-order.mjs +352 -0
  149. package/plugins/lisa-copilot/skills/lisa-automation-status/SKILL.md +10 -0
  150. package/plugins/lisa-copilot/skills/lisa-implement/SKILL.md +17 -2
  151. package/plugins/lisa-copilot/skills/lisa-secrets-access/scripts/validate-config.mjs +377 -5
  152. package/plugins/lisa-copilot/skills/lisa-session-manager/SKILL.md +285 -0
  153. package/plugins/lisa-copilot/skills/lisa-session-manager/references/runbook-template.md +66 -0
  154. package/plugins/lisa-copilot/skills/lisa-session-manager/scripts/capacity.mjs +186 -0
  155. package/plugins/lisa-copilot/skills/lisa-session-manager/scripts/cli-cycle.mjs +262 -0
  156. package/plugins/lisa-copilot/skills/lisa-session-manager/scripts/cli-manager.mjs +308 -0
  157. package/plugins/lisa-copilot/skills/lisa-session-manager/scripts/cli-queue.mjs +148 -0
  158. package/plugins/lisa-copilot/skills/lisa-session-manager/scripts/cli-runtime.mjs +326 -0
  159. package/plugins/lisa-copilot/skills/lisa-session-manager/scripts/cli-usage.mjs +114 -0
  160. package/plugins/lisa-copilot/skills/lisa-session-manager/scripts/cli-workers.mjs +295 -0
  161. package/plugins/lisa-copilot/skills/lisa-session-manager/scripts/config.mjs +265 -0
  162. package/plugins/lisa-copilot/skills/lisa-session-manager/scripts/isolation.mjs +130 -0
  163. package/plugins/lisa-copilot/skills/lisa-session-manager/scripts/launch.mjs +234 -0
  164. package/plugins/lisa-copilot/skills/lisa-session-manager/scripts/ledger.mjs +445 -0
  165. package/plugins/lisa-copilot/skills/lisa-session-manager/scripts/reconcile.mjs +134 -0
  166. package/plugins/lisa-copilot/skills/lisa-session-manager/scripts/session-manager.mjs +274 -0
  167. package/plugins/lisa-copilot/skills/lisa-session-manager/scripts/state-dir.mjs +112 -0
  168. package/plugins/lisa-copilot/skills/lisa-session-manager/scripts/telegram-route.mjs +210 -0
  169. package/plugins/lisa-copilot/skills/lisa-session-manager/scripts/token.mjs +191 -0
  170. package/plugins/lisa-copilot/skills/lisa-session-manager/scripts/worker-launch.mjs +92 -0
  171. package/plugins/lisa-copilot/skills/lisa-session-manager/scripts/workers.mjs +406 -0
  172. package/plugins/lisa-copilot/skills/lisa-setup-session-manager/SKILL.md +156 -0
  173. package/plugins/lisa-copilot/skills/lisa-tear-down-automations/SKILL.md +10 -0
  174. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  175. package/plugins/lisa-cursor/commands/lisa/implement.md +2 -2
  176. package/plugins/lisa-cursor/commands/lisa/session-manager.md +7 -0
  177. package/plugins/lisa-cursor/commands/lisa/setup/session-manager.md +6 -0
  178. package/plugins/lisa-cursor/rules/automation-runbook-contract-reference.mdc +14 -0
  179. package/plugins/lisa-cursor/rules/config-resolution-reference.mdc +58 -0
  180. package/plugins/lisa-cursor/scripts/automation-status-claude-adapter.mjs +4 -1
  181. package/plugins/lisa-cursor/scripts/automation-status-codex-adapter.mjs +4 -1
  182. package/plugins/lisa-cursor/scripts/automation-status-expected-fleet.mjs +98 -0
  183. package/plugins/lisa-cursor/scripts/automation-status-run-history.mjs +29 -7
  184. package/plugins/lisa-cursor/scripts/ready-queue-order.mjs +352 -0
  185. package/plugins/lisa-cursor/skills/lisa-automation-status/SKILL.md +10 -0
  186. package/plugins/lisa-cursor/skills/lisa-implement/SKILL.md +17 -2
  187. package/plugins/lisa-cursor/skills/lisa-secrets-access/scripts/validate-config.mjs +377 -5
  188. package/plugins/lisa-cursor/skills/lisa-session-manager/SKILL.md +285 -0
  189. package/plugins/lisa-cursor/skills/lisa-session-manager/references/runbook-template.md +66 -0
  190. package/plugins/lisa-cursor/skills/lisa-session-manager/scripts/capacity.mjs +186 -0
  191. package/plugins/lisa-cursor/skills/lisa-session-manager/scripts/cli-cycle.mjs +262 -0
  192. package/plugins/lisa-cursor/skills/lisa-session-manager/scripts/cli-manager.mjs +308 -0
  193. package/plugins/lisa-cursor/skills/lisa-session-manager/scripts/cli-queue.mjs +148 -0
  194. package/plugins/lisa-cursor/skills/lisa-session-manager/scripts/cli-runtime.mjs +326 -0
  195. package/plugins/lisa-cursor/skills/lisa-session-manager/scripts/cli-usage.mjs +114 -0
  196. package/plugins/lisa-cursor/skills/lisa-session-manager/scripts/cli-workers.mjs +295 -0
  197. package/plugins/lisa-cursor/skills/lisa-session-manager/scripts/config.mjs +265 -0
  198. package/plugins/lisa-cursor/skills/lisa-session-manager/scripts/isolation.mjs +130 -0
  199. package/plugins/lisa-cursor/skills/lisa-session-manager/scripts/launch.mjs +234 -0
  200. package/plugins/lisa-cursor/skills/lisa-session-manager/scripts/ledger.mjs +445 -0
  201. package/plugins/lisa-cursor/skills/lisa-session-manager/scripts/reconcile.mjs +134 -0
  202. package/plugins/lisa-cursor/skills/lisa-session-manager/scripts/session-manager.mjs +274 -0
  203. package/plugins/lisa-cursor/skills/lisa-session-manager/scripts/state-dir.mjs +112 -0
  204. package/plugins/lisa-cursor/skills/lisa-session-manager/scripts/telegram-route.mjs +210 -0
  205. package/plugins/lisa-cursor/skills/lisa-session-manager/scripts/token.mjs +191 -0
  206. package/plugins/lisa-cursor/skills/lisa-session-manager/scripts/worker-launch.mjs +92 -0
  207. package/plugins/lisa-cursor/skills/lisa-session-manager/scripts/workers.mjs +406 -0
  208. package/plugins/lisa-cursor/skills/lisa-setup-session-manager/SKILL.md +156 -0
  209. package/plugins/lisa-cursor/skills/lisa-tear-down-automations/SKILL.md +10 -0
  210. package/plugins/lisa-devin/.claude-plugin/plugin.json +1 -1
  211. package/plugins/lisa-devin/.devin-plugin/plugin.json +1 -1
  212. package/plugins/lisa-devin/rules/reference/automation-runbook-contract.md +14 -0
  213. package/plugins/lisa-devin/rules/reference/config-resolution.md +58 -0
  214. package/plugins/lisa-devin/scripts/automation-status-claude-adapter.mjs +4 -1
  215. package/plugins/lisa-devin/scripts/automation-status-codex-adapter.mjs +4 -1
  216. package/plugins/lisa-devin/scripts/automation-status-expected-fleet.mjs +98 -0
  217. package/plugins/lisa-devin/scripts/automation-status-run-history.mjs +29 -7
  218. package/plugins/lisa-devin/scripts/ready-queue-order.mjs +352 -0
  219. package/plugins/lisa-devin/skills/lisa-automation-status/SKILL.md +10 -0
  220. package/plugins/lisa-devin/skills/lisa-implement/SKILL.md +17 -2
  221. package/plugins/lisa-devin/skills/lisa-secrets-access/scripts/validate-config.mjs +377 -5
  222. package/plugins/lisa-devin/skills/lisa-session-manager/SKILL.md +285 -0
  223. package/plugins/lisa-devin/skills/lisa-session-manager/references/runbook-template.md +66 -0
  224. package/plugins/lisa-devin/skills/lisa-session-manager/scripts/capacity.mjs +186 -0
  225. package/plugins/lisa-devin/skills/lisa-session-manager/scripts/cli-cycle.mjs +262 -0
  226. package/plugins/lisa-devin/skills/lisa-session-manager/scripts/cli-manager.mjs +308 -0
  227. package/plugins/lisa-devin/skills/lisa-session-manager/scripts/cli-queue.mjs +148 -0
  228. package/plugins/lisa-devin/skills/lisa-session-manager/scripts/cli-runtime.mjs +326 -0
  229. package/plugins/lisa-devin/skills/lisa-session-manager/scripts/cli-usage.mjs +114 -0
  230. package/plugins/lisa-devin/skills/lisa-session-manager/scripts/cli-workers.mjs +295 -0
  231. package/plugins/lisa-devin/skills/lisa-session-manager/scripts/config.mjs +265 -0
  232. package/plugins/lisa-devin/skills/lisa-session-manager/scripts/isolation.mjs +130 -0
  233. package/plugins/lisa-devin/skills/lisa-session-manager/scripts/launch.mjs +234 -0
  234. package/plugins/lisa-devin/skills/lisa-session-manager/scripts/ledger.mjs +445 -0
  235. package/plugins/lisa-devin/skills/lisa-session-manager/scripts/reconcile.mjs +134 -0
  236. package/plugins/lisa-devin/skills/lisa-session-manager/scripts/session-manager.mjs +274 -0
  237. package/plugins/lisa-devin/skills/lisa-session-manager/scripts/state-dir.mjs +112 -0
  238. package/plugins/lisa-devin/skills/lisa-session-manager/scripts/telegram-route.mjs +210 -0
  239. package/plugins/lisa-devin/skills/lisa-session-manager/scripts/token.mjs +191 -0
  240. package/plugins/lisa-devin/skills/lisa-session-manager/scripts/worker-launch.mjs +92 -0
  241. package/plugins/lisa-devin/skills/lisa-session-manager/scripts/workers.mjs +406 -0
  242. package/plugins/lisa-devin/skills/lisa-setup-session-manager/SKILL.md +156 -0
  243. package/plugins/lisa-devin/skills/lisa-tear-down-automations/SKILL.md +10 -0
  244. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  245. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  246. package/plugins/lisa-expo-agy/plugin.json +1 -1
  247. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  248. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  249. package/plugins/lisa-expo-devin/.claude-plugin/plugin.json +1 -1
  250. package/plugins/lisa-expo-devin/.devin-plugin/plugin.json +1 -1
  251. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  252. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  253. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  254. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  255. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  256. package/plugins/lisa-harper-fabric-devin/.claude-plugin/plugin.json +1 -1
  257. package/plugins/lisa-harper-fabric-devin/.devin-plugin/plugin.json +1 -1
  258. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  259. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  260. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  261. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  262. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  263. package/plugins/lisa-nestjs-devin/.claude-plugin/plugin.json +1 -1
  264. package/plugins/lisa-nestjs-devin/.devin-plugin/plugin.json +1 -1
  265. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  266. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  267. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  268. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  269. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  270. package/plugins/lisa-openclaw-devin/.claude-plugin/plugin.json +1 -1
  271. package/plugins/lisa-openclaw-devin/.devin-plugin/plugin.json +1 -1
  272. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  273. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  274. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  275. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  276. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  277. package/plugins/lisa-phaser-devin/.claude-plugin/plugin.json +1 -1
  278. package/plugins/lisa-phaser-devin/.devin-plugin/plugin.json +1 -1
  279. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  280. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  281. package/plugins/lisa-rails-agy/plugin.json +1 -1
  282. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  283. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  284. package/plugins/lisa-rails-devin/.claude-plugin/plugin.json +1 -1
  285. package/plugins/lisa-rails-devin/.devin-plugin/plugin.json +1 -1
  286. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  287. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  288. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  289. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  290. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  291. package/plugins/lisa-typescript-devin/.claude-plugin/plugin.json +1 -1
  292. package/plugins/lisa-typescript-devin/.devin-plugin/plugin.json +1 -1
  293. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  294. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  295. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  296. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  297. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  298. package/plugins/lisa-wiki-devin/.claude-plugin/plugin.json +1 -1
  299. package/plugins/lisa-wiki-devin/.devin-plugin/plugin.json +1 -1
  300. package/plugins/src/base/commands/implement.md +2 -2
  301. package/plugins/src/base/commands/session-manager.md +7 -0
  302. package/plugins/src/base/commands/setup/session-manager.md +6 -0
  303. package/plugins/src/base/rules/reference/automation-runbook-contract.md +14 -0
  304. package/plugins/src/base/rules/reference/config-resolution.md +58 -0
  305. package/plugins/src/base/scripts/automation-status-claude-adapter.mjs +4 -1
  306. package/plugins/src/base/scripts/automation-status-codex-adapter.mjs +4 -1
  307. package/plugins/src/base/scripts/automation-status-expected-fleet.mjs +98 -0
  308. package/plugins/src/base/scripts/automation-status-run-history.mjs +29 -7
  309. package/plugins/src/base/scripts/ready-queue-order.mjs +352 -0
  310. package/plugins/src/base/skills/lisa-automation-status/SKILL.md +10 -0
  311. package/plugins/src/base/skills/lisa-implement/SKILL.md +17 -2
  312. package/plugins/src/base/skills/lisa-secrets-access/scripts/validate-config.mjs +377 -5
  313. package/plugins/src/base/skills/lisa-session-manager/SKILL.md +285 -0
  314. package/plugins/src/base/skills/lisa-session-manager/references/runbook-template.md +66 -0
  315. package/plugins/src/base/skills/lisa-session-manager/scripts/capacity.mjs +186 -0
  316. package/plugins/src/base/skills/lisa-session-manager/scripts/cli-cycle.mjs +262 -0
  317. package/plugins/src/base/skills/lisa-session-manager/scripts/cli-manager.mjs +308 -0
  318. package/plugins/src/base/skills/lisa-session-manager/scripts/cli-queue.mjs +148 -0
  319. package/plugins/src/base/skills/lisa-session-manager/scripts/cli-runtime.mjs +326 -0
  320. package/plugins/src/base/skills/lisa-session-manager/scripts/cli-usage.mjs +114 -0
  321. package/plugins/src/base/skills/lisa-session-manager/scripts/cli-workers.mjs +295 -0
  322. package/plugins/src/base/skills/lisa-session-manager/scripts/config.mjs +265 -0
  323. package/plugins/src/base/skills/lisa-session-manager/scripts/isolation.mjs +130 -0
  324. package/plugins/src/base/skills/lisa-session-manager/scripts/launch.mjs +234 -0
  325. package/plugins/src/base/skills/lisa-session-manager/scripts/ledger.mjs +445 -0
  326. package/plugins/src/base/skills/lisa-session-manager/scripts/reconcile.mjs +134 -0
  327. package/plugins/src/base/skills/lisa-session-manager/scripts/session-manager.mjs +274 -0
  328. package/plugins/src/base/skills/lisa-session-manager/scripts/state-dir.mjs +112 -0
  329. package/plugins/src/base/skills/lisa-session-manager/scripts/telegram-route.mjs +210 -0
  330. package/plugins/src/base/skills/lisa-session-manager/scripts/token.mjs +191 -0
  331. package/plugins/src/base/skills/lisa-session-manager/scripts/worker-launch.mjs +92 -0
  332. package/plugins/src/base/skills/lisa-session-manager/scripts/workers.mjs +406 -0
  333. package/plugins/src/base/skills/lisa-setup-session-manager/SKILL.md +156 -0
  334. package/plugins/src/base/skills/lisa-tear-down-automations/SKILL.md +10 -0
@@ -0,0 +1,295 @@
1
+ /**
2
+ * Worker subcommands of the session-manager CLI: capacity, spawn, resume and
3
+ * classify.
4
+ *
5
+ * `spawn` is the one that starts yolo-mode sessions, so its checks run in a
6
+ * fixed order and each has its own exit code the manager skill branches on:
7
+ * the repo must be a Lisa repo (2), there must be capacity (3), and no live
8
+ * worker may already hold the ref (4). Only then does it run
9
+ * `claude --bg`, with the cwd set to the routed repo and every `TELEGRAM_*`
10
+ * variable removed from the worker's environment. A repo whose workspace
11
+ * trust was never accepted is exit 2 too (see worker-launch.mjs).
12
+ * @module session-manager/cli-workers
13
+ */
14
+ import { createHash } from "node:crypto";
15
+ import { readFile } from "node:fs/promises";
16
+
17
+ import { computeCapacity, readMachineStats } from "./capacity.mjs";
18
+ import {
19
+ CliError,
20
+ EXIT,
21
+ asSetupError,
22
+ intFlag,
23
+ listAgents,
24
+ loadProject,
25
+ mustRun,
26
+ requireConfig,
27
+ requireLisaRepo,
28
+ requirePositional,
29
+ run,
30
+ stdinText,
31
+ validOrAbsent,
32
+ } from "./cli-runtime.mjs";
33
+ import { SESSION_MANAGER_DEFAULTS, resolveRepoDir } from "./config.mjs";
34
+ import {
35
+ appendSessionEvent,
36
+ readLedger,
37
+ recordProgress,
38
+ recordSpawn,
39
+ } from "./ledger.mjs";
40
+ import {
41
+ DEFAULT_STALL,
42
+ buildWorkerName,
43
+ buildWorkerResume,
44
+ buildWorkerSpawn,
45
+ classifyWorker,
46
+ countRunningWorkers,
47
+ findLiveWorker,
48
+ parseSessionId,
49
+ } from "./workers.mjs";
50
+ import { resolveFullSessionId, runWorkerSession } from "./worker-launch.mjs";
51
+
52
+ /**
53
+ * Compute the capacity report for this machine and these agents.
54
+ * @param {object} ctx - CLI context
55
+ * @param {object} capacityConfig - `sessionManager.capacity`
56
+ * @param {ReturnType<typeof listAgents>} agents - Normalized rows
57
+ * @returns {Promise<ReturnType<typeof computeCapacity>>} Report
58
+ */
59
+ async function measure(ctx, capacityConfig, agents) {
60
+ const meminfoPath = ctx.values.meminfo;
61
+ const stats = await readMachineStats({
62
+ // The hidden --meminfo flag reads a Linux meminfo file on any host.
63
+ platform: meminfoPath ? "linux" : ctx.platform,
64
+ meminfoPath,
65
+ exec: async command => mustRun(command, [], { env: ctx.env }),
66
+ readFile: file => readFile(file, "utf8"),
67
+ });
68
+ const { control } = await readLedger(ctx.root);
69
+ return computeCapacity({
70
+ stats,
71
+ running: countRunningWorkers(agents),
72
+ config: capacityConfig,
73
+ control,
74
+ flagMaxWorkers: intFlag(ctx.values["max-workers"], "--max-workers"),
75
+ });
76
+ }
77
+
78
+ /**
79
+ * `capacity`: the report the manager reads before every dispatch. Works with
80
+ * no `sessionManager` block (the documented defaults apply).
81
+ * @param {object} ctx - CLI context
82
+ * @returns {Promise<{ json: object }>} Capacity report
83
+ */
84
+ export async function capacity(ctx) {
85
+ const config = validOrAbsent(loadProject(ctx.root));
86
+ const capacityConfig = config?.capacity ?? SESSION_MANAGER_DEFAULTS.capacity;
87
+ return { json: await measure(ctx, capacityConfig, listAgents(ctx)) };
88
+ }
89
+
90
+ /**
91
+ * Every worker name this project has used or can see, so a retry never
92
+ * reuses one.
93
+ * @param {ReturnType<typeof listAgents>} agents - Normalized rows
94
+ * @param {Awaited<ReturnType<typeof readLedger>>} ledger - Ledger
95
+ * @returns {Set<string>} Names
96
+ */
97
+ function knownNames(agents, ledger) {
98
+ const fromLedger = Object.values(ledger.items).flatMap(item => [
99
+ item.worker,
100
+ ...(item.history ?? []).map(event => event.worker),
101
+ ]);
102
+ return new Set(
103
+ [...agents.map(agent => agent.name), ...fromLedger].filter(Boolean)
104
+ );
105
+ }
106
+
107
+ /**
108
+ * `spawn <ref> [--repo <key>]`: start one worker for one claimed item.
109
+ * @param {object} ctx - CLI context
110
+ * @returns {Promise<{ json: object }>} `{ref,name,sessionId,repo,cwd}`
111
+ */
112
+ export async function spawn(ctx) {
113
+ const ref = requirePositional(ctx, "ref");
114
+ const config = requireConfig(ctx);
115
+ const key = ctx.values.repo;
116
+ const repoDir = requireLisaRepo(
117
+ asSetupError(() => resolveRepoDir(config, key, ctx.root))
118
+ );
119
+
120
+ const agents = listAgents(ctx, { all: true });
121
+ const report = await measure(ctx, config.capacity, agents);
122
+ if (!report.can_spawn) {
123
+ throw new CliError(
124
+ `No capacity: ${report.running} running, cap ${report.cap}` +
125
+ `${report.paused ? ", paused" : ""}${report.overloaded ? ", overloaded" : ""}.`,
126
+ EXIT.NO_CAPACITY,
127
+ report
128
+ );
129
+ }
130
+ const live = asSetupError(() => findLiveWorker(agents, ref));
131
+ if (live) {
132
+ throw new CliError(
133
+ `${ref} already has a live worker: ${live.name}.`,
134
+ EXIT.LIVE_WORKER,
135
+ { ref, worker: live.name, sessionId: live.sessionId }
136
+ );
137
+ }
138
+
139
+ const ledger = await readLedger(ctx.root);
140
+ const spec = asSetupError(() => {
141
+ const name = buildWorkerName(ref, ctx.now(), knownNames(agents, ledger));
142
+ return buildWorkerSpawn({
143
+ ref,
144
+ name,
145
+ manager: config.name,
146
+ repoDir,
147
+ env: ctx.env,
148
+ });
149
+ });
150
+ const name = spec.args[2];
151
+ const stdout = runWorkerSession(spec);
152
+ const sessionId = parseSessionId(stdout);
153
+ if (sessionId === null) {
154
+ ctx.warn(
155
+ `claude --bg printed no session id; find ${name} in claude agents --json.`
156
+ );
157
+ }
158
+ const repo = key ?? ".";
159
+ await recordSpawn(ctx.root, { ref, worker: name, sessionId, repo });
160
+ await appendSessionEvent(ctx.root, {
161
+ event: "spawn",
162
+ ref,
163
+ worker: name,
164
+ sessionId,
165
+ repo,
166
+ cwd: repoDir,
167
+ });
168
+ return { json: { ref, name, sessionId, repo, cwd: repoDir } };
169
+ }
170
+
171
+ /**
172
+ * The ledger row for `ref`, or exit 2.
173
+ * @param {Awaited<ReturnType<typeof readLedger>>} ledger - Ledger
174
+ * @param {string} ref - Item ref
175
+ * @returns {object} Ledger item
176
+ */
177
+ function ledgerItem(ledger, ref) {
178
+ const item = Object.hasOwn(ledger.items, ref) ? ledger.items[ref] : null;
179
+ if (!item) throw new CliError(`${ref} is not in the ledger.`, 2);
180
+ return item;
181
+ }
182
+
183
+ /**
184
+ * `resume <ref>`: resume a stopped worker with the message on stdin, in the
185
+ * worker's own repo and with the same environment scrub as a spawn.
186
+ * @param {object} ctx - CLI context
187
+ * @returns {Promise<{ json: object }>} `{ref,worker,sessionId,resumed}`
188
+ */
189
+ export async function resume(ctx) {
190
+ const ref = requirePositional(ctx, "ref");
191
+ const text = stdinText(ctx, "message for the worker");
192
+ const config = requireConfig(ctx);
193
+ const item = ledgerItem(await readLedger(ctx.root), ref);
194
+ if (!item.sessionId && !item.worker) {
195
+ throw new CliError(`${ref} has no recorded session id to resume.`, 2);
196
+ }
197
+ // probe-direction: fail-closed — no full id means no resume: a short id
198
+ // would start an unseen copy of the conversation instead of the worker.
199
+ const sessionId = resolveFullSessionId(listAgents(ctx, { all: true }), item);
200
+ if (sessionId === null) {
201
+ throw new CliError(
202
+ `${ref}: no claude agents row gives the full session id for ` +
203
+ `${item.worker ?? item.sessionId}, and resuming by short id starts a ` +
204
+ `copy instead of the worker. Check claude agents --json --all.`,
205
+ 2
206
+ );
207
+ }
208
+ const repoKey = item.repo === "." ? undefined : item.repo;
209
+ const spec = asSetupError(() =>
210
+ buildWorkerResume({
211
+ sessionId,
212
+ text,
213
+ repoDir: requireLisaRepo(resolveRepoDir(config, repoKey, ctx.root)),
214
+ env: ctx.env,
215
+ })
216
+ );
217
+ runWorkerSession(spec);
218
+ await appendSessionEvent(ctx.root, {
219
+ event: "resume",
220
+ ref,
221
+ worker: item.worker,
222
+ sessionId,
223
+ });
224
+ return {
225
+ json: {
226
+ ref,
227
+ worker: item.worker,
228
+ sessionId,
229
+ resumed: true,
230
+ },
231
+ };
232
+ }
233
+
234
+ /**
235
+ * Hash of `claude logs <name>`, or null when it cannot be read. An unchanged
236
+ * hash across cycles is the only "no output" signal the CLI offers.
237
+ *
238
+ * probe-direction: fail-closed — a failed `claude logs` read yields null,
239
+ * which `classifyWorker` never credits as progress, so the worker can only
240
+ * move toward `stalled` (a retry), never be kept `working` on a read that
241
+ * did not happen.
242
+ * @param {object} ctx - CLI context
243
+ * @param {string} name - Worker name
244
+ * @returns {string | null} sha256 hex
245
+ */
246
+ function logsHash(ctx, name) {
247
+ const result = run("claude", ["logs", name], { env: ctx.env });
248
+ if (result.status !== 0) return null;
249
+ return createHash("sha256").update(result.stdout).digest("hex");
250
+ }
251
+
252
+ /**
253
+ * `classify [<ref>]`: classify one ledger item, or every one, for this cycle,
254
+ * and record each logs hash as progress.
255
+ * @param {object} ctx - CLI context
256
+ * @returns {Promise<{ json: object[] }>} One row per item
257
+ */
258
+ export async function classify(ctx) {
259
+ const config = validOrAbsent(loadProject(ctx.root));
260
+ const stall = config?.stall ?? DEFAULT_STALL;
261
+ const ledger = await readLedger(ctx.root);
262
+ const [only] = ctx.positionals;
263
+ const refs = only ? [only] : Object.keys(ledger.items).sort();
264
+ const agents = listAgents(ctx, { all: true });
265
+ const rows = [];
266
+ for (const ref of refs) {
267
+ const entry = ledgerItem(ledger, ref);
268
+ // A worker-less item (asked about, never dispatched) has no session to
269
+ // pair with; `null === null` would adopt any unnamed agent as its worker.
270
+ const named = entry.worker
271
+ ? agents.filter(agent => agent.name === entry.worker)
272
+ : [];
273
+ const agent = named.find(row => row.active) ?? named[0] ?? null;
274
+ const hash = agent ? logsHash(ctx, entry.worker) : null;
275
+ const classification = classifyWorker({
276
+ agent,
277
+ entry,
278
+ logsHash: hash,
279
+ now: ctx.now(),
280
+ stall,
281
+ });
282
+ if (hash !== null) await recordProgress(ctx.root, { ref, logsHash: hash });
283
+ rows.push({
284
+ ref,
285
+ worker: entry.worker ?? null,
286
+ sessionId: entry.sessionId ?? null,
287
+ repo: entry.repo ?? null,
288
+ classification,
289
+ attempts: entry.attempts ?? 0,
290
+ agentStatus: agent?.status ?? agent?.state ?? null,
291
+ pendingQuestion: entry.pendingQuestion ?? null,
292
+ });
293
+ }
294
+ return { json: rows };
295
+ }
@@ -0,0 +1,265 @@
1
+ /**
2
+ * Resolve the opt-in `sessionManager` block of `.lisa.config.json`.
3
+ *
4
+ * Every other session-manager module reads its settings from the object this
5
+ * returns, so the documented defaults live in exactly one place. Merging is per
6
+ * key, local over global, the same rule every other Lisa config key follows: a
7
+ * developer can lower `capacity.maxWorkers` on their own machine without
8
+ * restating the rest of the block.
9
+ *
10
+ * The block is deliberately not registered in `src/sync/registry.ts`. That
11
+ * registry populates defaults into every project, and the manager starts
12
+ * yolo-mode sessions; it must exist only where an operator wrote it.
13
+ *
14
+ * Pure apart from `detectRepos`, whose filesystem calls are injectable.
15
+ * @module session-manager/config
16
+ */
17
+
18
+ import { existsSync, readdirSync } from "node:fs";
19
+ import { isAbsolute, join, resolve, sep } from "node:path";
20
+
21
+ import {
22
+ mergeConfigLayers as mergeLayers,
23
+ parseLoopInterval,
24
+ validateSessionManager,
25
+ } from "../../lisa-secrets-access/scripts/validate-config.mjs";
26
+
27
+ export { parseLoopInterval, validateSessionManager };
28
+
29
+ /**
30
+ * Documented defaults for every optional key.
31
+ *
32
+ * `name` and `notify.stateDir` have none: both are per-project, and a shared
33
+ * default for either is how two projects end up fighting over one bot.
34
+ * `buildDefaultBlock` derives them from the project slug at setup time.
35
+ */
36
+ export const SESSION_MANAGER_DEFAULTS = Object.freeze({
37
+ interval: "5m",
38
+ capacity: Object.freeze({
39
+ maxWorkers: 6,
40
+ gbPerWorker: 3,
41
+ reserveGb: 8,
42
+ cpuFraction: 0.75,
43
+ maxLoadPerCore: 1.5,
44
+ }),
45
+ stall: Object.freeze({
46
+ noOutputMinutes: 60,
47
+ maxAttempts: 3,
48
+ requestionHours: 4,
49
+ }),
50
+ notify: Object.freeze({ provider: "telegram" }),
51
+ });
52
+
53
+ /**
54
+ * @typedef {{ root: string, entries: Record<string, { hints: string[] }> }} ReposBlock
55
+ * @typedef {{ maxWorkers: number, gbPerWorker: number, reserveGb: number,
56
+ * cpuFraction: number, maxLoadPerCore: number }} CapacityBlock
57
+ * @typedef {{ noOutputMinutes: number, maxAttempts: number,
58
+ * requestionHours: number }} StallBlock
59
+ * @typedef {{ provider: string, stateDir?: string }} NotifyBlock
60
+ * @typedef {{
61
+ * name: string,
62
+ * interval: string,
63
+ * repos: ReposBlock | null,
64
+ * capacity: CapacityBlock,
65
+ * stall: StallBlock,
66
+ * notify: NotifyBlock,
67
+ * trustedAuthorIds: string[] | null,
68
+ * }} SessionManagerConfig
69
+ * @typedef {{
70
+ * name: string,
71
+ * interval: string,
72
+ * repos?: ReposBlock,
73
+ * capacity: CapacityBlock,
74
+ * stall: StallBlock,
75
+ * notify: { provider: string, stateDir: string },
76
+ * }} SessionManagerBlock
77
+ */
78
+
79
+ /** Where child repos live when `repos.root` is omitted. */
80
+ const DEFAULT_REPOS_ROOT = "projects";
81
+
82
+ /** One path segment that cannot climb out of, or hide inside, its parent. */
83
+ const SAFE_SEGMENT = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
84
+
85
+ /**
86
+ * Whether a value is a plain object (not null, not an array).
87
+ * @param {unknown} value Candidate.
88
+ * @returns {boolean} True for `{}`-shaped values.
89
+ */
90
+ function isPlainObject(value) {
91
+ return typeof value === "object" && value !== null && !Array.isArray(value);
92
+ }
93
+
94
+ /**
95
+ * Resolve the effective `sessionManager` settings.
96
+ *
97
+ * Pass the two parsed config files, or one already-merged config. Returns
98
+ * `null` when neither declares the block, so callers can say "not configured"
99
+ * instead of running with a guessed name. Validation is separate
100
+ * (`validateSessionManager`), because doctor wants every problem listed while
101
+ * the CLI only needs a usable object.
102
+ * @param {object|undefined} globalConfig Parsed `.lisa.config.json` (or a
103
+ * merged config).
104
+ * @param {object|undefined} [localConfig] Parsed `.lisa.config.local.json`.
105
+ * @returns {SessionManagerConfig | null} The resolved block, or null when
106
+ * absent.
107
+ */
108
+ export function resolveSessionManagerConfig(globalConfig, localConfig) {
109
+ const declared = [globalConfig, localConfig]
110
+ .map(config => (isPlainObject(config) ? config.sessionManager : undefined))
111
+ .filter(isPlainObject);
112
+ if (declared.length === 0) return null;
113
+
114
+ const merged = mergeLayers(SESSION_MANAGER_DEFAULTS, ...declared);
115
+ const repos = isPlainObject(merged.repos)
116
+ ? mergeLayers({ root: DEFAULT_REPOS_ROOT, entries: {} }, merged.repos)
117
+ : null;
118
+ return {
119
+ ...merged,
120
+ repos,
121
+ trustedAuthorIds: merged.trustedAuthorIds ?? null,
122
+ };
123
+ }
124
+
125
+ /**
126
+ * List the Lisa-configured child repos of a multi-repo project.
127
+ *
128
+ * A child counts only when it carries its own `.lisa.config.json`: workers run
129
+ * `/lisa:implement` inside it, which needs that file, and a stray directory
130
+ * under `projects/` is not something to route tickets into.
131
+ * @param {string} root Project root.
132
+ * @param {{
133
+ * reposRoot?: string,
134
+ * fs?: { existsSync: (p: string) => boolean,
135
+ * readdirSync: (p: string, o: { withFileTypes: true }) =>
136
+ * Array<{ name: string, isDirectory: () => boolean }> },
137
+ * }} [options] Repos root (default `projects`) and filesystem seam.
138
+ * @returns {string[]} Child directory names, sorted.
139
+ */
140
+ export function detectRepos(root, options = {}) {
141
+ const fs = options.fs ?? { existsSync, readdirSync };
142
+ const base = join(root, options.reposRoot ?? DEFAULT_REPOS_ROOT);
143
+ if (!fs.existsSync(base)) return [];
144
+ return fs
145
+ .readdirSync(base, { withFileTypes: true })
146
+ .filter(entry => entry.isDirectory() && SAFE_SEGMENT.test(entry.name))
147
+ .filter(entry => fs.existsSync(join(base, entry.name, ".lisa.config.json")))
148
+ .map(entry => entry.name)
149
+ .sort();
150
+ }
151
+
152
+ /**
153
+ * Turn a project name into a slug the validator accepts.
154
+ * @param {string} projectSlug Project name, possibly with dots or capitals.
155
+ * @returns {string} Lowercase, dash-separated slug.
156
+ */
157
+ function normalizeSlug(projectSlug) {
158
+ const slug = String(projectSlug ?? "")
159
+ .toLowerCase()
160
+ .replace(/[^a-z0-9]+/g, "-")
161
+ .replace(/^-+|-+$/g, "")
162
+ // Leave room for the "-manager" suffix inside the 64-character name limit.
163
+ .slice(0, 56)
164
+ .replace(/-+$/, "");
165
+ if (!slug) {
166
+ throw new Error(
167
+ `project slug ${JSON.stringify(projectSlug)} has no letters or digits ` +
168
+ `to name the manager after.`
169
+ );
170
+ }
171
+ return slug;
172
+ }
173
+
174
+ /**
175
+ * Build the block setup writes into a project's `.lisa.config.json`.
176
+ *
177
+ * Defaults are written out rather than left implicit, so an operator sees
178
+ * every knob in the file they will edit. With no detected repos the block has
179
+ * no `repos` key, which is what makes a project single-repo. Each detected
180
+ * repo's hints start as its own name: valid, and an obvious prompt to add real
181
+ * ones.
182
+ * @param {{ projectSlug: string, detectedRepos?: string[] }} input Project
183
+ * slug and the output of `detectRepos`.
184
+ * @returns {SessionManagerBlock} A block `validateSessionManager` accepts.
185
+ */
186
+ export function buildDefaultBlock({ projectSlug, detectedRepos = [] }) {
187
+ const slug = normalizeSlug(projectSlug);
188
+ const repos = [...detectedRepos].sort();
189
+ return {
190
+ name: `${slug}-manager`,
191
+ interval: SESSION_MANAGER_DEFAULTS.interval,
192
+ ...(repos.length > 0
193
+ ? {
194
+ repos: {
195
+ root: DEFAULT_REPOS_ROOT,
196
+ entries: Object.fromEntries(
197
+ repos.map(name => [name, { hints: [name] }])
198
+ ),
199
+ },
200
+ }
201
+ : {}),
202
+ capacity: { ...SESSION_MANAGER_DEFAULTS.capacity },
203
+ stall: { ...SESSION_MANAGER_DEFAULTS.stall },
204
+ notify: {
205
+ provider: SESSION_MANAGER_DEFAULTS.notify.provider,
206
+ stateDir: `~/.claude/channels/telegram-${slug}`,
207
+ },
208
+ };
209
+ }
210
+
211
+ /**
212
+ * Resolve the directory a worker for `key` must start in.
213
+ *
214
+ * Workers run with permissions bypassed, so this refuses rather than guesses:
215
+ * an unknown key, a key on a single-repo project, or a missing key on a
216
+ * multi-repo one all throw. Only own `entries` keys resolve, so `constructor`
217
+ * and friends cannot.
218
+ * @param {{ repos?: unknown } | null | undefined} config Resolved (or raw)
219
+ * `sessionManager` block.
220
+ * @param {string|undefined} key Repo key; omit, or pass `.`, for single-repo.
221
+ * @param {string} projectRoot Absolute project root.
222
+ * @returns {string} Absolute repo directory.
223
+ */
224
+ export function resolveRepoDir(config, key, projectRoot) {
225
+ if (typeof projectRoot !== "string" || !isAbsolute(projectRoot)) {
226
+ throw new Error(
227
+ `project root ${JSON.stringify(projectRoot)} must be an absolute path.`
228
+ );
229
+ }
230
+ const repos = isPlainObject(config?.repos) ? config.repos : null;
231
+ const noKey = key === undefined || key === null || key === "" || key === ".";
232
+
233
+ if (!repos) {
234
+ if (noKey) return resolve(projectRoot);
235
+ throw new Error(
236
+ `repo ${JSON.stringify(key)} given, but this is a single-repo project ` +
237
+ `(sessionManager.repos is not set); workers start at the project root.`
238
+ );
239
+ }
240
+
241
+ const entries = isPlainObject(repos.entries) ? repos.entries : {};
242
+ const known = Object.keys(entries).sort();
243
+ if (noKey) {
244
+ throw new Error(
245
+ `this multi-repo project needs a repo key; one of: ${known.join(", ")}.`
246
+ );
247
+ }
248
+ if (!Object.hasOwn(entries, key) || !SAFE_SEGMENT.test(key)) {
249
+ throw new Error(
250
+ `repo ${JSON.stringify(key)} is not one of sessionManager.repos.entries: ` +
251
+ `${known.join(", ")}.`
252
+ );
253
+ }
254
+ const base = resolve(projectRoot);
255
+ const dir = resolve(base, repos.root ?? DEFAULT_REPOS_ROOT, key);
256
+ // `repos.root` is validated elsewhere, but this is the last step before a
257
+ // bypass-permissions launch, so it re-checks containment itself.
258
+ if (!dir.startsWith(`${base}${sep}`)) {
259
+ throw new Error(
260
+ `sessionManager.repos.root ${JSON.stringify(repos.root)} resolves ` +
261
+ `outside the project root.`
262
+ );
263
+ }
264
+ return dir;
265
+ }
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Keeping one project's manager, bot and token apart from every other
3
+ * project's on the same machine.
4
+ *
5
+ * The manager's tmux session name and its Telegram state dir are both derived
6
+ * from a project slug. A slug taken from the repository name alone collides
7
+ * for two checkouts of one repository, or for two projects that share a
8
+ * folder name, and the collision is silent: the second project's `start`
9
+ * reports "already-running" about the first project's manager, which then
10
+ * dispatches the wrong project's work with bypass permissions. So:
11
+ * - the default slug carries a short hash of the real project root, and
12
+ * - before treating a running session as ours, the CLI asks tmux where that
13
+ * session was started and compares real paths.
14
+ * @module session-manager/isolation
15
+ */
16
+ import { createHash } from "node:crypto";
17
+ import { realpathSync } from "node:fs";
18
+ import path from "node:path";
19
+
20
+ import { CliError, EXIT, run } from "./cli-runtime.mjs";
21
+ import { buildHasSession, buildSessionPath } from "./launch.mjs";
22
+
23
+ /** Characters of the repo name kept, so `<name>-<hash6>` fits in 56. */
24
+ const SLUG_BASE_LENGTH = 49;
25
+
26
+ /** Hex characters of the root hash in a default slug. */
27
+ const ROOT_HASH_LENGTH = 6;
28
+
29
+ /**
30
+ * The real path of `dir`, or its plain resolution when it does not exist.
31
+ * @param {string} dir - Directory
32
+ * @returns {string} Absolute path with symlinks resolved where possible
33
+ */
34
+ export function realpathOr(dir) {
35
+ try {
36
+ return realpathSync(dir);
37
+ } catch {
38
+ return path.resolve(dir);
39
+ }
40
+ }
41
+
42
+ /**
43
+ * The default project slug: the repo (or folder) name plus a stable hash of
44
+ * the real project root, `<name>-<hash6>`. The name is cut first so the hash
45
+ * always survives the 56-character slug limit `buildDefaultBlock` applies.
46
+ * @param {string} root - Absolute project root
47
+ * @param {string | undefined} repoName - `github.repo`, when configured
48
+ * @returns {string} Slug for `buildDefaultBlock`
49
+ */
50
+ export function defaultProjectSlug(root, repoName) {
51
+ const base = String(repoName ?? path.basename(root)).slice(
52
+ 0,
53
+ SLUG_BASE_LENGTH
54
+ );
55
+ const hash = createHash("sha256")
56
+ .update(realpathOr(root))
57
+ .digest("hex")
58
+ .slice(0, ROOT_HASH_LENGTH);
59
+ return `${base}-${hash}`;
60
+ }
61
+
62
+ /**
63
+ * Whether the manager's tmux session is running, and if so, that it is this
64
+ * project's. A session started anywhere else (or one tmux cannot place) is
65
+ * another project's manager under the same name, which is exit 2: neither
66
+ * "already running" nor "stopped" would be true.
67
+ * @param {{ root: string }} ctx - CLI context
68
+ * @param {string} name - Manager session name
69
+ * @param {Record<string, string | undefined>} env - Environment for tmux
70
+ * @returns {{ running: boolean, missing: boolean }} `missing` when tmux is
71
+ * not on PATH
72
+ */
73
+ export function probeOwnSession(ctx, name, env) {
74
+ const probe = run("tmux", buildHasSession(name), { env });
75
+ if (probe.missing) return { running: false, missing: true };
76
+ // probe-direction: fail-closed — a failed has-session reads as "not
77
+ // running", so `start` goes on to new-session, which tmux refuses for a
78
+ // duplicate name; it never reports someone else's session as ours.
79
+ if (probe.status !== 0) return { running: false, missing: false };
80
+ const where = run("tmux", buildSessionPath(name), { env }).stdout.trim();
81
+ if (where && realpathOr(where) === realpathOr(ctx.root)) {
82
+ return { running: true, missing: false };
83
+ }
84
+ const place = where
85
+ ? `was started in ${where}, not in ${ctx.root}`
86
+ : `is running, but tmux cannot say where it was started`;
87
+ throw new CliError(
88
+ `tmux session "${name}" ${place}. Another project is using the same ` +
89
+ `manager name: give this project its own sessionManager.name and ` +
90
+ `notify.stateDir (init-config derives unique ones), or stop that ` +
91
+ `project's manager from its own root.`,
92
+ EXIT.SETUP
93
+ );
94
+ }
95
+
96
+ /** How often the survival check asks tmux, in milliseconds. */
97
+ const SURVIVAL_POLL_MS = 250;
98
+
99
+ /**
100
+ * Wait `ms` milliseconds.
101
+ * @param {number} ms - Delay
102
+ * @returns {Promise<void>} Resolves after the delay
103
+ */
104
+ const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
105
+
106
+ /**
107
+ * Whether a just-started manager session is still there after `settleMs`.
108
+ *
109
+ * tmux `new-session -d` succeeds as soon as the session exists, even when its
110
+ * command (claude) exits a moment later on a bad argv; tmux then closes the
111
+ * window and the session with it. So a check right after `new-session` proves
112
+ * nothing: this asks `has-session -t =<name>` every 250 ms for the whole
113
+ * window and returns false the first time the session is gone. Bounded by
114
+ * `settleMs`, which the caller caps.
115
+ * @param {string} name - Manager session name
116
+ * @param {Record<string, string | undefined>} env - Environment for tmux
117
+ * @param {number} settleMs - How long the session must survive
118
+ * @returns {Promise<boolean>} True when it was there at every check
119
+ */
120
+ export async function sessionSurvives(name, env, settleMs) {
121
+ const checks = Math.max(1, Math.ceil(settleMs / SURVIVAL_POLL_MS));
122
+ const step = settleMs / checks;
123
+ for (let check = 0; check < checks; check += 1) {
124
+ await sleep(step);
125
+ // probe-direction: fail-closed — a failed has-session reads as "the manager
126
+ // died", so start refuses to report it as started.
127
+ if (run("tmux", buildHasSession(name), { env }).status !== 0) return false;
128
+ }
129
+ return true;
130
+ }