lastlight 0.1.8 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +265 -118
- package/agent-context/rules.md +62 -9
- package/agent-context/security.md +69 -0
- package/config/default.yaml +89 -0
- package/deploy/.env.production.example +20 -2
- package/deploy/entrypoint.sh +16 -17
- package/deploy/native/README.md +160 -0
- package/deploy/native/install.sh +110 -0
- package/deploy/native/lastlight.env.example +95 -0
- package/deploy/native/lastlight.service +50 -0
- package/deploy/sandbox-entrypoint.sh +59 -42
- package/dist/admin/auth.d.ts +1 -1
- package/dist/admin/auth.js +3 -1
- package/dist/admin/auth.js.map +1 -1
- package/dist/admin/chat-session-reader.d.ts +11 -14
- package/dist/admin/chat-session-reader.js +27 -51
- package/dist/admin/chat-session-reader.js.map +1 -1
- package/dist/admin/docker.d.ts +37 -0
- package/dist/admin/docker.js +86 -1
- package/dist/admin/docker.js.map +1 -1
- package/dist/admin/index.js +38 -3
- package/dist/admin/index.js.map +1 -1
- package/dist/admin/log-search.test.d.ts +1 -0
- package/dist/admin/log-search.test.js +78 -0
- package/dist/admin/log-search.test.js.map +1 -0
- package/dist/admin/routes.d.ts +52 -3
- package/dist/admin/routes.js +780 -63
- package/dist/admin/routes.js.map +1 -1
- package/dist/admin/routes.test.js +680 -14
- package/dist/admin/routes.test.js.map +1 -1
- package/dist/admin/server-logs.test.d.ts +1 -0
- package/dist/admin/server-logs.test.js +58 -0
- package/dist/admin/server-logs.test.js.map +1 -0
- package/dist/admin/sessions.d.ts +26 -39
- package/dist/admin/sessions.js +86 -310
- package/dist/admin/sessions.js.map +1 -1
- package/dist/admin/sessions.test.d.ts +1 -0
- package/dist/admin/sessions.test.js +78 -0
- package/dist/admin/sessions.test.js.map +1 -0
- package/dist/admin/version.d.ts +15 -0
- package/dist/admin/version.js +73 -0
- package/dist/admin/version.js.map +1 -0
- package/dist/admin/version.test.d.ts +1 -0
- package/dist/admin/version.test.js +71 -0
- package/dist/admin/version.test.js.map +1 -0
- package/dist/cli-config.d.ts +55 -0
- package/dist/cli-config.js +119 -0
- package/dist/cli-config.js.map +1 -0
- package/dist/cli-config.test.d.ts +1 -0
- package/dist/cli-config.test.js +92 -0
- package/dist/cli-config.test.js.map +1 -0
- package/dist/cli-format.d.ts +19 -0
- package/dist/cli-format.js +107 -0
- package/dist/cli-format.js.map +1 -0
- package/dist/cli-server.d.ts +73 -0
- package/dist/cli-server.js +347 -0
- package/dist/cli-server.js.map +1 -0
- package/dist/cli-server.test.d.ts +1 -0
- package/dist/cli-server.test.js +38 -0
- package/dist/cli-server.test.js.map +1 -0
- package/dist/cli-timeline.d.ts +35 -0
- package/dist/cli-timeline.js +373 -0
- package/dist/cli-timeline.js.map +1 -0
- package/dist/cli-timeline.test.d.ts +1 -0
- package/dist/cli-timeline.test.js +104 -0
- package/dist/cli-timeline.test.js.map +1 -0
- package/dist/cli.d.ts +0 -19
- package/dist/cli.js +910 -194
- package/dist/cli.js.map +1 -1
- package/dist/config-overlay.test.d.ts +1 -0
- package/dist/config-overlay.test.js +112 -0
- package/dist/config-overlay.test.js.map +1 -0
- package/dist/config-resolve.d.ts +28 -0
- package/dist/config-resolve.js +47 -0
- package/dist/config-resolve.js.map +1 -0
- package/dist/config-resolve.test.d.ts +1 -0
- package/dist/config-resolve.test.js +68 -0
- package/dist/config-resolve.test.js.map +1 -0
- package/dist/config.d.ts +62 -49
- package/dist/config.js +452 -76
- package/dist/config.js.map +1 -1
- package/dist/config.test.js +201 -32
- package/dist/config.test.js.map +1 -1
- package/dist/connectors/github-webhook.js +83 -5
- package/dist/connectors/github-webhook.js.map +1 -1
- package/dist/connectors/github-webhook.test.d.ts +1 -0
- package/dist/connectors/github-webhook.test.js +156 -0
- package/dist/connectors/github-webhook.test.js.map +1 -0
- package/dist/connectors/messaging/session-manager.d.ts +18 -0
- package/dist/connectors/messaging/session-manager.js +83 -2
- package/dist/connectors/messaging/session-manager.js.map +1 -1
- package/dist/connectors/messaging/session-manager.test.d.ts +1 -0
- package/dist/connectors/messaging/session-manager.test.js +144 -0
- package/dist/connectors/messaging/session-manager.test.js.map +1 -0
- package/dist/connectors/slack/connector.d.ts +9 -0
- package/dist/connectors/slack/connector.js +15 -0
- package/dist/connectors/slack/connector.js.map +1 -1
- package/dist/connectors/slack/mrkdwn.js +81 -0
- package/dist/connectors/slack/mrkdwn.js.map +1 -1
- package/dist/connectors/slack/mrkdwn.test.js +49 -0
- package/dist/connectors/slack/mrkdwn.test.js.map +1 -1
- package/dist/connectors/types.d.ts +1 -1
- package/dist/cron/fanout.d.ts +33 -0
- package/dist/cron/fanout.js +55 -0
- package/dist/cron/fanout.js.map +1 -0
- package/dist/cron/fanout.test.d.ts +1 -0
- package/dist/cron/fanout.test.js +73 -0
- package/dist/cron/fanout.test.js.map +1 -0
- package/dist/cron/jobs.d.ts +5 -0
- package/dist/cron/jobs.js +10 -3
- package/dist/cron/jobs.js.map +1 -1
- package/dist/cron/scheduler.d.ts +12 -0
- package/dist/cron/scheduler.js +27 -1
- package/dist/cron/scheduler.js.map +1 -1
- package/dist/engine/agent-executor.d.ts +158 -0
- package/dist/engine/agent-executor.js +1140 -0
- package/dist/engine/agent-executor.js.map +1 -0
- package/dist/engine/agent-executor.test.d.ts +1 -0
- package/dist/engine/agent-executor.test.js +395 -0
- package/dist/engine/agent-executor.test.js.map +1 -0
- package/dist/engine/chat-runner.d.ts +123 -0
- package/dist/engine/chat-runner.js +379 -0
- package/dist/engine/chat-runner.js.map +1 -0
- package/dist/engine/chat-runner.test.d.ts +1 -0
- package/dist/engine/chat-runner.test.js +104 -0
- package/dist/engine/chat-runner.test.js.map +1 -0
- package/dist/engine/chat-skills.d.ts +45 -0
- package/dist/engine/chat-skills.js +184 -0
- package/dist/engine/chat-skills.js.map +1 -0
- package/dist/engine/chat-skills.test.d.ts +1 -0
- package/dist/engine/chat-skills.test.js +20 -0
- package/dist/engine/chat-skills.test.js.map +1 -0
- package/dist/engine/chat.d.ts +20 -23
- package/dist/engine/chat.js +251 -155
- package/dist/engine/chat.js.map +1 -1
- package/dist/engine/chat.test.d.ts +1 -0
- package/dist/engine/chat.test.js +42 -0
- package/dist/engine/chat.test.js.map +1 -0
- package/dist/engine/classifier.d.ts +35 -2
- package/dist/engine/classifier.js +170 -31
- package/dist/engine/classifier.js.map +1 -1
- package/dist/engine/classifier.test.d.ts +1 -0
- package/dist/engine/classifier.test.js +115 -0
- package/dist/engine/classifier.test.js.map +1 -0
- package/dist/engine/dispatcher.d.ts +60 -0
- package/dist/engine/dispatcher.js +601 -0
- package/dist/engine/dispatcher.js.map +1 -0
- package/dist/engine/dispatcher.test.d.ts +1 -0
- package/dist/engine/dispatcher.test.js +511 -0
- package/dist/engine/dispatcher.test.js.map +1 -0
- package/dist/engine/event-shim.d.ts +117 -0
- package/dist/engine/event-shim.js +435 -0
- package/dist/engine/event-shim.js.map +1 -0
- package/dist/engine/event-shim.test.d.ts +1 -0
- package/dist/engine/event-shim.test.js +194 -0
- package/dist/engine/event-shim.test.js.map +1 -0
- package/dist/engine/git-auth.d.ts +13 -17
- package/dist/engine/git-auth.js +99 -42
- package/dist/engine/git-auth.js.map +1 -1
- package/dist/engine/git-auth.test.d.ts +1 -0
- package/dist/engine/git-auth.test.js +117 -0
- package/dist/engine/git-auth.test.js.map +1 -0
- package/dist/engine/github-app-client.d.ts +7 -0
- package/dist/engine/github-app-client.js +16 -0
- package/dist/engine/github-app-client.js.map +1 -0
- package/dist/engine/github-app-client.test.d.ts +1 -0
- package/dist/engine/github-app-client.test.js +44 -0
- package/dist/engine/github-app-client.test.js.map +1 -0
- package/dist/engine/github-tools.d.ts +12 -0
- package/dist/engine/github-tools.js +261 -0
- package/dist/engine/github-tools.js.map +1 -0
- package/dist/engine/github.d.ts +1027 -43
- package/dist/engine/github.js +153 -14
- package/dist/engine/github.js.map +1 -1
- package/dist/engine/llm.d.ts +47 -0
- package/dist/engine/llm.js +221 -0
- package/dist/engine/llm.js.map +1 -0
- package/dist/engine/llm.test.d.ts +1 -0
- package/dist/engine/llm.test.js +189 -0
- package/dist/engine/llm.test.js.map +1 -0
- package/dist/engine/profiles.d.ts +249 -0
- package/dist/engine/profiles.js +42 -0
- package/dist/engine/profiles.js.map +1 -0
- package/dist/engine/router.d.ts +12 -6
- package/dist/engine/router.js +339 -81
- package/dist/engine/router.js.map +1 -1
- package/dist/engine/router.test.js +428 -78
- package/dist/engine/router.test.js.map +1 -1
- package/dist/engine/screen.d.ts +41 -0
- package/dist/engine/screen.js +93 -0
- package/dist/engine/screen.js.map +1 -0
- package/dist/engine/screen.test.d.ts +1 -0
- package/dist/engine/screen.test.js +82 -0
- package/dist/engine/screen.test.js.map +1 -0
- package/dist/index.js +423 -437
- package/dist/index.js.map +1 -1
- package/dist/managed-repos.d.ts +7 -9
- package/dist/managed-repos.js +12 -15
- package/dist/managed-repos.js.map +1 -1
- package/dist/managed-repos.test.js +24 -19
- package/dist/managed-repos.test.js.map +1 -1
- package/dist/notify/index.d.ts +15 -0
- package/dist/notify/index.js +6 -0
- package/dist/notify/index.js.map +1 -0
- package/dist/notify/model.d.ts +57 -0
- package/dist/notify/model.js +89 -0
- package/dist/notify/model.js.map +1 -0
- package/dist/notify/model.test.d.ts +1 -0
- package/dist/notify/model.test.js +109 -0
- package/dist/notify/model.test.js.map +1 -0
- package/dist/notify/notifier.d.ts +17 -0
- package/dist/notify/notifier.js +87 -0
- package/dist/notify/notifier.js.map +1 -0
- package/dist/notify/notifier.test.d.ts +1 -0
- package/dist/notify/notifier.test.js +100 -0
- package/dist/notify/notifier.test.js.map +1 -0
- package/dist/notify/render.d.ts +21 -0
- package/dist/notify/render.js +59 -0
- package/dist/notify/render.js.map +1 -0
- package/dist/notify/render.test.d.ts +1 -0
- package/dist/notify/render.test.js +65 -0
- package/dist/notify/render.test.js.map +1 -0
- package/dist/notify/transports/github.d.ts +26 -0
- package/dist/notify/transports/github.js +26 -0
- package/dist/notify/transports/github.js.map +1 -0
- package/dist/notify/transports/slack.d.ts +26 -0
- package/dist/notify/transports/slack.js +28 -0
- package/dist/notify/transports/slack.js.map +1 -0
- package/dist/notify/transports.test.d.ts +1 -0
- package/dist/notify/transports.test.js +71 -0
- package/dist/notify/transports.test.js.map +1 -0
- package/dist/notify/types.d.ts +95 -0
- package/dist/notify/types.js +16 -0
- package/dist/notify/types.js.map +1 -0
- package/dist/sandbox/docker-compose.test.d.ts +1 -0
- package/dist/sandbox/docker-compose.test.js +128 -0
- package/dist/sandbox/docker-compose.test.js.map +1 -0
- package/dist/sandbox/docker.d.ts +97 -9
- package/dist/sandbox/docker.js +192 -41
- package/dist/sandbox/docker.js.map +1 -1
- package/dist/sandbox/docker.test.d.ts +1 -0
- package/dist/sandbox/docker.test.js +137 -0
- package/dist/sandbox/docker.test.js.map +1 -0
- package/dist/sandbox/egress-allowlist.d.ts +69 -0
- package/dist/sandbox/egress-allowlist.js +164 -0
- package/dist/sandbox/egress-allowlist.js.map +1 -0
- package/dist/sandbox/egress-allowlist.test.d.ts +1 -0
- package/dist/sandbox/egress-allowlist.test.js +65 -0
- package/dist/sandbox/egress-allowlist.test.js.map +1 -0
- package/dist/sandbox/egress-firewall-config.d.ts +127 -0
- package/dist/sandbox/egress-firewall-config.js +434 -0
- package/dist/sandbox/egress-firewall-config.js.map +1 -0
- package/dist/sandbox/egress-firewall-config.test.d.ts +1 -0
- package/dist/sandbox/egress-firewall-config.test.js +244 -0
- package/dist/sandbox/egress-firewall-config.test.js.map +1 -0
- package/dist/sandbox/images.d.ts +23 -0
- package/dist/sandbox/images.js +55 -0
- package/dist/sandbox/images.js.map +1 -0
- package/dist/sandbox/index.d.ts +82 -3
- package/dist/sandbox/index.js +223 -93
- package/dist/sandbox/index.js.map +1 -1
- package/dist/sandbox/index.test.d.ts +1 -0
- package/dist/sandbox/index.test.js +157 -0
- package/dist/sandbox/index.test.js.map +1 -0
- package/dist/session-log.d.ts +63 -0
- package/dist/session-log.js +290 -0
- package/dist/session-log.js.map +1 -0
- package/dist/session-log.test.d.ts +1 -0
- package/dist/session-log.test.js +85 -0
- package/dist/session-log.test.js.map +1 -0
- package/dist/setup.d.ts +31 -12
- package/dist/setup.js +516 -174
- package/dist/setup.js.map +1 -1
- package/dist/setup.test.js +138 -6
- package/dist/setup.test.js.map +1 -1
- package/dist/state/approval-store.d.ts +86 -0
- package/dist/state/approval-store.js +140 -0
- package/dist/state/approval-store.js.map +1 -0
- package/dist/state/build-assets.d.ts +50 -0
- package/dist/state/build-assets.js +154 -0
- package/dist/state/build-assets.js.map +1 -0
- package/dist/state/build-assets.test.d.ts +1 -0
- package/dist/state/build-assets.test.js +106 -0
- package/dist/state/build-assets.test.js.map +1 -0
- package/dist/state/db.d.ts +65 -333
- package/dist/state/db.js +112 -835
- package/dist/state/db.js.map +1 -1
- package/dist/state/db.test.js +127 -91
- package/dist/state/db.test.js.map +1 -1
- package/dist/state/execution-store.d.ts +257 -0
- package/dist/state/execution-store.js +527 -0
- package/dist/state/execution-store.js.map +1 -0
- package/dist/state/migrate.d.ts +15 -0
- package/dist/state/migrate.js +175 -0
- package/dist/state/migrate.js.map +1 -0
- package/dist/state/workflow-run-store.d.ts +195 -0
- package/dist/state/workflow-run-store.js +363 -0
- package/dist/state/workflow-run-store.js.map +1 -0
- package/dist/state/workflow-run-store.test.d.ts +1 -0
- package/dist/state/workflow-run-store.test.js +264 -0
- package/dist/state/workflow-run-store.test.js.map +1 -0
- package/dist/telemetry/index.d.ts +38 -0
- package/dist/telemetry/index.js +253 -0
- package/dist/telemetry/index.js.map +1 -0
- package/dist/telemetry/index.test.d.ts +1 -0
- package/dist/telemetry/index.test.js +73 -0
- package/dist/telemetry/index.test.js.map +1 -0
- package/dist/telemetry/pi-events.d.ts +12 -0
- package/dist/telemetry/pi-events.js +134 -0
- package/dist/telemetry/pi-events.js.map +1 -0
- package/dist/telemetry/pi-events.test.d.ts +1 -0
- package/dist/telemetry/pi-events.test.js +69 -0
- package/dist/telemetry/pi-events.test.js.map +1 -0
- package/dist/workflows/dag.d.ts +13 -1
- package/dist/workflows/dag.js +7 -3
- package/dist/workflows/dag.js.map +1 -1
- package/dist/workflows/dag.test.js +22 -0
- package/dist/workflows/dag.test.js.map +1 -1
- package/dist/workflows/golden-build.test.d.ts +1 -0
- package/dist/workflows/golden-build.test.js +45 -0
- package/dist/workflows/golden-build.test.js.map +1 -0
- package/dist/workflows/loader-overlay.test.d.ts +1 -0
- package/dist/workflows/loader-overlay.test.js +101 -0
- package/dist/workflows/loader-overlay.test.js.map +1 -0
- package/dist/workflows/loader.d.ts +36 -11
- package/dist/workflows/loader.js +311 -87
- package/dist/workflows/loader.js.map +1 -1
- package/dist/workflows/loader.test.js +114 -10
- package/dist/workflows/loader.test.js.map +1 -1
- package/dist/workflows/loop-eval.js +2 -0
- package/dist/workflows/loop-eval.js.map +1 -1
- package/dist/workflows/loop-eval.test.js +14 -0
- package/dist/workflows/loop-eval.test.js.map +1 -1
- package/dist/workflows/phase-executor.d.ts +145 -0
- package/dist/workflows/phase-executor.js +691 -0
- package/dist/workflows/phase-executor.js.map +1 -0
- package/dist/workflows/phase-executor.test.d.ts +1 -0
- package/dist/workflows/phase-executor.test.js +434 -0
- package/dist/workflows/phase-executor.test.js.map +1 -0
- package/dist/workflows/phase-ref.d.ts +44 -0
- package/dist/workflows/phase-ref.js +78 -0
- package/dist/workflows/phase-ref.js.map +1 -0
- package/dist/workflows/phase-ref.test.d.ts +1 -0
- package/dist/workflows/phase-ref.test.js +24 -0
- package/dist/workflows/phase-ref.test.js.map +1 -0
- package/dist/workflows/resume.d.ts +5 -11
- package/dist/workflows/resume.js +88 -7
- package/dist/workflows/resume.js.map +1 -1
- package/dist/workflows/runner.d.ts +34 -27
- package/dist/workflows/runner.js +252 -930
- package/dist/workflows/runner.js.map +1 -1
- package/dist/workflows/runner.test.js +346 -35
- package/dist/workflows/runner.test.js.map +1 -1
- package/dist/workflows/schema.d.ts +52 -10
- package/dist/workflows/schema.js +147 -7
- package/dist/workflows/schema.js.map +1 -1
- package/dist/workflows/simple.d.ts +67 -3
- package/dist/workflows/simple.js +248 -87
- package/dist/workflows/simple.js.map +1 -1
- package/dist/workflows/simple.test.d.ts +1 -0
- package/dist/workflows/simple.test.js +107 -0
- package/dist/workflows/simple.test.js.map +1 -0
- package/dist/workflows/templates.d.ts +29 -0
- package/dist/workflows/templates.js +42 -4
- package/dist/workflows/templates.js.map +1 -1
- package/dist/workflows/templates.test.js +103 -0
- package/dist/workflows/templates.test.js.map +1 -1
- package/dist/workflows/triggers.d.ts +21 -0
- package/dist/workflows/triggers.js +44 -0
- package/dist/workflows/triggers.js.map +1 -0
- package/dist/workflows/verdict.d.ts +19 -0
- package/dist/workflows/verdict.js +30 -0
- package/dist/workflows/verdict.js.map +1 -0
- package/dist/workflows/verdict.test.d.ts +1 -0
- package/dist/workflows/verdict.test.js +44 -0
- package/dist/workflows/verdict.test.js.map +1 -0
- package/dist/worktree/manager.d.ts +1 -1
- package/dist/worktree/manager.js +14 -14
- package/dist/worktree/manager.js.map +1 -1
- package/dist/worktree/manager.test.d.ts +1 -0
- package/dist/worktree/manager.test.js +87 -0
- package/dist/worktree/manager.test.js.map +1 -0
- package/docker-compose.yml +206 -5
- package/package.json +19 -7
- package/sandbox.Dockerfile +97 -18
- package/skills/README.md +40 -0
- package/skills/browser-qa/SKILL.md +193 -0
- package/skills/browser-qa/scripts/agent-browser.mjs +380 -0
- package/skills/building/SKILL.md +95 -0
- package/skills/chat/SKILL.md +30 -11
- package/skills/code-review/SKILL.md +59 -0
- package/skills/debug-production/SKILL.md +108 -0
- package/skills/demo/SKILL.md +194 -0
- package/skills/demo/scripts/compose-demo.sh +205 -0
- package/skills/issue-answer/SKILL.md +90 -0
- package/skills/issue-comment/SKILL.md +38 -24
- package/skills/issue-triage/SKILL.md +103 -37
- package/skills/issue-triage/references/AGENT-BRIEF.md +61 -0
- package/skills/pr-comment/SKILL.md +64 -0
- package/skills/pr-review/SKILL.md +65 -49
- package/skills/qa-test/SKILL.md +97 -0
- package/skills/repo-health/SKILL.md +48 -45
- package/skills/security-feedback/SKILL.md +173 -0
- package/skills/security-feedback/references/templates.md +77 -0
- package/skills/security-review/SKILL.md +213 -0
- package/skills/security-review/references/issue-format.md +302 -0
- package/skills/verify/SKILL.md +118 -0
- package/workflows/answer.yaml +37 -0
- package/workflows/build.yaml +32 -22
- package/workflows/cron-security.yaml +6 -0
- package/workflows/demo.yaml +55 -0
- package/workflows/explore.yaml +9 -0
- package/workflows/pr-comment.yaml +16 -0
- package/workflows/pr-fix.yaml +3 -0
- package/workflows/pr-review.yaml +4 -1
- package/workflows/prompts/answer.md +67 -0
- package/workflows/prompts/architect.md +24 -7
- package/workflows/prompts/demo.md +100 -0
- package/workflows/prompts/executor.md +29 -20
- package/workflows/prompts/explore-ask.md +51 -22
- package/workflows/prompts/explore-publish.md +2 -2
- package/workflows/prompts/explore-read.md +24 -10
- package/workflows/prompts/explore-synthesize.md +15 -8
- package/workflows/prompts/fix.md +9 -10
- package/workflows/prompts/guardrails.md +26 -8
- package/workflows/prompts/pr-fix.md +6 -7
- package/workflows/prompts/pr.md +11 -10
- package/workflows/prompts/qa-browser.md +95 -0
- package/workflows/prompts/qa-synth.md +44 -0
- package/workflows/prompts/qa-test.md +60 -0
- package/workflows/prompts/re-reviewer.md +8 -9
- package/workflows/prompts/reviewer.md +10 -9
- package/workflows/prompts/verify-browser.md +93 -0
- package/workflows/prompts/verify-synth.md +44 -0
- package/workflows/prompts/verify.md +63 -0
- package/workflows/qa-test.yaml +87 -0
- package/workflows/security-feedback.yaml +25 -0
- package/workflows/security-review.yaml +29 -0
- package/workflows/verify.yaml +85 -0
- package/deploy/mcp-config.tmpl.json +0 -14
- package/dist/cron/rate-limits.d.ts +0 -26
- package/dist/cron/rate-limits.js +0 -193
- package/dist/cron/rate-limits.js.map +0 -1
- package/dist/engine/executor.d.ts +0 -75
- package/dist/engine/executor.js +0 -292
- package/dist/engine/executor.js.map +0 -1
- package/mcp-github-app/package.json +0 -18
- package/mcp-github-app/src/auth.js +0 -66
- package/mcp-github-app/src/github.js +0 -371
- package/mcp-github-app/src/index.js +0 -534
- package/skills/devops/webhook-subscriptions/SKILL.md +0 -180
- package/skills/github/DESCRIPTION.md +0 -3
- package/skills/github/codebase-inspection/SKILL.md +0 -115
- package/skills/github/github-auth/SKILL.md +0 -54
- package/skills/github/github-auth/scripts/gh-env.sh +0 -66
- package/skills/github/github-code-review/SKILL.md +0 -480
- package/skills/github/github-code-review/references/review-output-template.md +0 -74
- package/skills/github/github-issues/SKILL.md +0 -369
- package/skills/github/github-issues/templates/bug-report.md +0 -35
- package/skills/github/github-issues/templates/feature-request.md +0 -31
- package/skills/github/github-pr-workflow/SKILL.md +0 -395
- package/skills/github/github-pr-workflow/references/ci-troubleshooting.md +0 -183
- package/skills/github/github-pr-workflow/references/conventional-commits.md +0 -71
- package/skills/github/github-pr-workflow/templates/pr-body-bugfix.md +0 -35
- package/skills/github/github-pr-workflow/templates/pr-body-feature.md +0 -33
- package/skills/github/github-repo-management/SKILL.md +0 -515
- package/skills/github/github-repo-management/references/github-api-cheatsheet.md +0 -161
- package/skills/github-orchestrator/SKILL.md +0 -103
- package/skills/mcp/DESCRIPTION.md +0 -3
- package/skills/mcp/mcporter/SKILL.md +0 -122
- package/skills/mcp/native-mcp/SKILL.md +0 -356
- package/skills/software-development/architect/SKILL.md +0 -154
- package/skills/software-development/assure-guardrails/SKILL.md +0 -239
- package/skills/software-development/plan/SKILL.md +0 -64
- package/skills/software-development/requesting-code-review/SKILL.md +0 -291
- package/skills/software-development/subagent-driven-development/SKILL.md +0 -433
- package/skills/software-development/systematic-debugging/SKILL.md +0 -366
- package/skills/software-development/test-driven-development/SKILL.md +0 -342
package/dist/state/db.js
CHANGED
|
@@ -1,870 +1,147 @@
|
|
|
1
1
|
import Database from "better-sqlite3";
|
|
2
2
|
import { resolve } from "path";
|
|
3
|
+
import { migrate } from "./migrate.js";
|
|
4
|
+
import { ExecutionStore } from "./execution-store.js";
|
|
5
|
+
import { ApprovalStore } from "./approval-store.js";
|
|
6
|
+
import { WorkflowRunStore } from "./workflow-run-store.js";
|
|
7
|
+
export { ExecutionStore } from "./execution-store.js";
|
|
8
|
+
export { ApprovalStore } from "./approval-store.js";
|
|
9
|
+
export { WorkflowRunStore } from "./workflow-run-store.js";
|
|
3
10
|
const DEFAULT_DB_PATH = "lastlight.db";
|
|
4
11
|
/**
|
|
5
12
|
* Lightweight SQLite state for operational tracking.
|
|
6
13
|
* NOT for conversation history — only execution logs and rate limits.
|
|
14
|
+
*
|
|
15
|
+
* Construction root for the three per-table stores (issue #97): it opens the
|
|
16
|
+
* single `Database` connection, runs {@link migrate}, and builds
|
|
17
|
+
* {@link ExecutionStore} / {@link ApprovalStore} / {@link WorkflowRunStore} on
|
|
18
|
+
* top of it (all sharing the one connection so better-sqlite3 transactions
|
|
19
|
+
* span every store). Callers reach the stores via `db.runs` / `db.approvals` /
|
|
20
|
+
* `db.executions`. StateDb itself retains only the cross-cutting cron-override
|
|
21
|
+
* and workflow kill-switch concerns.
|
|
7
22
|
*/
|
|
8
23
|
export class StateDb {
|
|
9
24
|
db;
|
|
25
|
+
/** Owns the `executions` ledger. */
|
|
26
|
+
executions;
|
|
27
|
+
/** Owns the `workflow_approvals` table. */
|
|
28
|
+
approvals;
|
|
29
|
+
/** Aggregate root for `workflow_runs` + the atomic lifecycle operations. */
|
|
30
|
+
runs;
|
|
10
31
|
constructor(dbPath) {
|
|
11
|
-
|
|
32
|
+
// ":memory:" stays a real per-connection in-memory DB (used by tests for
|
|
33
|
+
// isolation) — only filesystem paths go through resolve(). Previously
|
|
34
|
+
// resolve(":memory:") produced a shared on-disk file named ":memory:",
|
|
35
|
+
// which let parallel test files contend on one DB and deadlock once the
|
|
36
|
+
// stores started holding write transactions.
|
|
37
|
+
const target = dbPath === ":memory:" ? ":memory:" : resolve(dbPath || DEFAULT_DB_PATH);
|
|
38
|
+
this.db = new Database(target);
|
|
12
39
|
this.db.pragma("journal_mode = WAL");
|
|
13
|
-
this.
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
this.db.
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
finished_at TEXT,
|
|
26
|
-
success INTEGER,
|
|
27
|
-
error TEXT,
|
|
28
|
-
turns INTEGER,
|
|
29
|
-
duration_ms INTEGER
|
|
30
|
-
);
|
|
31
|
-
|
|
32
|
-
CREATE TABLE IF NOT EXISTS rate_limits (
|
|
33
|
-
resource TEXT PRIMARY KEY,
|
|
34
|
-
remaining INTEGER,
|
|
35
|
-
reset_at TEXT,
|
|
36
|
-
updated_at TEXT
|
|
37
|
-
);
|
|
38
|
-
|
|
39
|
-
CREATE TABLE IF NOT EXISTS system_status (
|
|
40
|
-
component TEXT PRIMARY KEY,
|
|
41
|
-
state TEXT NOT NULL,
|
|
42
|
-
reason TEXT,
|
|
43
|
-
since TEXT NOT NULL,
|
|
44
|
-
updated_at TEXT NOT NULL
|
|
45
|
-
);
|
|
46
|
-
|
|
47
|
-
CREATE INDEX IF NOT EXISTS idx_executions_trigger ON executions(trigger_type, trigger_id);
|
|
48
|
-
CREATE INDEX IF NOT EXISTS idx_executions_skill ON executions(skill, started_at);
|
|
49
|
-
|
|
50
|
-
CREATE TABLE IF NOT EXISTS workflow_runs (
|
|
51
|
-
id TEXT PRIMARY KEY,
|
|
52
|
-
workflow_name TEXT NOT NULL,
|
|
53
|
-
trigger_id TEXT NOT NULL,
|
|
54
|
-
repo TEXT,
|
|
55
|
-
issue_number INTEGER,
|
|
56
|
-
current_phase TEXT NOT NULL,
|
|
57
|
-
phase_history TEXT NOT NULL DEFAULT '[]',
|
|
58
|
-
status TEXT NOT NULL DEFAULT 'running',
|
|
59
|
-
context TEXT,
|
|
60
|
-
started_at TEXT NOT NULL,
|
|
61
|
-
updated_at TEXT NOT NULL,
|
|
62
|
-
finished_at TEXT
|
|
63
|
-
);
|
|
64
|
-
CREATE INDEX IF NOT EXISTS idx_workflow_runs_trigger ON workflow_runs(trigger_id, status);
|
|
65
|
-
CREATE INDEX IF NOT EXISTS idx_workflow_runs_status ON workflow_runs(status);
|
|
66
|
-
|
|
67
|
-
CREATE TABLE IF NOT EXISTS workflow_approvals (
|
|
68
|
-
id TEXT PRIMARY KEY,
|
|
69
|
-
workflow_run_id TEXT NOT NULL,
|
|
70
|
-
gate TEXT NOT NULL,
|
|
71
|
-
summary TEXT NOT NULL,
|
|
72
|
-
status TEXT NOT NULL DEFAULT 'pending',
|
|
73
|
-
requested_by TEXT,
|
|
74
|
-
responded_by TEXT,
|
|
75
|
-
response TEXT,
|
|
76
|
-
responded_at TEXT,
|
|
77
|
-
created_at TEXT NOT NULL
|
|
78
|
-
);
|
|
79
|
-
CREATE INDEX IF NOT EXISTS idx_approvals_workflow ON workflow_approvals(workflow_run_id);
|
|
80
|
-
CREATE INDEX IF NOT EXISTS idx_approvals_status ON workflow_approvals(status);
|
|
81
|
-
`);
|
|
82
|
-
// Add node_statuses column for DAG parallelism (safe for existing DBs)
|
|
83
|
-
try {
|
|
84
|
-
this.db.exec(`ALTER TABLE workflow_runs ADD COLUMN node_statuses TEXT`);
|
|
85
|
-
}
|
|
86
|
-
catch {
|
|
87
|
-
// Column already exists — ignore
|
|
88
|
-
}
|
|
89
|
-
// Mutable phase-to-phase state for features like the socratic explore
|
|
90
|
-
// loop that accumulate data across reply-gate pauses.
|
|
91
|
-
try {
|
|
92
|
-
this.db.exec(`ALTER TABLE workflow_runs ADD COLUMN scratch TEXT`);
|
|
93
|
-
}
|
|
94
|
-
catch {
|
|
95
|
-
// Column already exists — ignore
|
|
96
|
-
}
|
|
97
|
-
// Gate flavor: 'approve' (explicit approve/reject) vs 'reply' (resolves
|
|
98
|
-
// on any free-form reply in the same thread — used by the socratic
|
|
99
|
-
// explore loop).
|
|
100
|
-
try {
|
|
101
|
-
this.db.exec(`ALTER TABLE workflow_approvals ADD COLUMN kind TEXT NOT NULL DEFAULT 'approve'`);
|
|
102
|
-
}
|
|
103
|
-
catch {
|
|
104
|
-
// Column already exists — ignore
|
|
105
|
-
}
|
|
106
|
-
// Add session_id column to executions so the dashboard can resolve a
|
|
107
|
-
// phase click to its agent session log. Additive — safe on existing DBs.
|
|
108
|
-
try {
|
|
109
|
-
this.db.exec(`ALTER TABLE executions ADD COLUMN session_id TEXT`);
|
|
110
|
-
}
|
|
111
|
-
catch {
|
|
112
|
-
// Column already exists — ignore
|
|
113
|
-
}
|
|
114
|
-
// Per-execution Claude usage metrics, captured from the stream-json
|
|
115
|
-
// result message. Lets the dashboard show cost / tokens per phase and
|
|
116
|
-
// lets us aggregate spend later. All additive, safe on existing DBs.
|
|
117
|
-
for (const col of [
|
|
118
|
-
"cost_usd REAL",
|
|
119
|
-
"input_tokens INTEGER",
|
|
120
|
-
"cache_creation_input_tokens INTEGER",
|
|
121
|
-
"cache_read_input_tokens INTEGER",
|
|
122
|
-
"output_tokens INTEGER",
|
|
123
|
-
"api_duration_ms INTEGER",
|
|
124
|
-
"stop_reason TEXT",
|
|
125
|
-
// Links a phase execution to its owning workflow_run. Dedup and
|
|
126
|
-
// "already running / done" checks scope to this column so a fresh
|
|
127
|
-
// re-trigger of the same workflow on the same issue creates new
|
|
128
|
-
// executions instead of reusing the old run's rows.
|
|
129
|
-
"workflow_run_id TEXT",
|
|
130
|
-
]) {
|
|
131
|
-
try {
|
|
132
|
-
this.db.exec(`ALTER TABLE executions ADD COLUMN ${col}`);
|
|
133
|
-
}
|
|
134
|
-
catch {
|
|
135
|
-
// Column already exists — ignore
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
try {
|
|
139
|
-
this.db.exec(`CREATE INDEX IF NOT EXISTS idx_executions_workflow_run ON executions(workflow_run_id, skill)`);
|
|
140
|
-
}
|
|
141
|
-
catch {
|
|
142
|
-
// Index already exists — ignore
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
recordStart(record) {
|
|
146
|
-
this.db.prepare(`
|
|
147
|
-
INSERT INTO executions (id, trigger_type, trigger_id, skill, repo, issue_number, started_at, workflow_run_id)
|
|
148
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
149
|
-
`).run(record.id, record.triggerType, record.triggerId, record.skill, record.repo, record.issueNumber, record.startedAt, record.workflowRunId ?? null);
|
|
150
|
-
}
|
|
151
|
-
/**
|
|
152
|
-
* Mid-run update: persist the agent session id as soon as the executor sees
|
|
153
|
-
* the stream-json `system/init` line. Lets the dashboard surface the live
|
|
154
|
-
* session log for an in-flight phase, not just for completed ones.
|
|
155
|
-
*/
|
|
156
|
-
recordSessionId(id, sessionId) {
|
|
157
|
-
this.db.prepare(`UPDATE executions SET session_id = ? WHERE id = ?`).run(sessionId, id);
|
|
158
|
-
}
|
|
159
|
-
recordFinish(id, result) {
|
|
160
|
-
this.db.prepare(`
|
|
161
|
-
UPDATE executions
|
|
162
|
-
SET finished_at = ?,
|
|
163
|
-
success = ?,
|
|
164
|
-
error = ?,
|
|
165
|
-
turns = ?,
|
|
166
|
-
duration_ms = ?,
|
|
167
|
-
session_id = COALESCE(?, session_id),
|
|
168
|
-
cost_usd = COALESCE(?, cost_usd),
|
|
169
|
-
input_tokens = COALESCE(?, input_tokens),
|
|
170
|
-
cache_creation_input_tokens = COALESCE(?, cache_creation_input_tokens),
|
|
171
|
-
cache_read_input_tokens = COALESCE(?, cache_read_input_tokens),
|
|
172
|
-
output_tokens = COALESCE(?, output_tokens),
|
|
173
|
-
api_duration_ms = COALESCE(?, api_duration_ms),
|
|
174
|
-
stop_reason = COALESCE(?, stop_reason)
|
|
175
|
-
WHERE id = ?
|
|
176
|
-
`).run(new Date().toISOString(), result.success ? 1 : 0, result.error, result.turns, result.durationMs, result.sessionId ?? null, result.costUsd ?? null, result.inputTokens ?? null, result.cacheCreationInputTokens ?? null, result.cacheReadInputTokens ?? null, result.outputTokens ?? null, result.apiDurationMs ?? null, result.stopReason ?? null, id);
|
|
177
|
-
}
|
|
178
|
-
/**
|
|
179
|
-
* List chat threads aggregated from the executions table.
|
|
180
|
-
*
|
|
181
|
-
* Each row collapses every execution sharing a `trigger_id` (the messaging
|
|
182
|
-
* session id) into one summary so the dashboard's chat-sessions tab can
|
|
183
|
-
* show one card per Slack thread, not one per turn. Joined with
|
|
184
|
-
* `messaging_sessions` for the agent SDK session id (used to resolve to
|
|
185
|
-
* the on-disk jsonl when streaming messages) and with `messaging_messages`
|
|
186
|
-
* for the most recent assistant text (preview snippet).
|
|
187
|
-
*/
|
|
188
|
-
listChatThreads(limit) {
|
|
189
|
-
const rows = this.db.prepare(`
|
|
190
|
-
SELECT
|
|
191
|
-
e.trigger_id AS triggerId,
|
|
192
|
-
ms.agent_session_id AS agentSessionId,
|
|
193
|
-
MIN(e.started_at) AS firstStartedAt,
|
|
194
|
-
MAX(COALESCE(e.finished_at, e.started_at)) AS lastActivityAt,
|
|
195
|
-
COUNT(*) AS turnCount,
|
|
196
|
-
SUM(COALESCE(e.cost_usd, 0)) AS totalCost,
|
|
197
|
-
SUM(COALESCE(e.input_tokens, 0)) AS inputTokens,
|
|
198
|
-
SUM(COALESCE(e.output_tokens, 0)) AS outputTokens,
|
|
199
|
-
SUM(COALESCE(e.cache_read_input_tokens, 0)) AS cacheReadTokens,
|
|
200
|
-
(
|
|
201
|
-
SELECT mm.content
|
|
202
|
-
FROM messaging_messages mm
|
|
203
|
-
WHERE mm.session_id = e.trigger_id AND mm.role = 'assistant'
|
|
204
|
-
ORDER BY mm.timestamp DESC
|
|
205
|
-
LIMIT 1
|
|
206
|
-
) AS lastAssistantContent
|
|
207
|
-
FROM executions e
|
|
208
|
-
LEFT JOIN messaging_sessions ms ON ms.id = e.trigger_id
|
|
209
|
-
WHERE e.skill = 'chat'
|
|
210
|
-
GROUP BY e.trigger_id
|
|
211
|
-
ORDER BY lastActivityAt DESC
|
|
212
|
-
LIMIT ?
|
|
213
|
-
`).all(limit);
|
|
214
|
-
return rows;
|
|
215
|
-
}
|
|
216
|
-
/** Look up a single chat thread by its messaging session id (= trigger_id). */
|
|
217
|
-
getChatThread(triggerId) {
|
|
218
|
-
const row = this.db.prepare(`
|
|
219
|
-
SELECT
|
|
220
|
-
e.trigger_id AS triggerId,
|
|
221
|
-
ms.agent_session_id AS agentSessionId,
|
|
222
|
-
MIN(e.started_at) AS firstStartedAt,
|
|
223
|
-
MAX(COALESCE(e.finished_at, e.started_at)) AS lastActivityAt,
|
|
224
|
-
COUNT(*) AS turnCount,
|
|
225
|
-
SUM(COALESCE(e.cost_usd, 0)) AS totalCost,
|
|
226
|
-
SUM(COALESCE(e.input_tokens, 0)) AS inputTokens,
|
|
227
|
-
SUM(COALESCE(e.output_tokens, 0)) AS outputTokens,
|
|
228
|
-
SUM(COALESCE(e.cache_read_input_tokens, 0)) AS cacheReadTokens,
|
|
229
|
-
(
|
|
230
|
-
SELECT mm.content
|
|
231
|
-
FROM messaging_messages mm
|
|
232
|
-
WHERE mm.session_id = e.trigger_id AND mm.role = 'assistant'
|
|
233
|
-
ORDER BY mm.timestamp DESC
|
|
234
|
-
LIMIT 1
|
|
235
|
-
) AS lastAssistantContent
|
|
236
|
-
FROM executions e
|
|
237
|
-
LEFT JOIN messaging_sessions ms ON ms.id = e.trigger_id
|
|
238
|
-
WHERE e.skill = 'chat' AND e.trigger_id = ?
|
|
239
|
-
GROUP BY e.trigger_id
|
|
240
|
-
`).get(triggerId);
|
|
241
|
-
return row ?? null;
|
|
242
|
-
}
|
|
243
|
-
/** Check if a skill is currently running for a given trigger */
|
|
244
|
-
isRunning(skill, triggerId) {
|
|
245
|
-
const row = this.db.prepare(`
|
|
246
|
-
SELECT 1 FROM executions
|
|
247
|
-
WHERE skill = ? AND trigger_id = ? AND finished_at IS NULL
|
|
248
|
-
LIMIT 1
|
|
249
|
-
`).get(skill, triggerId);
|
|
250
|
-
return !!row;
|
|
251
|
-
}
|
|
252
|
-
/** Check if a skill has already completed successfully for a given trigger */
|
|
253
|
-
isCompleted(skill, triggerId) {
|
|
254
|
-
const row = this.db.prepare(`
|
|
255
|
-
SELECT 1 FROM executions
|
|
256
|
-
WHERE skill = ? AND trigger_id = ? AND success = 1
|
|
257
|
-
LIMIT 1
|
|
258
|
-
`).get(skill, triggerId);
|
|
259
|
-
return !!row;
|
|
260
|
-
}
|
|
261
|
-
/**
|
|
262
|
-
* Check if a phase should run: not currently running AND not already
|
|
263
|
-
* succeeded. When `workflowRunId` is provided, scopes the check to that
|
|
264
|
-
* specific run — a fresh re-trigger (new workflow_run_id) will see no
|
|
265
|
-
* matching rows and correctly return "run" even if a prior completed run
|
|
266
|
-
* for the same trigger exists. Omit `workflowRunId` for legacy callers
|
|
267
|
-
* (e.g. chat/status-report dedup) that want the old trigger-wide scope.
|
|
268
|
-
*/
|
|
269
|
-
shouldRunPhase(skill, triggerId, workflowRunId) {
|
|
270
|
-
const [scopeClause, scopeParam] = workflowRunId
|
|
271
|
-
? ["workflow_run_id = ?", workflowRunId]
|
|
272
|
-
: ["trigger_id = ?", triggerId];
|
|
273
|
-
const running = this.db.prepare(`
|
|
274
|
-
SELECT 1 FROM executions
|
|
275
|
-
WHERE skill = ? AND ${scopeClause} AND finished_at IS NULL
|
|
276
|
-
LIMIT 1
|
|
277
|
-
`).get(skill, scopeParam);
|
|
278
|
-
if (running)
|
|
279
|
-
return "running";
|
|
280
|
-
const done = this.db.prepare(`
|
|
281
|
-
SELECT 1 FROM executions
|
|
282
|
-
WHERE skill = ? AND ${scopeClause} AND success = 1
|
|
283
|
-
LIMIT 1
|
|
284
|
-
`).get(skill, scopeParam);
|
|
285
|
-
if (done)
|
|
286
|
-
return "done";
|
|
287
|
-
return "run";
|
|
288
|
-
}
|
|
289
|
-
/** Mark all stale "running" executions for a skill/trigger as failed.
|
|
290
|
-
* Called when we detect no matching Docker container is alive. */
|
|
291
|
-
markStaleAsFailed(skill, triggerId, workflowRunId) {
|
|
292
|
-
const [scopeClause, scopeParam] = workflowRunId
|
|
293
|
-
? ["workflow_run_id = ?", workflowRunId]
|
|
294
|
-
: ["trigger_id = ?", triggerId];
|
|
295
|
-
const result = this.db.prepare(`
|
|
296
|
-
UPDATE executions
|
|
297
|
-
SET finished_at = ?, success = 0, error = 'stale: container no longer running'
|
|
298
|
-
WHERE skill = ? AND ${scopeClause} AND finished_at IS NULL
|
|
299
|
-
`).run(new Date().toISOString(), skill, scopeParam);
|
|
300
|
-
return result.changes;
|
|
301
|
-
}
|
|
302
|
-
/**
|
|
303
|
-
* Mark every unfinished execution row for a trigger as failed, regardless
|
|
304
|
-
* of skill. Used by the boot-time recovery sweep to clear out phases that
|
|
305
|
-
* were running when the harness crashed/restarted, so the runner's dedup
|
|
306
|
-
* logic doesn't think they're still in flight.
|
|
307
|
-
*/
|
|
308
|
-
markAllStaleForTrigger(triggerId, reason) {
|
|
309
|
-
const result = this.db.prepare(`
|
|
310
|
-
UPDATE executions
|
|
311
|
-
SET finished_at = ?, success = 0, error = ?
|
|
312
|
-
WHERE trigger_id = ? AND finished_at IS NULL
|
|
313
|
-
`).run(new Date().toISOString(), reason, triggerId);
|
|
314
|
-
return result.changes;
|
|
315
|
-
}
|
|
316
|
-
/**
|
|
317
|
-
* Mark the most recent execution for a skill/trigger as failed.
|
|
318
|
-
* Used when the agent SDK reported success but the orchestrator decided
|
|
319
|
-
* the business outcome was a failure (e.g. guardrails BLOCKED, reviewer
|
|
320
|
-
* REQUEST_CHANGES). Without this, runPhase would skip the phase on the
|
|
321
|
-
* next run because the DB still says success=1.
|
|
322
|
-
*/
|
|
323
|
-
markLatestAsFailed(skill, triggerId, reason, workflowRunId) {
|
|
324
|
-
const [scopeClause, scopeParam] = workflowRunId
|
|
325
|
-
? ["workflow_run_id = ?", workflowRunId]
|
|
326
|
-
: ["trigger_id = ?", triggerId];
|
|
327
|
-
const result = this.db.prepare(`
|
|
328
|
-
UPDATE executions
|
|
329
|
-
SET success = 0, error = ?
|
|
330
|
-
WHERE id = (
|
|
331
|
-
SELECT id FROM executions
|
|
332
|
-
WHERE skill = ? AND ${scopeClause}
|
|
333
|
-
ORDER BY started_at DESC
|
|
334
|
-
LIMIT 1
|
|
335
|
-
)
|
|
336
|
-
`).run(reason, skill, scopeParam);
|
|
337
|
-
return result.changes;
|
|
338
|
-
}
|
|
339
|
-
/** Get recent executions for a skill */
|
|
340
|
-
recentExecutions(skill, limit = 10) {
|
|
341
|
-
return this.db.prepare(`
|
|
342
|
-
SELECT * FROM executions
|
|
343
|
-
WHERE skill = ?
|
|
344
|
-
ORDER BY started_at DESC
|
|
345
|
-
LIMIT ?
|
|
346
|
-
`).all(skill, limit);
|
|
40
|
+
migrate(this.db);
|
|
41
|
+
this.executions = new ExecutionStore(this.db);
|
|
42
|
+
this.approvals = new ApprovalStore(this.db);
|
|
43
|
+
this.runs = new WorkflowRunStore(this.db, { approvals: this.approvals });
|
|
44
|
+
}
|
|
45
|
+
// ── Cron overrides ─────────────────────────────────────────────
|
|
46
|
+
/** Get the override row for a single cron, or null if none. */
|
|
47
|
+
getCronOverride(name) {
|
|
48
|
+
const row = this.db
|
|
49
|
+
.prepare(`SELECT name, enabled, schedule, updated_at, updated_by FROM cron_overrides WHERE name = ?`)
|
|
50
|
+
.get(name);
|
|
51
|
+
return row ? this.deserializeCronOverride(row) : null;
|
|
347
52
|
}
|
|
348
|
-
/**
|
|
349
|
-
|
|
350
|
-
const rows = this.db
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
LIMIT 10
|
|
355
|
-
`).all(skill);
|
|
356
|
-
let count = 0;
|
|
53
|
+
/** All override rows keyed by cron name. */
|
|
54
|
+
getAllCronOverrides() {
|
|
55
|
+
const rows = this.db
|
|
56
|
+
.prepare(`SELECT name, enabled, schedule, updated_at, updated_by FROM cron_overrides`)
|
|
57
|
+
.all();
|
|
58
|
+
const map = new Map();
|
|
357
59
|
for (const row of rows) {
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
else
|
|
361
|
-
break;
|
|
60
|
+
const o = this.deserializeCronOverride(row);
|
|
61
|
+
map.set(o.name, o);
|
|
362
62
|
}
|
|
363
|
-
return
|
|
364
|
-
}
|
|
365
|
-
/** Update rate limit state */
|
|
366
|
-
updateRateLimit(resource, remaining, resetAt) {
|
|
367
|
-
this.db.prepare(`
|
|
368
|
-
INSERT INTO rate_limits (resource, remaining, reset_at, updated_at)
|
|
369
|
-
VALUES (?, ?, ?, ?)
|
|
370
|
-
ON CONFLICT(resource) DO UPDATE SET remaining = ?, reset_at = ?, updated_at = ?
|
|
371
|
-
`).run(resource, remaining, resetAt, new Date().toISOString(), remaining, resetAt, new Date().toISOString());
|
|
372
|
-
}
|
|
373
|
-
/** Get all rate limit records */
|
|
374
|
-
getRateLimits() {
|
|
375
|
-
return this.db.prepare(`
|
|
376
|
-
SELECT resource, remaining, reset_at, updated_at FROM rate_limits
|
|
377
|
-
ORDER BY resource
|
|
378
|
-
`).all();
|
|
379
|
-
}
|
|
380
|
-
// ── System status (component health tracking) ──
|
|
381
|
-
/**
|
|
382
|
-
* Get the current state of a system component.
|
|
383
|
-
* Returns null if the component has never reported.
|
|
384
|
-
*/
|
|
385
|
-
getSystemStatus(component) {
|
|
386
|
-
const row = this.db.prepare(`
|
|
387
|
-
SELECT state, reason, since, updated_at FROM system_status WHERE component = ?
|
|
388
|
-
`).get(component);
|
|
389
|
-
return row || null;
|
|
63
|
+
return map;
|
|
390
64
|
}
|
|
391
65
|
/**
|
|
392
|
-
*
|
|
393
|
-
*
|
|
394
|
-
*
|
|
66
|
+
* Upsert an override. Pass only the fields you want to change — undefined
|
|
67
|
+
* fields preserve the existing value (or default to enabled=1, schedule=null
|
|
68
|
+
* on insert).
|
|
395
69
|
*/
|
|
396
|
-
|
|
397
|
-
const now = new Date().toISOString();
|
|
398
|
-
const existing = this.getSystemStatus(component);
|
|
399
|
-
const transitioned = !existing || existing.state !== state;
|
|
400
|
-
const since = transitioned ? now : existing.since;
|
|
401
|
-
this.db.prepare(`
|
|
402
|
-
INSERT INTO system_status (component, state, reason, since, updated_at)
|
|
403
|
-
VALUES (?, ?, ?, ?, ?)
|
|
404
|
-
ON CONFLICT(component) DO UPDATE SET
|
|
405
|
-
state = excluded.state,
|
|
406
|
-
reason = excluded.reason,
|
|
407
|
-
since = excluded.since,
|
|
408
|
-
updated_at = excluded.updated_at
|
|
409
|
-
`).run(component, state, reason || null, since, now);
|
|
410
|
-
return transitioned;
|
|
411
|
-
}
|
|
412
|
-
/** List all known component statuses. */
|
|
413
|
-
listSystemStatus() {
|
|
414
|
-
return this.db.prepare(`
|
|
415
|
-
SELECT component, state, reason, since, updated_at FROM system_status
|
|
416
|
-
ORDER BY component
|
|
417
|
-
`).all();
|
|
418
|
-
}
|
|
419
|
-
/** Get all executions with pagination */
|
|
420
|
-
allExecutions(limit = 100, offset = 0) {
|
|
421
|
-
return this.db.prepare(`
|
|
422
|
-
SELECT * FROM executions
|
|
423
|
-
ORDER BY started_at DESC
|
|
424
|
-
LIMIT ? OFFSET ?
|
|
425
|
-
`).all(limit, offset);
|
|
426
|
-
}
|
|
427
|
-
/**
|
|
428
|
-
* Get every execution recorded for a workflow run, with camelCase fields
|
|
429
|
-
* ready for the dashboard. Ordered by start time ascending so phases
|
|
430
|
-
* display in the order they ran. Scopes to a specific `workflow_run_id`
|
|
431
|
-
* when provided (preferred — isolates fresh re-triggers from prior runs);
|
|
432
|
-
* falls back to the legacy trigger-wide scope for rows written before the
|
|
433
|
-
* workflow_run_id column existed.
|
|
434
|
-
*/
|
|
435
|
-
getExecutionsForWorkflowRun(workflowRunId, triggerId, workflowName) {
|
|
436
|
-
const skillPattern = workflowName ? `${workflowName}:%` : "%:%";
|
|
437
|
-
const rows = this.db.prepare(`
|
|
438
|
-
SELECT
|
|
439
|
-
id,
|
|
440
|
-
trigger_type AS triggerType,
|
|
441
|
-
trigger_id AS triggerId,
|
|
442
|
-
skill,
|
|
443
|
-
repo,
|
|
444
|
-
issue_number AS issueNumber,
|
|
445
|
-
started_at AS startedAt,
|
|
446
|
-
finished_at AS finishedAt,
|
|
447
|
-
success,
|
|
448
|
-
error,
|
|
449
|
-
turns,
|
|
450
|
-
duration_ms AS durationMs,
|
|
451
|
-
session_id AS sessionId,
|
|
452
|
-
cost_usd AS costUsd,
|
|
453
|
-
input_tokens AS inputTokens,
|
|
454
|
-
cache_creation_input_tokens AS cacheCreationInputTokens,
|
|
455
|
-
cache_read_input_tokens AS cacheReadInputTokens,
|
|
456
|
-
output_tokens AS outputTokens,
|
|
457
|
-
api_duration_ms AS apiDurationMs,
|
|
458
|
-
stop_reason AS stopReason,
|
|
459
|
-
workflow_run_id AS workflowRunId
|
|
460
|
-
FROM executions
|
|
461
|
-
WHERE (workflow_run_id = ? OR (workflow_run_id IS NULL AND trigger_id = ?))
|
|
462
|
-
AND skill LIKE ?
|
|
463
|
-
ORDER BY started_at ASC
|
|
464
|
-
`).all(workflowRunId, triggerId, skillPattern);
|
|
465
|
-
return rows.map((r) => ({
|
|
466
|
-
id: r.id,
|
|
467
|
-
triggerType: r.triggerType,
|
|
468
|
-
triggerId: r.triggerId,
|
|
469
|
-
skill: r.skill,
|
|
470
|
-
repo: r.repo ?? undefined,
|
|
471
|
-
issueNumber: r.issueNumber ?? undefined,
|
|
472
|
-
startedAt: r.startedAt,
|
|
473
|
-
finishedAt: r.finishedAt ?? undefined,
|
|
474
|
-
success: r.success === null || r.success === undefined ? undefined : Boolean(r.success),
|
|
475
|
-
error: r.error ?? undefined,
|
|
476
|
-
turns: r.turns ?? undefined,
|
|
477
|
-
durationMs: r.durationMs ?? undefined,
|
|
478
|
-
sessionId: r.sessionId ?? undefined,
|
|
479
|
-
costUsd: r.costUsd ?? undefined,
|
|
480
|
-
inputTokens: r.inputTokens ?? undefined,
|
|
481
|
-
cacheCreationInputTokens: r.cacheCreationInputTokens ?? undefined,
|
|
482
|
-
cacheReadInputTokens: r.cacheReadInputTokens ?? undefined,
|
|
483
|
-
outputTokens: r.outputTokens ?? undefined,
|
|
484
|
-
apiDurationMs: r.apiDurationMs ?? undefined,
|
|
485
|
-
stopReason: r.stopReason ?? undefined,
|
|
486
|
-
workflowRunId: r.workflowRunId ?? undefined,
|
|
487
|
-
}));
|
|
488
|
-
}
|
|
489
|
-
/** Get currently running executions (no finished_at) */
|
|
490
|
-
runningExecutions() {
|
|
491
|
-
return this.db.prepare(`
|
|
492
|
-
SELECT * FROM executions
|
|
493
|
-
WHERE finished_at IS NULL
|
|
494
|
-
ORDER BY started_at DESC
|
|
495
|
-
`).all();
|
|
496
|
-
}
|
|
497
|
-
/** Aggregate execution stats */
|
|
498
|
-
executionStats() {
|
|
499
|
-
const today = new Date();
|
|
500
|
-
today.setHours(0, 0, 0, 0);
|
|
501
|
-
const todayIso = today.toISOString();
|
|
502
|
-
const total = this.db.prepare(`SELECT COUNT(*) as c FROM executions`).get().c;
|
|
503
|
-
const todayCount = this.db.prepare(`SELECT COUNT(*) as c FROM executions WHERE started_at >= ?`).get(todayIso).c;
|
|
504
|
-
const running = this.db.prepare(`SELECT COUNT(*) as c FROM executions WHERE finished_at IS NULL`).get().c;
|
|
505
|
-
const skillRows = this.db.prepare(`
|
|
506
|
-
SELECT skill, COUNT(*) as count,
|
|
507
|
-
SUM(CASE WHEN success = 1 THEN 1 ELSE 0 END) as success,
|
|
508
|
-
SUM(CASE WHEN success = 0 THEN 1 ELSE 0 END) as fail
|
|
509
|
-
FROM executions GROUP BY skill
|
|
510
|
-
`).all();
|
|
511
|
-
const by_skill = {};
|
|
512
|
-
for (const r of skillRows) {
|
|
513
|
-
by_skill[r.skill] = { count: r.count, success: r.success, fail: r.fail };
|
|
514
|
-
}
|
|
515
|
-
const triggerRows = this.db.prepare(`
|
|
516
|
-
SELECT trigger_type, COUNT(*) as count FROM executions GROUP BY trigger_type
|
|
517
|
-
`).all();
|
|
518
|
-
const by_trigger = {};
|
|
519
|
-
for (const r of triggerRows) {
|
|
520
|
-
by_trigger[r.trigger_type] = r.count;
|
|
521
|
-
}
|
|
522
|
-
return { total_executions: total, today_count: todayCount, by_skill, by_trigger, running };
|
|
523
|
-
}
|
|
524
|
-
/** Daily aggregated stats for the last N days */
|
|
525
|
-
dailyStats(days) {
|
|
526
|
-
// Build the inclusive UTC date window: [today - (days-1), today].
|
|
527
|
-
// SQLite's date(started_at) returns a UTC YYYY-MM-DD string, so we
|
|
528
|
-
// generate the same format here to align keys.
|
|
529
|
-
const today = new Date();
|
|
530
|
-
const startUtc = new Date(Date.UTC(today.getUTCFullYear(), today.getUTCMonth(), today.getUTCDate() - (days - 1)));
|
|
531
|
-
const dateKeys = [];
|
|
532
|
-
for (let i = 0; i < days; i++) {
|
|
533
|
-
const d = new Date(startUtc);
|
|
534
|
-
d.setUTCDate(startUtc.getUTCDate() + i);
|
|
535
|
-
dateKeys.push(d.toISOString().slice(0, 10));
|
|
536
|
-
}
|
|
537
|
-
const rows = this.db.prepare(`
|
|
538
|
-
SELECT
|
|
539
|
-
date(started_at) AS date,
|
|
540
|
-
COUNT(*) AS executions,
|
|
541
|
-
SUM(CASE WHEN success = 1 THEN 1 ELSE 0 END) AS successes,
|
|
542
|
-
SUM(CASE WHEN success = 0 THEN 1 ELSE 0 END) AS failures,
|
|
543
|
-
COALESCE(SUM(input_tokens), 0) + COALESCE(SUM(output_tokens), 0) + COALESCE(SUM(cache_read_input_tokens), 0) AS totalTokens,
|
|
544
|
-
COALESCE(SUM(input_tokens), 0) AS inputTokens,
|
|
545
|
-
COALESCE(SUM(output_tokens), 0) AS outputTokens,
|
|
546
|
-
COALESCE(SUM(cache_read_input_tokens), 0) AS cacheReadTokens,
|
|
547
|
-
COALESCE(SUM(cost_usd), 0) AS costUsd
|
|
548
|
-
FROM executions
|
|
549
|
-
WHERE date(started_at) >= ?
|
|
550
|
-
GROUP BY date(started_at)
|
|
551
|
-
`).all(dateKeys[0]);
|
|
552
|
-
const byDate = new Map(rows.map((r) => [r.date, r]));
|
|
553
|
-
return dateKeys.map((date) => byDate.get(date) ?? {
|
|
554
|
-
date,
|
|
555
|
-
executions: 0,
|
|
556
|
-
successes: 0,
|
|
557
|
-
failures: 0,
|
|
558
|
-
totalTokens: 0,
|
|
559
|
-
inputTokens: 0,
|
|
560
|
-
outputTokens: 0,
|
|
561
|
-
cacheReadTokens: 0,
|
|
562
|
-
costUsd: 0,
|
|
563
|
-
});
|
|
564
|
-
}
|
|
565
|
-
/** Hourly aggregated stats for the last N hours (UTC). Bucket key is `YYYY-MM-DDTHH`. */
|
|
566
|
-
hourlyStats(hours) {
|
|
567
|
-
const now = new Date();
|
|
568
|
-
const startUtc = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate(), now.getUTCHours() - (hours - 1)));
|
|
569
|
-
const hourKeys = [];
|
|
570
|
-
for (let i = 0; i < hours; i++) {
|
|
571
|
-
const d = new Date(startUtc);
|
|
572
|
-
d.setUTCHours(startUtc.getUTCHours() + i);
|
|
573
|
-
// YYYY-MM-DDTHH (matches strftime('%Y-%m-%dT%H', …))
|
|
574
|
-
hourKeys.push(d.toISOString().slice(0, 13));
|
|
575
|
-
}
|
|
576
|
-
const rows = this.db.prepare(`
|
|
577
|
-
SELECT
|
|
578
|
-
strftime('%Y-%m-%dT%H', started_at) AS date,
|
|
579
|
-
COUNT(*) AS executions,
|
|
580
|
-
SUM(CASE WHEN success = 1 THEN 1 ELSE 0 END) AS successes,
|
|
581
|
-
SUM(CASE WHEN success = 0 THEN 1 ELSE 0 END) AS failures,
|
|
582
|
-
COALESCE(SUM(input_tokens), 0) + COALESCE(SUM(output_tokens), 0) + COALESCE(SUM(cache_read_input_tokens), 0) AS totalTokens,
|
|
583
|
-
COALESCE(SUM(input_tokens), 0) AS inputTokens,
|
|
584
|
-
COALESCE(SUM(output_tokens), 0) AS outputTokens,
|
|
585
|
-
COALESCE(SUM(cache_read_input_tokens), 0) AS cacheReadTokens,
|
|
586
|
-
COALESCE(SUM(cost_usd), 0) AS costUsd
|
|
587
|
-
FROM executions
|
|
588
|
-
WHERE strftime('%Y-%m-%dT%H', started_at) >= ?
|
|
589
|
-
GROUP BY strftime('%Y-%m-%dT%H', started_at)
|
|
590
|
-
`).all(hourKeys[0]);
|
|
591
|
-
const byHour = new Map(rows.map((r) => [r.date, r]));
|
|
592
|
-
return hourKeys.map((date) => byHour.get(date) ?? {
|
|
593
|
-
date,
|
|
594
|
-
executions: 0,
|
|
595
|
-
successes: 0,
|
|
596
|
-
failures: 0,
|
|
597
|
-
totalTokens: 0,
|
|
598
|
-
inputTokens: 0,
|
|
599
|
-
outputTokens: 0,
|
|
600
|
-
cacheReadTokens: 0,
|
|
601
|
-
costUsd: 0,
|
|
602
|
-
});
|
|
603
|
-
}
|
|
604
|
-
// ── Workflow Runs ──────────────────────────────────────────────
|
|
605
|
-
/** Create a new workflow run record */
|
|
606
|
-
createWorkflowRun(run) {
|
|
70
|
+
setCronOverride(name, patch) {
|
|
607
71
|
const now = new Date().toISOString();
|
|
608
|
-
this.
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
72
|
+
const existing = this.getCronOverride(name);
|
|
73
|
+
const enabled = patch.enabled ?? existing?.enabled ?? true;
|
|
74
|
+
const schedule = patch.schedule === undefined ? existing?.schedule ?? null : patch.schedule;
|
|
75
|
+
this.db
|
|
76
|
+
.prepare(`INSERT INTO cron_overrides (name, enabled, schedule, updated_at, updated_by)
|
|
77
|
+
VALUES (?, ?, ?, ?, ?)
|
|
78
|
+
ON CONFLICT(name) DO UPDATE SET
|
|
79
|
+
enabled = excluded.enabled,
|
|
80
|
+
schedule = excluded.schedule,
|
|
81
|
+
updated_at = excluded.updated_at,
|
|
82
|
+
updated_by = excluded.updated_by`)
|
|
83
|
+
.run(name, enabled ? 1 : 0, schedule, now, patch.updatedBy ?? null);
|
|
84
|
+
}
|
|
85
|
+
/** Remove the override entirely (revert to YAML defaults). */
|
|
86
|
+
clearCronOverride(name) {
|
|
87
|
+
this.db.prepare(`DELETE FROM cron_overrides WHERE name = ?`).run(name);
|
|
88
|
+
}
|
|
89
|
+
deserializeCronOverride(row) {
|
|
90
|
+
return {
|
|
91
|
+
name: row.name,
|
|
92
|
+
enabled: row.enabled === 1,
|
|
93
|
+
schedule: row.schedule ?? null,
|
|
94
|
+
updatedAt: row.updated_at,
|
|
95
|
+
updatedBy: row.updated_by ?? null,
|
|
96
|
+
};
|
|
612
97
|
}
|
|
98
|
+
// ── Workflow overrides (kill switch) ───────────────────────────
|
|
613
99
|
/**
|
|
614
|
-
*
|
|
615
|
-
*
|
|
616
|
-
* other keys.
|
|
100
|
+
* Cheap check used by every dispatch path. Returns true unless an explicit
|
|
101
|
+
* `workflow_overrides` row says otherwise.
|
|
617
102
|
*/
|
|
618
|
-
|
|
619
|
-
const now = new Date().toISOString();
|
|
103
|
+
isWorkflowEnabled(name) {
|
|
620
104
|
const row = this.db
|
|
621
|
-
.prepare(`SELECT
|
|
622
|
-
.get(
|
|
105
|
+
.prepare(`SELECT enabled FROM workflow_overrides WHERE name = ?`)
|
|
106
|
+
.get(name);
|
|
623
107
|
if (!row)
|
|
624
|
-
return;
|
|
625
|
-
|
|
626
|
-
const merged = { ...current, ...patch };
|
|
627
|
-
this.db
|
|
628
|
-
.prepare(`UPDATE workflow_runs SET scratch = ?, updated_at = ? WHERE id = ?`)
|
|
629
|
-
.run(JSON.stringify(merged), now, id);
|
|
630
|
-
}
|
|
631
|
-
/** Update the current phase and append to phase history */
|
|
632
|
-
updateWorkflowPhase(id, phase, entry) {
|
|
633
|
-
const now = new Date().toISOString();
|
|
634
|
-
const row = this.db.prepare(`SELECT phase_history FROM workflow_runs WHERE id = ?`).get(id);
|
|
635
|
-
if (!row)
|
|
636
|
-
return;
|
|
637
|
-
const history = JSON.parse(row.phase_history);
|
|
638
|
-
history.push(entry);
|
|
639
|
-
this.db.prepare(`
|
|
640
|
-
UPDATE workflow_runs SET current_phase = ?, phase_history = ?, updated_at = ? WHERE id = ?
|
|
641
|
-
`).run(phase, JSON.stringify(history), now, id);
|
|
642
|
-
}
|
|
643
|
-
/** Mark a workflow run as finished */
|
|
644
|
-
finishWorkflowRun(id, status, error) {
|
|
645
|
-
const now = new Date().toISOString();
|
|
646
|
-
this.db.prepare(`
|
|
647
|
-
UPDATE workflow_runs SET status = ?, finished_at = ?, updated_at = ?, context = CASE
|
|
648
|
-
WHEN ? IS NOT NULL THEN json_patch(COALESCE(context, '{}'), json_object('error', ?))
|
|
649
|
-
ELSE context
|
|
650
|
-
END WHERE id = ?
|
|
651
|
-
`).run(status, now, now, error ?? null, error ?? null, id);
|
|
652
|
-
}
|
|
653
|
-
/** Get a single workflow run by ID */
|
|
654
|
-
getWorkflowRun(id) {
|
|
655
|
-
const row = this.db.prepare(`SELECT * FROM workflow_runs WHERE id = ?`).get(id);
|
|
656
|
-
return row ? this.deserializeWorkflowRun(row) : null;
|
|
108
|
+
return true;
|
|
109
|
+
return row.enabled === 1;
|
|
657
110
|
}
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
ORDER BY started_at DESC
|
|
664
|
-
LIMIT 1
|
|
665
|
-
`).get(triggerId);
|
|
666
|
-
return row ? this.deserializeWorkflowRun(row) : null;
|
|
667
|
-
}
|
|
668
|
-
/** List all active (running or paused) workflow runs */
|
|
669
|
-
activeWorkflowRuns() {
|
|
670
|
-
const rows = this.db.prepare(`
|
|
671
|
-
SELECT * FROM workflow_runs WHERE status IN ('running', 'paused') ORDER BY started_at DESC
|
|
672
|
-
`).all();
|
|
673
|
-
return rows.map((r) => this.deserializeWorkflowRun(r));
|
|
674
|
-
}
|
|
675
|
-
/** List recent workflow runs, ordered by start time descending */
|
|
676
|
-
recentWorkflowRuns(limit = 20) {
|
|
677
|
-
const rows = this.db.prepare(`
|
|
678
|
-
SELECT * FROM workflow_runs ORDER BY started_at DESC LIMIT ?
|
|
679
|
-
`).all(limit);
|
|
680
|
-
return rows.map((r) => this.deserializeWorkflowRun(r));
|
|
681
|
-
}
|
|
682
|
-
/**
|
|
683
|
-
* List workflow runs with pagination + filters. Returns the page slice
|
|
684
|
-
* AND the post-filter total so the dashboard knows how many remain.
|
|
685
|
-
*
|
|
686
|
-
* - sinceIso filters `started_at >= sinceIso` (used for the header date range).
|
|
687
|
-
* - workflowName filters by exact `workflow_name`.
|
|
688
|
-
* - statuses filters to one of the workflow statuses (`running`, `paused`,
|
|
689
|
-
* `succeeded`, `failed`, `cancelled`). Used for the dashboard's "live"
|
|
690
|
-
* filter, which maps to ['running','paused'].
|
|
691
|
-
*/
|
|
692
|
-
listWorkflowRuns(opts = {}) {
|
|
693
|
-
const limit = opts.limit ?? 20;
|
|
694
|
-
const offset = opts.offset ?? 0;
|
|
695
|
-
const where = [];
|
|
696
|
-
const params = [];
|
|
697
|
-
if (opts.sinceIso) {
|
|
698
|
-
where.push("started_at >= ?");
|
|
699
|
-
params.push(opts.sinceIso);
|
|
700
|
-
}
|
|
701
|
-
if (opts.workflowName) {
|
|
702
|
-
where.push("workflow_name = ?");
|
|
703
|
-
params.push(opts.workflowName);
|
|
704
|
-
}
|
|
705
|
-
if (opts.statuses && opts.statuses.length > 0) {
|
|
706
|
-
where.push(`status IN (${opts.statuses.map(() => "?").join(",")})`);
|
|
707
|
-
params.push(...opts.statuses);
|
|
708
|
-
}
|
|
709
|
-
const whereClause = where.length > 0 ? `WHERE ${where.join(" AND ")}` : "";
|
|
710
|
-
const total = this.db
|
|
711
|
-
.prepare(`SELECT COUNT(*) as c FROM workflow_runs ${whereClause}`)
|
|
712
|
-
.get(...params).c;
|
|
713
|
-
const rows = this.db
|
|
714
|
-
.prepare(`SELECT * FROM workflow_runs ${whereClause} ORDER BY started_at DESC LIMIT ? OFFSET ?`)
|
|
715
|
-
.all(...params, limit, offset);
|
|
716
|
-
return {
|
|
717
|
-
runs: rows.map((r) => this.deserializeWorkflowRun(r)),
|
|
718
|
-
total,
|
|
719
|
-
};
|
|
111
|
+
getWorkflowOverride(name) {
|
|
112
|
+
const row = this.db
|
|
113
|
+
.prepare(`SELECT name, enabled, updated_at, updated_by FROM workflow_overrides WHERE name = ?`)
|
|
114
|
+
.get(name);
|
|
115
|
+
return row ? this.deserializeWorkflowOverride(row) : null;
|
|
720
116
|
}
|
|
721
|
-
|
|
722
|
-
distinctWorkflowNames() {
|
|
117
|
+
getAllWorkflowOverrides() {
|
|
723
118
|
const rows = this.db
|
|
724
|
-
.prepare(`SELECT
|
|
119
|
+
.prepare(`SELECT name, enabled, updated_at, updated_by FROM workflow_overrides`)
|
|
725
120
|
.all();
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
UPDATE workflow_runs SET status = 'cancelled', updated_at = ?, finished_at = ? WHERE id = ?
|
|
733
|
-
`).run(now, now, id);
|
|
734
|
-
}
|
|
735
|
-
/** Pause a workflow run (waiting for approval) */
|
|
736
|
-
pauseWorkflowRun(id) {
|
|
737
|
-
const now = new Date().toISOString();
|
|
738
|
-
this.db.prepare(`
|
|
739
|
-
UPDATE workflow_runs SET status = 'paused', updated_at = ? WHERE id = ?
|
|
740
|
-
`).run(now, id);
|
|
741
|
-
}
|
|
742
|
-
/** Resume a paused workflow run (set back to running) */
|
|
743
|
-
resumeWorkflowRun(id) {
|
|
744
|
-
const now = new Date().toISOString();
|
|
745
|
-
this.db.prepare(`
|
|
746
|
-
UPDATE workflow_runs SET status = 'running', updated_at = ? WHERE id = ?
|
|
747
|
-
`).run(now, id);
|
|
748
|
-
}
|
|
749
|
-
// ── Workflow Approvals ─────────────────────────────────────────
|
|
750
|
-
/** Create a new pending approval request */
|
|
751
|
-
createApproval(approval) {
|
|
752
|
-
this.db
|
|
753
|
-
.prepare(`
|
|
754
|
-
INSERT INTO workflow_approvals (id, workflow_run_id, gate, summary, status, kind, requested_by, created_at)
|
|
755
|
-
VALUES (?, ?, ?, ?, 'pending', ?, ?, ?)
|
|
756
|
-
`)
|
|
757
|
-
.run(approval.id, approval.workflowRunId, approval.gate, approval.summary, approval.kind ?? "approve", approval.requestedBy ?? null, approval.createdAt);
|
|
121
|
+
const map = new Map();
|
|
122
|
+
for (const row of rows) {
|
|
123
|
+
const o = this.deserializeWorkflowOverride(row);
|
|
124
|
+
map.set(o.name, o);
|
|
125
|
+
}
|
|
126
|
+
return map;
|
|
758
127
|
}
|
|
759
|
-
|
|
760
|
-
* Resolve a reply gate: marks the approval row as approved (reply gates
|
|
761
|
-
* don't have approve/reject semantics — any reply is a "go") and stores
|
|
762
|
-
* the reply text as the `response`. Used by the socratic explore loop.
|
|
763
|
-
*/
|
|
764
|
-
resolveReplyGate(id, replyText, responder) {
|
|
128
|
+
setWorkflowEnabled(name, enabled, updatedBy) {
|
|
765
129
|
const now = new Date().toISOString();
|
|
766
130
|
this.db
|
|
767
|
-
.prepare(`
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
.run(
|
|
774
|
-
}
|
|
775
|
-
|
|
776
|
-
* Find the most recent pending reply gate for a given trigger id. Used by
|
|
777
|
-
* the router to short-circuit free-form replies on a paused socratic
|
|
778
|
-
* explore loop without re-running classifier logic.
|
|
779
|
-
*/
|
|
780
|
-
getPendingReplyGateByTrigger(triggerId) {
|
|
781
|
-
const row = this.db
|
|
782
|
-
.prepare(`SELECT wa.* FROM workflow_approvals wa
|
|
783
|
-
JOIN workflow_runs wr ON wa.workflow_run_id = wr.id
|
|
784
|
-
WHERE wr.trigger_id = ? AND wa.status = 'pending' AND wa.kind = 'reply'
|
|
785
|
-
ORDER BY wa.created_at DESC
|
|
786
|
-
LIMIT 1`)
|
|
787
|
-
.get(triggerId);
|
|
788
|
-
return row ? this.deserializeApproval(row) : null;
|
|
789
|
-
}
|
|
790
|
-
/** Get a single approval by ID */
|
|
791
|
-
getApproval(id) {
|
|
792
|
-
const row = this.db.prepare(`SELECT * FROM workflow_approvals WHERE id = ?`).get(id);
|
|
793
|
-
return row ? this.deserializeApproval(row) : null;
|
|
794
|
-
}
|
|
795
|
-
/** Find the pending approval for a workflow run */
|
|
796
|
-
getPendingApprovalForWorkflow(workflowRunId) {
|
|
797
|
-
const row = this.db.prepare(`
|
|
798
|
-
SELECT * FROM workflow_approvals WHERE workflow_run_id = ? AND status = 'pending' LIMIT 1
|
|
799
|
-
`).get(workflowRunId);
|
|
800
|
-
return row ? this.deserializeApproval(row) : null;
|
|
801
|
-
}
|
|
802
|
-
/** Find the pending approval by trigger ID (join with workflow_runs) */
|
|
803
|
-
getPendingApprovalByTrigger(triggerId) {
|
|
804
|
-
const row = this.db.prepare(`
|
|
805
|
-
SELECT wa.* FROM workflow_approvals wa
|
|
806
|
-
JOIN workflow_runs wr ON wa.workflow_run_id = wr.id
|
|
807
|
-
WHERE wr.trigger_id = ? AND wa.status = 'pending'
|
|
808
|
-
ORDER BY wa.created_at DESC
|
|
809
|
-
LIMIT 1
|
|
810
|
-
`).get(triggerId);
|
|
811
|
-
return row ? this.deserializeApproval(row) : null;
|
|
812
|
-
}
|
|
813
|
-
/** List all pending approvals */
|
|
814
|
-
listPendingApprovals() {
|
|
815
|
-
const rows = this.db.prepare(`
|
|
816
|
-
SELECT * FROM workflow_approvals WHERE status = 'pending' ORDER BY created_at DESC
|
|
817
|
-
`).all();
|
|
818
|
-
return rows.map((r) => this.deserializeApproval(r));
|
|
819
|
-
}
|
|
820
|
-
/** Record the response to an approval */
|
|
821
|
-
respondToApproval(id, status, respondedBy, response) {
|
|
822
|
-
const now = new Date().toISOString();
|
|
823
|
-
this.db.prepare(`
|
|
824
|
-
UPDATE workflow_approvals SET status = ?, responded_by = ?, response = ?, responded_at = ? WHERE id = ?
|
|
825
|
-
`).run(status, respondedBy, response ?? null, now, id);
|
|
826
|
-
}
|
|
827
|
-
/** Update a single node's status in the workflow run's nodeStatuses map. */
|
|
828
|
-
updateNodeStatus(workflowId, nodeName, status) {
|
|
829
|
-
const now = new Date().toISOString();
|
|
830
|
-
const row = this.db.prepare(`SELECT node_statuses FROM workflow_runs WHERE id = ?`).get(workflowId);
|
|
831
|
-
if (!row)
|
|
832
|
-
return;
|
|
833
|
-
const statuses = row.node_statuses ? JSON.parse(row.node_statuses) : {};
|
|
834
|
-
statuses[nodeName] = status;
|
|
835
|
-
this.db.prepare(`UPDATE workflow_runs SET node_statuses = ?, updated_at = ? WHERE id = ?`).run(JSON.stringify(statuses), now, workflowId);
|
|
836
|
-
}
|
|
837
|
-
deserializeApproval(row) {
|
|
838
|
-
return {
|
|
839
|
-
id: row.id,
|
|
840
|
-
workflowRunId: row.workflow_run_id,
|
|
841
|
-
gate: row.gate,
|
|
842
|
-
summary: row.summary,
|
|
843
|
-
status: row.status,
|
|
844
|
-
kind: (row.kind || "approve"),
|
|
845
|
-
requestedBy: row.requested_by || undefined,
|
|
846
|
-
respondedBy: row.responded_by || undefined,
|
|
847
|
-
response: row.response || undefined,
|
|
848
|
-
respondedAt: row.responded_at || undefined,
|
|
849
|
-
createdAt: row.created_at,
|
|
850
|
-
};
|
|
851
|
-
}
|
|
852
|
-
deserializeWorkflowRun(row) {
|
|
131
|
+
.prepare(`INSERT INTO workflow_overrides (name, enabled, updated_at, updated_by)
|
|
132
|
+
VALUES (?, ?, ?, ?)
|
|
133
|
+
ON CONFLICT(name) DO UPDATE SET
|
|
134
|
+
enabled = excluded.enabled,
|
|
135
|
+
updated_at = excluded.updated_at,
|
|
136
|
+
updated_by = excluded.updated_by`)
|
|
137
|
+
.run(name, enabled ? 1 : 0, now, updatedBy ?? null);
|
|
138
|
+
}
|
|
139
|
+
deserializeWorkflowOverride(row) {
|
|
853
140
|
return {
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
triggerId: row.trigger_id,
|
|
857
|
-
repo: row.repo,
|
|
858
|
-
issueNumber: row.issue_number,
|
|
859
|
-
currentPhase: row.current_phase,
|
|
860
|
-
phaseHistory: JSON.parse(row.phase_history),
|
|
861
|
-
status: row.status,
|
|
862
|
-
context: row.context ? JSON.parse(row.context) : undefined,
|
|
863
|
-
scratch: row.scratch ? JSON.parse(row.scratch) : undefined,
|
|
864
|
-
nodeStatuses: row.node_statuses ? JSON.parse(row.node_statuses) : undefined,
|
|
865
|
-
startedAt: row.started_at,
|
|
141
|
+
name: row.name,
|
|
142
|
+
enabled: row.enabled === 1,
|
|
866
143
|
updatedAt: row.updated_at,
|
|
867
|
-
|
|
144
|
+
updatedBy: row.updated_by ?? null,
|
|
868
145
|
};
|
|
869
146
|
}
|
|
870
147
|
/** Expose the underlying Database instance (for SessionManager, etc.) */
|