@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
@@ -280,6 +280,7 @@ fi
280
280
  | `health.schedule` | no | `off` | Health cadence contract: `off`, `daily`, or `weekly`. `lisa sync` populates the default with `_lisaSync.populated` provenance and rejects values outside this closed vocabulary. Runtime results live only at the gitignored `.lisa/health/latest.json`, never in either config file. |
281
281
  | `usage` | no | — | Optional token/cost pricing metadata consumed by the `usage-accounting` rule. Missing pricing never blocks a lifecycle flow; Lisa records token counts with `estimated_cost: null` when no trustworthy price source is configured. |
282
282
  | `wiki` | no | — | Wiki location for the `wiki-knowledge-source` rule. Omit for a local in-repo wiki (`wiki/`). See **Wiki source** below. |
283
+ | `sessionManager` | no | — (opt-in) | Local session manager: polls the Ready queue, runs each item in a fresh background Claude Code session, and relays questions over a per-project Telegram bot. Absent means the manager is not used; `lisa sync` never populates it. Written by `/lisa:setup:session-manager`. See **Session manager (`sessionManager`)** below. |
283
284
 
284
285
  ### Host rules (`.agents/rules/`)
285
286
 
@@ -688,6 +689,63 @@ Resolution order matches every other key: `$ARGUMENTS` override → `.lisa.confi
688
689
  roles count as "stuck", what each repair does) are fixed like every other lifecycle transition;
689
690
  only these thresholds are tunable.
690
691
 
692
+ ### Session manager (`sessionManager`)
693
+
694
+ The optional `sessionManager` block configures `lisa-session-manager`, a locally hosted loop that
695
+ claims Ready leaf items and runs each one as `/lisa:implement` in a fresh background Claude Code
696
+ session. It is **opt-in**: an absent block means no manager, and `lisa sync` never populates it
697
+ (it is deliberately not in the sync registry, because the manager starts sessions with permissions
698
+ bypassed). `/lisa:setup:session-manager` writes it, detecting a multi-repo layout from
699
+ `projects/*/.lisa.config.json`.
700
+
701
+ ```json
702
+ "sessionManager": {
703
+ "name": "<project>-manager",
704
+ "interval": "5m",
705
+ "repos": {
706
+ "root": "projects",
707
+ "entries": {
708
+ "api": { "hints": ["NestJS", "API", "migrations"] },
709
+ "web": { "hints": ["Expo", "React", "screens"] }
710
+ }
711
+ },
712
+ "capacity": { "maxWorkers": 6, "gbPerWorker": 3, "reserveGb": 8, "cpuFraction": 0.75, "maxLoadPerCore": 1.5 },
713
+ "stall": { "noOutputMinutes": 60, "maxAttempts": 3, "requestionHours": 4 },
714
+ "notify": { "provider": "telegram", "stateDir": "~/.claude/channels/telegram-<project>" }
715
+ }
716
+ ```
717
+
718
+ | Key | Required | Default | Notes |
719
+ |-----|----------|---------|-------|
720
+ | `sessionManager.name` | **yes** | — | Manager session name: lowercase letters, digits, `-` or `_`, at most 64 characters. It names the tmux session and the session workers `SendMessage` to. Setup writes `<project>-manager`. |
721
+ | `sessionManager.interval` | no | `5m` | `/loop` cadence: a positive integer plus `s`, `m`, `h` or `d`. |
722
+ | `sessionManager.repos` | no | — | Omit for a single-repo project: every item routes to the project root. When present, `entries` must name at least one repo. |
723
+ | `sessionManager.repos.root` | no | `projects` | Relative directory holding the child repos. It must stay inside the project. |
724
+ | `sessionManager.repos.entries.<key>.hints` | **yes** (per entry) | — | Non-empty list of words the manager matches a work item against to choose the repo. `<key>` is the child directory name (one safe path segment). |
725
+ | `sessionManager.capacity.maxWorkers` | no | `6` | Hard cap on concurrent workers (a positive integer). Telegram `cap N` overrides it at runtime through the manager's ledger, not config. |
726
+ | `sessionManager.capacity.gbPerWorker` | no | `3` | Memory budgeted per worker, in GB (positive). |
727
+ | `sessionManager.capacity.reserveGb` | no | `8` | Memory kept free for everything else, in GB (zero or positive). |
728
+ | `sessionManager.capacity.cpuFraction` | no | `0.75` | Share of CPU cores workers may occupy, greater than 0 and at most 1. |
729
+ | `sessionManager.capacity.maxLoadPerCore` | no | `1.5` | No new worker starts while the 1-minute load average exceeds cores × this (positive). |
730
+ | `sessionManager.stall.noOutputMinutes` | no | `60` | A live worker with no new output for this long counts as stalled (zero or positive). |
731
+ | `sessionManager.stall.maxAttempts` | no | `3` | Fresh sessions per item before it moves to the configured `blocked` role (a positive integer). |
732
+ | `sessionManager.stall.requestionHours` | no | `4` | How long an unanswered worker question waits before the manager asks again (zero or positive). |
733
+ | `sessionManager.notify.provider` | no | `telegram` | Only `telegram` is implemented. |
734
+ | `sessionManager.notify.stateDir` | **yes** | — | This project's own Telegram state dir. Never the shared default `~/.claude/channels/telegram`, because one bot per state dir is how projects stay isolated. Setup writes `~/.claude/channels/telegram-<project>`. |
735
+ | `sessionManager.trustedAuthorIds` | no | — | Optional list of **stable tracker user ids**, as strings: the GitHub numeric user id (`gh api users/<login> --jq .id`), the Jira `accountId`, or the Linear user id. Not logins or display names: matching compares ids, so a login silently matches no one and excludes every item. When set, the manager takes only items authored by these people. |
736
+
737
+ - **Non-secret only.** The bot token lives in `<stateDir>/.env` (directory 0700, file 0600),
738
+ written by setup. A credential-named field or a token-shaped value anywhere in this block is a
739
+ validation error.
740
+ - **Local over global, per key.** `.lisa.config.local.json` overrides `.lisa.config.json` key by key
741
+ (for example, a smaller `capacity.maxWorkers` on one machine); arrays such as `hints` are
742
+ replaced, not merged. Built-in defaults fill whatever neither file sets.
743
+ - **Workflow roles are not here.** Ready, claimed and blocked come from the existing tracker
744
+ workflow config, through the single role resolver, like every other lifecycle flow.
745
+ - **Shape check.** `validateSessionManager` in
746
+ `lisa-secrets-access/scripts/validate-config.mjs` runs as part of `validateConfig`, so
747
+ `/lisa:doctor` reports a malformed block.
748
+
691
749
  ### Env-keyed `done`
692
750
 
693
751
  The `done` role is special: the terminal status/label depends on which environment a PR was merged into. A hotfix to staging ends at `On Stg`; a production hotfix ends at `Done`. So `done` is a **map** keyed by env name (`dev`, `staging`, `production`).
@@ -123,7 +123,9 @@ export async function inspectClaudeAutomationFleet(input) {
123
123
  status: "UNSUPPORTED",
124
124
  summary: unsupported.reason,
125
125
  expectedCadence: unsupported.expectedCadence,
126
- observed: "No automation is expected for this repo/runtime combination.",
126
+ observed:
127
+ unsupported.observed ??
128
+ "No automation is expected for this repo/runtime combination.",
127
129
  runbook: runDisplay?.runbook,
128
130
  lastOutcome: runDisplay?.lastOutcome,
129
131
  outcomeHistory: runDisplay?.outcomeHistory,
@@ -233,6 +235,7 @@ async function resolveFleetRunDisplays(projectRoot, expectedFleet) {
233
235
  projectRoot,
234
236
  loopId: entry.id,
235
237
  runbookPath: entry.runbookPath,
238
+ runbookSetupCommand: entry.runbookSetupCommand,
236
239
  })
237
240
  )
238
241
  );
@@ -214,7 +214,9 @@ export async function inspectCodexAutomationFleet(input) {
214
214
  status: "UNSUPPORTED",
215
215
  summary: unsupported.reason,
216
216
  expectedCadence: unsupported.expectedCadence,
217
- observed: "No automation is expected for this repo/runtime combination.",
217
+ observed:
218
+ unsupported.observed ??
219
+ "No automation is expected for this repo/runtime combination.",
218
220
  runbook: runDisplay?.runbook,
219
221
  lastOutcome: runDisplay?.lastOutcome,
220
222
  outcomeHistory: runDisplay?.outcomeHistory,
@@ -656,6 +658,7 @@ async function resolveFleetRunDisplays(projectRoot, expectedFleet) {
656
658
  projectRoot,
657
659
  loopId: entry.id,
658
660
  runbookPath: entry.runbookPath,
661
+ runbookSetupCommand: entry.runbookSetupCommand,
659
662
  })
660
663
  )
661
664
  );
@@ -140,7 +140,16 @@ export function renderAutomationGroups(bins) {
140
140
  * readonly expectedCadence: string
141
141
  * readonly expectedRRule: string
142
142
  * readonly runbookPath: string
143
+ * readonly observed?: string
144
+ * readonly runbookSetupCommand?: string
143
145
  * }} UnsupportedAutomationEntry
146
+ *
147
+ * `observed` overrides the adapters' default "No automation is expected" text
148
+ * for an entry that does run, just not where the scheduler can see it.
149
+ * `runbookSetupCommand` names the command that scaffolds this loop's runbook
150
+ * when it is not `/lisa:setup-automations` (the session manager's runbook is
151
+ * seeded by `/lisa:setup:session-manager`), so the "not scaffolded" fix-it
152
+ * text sends the operator to the command that can actually fix it.
144
153
  */
145
154
 
146
155
  /**
@@ -351,6 +360,12 @@ export function resolveExpectedAutomationFleet(input = {}) {
351
360
  );
352
361
  }
353
362
 
363
+ if (isPlainObject(config.sessionManager)) {
364
+ unsupported.push(
365
+ createSessionManagerEntry(identity, config.sessionManager)
366
+ );
367
+ }
368
+
354
369
  return {
355
370
  ...identity,
356
371
  expected,
@@ -413,6 +428,89 @@ function createUnsupportedEntry(identity, id, reason, group) {
413
428
  };
414
429
  }
415
430
 
431
+ /** Loop id the session manager records its cycles under. */
432
+ export const SESSION_MANAGER_LOOP_ID = "session-manager";
433
+
434
+ /** Interval used when `sessionManager.interval` is absent or unparseable. */
435
+ const SESSION_MANAGER_DEFAULT_INTERVAL = "5m";
436
+
437
+ /**
438
+ * RRULE units, largest first, so `120m` reads as `HOURLY;INTERVAL=2`.
439
+ * @type {readonly (readonly [string, number])[]}
440
+ */
441
+ const RRULE_UNITS = [
442
+ ["DAILY", 86400],
443
+ ["HOURLY", 3600],
444
+ ["MINUTELY", 60],
445
+ ["SECONDLY", 1],
446
+ ];
447
+
448
+ /** Seconds per `sessionManager.interval` unit suffix. */
449
+ const LOOP_INTERVAL_UNIT_SECONDS = { s: 1, m: 60, h: 3600, d: 86400 };
450
+
451
+ /**
452
+ * Build the UNSUPPORTED entry for the locally hosted session manager.
453
+ *
454
+ * The manager is a `/loop` in a tmux session that setup starts, so no runtime
455
+ * scheduler can see it: MISSING would be a lie and HEALTHY unprovable. It is
456
+ * listed as UNSUPPORTED with a reason that says where it runs and how to check
457
+ * it, and the adapters still attach its runbook and recorded cycles (it records
458
+ * through `automation-run-record.mjs --loop-id session-manager`). Opt-in: only
459
+ * called when `config.sessionManager` exists, so other fleets are unchanged.
460
+ *
461
+ * The interval parser mirrors `parseLoopInterval` in the config validator; it
462
+ * is inlined because plugin-level scripts do not import from skill folders.
463
+ *
464
+ * @param {{ readonly automationPrefix: string }} identity
465
+ * @param {Record<string, any>} block The `sessionManager` config block.
466
+ * @returns {UnsupportedAutomationEntry}
467
+ */
468
+ function createSessionManagerEntry(identity, block) {
469
+ const parsed = parseIntervalSeconds(block.interval);
470
+ const interval =
471
+ parsed === null ? SESSION_MANAGER_DEFAULT_INTERVAL : block.interval;
472
+ const seconds = parsed ?? parseIntervalSeconds(interval);
473
+ const [freq, unit] = RRULE_UNITS.find(([, size]) => seconds % size === 0);
474
+ const session =
475
+ typeof block.name === "string" && block.name !== ""
476
+ ? `tmux session \`${block.name}\``
477
+ : "the manager's tmux session";
478
+ return {
479
+ id: SESSION_MANAGER_LOOP_ID,
480
+ automationId: `${identity.automationPrefix}${SESSION_MANAGER_LOOP_ID}`,
481
+ expectedCadence: `every ${interval} (tmux-hosted /loop)`,
482
+ expectedRRule: `FREQ=${freq};INTERVAL=${seconds / unit}`,
483
+ group: "opt-in",
484
+ reason: `Hosted locally in ${session} by /lisa:setup:session-manager, not by this runtime's scheduler, so the scheduler cannot see it. Its recorded cycles are below; check it is alive with \`/lisa:session-manager status\`.`,
485
+ observed:
486
+ "Hosted locally in tmux; the scheduler cannot observe it — check `/lisa:session-manager status`",
487
+ runbookPath: resolveAutomationRunbookPath(SESSION_MANAGER_LOOP_ID),
488
+ runbookSetupCommand: "/lisa:setup:session-manager",
489
+ };
490
+ }
491
+
492
+ /**
493
+ * Parse a `/loop` interval such as `5m` into seconds.
494
+ *
495
+ * @param {unknown} text
496
+ * @returns {number | null} Seconds, or null when unparseable or zero.
497
+ */
498
+ function parseIntervalSeconds(text) {
499
+ if (typeof text !== "string") return null;
500
+ const match = /^(\d+)([smhd])$/.exec(text);
501
+ if (!match) return null;
502
+ const seconds = Number(match[1]) * LOOP_INTERVAL_UNIT_SECONDS[match[2]];
503
+ return seconds > 0 ? seconds : null;
504
+ }
505
+
506
+ /**
507
+ * @param {unknown} value
508
+ * @returns {boolean} True for `{}`-shaped values.
509
+ */
510
+ function isPlainObject(value) {
511
+ return typeof value === "object" && value !== null && !Array.isArray(value);
512
+ }
513
+
416
514
  /**
417
515
  * @param {Record<string, any>} config
418
516
  * @param {string | undefined} source
@@ -41,8 +41,23 @@ export const RECOVERY_REQUIRED_OUTCOME = "recovery-required";
41
41
  */
42
42
  export const RECOVERY_REQUIRED_ESCALATION_THRESHOLD = 3;
43
43
 
44
- const RUNBOOK_NOT_SCAFFOLDED_LINE =
45
- "not scaffolded — run /lisa:setup-automations";
44
+ /** The command that scaffolds a scheduled loop's runbook. */
45
+ const DEFAULT_RUNBOOK_SETUP_COMMAND = "/lisa:setup-automations";
46
+
47
+ /**
48
+ * The "not scaffolded" fix-it line, naming the command that scaffolds this
49
+ * loop's runbook.
50
+ *
51
+ * @param {string} [setupCommand]
52
+ * @returns {string}
53
+ */
54
+ function runbookNotScaffoldedLine(
55
+ setupCommand = DEFAULT_RUNBOOK_SETUP_COMMAND
56
+ ) {
57
+ return `not scaffolded — run ${setupCommand}`;
58
+ }
59
+
60
+ const RUNBOOK_NOT_SCAFFOLDED_LINE = runbookNotScaffoldedLine();
46
61
  const NO_RECORDED_RUNS_LINE = "no recorded runs yet";
47
62
 
48
63
  /**
@@ -72,12 +87,18 @@ const NO_RECORDED_RUNS_LINE = "no recorded runs yet";
72
87
  * readonly projectRoot?: string
73
88
  * readonly loopId: string
74
89
  * readonly runbookPath?: string
75
- * }} input
90
+ * readonly runbookSetupCommand?: string
91
+ * }} input `runbookSetupCommand` overrides `/lisa:setup-automations` in the
92
+ * "not scaffolded" line for a loop another setup command scaffolds.
76
93
  * @returns {Promise<AutomationRunDisplay>}
77
94
  */
78
95
  export async function resolveAutomationRunDisplay(input) {
79
96
  const projectRoot = path.resolve(input.projectRoot ?? process.cwd());
80
- const runbook = await resolveRunbookLine(projectRoot, input.runbookPath);
97
+ const runbook = await resolveRunbookLine(
98
+ projectRoot,
99
+ input.runbookPath,
100
+ runbookNotScaffoldedLine(input.runbookSetupCommand)
101
+ );
81
102
  const recordsPath = automationRunRecordPath(projectRoot, input.loopId);
82
103
  const { records, skippedCorruptLines } =
83
104
  await readAutomationRunRecords(recordsPath);
@@ -168,16 +189,17 @@ export function resolveRecoveryEscalation(runDisplay) {
168
189
  /**
169
190
  * @param {string} projectRoot
170
191
  * @param {string | undefined} runbookPath
192
+ * @param {string} missingLine The "not scaffolded" line for this loop
171
193
  * @returns {Promise<string>}
172
194
  */
173
- async function resolveRunbookLine(projectRoot, runbookPath) {
195
+ async function resolveRunbookLine(projectRoot, runbookPath, missingLine) {
174
196
  if (!runbookPath) {
175
- return RUNBOOK_NOT_SCAFFOLDED_LINE;
197
+ return missingLine;
176
198
  }
177
199
  const scaffolded = await runbookIsScaffolded(
178
200
  path.join(projectRoot, runbookPath)
179
201
  );
180
- return scaffolded ? runbookPath : RUNBOOK_NOT_SCAFFOLDED_LINE;
202
+ return scaffolded ? runbookPath : missingLine;
181
203
  }
182
204
 
183
205
  /**
@@ -0,0 +1,352 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Vendor-neutral Ready-queue normalization and mine-first dispatch order.
4
+ *
5
+ * ## Why this exists
6
+ *
7
+ * Intake has always taken Ready work in whatever order the tracker returned
8
+ * it, and `intake.assignee` only NARROWS a selection — it never ranks one. A
9
+ * dispatcher that runs several workers at once needs a stable answer to "what
10
+ * next?": the work already assigned to me first (somebody chose me for it),
11
+ * then unassigned work, most urgent and oldest first. Work assigned to someone
12
+ * else is theirs, so it is excluded, never re-ordered.
13
+ *
14
+ * The three trackers disagree on every field this needs — GitHub has priority
15
+ * LABELS, Linear a native 0..4 number where 0 means "none", Jira a priority
16
+ * NAME from a scheme that varies by site — so each vendor gets one normalizer
17
+ * onto a single item shape, and ordering only ever sees that shape.
18
+ *
19
+ * `queue-status-build-readers.mjs` reads raw GitHub only; Jira and Linear
20
+ * arrive here already fetched by their vendor access skills. This module never
21
+ * fetches: the caller pages the tracker to completion, and
22
+ * {@link assertPagedToCompletion} refuses to order a half-read queue, because
23
+ * a truncated sweep silently reorders by omission (#3529, #4105).
24
+ *
25
+ * Held items are judged by {@link isHumanGated}, the one shared answer to "is
26
+ * a person holding this?", rather than by a local label check (#3805).
27
+ * @module ready-queue-order
28
+ */
29
+
30
+ import {
31
+ DEFAULT_HUMAN_NEEDED_LABEL,
32
+ isHumanGated,
33
+ } from "./intake-blocker-reprobe.mjs";
34
+
35
+ /** Rank for GitHub `priority:<value>` labels; no label ranks 4. */
36
+ const GITHUB_PRIORITY_RANKS = Object.freeze({
37
+ critical: 0,
38
+ high: 1,
39
+ medium: 2,
40
+ low: 3,
41
+ });
42
+ const GITHUB_NO_PRIORITY = 4;
43
+
44
+ /** Rank for Jira priority names, across the default and legacy schemes. */
45
+ const JIRA_PRIORITY_RANKS = Object.freeze({
46
+ highest: 0,
47
+ blocker: 0,
48
+ high: 1,
49
+ critical: 1,
50
+ medium: 2,
51
+ major: 2,
52
+ low: 3,
53
+ minor: 3,
54
+ lowest: 4,
55
+ trivial: 4,
56
+ });
57
+ const JIRA_UNKNOWN_PRIORITY = 5;
58
+
59
+ /** Linear native priority: 1 urgent .. 4 low, and 0 means no priority. */
60
+ const LINEAR_NO_PRIORITY = 4;
61
+
62
+ /** Linear workflow state types that leave a child finished. */
63
+ const LINEAR_FINISHED_STATE_TYPES = new Set(["completed", "canceled"]);
64
+
65
+ /**
66
+ * @param {unknown} value
67
+ * @returns {string}
68
+ */
69
+ function text(value) {
70
+ return typeof value === "string" ? value : "";
71
+ }
72
+
73
+ /**
74
+ * A stable id as a string, or null. Numeric GitHub ids become strings so every
75
+ * comparison in this module is string-to-string.
76
+ * @param {unknown} value
77
+ * @returns {string | null}
78
+ */
79
+ function idOrNull(value) {
80
+ if (typeof value === "string" && value.trim().length > 0) {
81
+ return value.trim();
82
+ }
83
+ return Number.isSafeInteger(value) ? String(value) : null;
84
+ }
85
+
86
+ /**
87
+ * Label names from strings, `{name}` objects or a GraphQL `{nodes}` connection.
88
+ * @param {unknown} labels
89
+ * @returns {string[]}
90
+ */
91
+ function labelNames(labels) {
92
+ const list = Array.isArray(labels)
93
+ ? labels
94
+ : Array.isArray(labels?.nodes)
95
+ ? labels.nodes
96
+ : [];
97
+ return list
98
+ .map(label => (typeof label === "string" ? label : text(label?.name)))
99
+ .map(label => label.trim())
100
+ .filter(label => label.length > 0);
101
+ }
102
+
103
+ /**
104
+ * The `repo:<name>` labels, lowercased, as `repo-scope-split` reads them.
105
+ * @param {readonly string[]} labels
106
+ * @returns {string[]}
107
+ */
108
+ function repoLabelsOf(labels) {
109
+ return labels
110
+ .map(label => label.toLowerCase())
111
+ .filter(label => label.startsWith("repo:") && label.length > 5);
112
+ }
113
+
114
+ /**
115
+ * Flatten an Atlassian Document Format node to plain text, one line per block.
116
+ * A string description (the Markdown some access paths return) passes through.
117
+ * @param {unknown} node
118
+ * @returns {string}
119
+ */
120
+ function adfText(node) {
121
+ if (typeof node === "string") return node;
122
+ if (!node || typeof node !== "object") return "";
123
+ if (node.type === "text") return text(node.text);
124
+ if (node.type === "hardBreak") return "\n";
125
+ const children = Array.isArray(node.content) ? node.content : [];
126
+ const joiner = node.type === "doc" ? "\n" : "";
127
+ return children.map(adfText).join(joiner);
128
+ }
129
+
130
+ /**
131
+ * Normalize one GitHub REST issue row. Returns null for a pull-request row,
132
+ * which the issues endpoint returns alongside issues and which is never a
133
+ * dispatchable leaf.
134
+ * @param {Record<string, any>} issue - REST `issues` list row
135
+ * @param {{ repo?: string }} [options] - `owner/repo`, else read from repository_url
136
+ * @returns {ReturnType<typeof shape> | null}
137
+ */
138
+ export function normalizeGithubIssue(issue, options = {}) {
139
+ if (!issue || issue.pull_request) return null;
140
+ const labels = labelNames(issue.labels);
141
+ const ranks = labels
142
+ .map(label => label.toLowerCase())
143
+ .filter(label => label.startsWith("priority:"))
144
+ .map(label => GITHUB_PRIORITY_RANKS[label.slice("priority:".length)])
145
+ .filter(rank => rank !== undefined);
146
+ const repo =
147
+ text(options.repo) || text(issue.repository_url).split("/repos/")[1] || "";
148
+ const summary = issue.sub_issues_summary;
149
+ const assignee = issue.assignee ?? issue.assignees?.[0] ?? null;
150
+
151
+ return shape({
152
+ ref: `${repo}#${issue.number}`,
153
+ title: text(issue.title),
154
+ // GitHub "me" is the login (`gh api user --jq .login`), so match on it.
155
+ assigneeId: idOrNull(assignee?.login),
156
+ priorityRank: ranks.length > 0 ? Math.min(...ranks) : GITHUB_NO_PRIORITY,
157
+ createdAt: text(issue.created_at),
158
+ labels,
159
+ body: text(issue.body),
160
+ // Authors are trusted by stable id, never by login (ready-role-filing).
161
+ authorId: idOrNull(issue.user?.id),
162
+ isContainer: Number(summary?.total ?? 0) > Number(summary?.completed ?? 0),
163
+ });
164
+ }
165
+
166
+ /**
167
+ * Normalize one Jira REST/MCP issue. An epic (hierarchy level above 0) or an
168
+ * issue with any subtask not yet in the done category is a container.
169
+ * @param {Record<string, any>} issue - Issue with `key` and `fields`
170
+ * @returns {ReturnType<typeof shape>}
171
+ */
172
+ export function normalizeJiraIssue(issue) {
173
+ const fields = issue?.fields ?? {};
174
+ const labels = labelNames(fields.labels);
175
+ const priority = text(fields.priority?.name).trim().toLowerCase();
176
+ const type = fields.issuetype ?? {};
177
+ const openSubtasks = (Array.isArray(fields.subtasks) ? fields.subtasks : [])
178
+ .map(sub => sub?.fields?.status?.statusCategory?.key)
179
+ .filter(category => category !== "done");
180
+
181
+ return shape({
182
+ ref: text(issue?.key),
183
+ title: text(fields.summary),
184
+ assigneeId: idOrNull(fields.assignee?.accountId),
185
+ priorityRank: JIRA_PRIORITY_RANKS[priority] ?? JIRA_UNKNOWN_PRIORITY,
186
+ createdAt: text(fields.created),
187
+ labels,
188
+ body: adfText(fields.description),
189
+ authorId: idOrNull(fields.reporter?.accountId ?? fields.creator?.accountId),
190
+ isContainer:
191
+ Number(type.hierarchyLevel ?? 0) > 0 ||
192
+ text(type.name).toLowerCase() === "epic" ||
193
+ openSubtasks.length > 0,
194
+ });
195
+ }
196
+
197
+ /**
198
+ * Normalize one Linear GraphQL issue. An issue with any child not completed
199
+ * or canceled is a container.
200
+ * @param {Record<string, any>} issue - Issue node
201
+ * @returns {ReturnType<typeof shape>}
202
+ */
203
+ export function normalizeLinearIssue(issue) {
204
+ const native = Number(issue?.priority ?? 0);
205
+ const children = Array.isArray(issue?.children?.nodes)
206
+ ? issue.children.nodes
207
+ : Array.isArray(issue?.children)
208
+ ? issue.children
209
+ : [];
210
+ const labels = labelNames(issue?.labels);
211
+
212
+ return shape({
213
+ ref: text(issue?.identifier),
214
+ title: text(issue?.title),
215
+ assigneeId: idOrNull(issue?.assignee?.id),
216
+ priorityRank:
217
+ Number.isInteger(native) && native >= 1 && native <= 4
218
+ ? native - 1
219
+ : LINEAR_NO_PRIORITY,
220
+ createdAt: text(issue?.createdAt),
221
+ labels,
222
+ body: text(issue?.description),
223
+ authorId: idOrNull(issue?.creator?.id),
224
+ isContainer: children.some(
225
+ child => !LINEAR_FINISHED_STATE_TYPES.has(child?.state?.type)
226
+ ),
227
+ });
228
+ }
229
+
230
+ /**
231
+ * The one item shape every normalizer returns, in a fixed key order.
232
+ * @param {{ ref: string, title: string, assigneeId: string | null, priorityRank: number, createdAt: string, labels: string[], body: string, authorId: string | null, isContainer: boolean }} input
233
+ */
234
+ function shape(input) {
235
+ return {
236
+ ref: input.ref,
237
+ title: input.title,
238
+ assigneeId: input.assigneeId,
239
+ priorityRank: input.priorityRank,
240
+ createdAt: input.createdAt,
241
+ labels: input.labels,
242
+ body: input.body,
243
+ authorId: input.authorId,
244
+ isContainer: input.isContainer,
245
+ repoLabels: repoLabelsOf(input.labels),
246
+ };
247
+ }
248
+
249
+ /**
250
+ * Why an item may not be dispatched, or null when it may.
251
+ *
252
+ * Order matters only for the reason reported: an item can fail several rules,
253
+ * and "someone else owns it" is the most useful thing to say about it.
254
+ * @param {Record<string, any>} item
255
+ * @param {{ me: string | null, trusted: ReadonlySet<string>, humanNeededLabel: string }} context
256
+ * @returns {string | null}
257
+ */
258
+ function exclusionReason(item, context) {
259
+ const assignee = idOrNull(item.assigneeId);
260
+ if (assignee !== null && assignee !== context.me) return "assigned-to-other";
261
+ if (item.isContainer === true) return "container";
262
+ if (
263
+ isHumanGated({
264
+ labels: item.labels,
265
+ body: item.body,
266
+ comments: item.comments,
267
+ trustedHumanActorIds: item.trustedHumanActorIds,
268
+ humanNeededLabel: context.humanNeededLabel,
269
+ })
270
+ ) {
271
+ return "human-gated";
272
+ }
273
+ if (
274
+ context.trusted.size > 0 &&
275
+ !context.trusted.has(idOrNull(item.authorId))
276
+ ) {
277
+ return "untrusted-author";
278
+ }
279
+ return null;
280
+ }
281
+
282
+ /**
283
+ * @param {Record<string, any>} left
284
+ * @param {Record<string, any>} right
285
+ * @returns {number}
286
+ */
287
+ function compareWithinGroup(left, right) {
288
+ return (
289
+ Number(left.priorityRank) - Number(right.priorityRank) ||
290
+ text(left.createdAt).localeCompare(text(right.createdAt)) ||
291
+ text(left.ref).localeCompare(text(right.ref))
292
+ );
293
+ }
294
+
295
+ /**
296
+ * Order normalized Ready items for dispatch, mine first.
297
+ *
298
+ * Group 0 is assigned to `me`, group 1 unassigned; within a group, priority
299
+ * rank ascending, then creation time ascending, then ref as a stable
300
+ * tiebreak. Excluded items keep their input order. With no `me`, every
301
+ * assigned item is someone else's — the safe direction, since claiming
302
+ * assigned work steals it.
303
+ * @param {readonly Record<string, any>[]} items - Normalized items
304
+ * @param {{ me?: string | null, trustedAuthorIds?: readonly unknown[], humanNeededLabel?: string }} [options]
305
+ * @returns {{ ordered: Record<string, any>[], excluded: { ref: string, reason: string }[] }}
306
+ */
307
+ export function orderReadyQueue(items, options = {}) {
308
+ const context = {
309
+ me: idOrNull(options.me),
310
+ trusted: new Set(
311
+ (options.trustedAuthorIds ?? []).map(idOrNull).filter(Boolean)
312
+ ),
313
+ humanNeededLabel: options.humanNeededLabel ?? DEFAULT_HUMAN_NEEDED_LABEL,
314
+ };
315
+ const judged = (items ?? []).map(item => ({
316
+ item,
317
+ reason: exclusionReason(item, context),
318
+ }));
319
+ const eligible = judged
320
+ .filter(one => one.reason === null)
321
+ .map(one => one.item);
322
+ const group = item => (idOrNull(item.assigneeId) === null ? 1 : 0);
323
+
324
+ return {
325
+ ordered: [...eligible].sort(
326
+ (left, right) =>
327
+ group(left) - group(right) || compareWithinGroup(left, right)
328
+ ),
329
+ excluded: judged
330
+ .filter(one => one.reason !== null)
331
+ .map(one => ({ ref: one.item.ref, reason: one.reason })),
332
+ };
333
+ }
334
+
335
+ /**
336
+ * Refuse a queue envelope that was not paged to completion.
337
+ *
338
+ * The vendor fetch writes `{complete, pages, items}`; anything but a literal
339
+ * `complete: true` means some pages may be missing, and ordering a partial
340
+ * queue would dispatch the wrong item with full confidence.
341
+ * @template T
342
+ * @param {T} envelope - `{ complete: true, pages, items }`
343
+ * @returns {T} The same envelope
344
+ */
345
+ export function assertPagedToCompletion(envelope) {
346
+ if (envelope?.complete !== true) {
347
+ throw new Error(
348
+ "Ready queue was not paged to completion (envelope.complete !== true); refusing to order a partial queue."
349
+ );
350
+ }
351
+ return envelope;
352
+ }
@@ -31,6 +31,16 @@ scheduler means the project opted in, so the gardener is compared like any other
31
31
  means it is reported `UNSUPPORTED` (opted out), never `MISSING`. This skill stays read-only — it
32
32
  infers, it never writes the flag anywhere.
33
33
 
34
+ **Locally hosted loops.** When `.lisa.config.json` has a `sessionManager` block, the resolved fleet
35
+ carries one extra opt-in entry, `session-manager`, reported `UNSUPPORTED`. That status is honest:
36
+ `/lisa:setup:session-manager` hosts the manager as a `/loop` in a local tmux session, not on this
37
+ runtime's scheduler, so `/schedule` or Codex automations will never list it, and it is neither
38
+ `MISSING` nor provably `HEALTHY` from here. The manager records every cycle through
39
+ `automation-run-record.mjs` under loop id `session-manager`, so the row still shows its runbook
40
+ (`.lisa/automations/session-manager.runbook.md`), last outcome and history. Pass the entry's reason
41
+ through to the operator: it names the tmux session and says to check liveness with
42
+ `/lisa:session-manager status`. With no `sessionManager` block the entry does not exist.
43
+
34
44
  Resolve the expected project identifier, fleet naming prefix, queue arguments, cadence, and stack-support rules from the same contract used by `setup-automations` and `tear-down-automations`. Do **not** invent a second source of truth for fleet naming or queue resolution.
35
45
 
36
46
  ## Runtime inspection