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