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