lastlight 0.1.15 → 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 +377 -432
- 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 +30 -1
- package/dist/setup.js +241 -25
- 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 +17 -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/worktree/manager.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { existsSync, rmSync } from "fs";
|
|
1
|
+
import { execFileSync } from "child_process";
|
|
2
|
+
import { existsSync, mkdirSync, rmSync } from "fs";
|
|
3
3
|
import { join, resolve } from "path";
|
|
4
4
|
const WORKTREE_DIR = ".worktrees";
|
|
5
5
|
/**
|
|
@@ -14,8 +14,8 @@ export class WorktreeManager {
|
|
|
14
14
|
constructor(baseDir) {
|
|
15
15
|
this.baseDir = baseDir || resolve(WORKTREE_DIR);
|
|
16
16
|
}
|
|
17
|
-
|
|
18
|
-
return
|
|
17
|
+
execGit(args, opts) {
|
|
18
|
+
return execFileSync("git", args, { encoding: "utf-8", stdio: "pipe", ...opts });
|
|
19
19
|
}
|
|
20
20
|
/**
|
|
21
21
|
* Create a new worktree for a task.
|
|
@@ -29,7 +29,7 @@ export class WorktreeManager {
|
|
|
29
29
|
const worktreePath = join(this.baseDir, taskId);
|
|
30
30
|
// Ensure base directory exists
|
|
31
31
|
if (!existsSync(this.baseDir)) {
|
|
32
|
-
|
|
32
|
+
mkdirSync(this.baseDir, { recursive: true });
|
|
33
33
|
}
|
|
34
34
|
// We need a bare/normal repo to create worktrees from.
|
|
35
35
|
// Use a shared bare clone per repo to avoid re-cloning each time.
|
|
@@ -37,11 +37,11 @@ export class WorktreeManager {
|
|
|
37
37
|
const bareDir = join(this.baseDir, `.bare-${repoName}`);
|
|
38
38
|
if (!existsSync(bareDir)) {
|
|
39
39
|
console.log(`[worktree] Bare clone: ${repoUrl}`);
|
|
40
|
-
this.
|
|
40
|
+
this.execGit(["clone", "--bare", repoUrl, bareDir]);
|
|
41
41
|
}
|
|
42
42
|
else {
|
|
43
43
|
// Fetch latest
|
|
44
|
-
this.
|
|
44
|
+
this.execGit(["-C", bareDir, "fetch", "--all", "--prune"]);
|
|
45
45
|
}
|
|
46
46
|
// Create worktree with new branch from base
|
|
47
47
|
const base = baseBranch || "main";
|
|
@@ -49,7 +49,7 @@ export class WorktreeManager {
|
|
|
49
49
|
// Check if branch already exists remotely
|
|
50
50
|
let branchExists = false;
|
|
51
51
|
try {
|
|
52
|
-
this.
|
|
52
|
+
this.execGit(["-C", bareDir, "rev-parse", "--verify", `origin/${branch}`]);
|
|
53
53
|
branchExists = true;
|
|
54
54
|
}
|
|
55
55
|
catch {
|
|
@@ -58,13 +58,13 @@ export class WorktreeManager {
|
|
|
58
58
|
if (branchExists) {
|
|
59
59
|
// Resume existing branch
|
|
60
60
|
console.log(`[worktree] Resuming branch: ${branch}`);
|
|
61
|
-
this.
|
|
62
|
-
this.
|
|
61
|
+
this.execGit(["-C", bareDir, "worktree", "add", worktreePath, `origin/${branch}`]);
|
|
62
|
+
this.execGit(["-C", worktreePath, "checkout", "-B", branch, `origin/${branch}`]);
|
|
63
63
|
}
|
|
64
64
|
else {
|
|
65
65
|
// Create new branch from base
|
|
66
66
|
console.log(`[worktree] New branch: ${branch} from ${baseRef}`);
|
|
67
|
-
this.
|
|
67
|
+
this.execGit(["-C", bareDir, "worktree", "add", "-b", branch, worktreePath, baseRef]);
|
|
68
68
|
}
|
|
69
69
|
const info = {
|
|
70
70
|
taskId,
|
|
@@ -94,7 +94,7 @@ export class WorktreeManager {
|
|
|
94
94
|
try {
|
|
95
95
|
// Remove worktree
|
|
96
96
|
if (existsSync(info.path)) {
|
|
97
|
-
this.
|
|
97
|
+
this.execGit(["-C", bareDir, "worktree", "remove", "--force", info.path]);
|
|
98
98
|
}
|
|
99
99
|
}
|
|
100
100
|
catch (err) {
|
|
@@ -102,7 +102,7 @@ export class WorktreeManager {
|
|
|
102
102
|
if (existsSync(info.path)) {
|
|
103
103
|
rmSync(info.path, { recursive: true, force: true });
|
|
104
104
|
try {
|
|
105
|
-
this.
|
|
105
|
+
this.execGit(["-C", bareDir, "worktree", "prune"]);
|
|
106
106
|
}
|
|
107
107
|
catch { /* best effort */ }
|
|
108
108
|
}
|
|
@@ -110,7 +110,7 @@ export class WorktreeManager {
|
|
|
110
110
|
// Optionally delete the branch
|
|
111
111
|
if (opts?.deleteBranch) {
|
|
112
112
|
try {
|
|
113
|
-
this.
|
|
113
|
+
this.execGit(["-C", bareDir, "branch", "-D", info.branch]);
|
|
114
114
|
}
|
|
115
115
|
catch { /* branch may not exist locally */ }
|
|
116
116
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manager.js","sourceRoot":"","sources":["../../src/worktree/manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"manager.js","sourceRoot":"","sources":["../../src/worktree/manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAwB,MAAM,eAAe,CAAC;AACnE,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,IAAI,CAAC;AACnD,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAErC,MAAM,YAAY,GAAG,YAAY,CAAC;AAUlC;;;;;GAKG;AACH,MAAM,OAAO,eAAe;IAClB,OAAO,CAAS;IAChB,MAAM,GAA8B,IAAI,GAAG,EAAE,CAAC;IAEtD,YAAY,OAAgB;QAC1B,IAAI,CAAC,OAAO,GAAG,OAAO,IAAI,OAAO,CAAC,YAAY,CAAC,CAAC;IAClD,CAAC;IAEO,OAAO,CAAC,IAAc,EAAE,IAAsB;QACpD,OAAO,YAAY,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,CAAW,CAAC;IAC5F,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,MAAM,CAAC,IAKZ;QACC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,GAAG,IAAI,CAAC;QAErD,IAAI,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;YAC5B,OAAO,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,CAAE,CAAC;QAClC,CAAC;QAED,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QAEhD,+BAA+B;QAC/B,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;YAC9B,SAAS,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC/C,CAAC;QAED,uDAAuD;QACvD,kEAAkE;QAClE,MAAM,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,yBAAyB,EAAE,IAAI,CAAC,CAAC;QAClE,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,SAAS,QAAQ,EAAE,CAAC,CAAC;QAExD,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;YACzB,OAAO,CAAC,GAAG,CAAC,0BAA0B,OAAO,EAAE,CAAC,CAAC;YACjD,IAAI,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC;QACtD,CAAC;aAAM,CAAC;YACN,eAAe;YACf,IAAI,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,SAAS,CAAC,CAAC,CAAC;QAC7D,CAAC;QAED,4CAA4C;QAC5C,MAAM,IAAI,GAAG,UAAU,IAAI,MAAM,CAAC;QAClC,MAAM,OAAO,GAAG,UAAU,IAAI,EAAE,CAAC;QAEjC,0CAA0C;QAC1C,IAAI,YAAY,GAAG,KAAK,CAAC;QACzB,IAAI,CAAC;YACH,IAAI,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,UAAU,MAAM,EAAE,CAAC,CAAC,CAAC;YAC3E,YAAY,GAAG,IAAI,CAAC;QACtB,CAAC;QAAC,MAAM,CAAC;YACP,8CAA8C;QAChD,CAAC;QAED,IAAI,YAAY,EAAE,CAAC;YACjB,yBAAyB;YACzB,OAAO,CAAC,GAAG,CAAC,+BAA+B,MAAM,EAAE,CAAC,CAAC;YACrD,IAAI,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,YAAY,EAAE,UAAU,MAAM,EAAE,CAAC,CAAC,CAAC;YACnF,IAAI,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,YAAY,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,MAAM,EAAE,CAAC,CAAC,CAAC;QACnF,CAAC;aAAM,CAAC;YACN,8BAA8B;YAC9B,OAAO,CAAC,GAAG,CAAC,0BAA0B,MAAM,SAAS,OAAO,EAAE,CAAC,CAAC;YAChE,IAAI,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,OAAO,CAAC,CAAC,CAAC;QACxF,CAAC;QAED,MAAM,IAAI,GAAiB;YACzB,MAAM;YACN,IAAI,EAAE,YAAY;YAClB,MAAM;YACN,IAAI,EAAE,OAAO;YACb,SAAS,EAAE,IAAI,IAAI,EAAE;SACtB,CAAC;QAEF,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;QAC9B,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;OAEG;IACH,OAAO,CAAC,MAAc;QACpB,OAAO,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,IAAI,IAAI,IAAI,CAAC;IAC/C,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,OAAO,CAAC,MAAc,EAAE,IAAiC;QAC7D,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACrC,IAAI,CAAC,IAAI;YAAE,OAAO;QAElB,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,yBAAyB,EAAE,IAAI,CAAC,CAAC;QACpE,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,SAAS,QAAQ,EAAE,CAAC,CAAC;QAExD,IAAI,CAAC;YACH,kBAAkB;YAClB,IAAI,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC1B,IAAI,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,SAAS,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;YAC5E,CAAC;QACH,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,6CAA6C;YAC7C,IAAI,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC1B,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;gBACpD,IAAI,CAAC;oBACH,IAAI,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,OAAO,EAAE,UAAU,EAAE,OAAO,CAAC,CAAC,CAAC;gBACrD,CAAC;gBAAC,MAAM,CAAC,CAAC,iBAAiB,CAAC,CAAC;YAC/B,CAAC;QACH,CAAC;QAED,+BAA+B;QAC/B,IAAI,IAAI,EAAE,YAAY,EAAE,CAAC;YACvB,IAAI,CAAC;gBACH,IAAI,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;YAC7D,CAAC;YAAC,MAAM,CAAC,CAAC,kCAAkC,CAAC,CAAC;QAChD,CAAC;QAED,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAC3B,OAAO,CAAC,GAAG,CAAC,0BAA0B,MAAM,EAAE,CAAC,CAAC;IAClD,CAAC;IAED;;OAEG;IACH,UAAU;QACR,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;IAC1C,CAAC;CACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
|
+
vi.mock("child_process", () => ({
|
|
3
|
+
execSync: vi.fn(),
|
|
4
|
+
execFileSync: vi.fn().mockReturnValue(""),
|
|
5
|
+
}));
|
|
6
|
+
vi.mock("fs", async (importOriginal) => {
|
|
7
|
+
const actual = await importOriginal();
|
|
8
|
+
return {
|
|
9
|
+
...actual,
|
|
10
|
+
existsSync: vi.fn().mockReturnValue(false),
|
|
11
|
+
mkdirSync: vi.fn(),
|
|
12
|
+
rmSync: vi.fn(),
|
|
13
|
+
};
|
|
14
|
+
});
|
|
15
|
+
import { execSync, execFileSync } from "child_process";
|
|
16
|
+
import { existsSync, mkdirSync } from "fs";
|
|
17
|
+
import { WorktreeManager } from "./manager.js";
|
|
18
|
+
const mockExecSync = vi.mocked(execSync);
|
|
19
|
+
const mockExecFileSync = vi.mocked(execFileSync);
|
|
20
|
+
const mockExistsSync = vi.mocked(existsSync);
|
|
21
|
+
const mockMkdirSync = vi.mocked(mkdirSync);
|
|
22
|
+
describe("WorktreeManager — no shell injection via execFileSync", () => {
|
|
23
|
+
let manager;
|
|
24
|
+
beforeEach(() => {
|
|
25
|
+
vi.clearAllMocks();
|
|
26
|
+
manager = new WorktreeManager("/tmp/test-worktrees");
|
|
27
|
+
// baseDir doesn't exist → triggers mkdir and clone
|
|
28
|
+
mockExistsSync.mockReturnValue(false);
|
|
29
|
+
});
|
|
30
|
+
it("create() does not use execSync", async () => {
|
|
31
|
+
await manager.create({
|
|
32
|
+
taskId: "task-001",
|
|
33
|
+
repoUrl: "https://github.com/cliftonc/lastlight.git",
|
|
34
|
+
branch: "feature/test",
|
|
35
|
+
});
|
|
36
|
+
expect(mockExecSync).not.toHaveBeenCalled();
|
|
37
|
+
});
|
|
38
|
+
it("create() calls execFileSync with 'git' as the command", async () => {
|
|
39
|
+
await manager.create({
|
|
40
|
+
taskId: "task-002",
|
|
41
|
+
repoUrl: "https://github.com/cliftonc/lastlight.git",
|
|
42
|
+
branch: "feature/test",
|
|
43
|
+
});
|
|
44
|
+
expect(mockExecFileSync).toHaveBeenCalled();
|
|
45
|
+
for (const call of mockExecFileSync.mock.calls) {
|
|
46
|
+
expect(call[0]).toBe("git");
|
|
47
|
+
expect(Array.isArray(call[1])).toBe(true);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
it("create() uses mkdirSync instead of execSync for directory creation", async () => {
|
|
51
|
+
await manager.create({
|
|
52
|
+
taskId: "task-003",
|
|
53
|
+
repoUrl: "https://github.com/cliftonc/lastlight.git",
|
|
54
|
+
branch: "feature/test",
|
|
55
|
+
});
|
|
56
|
+
expect(mockMkdirSync).toHaveBeenCalledWith(expect.any(String), expect.objectContaining({ recursive: true }));
|
|
57
|
+
// execSync must not have been used for mkdir
|
|
58
|
+
const mkdirCalls = mockExecSync.mock.calls.filter((c) => String(c[0]).includes("mkdir"));
|
|
59
|
+
expect(mkdirCalls).toHaveLength(0);
|
|
60
|
+
});
|
|
61
|
+
it("create() passes repoUrl as a separate argument (not shell-interpolated)", async () => {
|
|
62
|
+
const repoUrl = "https://github.com/cliftonc/lastlight.git";
|
|
63
|
+
await manager.create({
|
|
64
|
+
taskId: "task-004",
|
|
65
|
+
repoUrl,
|
|
66
|
+
branch: "feature/test",
|
|
67
|
+
});
|
|
68
|
+
const cloneCall = mockExecFileSync.mock.calls.find((c) => Array.isArray(c[1]) && c[1].includes("clone"));
|
|
69
|
+
expect(cloneCall).toBeDefined();
|
|
70
|
+
const args = cloneCall[1];
|
|
71
|
+
expect(args).toContain(repoUrl);
|
|
72
|
+
});
|
|
73
|
+
it("cleanup() does not use execSync", async () => {
|
|
74
|
+
// First create so cleanup has something to work with
|
|
75
|
+
mockExistsSync.mockReturnValue(true);
|
|
76
|
+
await manager.create({
|
|
77
|
+
taskId: "task-005",
|
|
78
|
+
repoUrl: "https://github.com/cliftonc/lastlight.git",
|
|
79
|
+
branch: "feature/test",
|
|
80
|
+
});
|
|
81
|
+
vi.clearAllMocks();
|
|
82
|
+
mockExistsSync.mockReturnValue(true);
|
|
83
|
+
await manager.cleanup("task-005", { deleteBranch: true });
|
|
84
|
+
expect(mockExecSync).not.toHaveBeenCalled();
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
//# sourceMappingURL=manager.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manager.test.js","sourceRoot":"","sources":["../../src/worktree/manager.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAE9D,EAAE,CAAC,IAAI,CAAC,eAAe,EAAE,GAAG,EAAE,CAAC,CAAC;IAC9B,QAAQ,EAAE,EAAE,CAAC,EAAE,EAAE;IACjB,YAAY,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,eAAe,CAAC,EAAE,CAAC;CAC1C,CAAC,CAAC,CAAC;AAEJ,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,cAAc,EAAE,EAAE;IACrC,MAAM,MAAM,GAAG,MAAM,cAAc,EAAuB,CAAC;IAC3D,OAAO;QACL,GAAG,MAAM;QACT,UAAU,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,eAAe,CAAC,KAAK,CAAC;QAC1C,SAAS,EAAE,EAAE,CAAC,EAAE,EAAE;QAClB,MAAM,EAAE,EAAE,CAAC,EAAE,EAAE;KAChB,CAAC;AACJ,CAAC,CAAC,CAAC;AAEH,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AACvD,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AAC3C,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAE/C,MAAM,YAAY,GAAG,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;AACzC,MAAM,gBAAgB,GAAG,EAAE,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;AACjD,MAAM,cAAc,GAAG,EAAE,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;AAC7C,MAAM,aAAa,GAAG,EAAE,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;AAE3C,QAAQ,CAAC,uDAAuD,EAAE,GAAG,EAAE;IACrE,IAAI,OAAwB,CAAC;IAE7B,UAAU,CAAC,GAAG,EAAE;QACd,EAAE,CAAC,aAAa,EAAE,CAAC;QACnB,OAAO,GAAG,IAAI,eAAe,CAAC,qBAAqB,CAAC,CAAC;QACrD,mDAAmD;QACnD,cAAc,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC;IACxC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gCAAgC,EAAE,KAAK,IAAI,EAAE;QAC9C,MAAM,OAAO,CAAC,MAAM,CAAC;YACnB,MAAM,EAAE,UAAU;YAClB,OAAO,EAAE,2CAA2C;YACpD,MAAM,EAAE,cAAc;SACvB,CAAC,CAAC;QACH,MAAM,CAAC,YAAY,CAAC,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAC;IAC9C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uDAAuD,EAAE,KAAK,IAAI,EAAE;QACrE,MAAM,OAAO,CAAC,MAAM,CAAC;YACnB,MAAM,EAAE,UAAU;YAClB,OAAO,EAAE,2CAA2C;YACpD,MAAM,EAAE,cAAc;SACvB,CAAC,CAAC;QACH,MAAM,CAAC,gBAAgB,CAAC,CAAC,gBAAgB,EAAE,CAAC;QAC5C,KAAK,MAAM,IAAI,IAAI,gBAAgB,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;YAC/C,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAC5B,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC5C,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oEAAoE,EAAE,KAAK,IAAI,EAAE;QAClF,MAAM,OAAO,CAAC,MAAM,CAAC;YACnB,MAAM,EAAE,UAAU;YAClB,OAAO,EAAE,2CAA2C;YACpD,MAAM,EAAE,cAAc;SACvB,CAAC,CAAC;QACH,MAAM,CAAC,aAAa,CAAC,CAAC,oBAAoB,CACxC,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,EAClB,MAAM,CAAC,gBAAgB,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAC7C,CAAC;QACF,6CAA6C;QAC7C,MAAM,UAAU,GAAG,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAC/C,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CACtC,CAAC;QACF,MAAM,CAAC,UAAU,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IACrC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yEAAyE,EAAE,KAAK,IAAI,EAAE;QACvF,MAAM,OAAO,GAAG,2CAA2C,CAAC;QAC5D,MAAM,OAAO,CAAC,MAAM,CAAC;YACnB,MAAM,EAAE,UAAU;YAClB,OAAO;YACP,MAAM,EAAE,cAAc;SACvB,CAAC,CAAC;QACH,MAAM,SAAS,GAAG,gBAAgB,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAChD,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAK,CAAC,CAAC,CAAC,CAAc,CAAC,QAAQ,CAAC,OAAO,CAAC,CACnE,CAAC;QACF,MAAM,CAAC,SAAS,CAAC,CAAC,WAAW,EAAE,CAAC;QAChC,MAAM,IAAI,GAAG,SAAU,CAAC,CAAC,CAAa,CAAC;QACvC,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;IAClC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iCAAiC,EAAE,KAAK,IAAI,EAAE;QAC/C,qDAAqD;QACrD,cAAc,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC;QACrC,MAAM,OAAO,CAAC,MAAM,CAAC;YACnB,MAAM,EAAE,UAAU;YAClB,OAAO,EAAE,2CAA2C;YACpD,MAAM,EAAE,cAAc;SACvB,CAAC,CAAC;QACH,EAAE,CAAC,aAAa,EAAE,CAAC;QACnB,cAAc,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC;QACrC,MAAM,OAAO,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC,CAAC;QAC1D,MAAM,CAAC,YAAY,CAAC,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAC;IAC9C,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
package/docker-compose.yml
CHANGED
|
@@ -8,11 +8,35 @@ services:
|
|
|
8
8
|
- 8.8.8.8
|
|
9
9
|
- 1.1.1.1
|
|
10
10
|
volumes:
|
|
11
|
-
|
|
11
|
+
# Single private deployment folder: overlay config.yaml + asset overrides
|
|
12
|
+
# plus a gitignored secrets/ subdir (.env, *.pem). LASTLIGHT_OVERLAY_DIR
|
|
13
|
+
# points the harness at /app/instance; the entrypoint reads secrets from
|
|
14
|
+
# /app/instance/secrets. This is the only deployment-specific input, and
|
|
15
|
+
# the harness reads it at startup — edit it and `docker compose restart
|
|
16
|
+
# agent` to apply, no image rebuild. Built-in assets (config/, workflows/,
|
|
17
|
+
# skills/, agent-context/) stay baked into the image; they're lastlight
|
|
18
|
+
# source and change via a normal rebuild.
|
|
19
|
+
- ./instance:/app/instance:ro
|
|
12
20
|
- agent-data:/app/data
|
|
13
21
|
- /var/run/docker.sock:/var/run/docker.sock # spawn sandbox containers
|
|
22
|
+
# Env comes from the consolidated overlay (instance/secrets/.env). The
|
|
23
|
+
# entrypoint also sources it inside the container; this env_file is the
|
|
24
|
+
# belt-and-suspenders copy. required: false so a fresh checkout (no
|
|
25
|
+
# instance/ yet) doesn't error. No repo-root .env is needed.
|
|
14
26
|
env_file:
|
|
15
|
-
-
|
|
27
|
+
- path: ./instance/secrets/.env
|
|
28
|
+
required: false
|
|
29
|
+
environment:
|
|
30
|
+
# V8's default old-space cap (~1.5 GB on 64-bit) is well below the 2g
|
|
31
|
+
# cgroup limit, so Node would OOM before docker did. Raise it close to
|
|
32
|
+
# the container limit so transient bursts have somewhere to land.
|
|
33
|
+
- NODE_OPTIONS=--max-old-space-size=1800
|
|
34
|
+
# Steady-state ~150 MB; bursts during webhook fan-out, the in-process
|
|
35
|
+
# chat skill (full Agent SDK), and the dispatch screening call. 2 GB
|
|
36
|
+
# gives headroom for those bursts while still preventing a runaway leak
|
|
37
|
+
# from taking the host down.
|
|
38
|
+
mem_limit: 2g
|
|
39
|
+
memswap_limit: 2g
|
|
16
40
|
networks:
|
|
17
41
|
- internal
|
|
18
42
|
|
|
@@ -25,6 +49,150 @@ services:
|
|
|
25
49
|
profiles:
|
|
26
50
|
- build-only # never started as a service, just built
|
|
27
51
|
|
|
52
|
+
# Build the enriched browser-QA sandbox image (Tier B). Separate + heavier
|
|
53
|
+
# (Playwright + a baked Chromium), built only when QA is enabled. Its FROM
|
|
54
|
+
# depends on lastlight-sandbox:latest existing first, so build in that order:
|
|
55
|
+
# docker compose --profile build-only build sandbox sandbox-qa
|
|
56
|
+
sandbox-qa:
|
|
57
|
+
build:
|
|
58
|
+
context: .
|
|
59
|
+
dockerfile: sandbox-qa.Dockerfile
|
|
60
|
+
image: lastlight-sandbox-qa:latest
|
|
61
|
+
profiles:
|
|
62
|
+
- build-only # never started as a service, just built
|
|
63
|
+
|
|
64
|
+
# ── Egress firewall (replaces the earlier tinyproxy CONNECT-proxy) ────
|
|
65
|
+
#
|
|
66
|
+
# See src/sandbox/egress-firewall-config.ts for the architecture rationale.
|
|
67
|
+
# Two pairs: (coredns-strict + nginx-egress-strict) and
|
|
68
|
+
# (coredns-open + nginx-egress-open). Sandbox containers spawn with
|
|
69
|
+
# `--dns <coredns-ip>` and dial allowlisted hosts directly — coredns
|
|
70
|
+
# resolves the hostname to the nginx IP, nginx peeks SNI and tunnels.
|
|
71
|
+
#
|
|
72
|
+
# The sandbox sees no proxy at all. Works for any SDK regardless of
|
|
73
|
+
# whether it honours HTTP(S)_PROXY env vars.
|
|
74
|
+
|
|
75
|
+
# One-shot init that writes nginx-strict.conf / nginx-open.conf /
|
|
76
|
+
# Corefile.strict / Corefile.open into the agent-data volume before
|
|
77
|
+
# coredns + nginx try to read them. Without this, the firewall
|
|
78
|
+
# services boot first, fail to find the configs, and run with empty
|
|
79
|
+
# state (returning NXDOMAIN for everything) — even after the harness
|
|
80
|
+
# later writes the files, the running processes never re-read them.
|
|
81
|
+
# `condition: service_completed_successfully` on the firewalls below
|
|
82
|
+
# makes them wait for this to exit 0.
|
|
83
|
+
egress-init:
|
|
84
|
+
image: lastlight-agent
|
|
85
|
+
restart: "no"
|
|
86
|
+
# Mounts the same overlay + secrets the agent does and runs the SAME
|
|
87
|
+
# config loader, so the collector config it pre-writes is computed from
|
|
88
|
+
# the effective config (env AND instance/config.yaml) — identical to what
|
|
89
|
+
# the harness writes at boot. This is what keeps the running collector's
|
|
90
|
+
# forwarding gate from diverging from the harness when OTEL is enabled via
|
|
91
|
+
# YAML rather than env.
|
|
92
|
+
env_file:
|
|
93
|
+
- path: ./instance/secrets/.env
|
|
94
|
+
required: false
|
|
95
|
+
volumes:
|
|
96
|
+
- ./instance:/app/instance:ro
|
|
97
|
+
- agent-data:/app/data
|
|
98
|
+
command:
|
|
99
|
+
["node", "-e",
|
|
100
|
+
"const c=require('/app/dist/config.js').loadConfig();const m=require('/app/dist/sandbox/egress-firewall-config.js');const d=c.stateDir;m.writeEgressFirewallConfigs(d);m.writeOtelCollectorConfig(d,{active:c.otel.enabled&&c.otel.forwardToSandbox})"]
|
|
101
|
+
depends_on:
|
|
102
|
+
- agent # ensures the image has been built when init runs
|
|
103
|
+
|
|
104
|
+
coredns-strict:
|
|
105
|
+
image: coredns/coredns:1.11.3
|
|
106
|
+
restart: unless-stopped
|
|
107
|
+
command: ["-conf", "/app/data/proxy/Corefile.strict"]
|
|
108
|
+
volumes:
|
|
109
|
+
- agent-data:/app/data:ro
|
|
110
|
+
depends_on:
|
|
111
|
+
egress-init:
|
|
112
|
+
condition: service_completed_successfully
|
|
113
|
+
networks:
|
|
114
|
+
sandbox-egress:
|
|
115
|
+
ipv4_address: 172.30.0.10
|
|
116
|
+
|
|
117
|
+
coredns-open:
|
|
118
|
+
image: coredns/coredns:1.11.3
|
|
119
|
+
restart: unless-stopped
|
|
120
|
+
command: ["-conf", "/app/data/proxy/Corefile.open"]
|
|
121
|
+
volumes:
|
|
122
|
+
- agent-data:/app/data:ro
|
|
123
|
+
depends_on:
|
|
124
|
+
egress-init:
|
|
125
|
+
condition: service_completed_successfully
|
|
126
|
+
networks:
|
|
127
|
+
sandbox-egress:
|
|
128
|
+
ipv4_address: 172.30.0.11
|
|
129
|
+
|
|
130
|
+
nginx-egress-strict:
|
|
131
|
+
image: nginx:alpine
|
|
132
|
+
restart: unless-stopped
|
|
133
|
+
command:
|
|
134
|
+
["nginx", "-c", "/app/data/proxy/nginx-strict.conf", "-g", "daemon off;"]
|
|
135
|
+
volumes:
|
|
136
|
+
- agent-data:/app/data:ro
|
|
137
|
+
depends_on:
|
|
138
|
+
egress-init:
|
|
139
|
+
condition: service_completed_successfully
|
|
140
|
+
# Deliberately NOT on `internal`. Splitting ingress (sandbox-egress)
|
|
141
|
+
# and egress (proxy-egress) onto separate networks that don't include
|
|
142
|
+
# harness services keeps compose-internal hostnames unresolvable to
|
|
143
|
+
# the firewall — so an `unrestricted_egress` phase can't ask nginx to
|
|
144
|
+
# tunnel to `agent:8644` or similar.
|
|
145
|
+
networks:
|
|
146
|
+
sandbox-egress:
|
|
147
|
+
ipv4_address: 172.30.0.20
|
|
148
|
+
proxy-egress: {}
|
|
149
|
+
|
|
150
|
+
nginx-egress-open:
|
|
151
|
+
image: nginx:alpine
|
|
152
|
+
restart: unless-stopped
|
|
153
|
+
command:
|
|
154
|
+
["nginx", "-c", "/app/data/proxy/nginx-open.conf", "-g", "daemon off;"]
|
|
155
|
+
volumes:
|
|
156
|
+
- agent-data:/app/data:ro
|
|
157
|
+
depends_on:
|
|
158
|
+
egress-init:
|
|
159
|
+
condition: service_completed_successfully
|
|
160
|
+
networks:
|
|
161
|
+
sandbox-egress:
|
|
162
|
+
ipv4_address: 172.30.0.21
|
|
163
|
+
proxy-egress: {}
|
|
164
|
+
|
|
165
|
+
# In-network OTEL collector. Sandboxes export telemetry here (by IP, on
|
|
166
|
+
# sandbox-egress) and it re-exports to the configured backend over
|
|
167
|
+
# proxy-egress. This is why the strict SNI firewall doesn't need to know
|
|
168
|
+
# about collector hosts or non-443 ports: the sandbox only ever dials this
|
|
169
|
+
# fixed internal endpoint, and the backend hop runs from a trusted,
|
|
170
|
+
# harness-controlled service that can use any port/scheme/headers. Backend
|
|
171
|
+
# auth headers live in the generated config (mode 0600 on the host volume)
|
|
172
|
+
# and are never forwarded into a sandbox.
|
|
173
|
+
#
|
|
174
|
+
# Dual-homed like the nginx firewalls — sandbox-egress (ingress) +
|
|
175
|
+
# proxy-egress (outbound). Deliberately NOT on `internal`, so it can't
|
|
176
|
+
# bridge a sandbox into harness services.
|
|
177
|
+
otel-collector:
|
|
178
|
+
image: otel/opentelemetry-collector:0.115.0
|
|
179
|
+
restart: unless-stopped
|
|
180
|
+
# Runs as the image's default non-root UID (10001). The harness `lastlight`
|
|
181
|
+
# user is pinned to that same UID (Dockerfile `useradd -u 10001`), so the
|
|
182
|
+
# collector reads the mode-0600 config the harness writes as its owner —
|
|
183
|
+
# no root, no world-readable secret. The image tag and that pinned UID must
|
|
184
|
+
# move together if a future image changes its user.
|
|
185
|
+
command: ["--config=/app/data/proxy/otel-collector.yaml"]
|
|
186
|
+
volumes:
|
|
187
|
+
- agent-data:/app/data:ro
|
|
188
|
+
depends_on:
|
|
189
|
+
egress-init:
|
|
190
|
+
condition: service_completed_successfully
|
|
191
|
+
networks:
|
|
192
|
+
sandbox-egress:
|
|
193
|
+
ipv4_address: 172.30.0.30
|
|
194
|
+
proxy-egress: {}
|
|
195
|
+
|
|
28
196
|
caddy:
|
|
29
197
|
image: caddy:2-alpine
|
|
30
198
|
restart: unless-stopped
|
|
@@ -35,15 +203,48 @@ services:
|
|
|
35
203
|
- ./Caddyfile:/etc/caddy/Caddyfile:ro
|
|
36
204
|
- caddy-data:/data
|
|
37
205
|
- caddy-config:/config
|
|
38
|
-
|
|
39
|
-
|
|
206
|
+
# Caddyfile uses `{$DOMAIN:localhost}` (Caddy's own env substitution), so
|
|
207
|
+
# DOMAIN just needs to be in the container env. Read it from the overlay
|
|
208
|
+
# secrets file directly — no repo-root .env / ${DOMAIN} interpolation.
|
|
209
|
+
env_file:
|
|
210
|
+
- path: ./instance/secrets/.env
|
|
211
|
+
required: false
|
|
212
|
+
# Caddy's memory footprint is tiny (typically <50 MB even under load).
|
|
213
|
+
mem_limit: 256m
|
|
214
|
+
memswap_limit: 256m
|
|
40
215
|
networks:
|
|
41
216
|
- internal
|
|
42
217
|
|
|
43
218
|
volumes:
|
|
44
|
-
agent-data: # contains: lastlight.db,
|
|
219
|
+
agent-data: # contains: lastlight.db, agent-sessions/, sandboxes/, secrets/, logs/, proxy/
|
|
220
|
+
# Shared package-manager download cache (npm/pnpm/yarn), mounted at /cache in
|
|
221
|
+
# every dynamically-created sandbox container by src/sandbox/docker.ts so
|
|
222
|
+
# repeated installs reuse already-fetched tarballs (issue #107). Declared
|
|
223
|
+
# here so the stack manages its lifecycle; the agent service does not mount
|
|
224
|
+
# it. Override the name with LASTLIGHT_PKG_CACHE_VOLUME (defaults to the
|
|
225
|
+
# compose-prefixed `lastlight_pkg-cache`).
|
|
226
|
+
pkg-cache:
|
|
45
227
|
caddy-data:
|
|
46
228
|
caddy-config:
|
|
47
229
|
|
|
48
230
|
networks:
|
|
49
231
|
internal:
|
|
232
|
+
# Sandbox containers (created dynamically by src/sandbox/docker.ts) attach
|
|
233
|
+
# to this network only with `--dns 172.30.0.10` (or .11 for open mode).
|
|
234
|
+
# `internal: true` disables direct host egress, so the only path to the
|
|
235
|
+
# public internet is through the nginx firewalls, which receive sandbox
|
|
236
|
+
# traffic on this network. Static subnet so the coredns + nginx IPs
|
|
237
|
+
# baked into the generated configs are stable.
|
|
238
|
+
sandbox-egress:
|
|
239
|
+
internal: true
|
|
240
|
+
ipam:
|
|
241
|
+
config:
|
|
242
|
+
- subnet: 172.30.0.0/24
|
|
243
|
+
# Egress-only network for the nginx firewalls. Regular bridge (has a
|
|
244
|
+
# default route to the host's network, so nginx can reach the public
|
|
245
|
+
# internet). Critically: NO other compose services attach here. That way
|
|
246
|
+
# docker's embedded DNS on this network resolves only public names — the
|
|
247
|
+
# harness service names (`agent`, `caddy`) are not reachable from the
|
|
248
|
+
# firewall's perspective, so an unrestricted sandbox can't ask nginx to
|
|
249
|
+
# bridge into the compose-internal services.
|
|
250
|
+
proxy-egress:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lastlight",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "GitHub repository maintenance agent — Agent SDK harness",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -9,12 +9,10 @@
|
|
|
9
9
|
},
|
|
10
10
|
"files": [
|
|
11
11
|
"dist",
|
|
12
|
+
"config",
|
|
12
13
|
"workflows",
|
|
13
14
|
"skills",
|
|
14
15
|
"agent-context",
|
|
15
|
-
"mcp-github-app/src",
|
|
16
|
-
"mcp-github-app/package.json",
|
|
17
|
-
"mcp-github-app/tsconfig.json",
|
|
18
16
|
"deploy",
|
|
19
17
|
"sandbox.Dockerfile",
|
|
20
18
|
"docker-compose.yml"
|
|
@@ -50,15 +48,27 @@
|
|
|
50
48
|
"test:watch": "vitest"
|
|
51
49
|
},
|
|
52
50
|
"dependencies": {
|
|
53
|
-
"@anthropic-ai/claude-agent-sdk": "^0.1.0",
|
|
54
51
|
"@clack/prompts": "^1.2.0",
|
|
55
|
-
"@
|
|
52
|
+
"@earendil-works/pi-ai": "^0.79.8",
|
|
53
|
+
"@hono/node-server": "^1.19.13",
|
|
54
|
+
"@opentelemetry/api": "^1.9.1",
|
|
55
|
+
"@opentelemetry/exporter-logs-otlp-http": "^0.219.0",
|
|
56
|
+
"@opentelemetry/exporter-metrics-otlp-http": "^0.219.0",
|
|
57
|
+
"@opentelemetry/exporter-trace-otlp-http": "^0.219.0",
|
|
58
|
+
"@opentelemetry/resources": "^2.8.0",
|
|
59
|
+
"@opentelemetry/sdk-logs": "^0.219.0",
|
|
60
|
+
"@opentelemetry/sdk-metrics": "^2.8.0",
|
|
61
|
+
"@opentelemetry/sdk-node": "^0.219.0",
|
|
62
|
+
"@opentelemetry/semantic-conventions": "^1.41.1",
|
|
63
|
+
"@sinclair/typebox": "^0.34.49",
|
|
56
64
|
"@slack/bolt": "^4.6.0",
|
|
65
|
+
"agentic-pi": "^0.2.7",
|
|
57
66
|
"arctic": "^3.7.0",
|
|
58
67
|
"better-sqlite3": "^11.0.0",
|
|
59
68
|
"chalk": "^5.6.2",
|
|
69
|
+
"cli-table3": "^0.6.5",
|
|
60
70
|
"croner": "^9.0.0",
|
|
61
|
-
"hono": "^4.
|
|
71
|
+
"hono": "^4.12.14",
|
|
62
72
|
"octokit": "^5.0.5",
|
|
63
73
|
"yaml": "^2.8.3",
|
|
64
74
|
"zod": "^4.3.6"
|
package/sandbox.Dockerfile
CHANGED
|
@@ -1,36 +1,108 @@
|
|
|
1
|
-
# Sandbox image for Last Light agent tasks
|
|
1
|
+
# Sandbox image for Last Light agent tasks (LASTLIGHT_SANDBOX=docker
|
|
2
|
+
# fallback only — the default production sandbox is gondolin, invoked
|
|
3
|
+
# in-process by agentic-pi without docker).
|
|
4
|
+
#
|
|
2
5
|
# Immutable assets baked at /app/. Entrypoint wires them into the workspace
|
|
3
6
|
# after volumes are mounted, then drops to the runtime user.
|
|
4
7
|
FROM node:20-slim
|
|
5
8
|
|
|
6
9
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
7
10
|
git ripgrep curl jq ca-certificates gettext-base gosu \
|
|
11
|
+
build-essential pkg-config python3 unzip \
|
|
8
12
|
&& rm -rf /var/lib/apt/lists/*
|
|
9
13
|
|
|
10
|
-
#
|
|
11
|
-
|
|
14
|
+
# fnm + multiple Node versions so repos pinning a specific Node via .nvmrc /
|
|
15
|
+
# package.json#engines just work. System node from the base image stays at
|
|
16
|
+
# /usr/local/bin; fnm-managed versions are pre-installed under FNM_DIR and
|
|
17
|
+
# selected per-shell by the bashrc hook below.
|
|
18
|
+
ENV FNM_DIR=/usr/local/share/fnm
|
|
19
|
+
ENV PATH=$FNM_DIR/aliases/default/bin:$PATH
|
|
20
|
+
RUN curl -fsSL https://fnm.vercel.app/install \
|
|
21
|
+
| bash -s -- --install-dir "$FNM_DIR" --skip-shell \
|
|
22
|
+
&& ln -s "$FNM_DIR/fnm" /usr/local/bin/fnm \
|
|
23
|
+
&& fnm install 22 \
|
|
24
|
+
&& fnm install 24 \
|
|
25
|
+
&& fnm default 22 \
|
|
26
|
+
&& chmod -R a+rX "$FNM_DIR" \
|
|
27
|
+
&& mkdir -p "$FNM_DIR/multishells" \
|
|
28
|
+
&& chmod 1777 "$FNM_DIR/multishells"
|
|
12
29
|
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
30
|
+
# Source fnm in every bash invocation (interactive or not). BASH_ENV makes
|
|
31
|
+
# non-interactive `bash -c` read this file — that's how opencode's bash tool
|
|
32
|
+
# inherits the right node version when it runs `npm ci` inside a repo with
|
|
33
|
+
# an .nvmrc pinning Node 24.
|
|
34
|
+
RUN printf '%s\n' \
|
|
35
|
+
'export FNM_DIR=/usr/local/share/fnm' \
|
|
36
|
+
'export PATH="$FNM_DIR:$PATH"' \
|
|
37
|
+
'# --shell bash is required: when sourced via BASH_ENV the parent process' \
|
|
38
|
+
'# is not a shell so fnm cannot auto-detect.' \
|
|
39
|
+
'eval "$(fnm env --shell bash --use-on-cd --version-file-strategy=recursive)"' \
|
|
40
|
+
'# The cd hook fires only on cd. Tools often launch `bash -c "..."` with' \
|
|
41
|
+
'# cwd already set via the spawn options — no cd happens — so also' \
|
|
42
|
+
'# auto-switch on shell start when the cwd has a version file.' \
|
|
43
|
+
'if [ -f "$PWD/.nvmrc" ] || [ -f "$PWD/.node-version" ]; then' \
|
|
44
|
+
' fnm use --silent-if-unchanged 2>/dev/null \' \
|
|
45
|
+
' || { fnm install 2>/dev/null && fnm use --silent-if-unchanged 2>/dev/null; } \' \
|
|
46
|
+
' || true' \
|
|
47
|
+
'fi' \
|
|
48
|
+
> /etc/bash.bashrc.fnm \
|
|
49
|
+
&& printf '\n[ -r /etc/bash.bashrc.fnm ] && . /etc/bash.bashrc.fnm\n' >> /etc/bash.bashrc \
|
|
50
|
+
&& ln -s /etc/bash.bashrc.fnm /etc/profile.d/fnm.sh
|
|
51
|
+
# Source the fnm file directly — Debian's /etc/bash.bashrc bails early on
|
|
52
|
+
# non-interactive shells (`[ -z "$PS1" ] && return`), so pointing BASH_ENV
|
|
53
|
+
# at it would skip our setup for `bash -c` invocations (which is the path
|
|
54
|
+
# the agent's bash tool uses).
|
|
55
|
+
ENV BASH_ENV=/etc/bash.bashrc.fnm
|
|
19
56
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
57
|
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
58
|
+
python3 python3-pip pipx \
|
|
59
|
+
&& PIPX_HOME=/opt/pipx PIPX_BIN_DIR=/usr/local/bin pipx install semgrep \
|
|
60
|
+
&& curl -sSfL https://github.com/gitleaks/gitleaks/releases/download/v8.21.2/gitleaks_8.21.2_linux_x64.tar.gz \
|
|
61
|
+
| tar -xz -C /usr/local/bin gitleaks \
|
|
62
|
+
&& apt-get purge -y python3-pip \
|
|
63
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
24
64
|
|
|
25
|
-
#
|
|
26
|
-
|
|
65
|
+
# Create non-root agent user, UID/GID pinned to 10001 to MATCH the harness
|
|
66
|
+
# `lastlight` user (see Dockerfile: `useradd -u 10001 lastlight`). The
|
|
67
|
+
# entrypoint chowns the bind-mounted workspace to `agent`; per-PR workspaces
|
|
68
|
+
# are persistent and reused across runs (issue #107), so the harness re-stages
|
|
69
|
+
# AGENTS.md + skills into them on the next run. If `agent` lands on the default
|
|
70
|
+
# uid (1001) those files become unwritable by the harness (10001) on reuse —
|
|
71
|
+
# EACCES, and the review silently runs with no skill/AGENTS.md. Sharing the uid
|
|
72
|
+
# keeps the reused workspace mutually writable by both the agent (in-container)
|
|
73
|
+
# and the harness (host). Also self-heals dirs left at 1001 by older images:
|
|
74
|
+
# the entrypoint's `chown -R agent:agent` re-owns them to 10001 on next reuse.
|
|
75
|
+
RUN groupadd -g 10001 agent && useradd -m -s /bin/bash -u 10001 -g 10001 agent
|
|
27
76
|
|
|
28
|
-
#
|
|
77
|
+
# Install agentic-pi globally so the harness can `docker exec ...
|
|
78
|
+
# agentic-pi run ...` against this container. The version + integrity
|
|
79
|
+
# come straight from lastlight's `package-lock.json` so `npm i agentic-pi@…`
|
|
80
|
+
# in the harness automatically flows into the sandbox image — no
|
|
81
|
+
# manual Dockerfile bumps. `npm install -g <tarball>` itself doesn't
|
|
82
|
+
# consult a lockfile, hence the explicit integrity verification.
|
|
83
|
+
COPY package-lock.json /tmp/lastlight-lock.json
|
|
84
|
+
RUN node -e "const p=require('/tmp/lastlight-lock.json').packages['node_modules/agentic-pi']; \
|
|
85
|
+
if(!p) throw new Error('agentic-pi missing from lockfile'); \
|
|
86
|
+
process.stdout.write(p.version + '\n' + p.integrity + '\n')" > /tmp/agentic-pi.pin \
|
|
87
|
+
&& version=$(sed -n '1p' /tmp/agentic-pi.pin) \
|
|
88
|
+
&& expected=$(sed -n '2p' /tmp/agentic-pi.pin) \
|
|
89
|
+
&& echo "Installing agentic-pi@${version} (${expected})" \
|
|
90
|
+
&& curl -fsSL "https://registry.npmjs.org/agentic-pi/-/agentic-pi-${version}.tgz" -o /tmp/agentic-pi.tgz \
|
|
91
|
+
&& actual="sha512-$(node -e "const c=require('crypto'),f=require('fs');process.stdout.write(c.createHash('sha512').update(f.readFileSync('/tmp/agentic-pi.tgz')).digest('base64'))")" \
|
|
92
|
+
&& if [ "$actual" != "$expected" ]; then \
|
|
93
|
+
echo "agentic-pi tarball integrity mismatch:" >&2; \
|
|
94
|
+
echo " expected: $expected" >&2; \
|
|
95
|
+
echo " actual: $actual" >&2; \
|
|
96
|
+
exit 1; \
|
|
97
|
+
fi \
|
|
98
|
+
&& npm install -g --no-audit --no-fund /tmp/agentic-pi.tgz \
|
|
99
|
+
&& rm /tmp/agentic-pi.tgz /tmp/agentic-pi.pin /tmp/lastlight-lock.json
|
|
100
|
+
|
|
101
|
+
# Agent context (baked at /app/ — entrypoint cats into workspace/AGENTS.md)
|
|
29
102
|
COPY agent-context/ /app/agent-context/
|
|
30
103
|
|
|
31
|
-
# Entrypoint
|
|
104
|
+
# Entrypoint
|
|
32
105
|
COPY deploy/sandbox-entrypoint.sh /app/sandbox-entrypoint.sh
|
|
33
|
-
COPY deploy/mcp-config.tmpl.json /app/mcp-config.tmpl.json
|
|
34
106
|
RUN chmod +x /app/sandbox-entrypoint.sh
|
|
35
107
|
|
|
36
108
|
# Own app dir for agent user
|
|
@@ -38,6 +110,13 @@ RUN chown -R agent:agent /app /home/agent
|
|
|
38
110
|
|
|
39
111
|
WORKDIR /home/agent/workspace
|
|
40
112
|
|
|
113
|
+
# Image-level env so every `docker exec` (the entrypoint just runs once at
|
|
114
|
+
# container start) sees these. Exporting them in sandbox-entrypoint.sh only
|
|
115
|
+
# affects PID 1 — subsequent `docker exec agentic-pi run …` calls get a
|
|
116
|
+
# fresh environment and would otherwise miss these paths.
|
|
117
|
+
ENV LASTLIGHT_WORKSPACE=/home/agent/workspace
|
|
118
|
+
ENV LASTLIGHT_GIT_CREDENTIALS=/home/agent/.lastlight-git-credentials
|
|
119
|
+
|
|
41
120
|
# Entrypoint runs as root, fixes permissions, then drops to agent via gosu
|
|
42
121
|
ENTRYPOINT ["/app/sandbox-entrypoint.sh"]
|
|
43
122
|
CMD ["sleep", "infinity"]
|