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/skills/README.md
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Skills
|
|
2
|
+
|
|
3
|
+
Skill directories staged into the sandbox per workflow phase (and a subset into
|
|
4
|
+
chat). A phase declares `skill: <name>` / `skills: [<name>, …]`; the runner
|
|
5
|
+
stages the whole directory into `<agentCwd>/.agents/skills/<name>/` and
|
|
6
|
+
pi-coding-agent surfaces `name` + `description` in the system prompt. The agent
|
|
7
|
+
reads SKILL.md (and any `references/`) on demand via its `read` tool.
|
|
8
|
+
|
|
9
|
+
**Authoring guide:** [`docs/agents/writing-skills.md`](../docs/agents/writing-skills.md)
|
|
10
|
+
— the loading contract, the craft, and house conventions. Read it before adding
|
|
11
|
+
or editing a skill.
|
|
12
|
+
|
|
13
|
+
## Skills
|
|
14
|
+
|
|
15
|
+
| Skill | Used by | Purpose |
|
|
16
|
+
|-------|---------|---------|
|
|
17
|
+
| [`chat`](chat/) | chat | Conversational assistant for Slack/Discord threads — answer repo/PR/issue questions, guide to natural-language triggers. |
|
|
18
|
+
| [`issue-triage`](issue-triage/) | `issue-triage.yaml`, chat | Move issues through the canonical triage state machine — categorise, label, dedupe, manage stale, write agent briefs. |
|
|
19
|
+
| [`issue-comment`](issue-comment/) | `issue-comment.yaml` | Handle non-build maintainer comments on issues/PRs — close, label, answer briefly, redirect build requests. |
|
|
20
|
+
| [`pr-review`](pr-review/) | `pr-review.yaml`, chat | Review a PR with structured, tiered feedback; build and test from the local checkout; post a formal review. |
|
|
21
|
+
| [`pr-comment`](pr-comment/) | `pr-comment.yaml` | Answer a maintainer's question about an open PR with concrete, code-cited evidence (not a full review). |
|
|
22
|
+
| [`repo-health`](repo-health/) | `repo-health.yaml`, chat | Generate a repository health report — open issues, PR backlog, CI status, action items. |
|
|
23
|
+
| [`security-review`](security-review/) | `security-review.yaml` | Diff-scoped SDLC security review; file one dated summary issue with a task-list of findings. |
|
|
24
|
+
| [`security-feedback`](security-feedback/) | `security-feedback.yaml` | Process a maintainer's comment on a security scan summary — break findings into issues or record suppressions. |
|
|
25
|
+
| [`building`](building/) | build (executor, reviewer), `pr-fix.yaml`, `pr-review.yaml` | Shared craft: install-first + package-manager detection, the test/lint/typecheck gate, and TDD discipline in the sandbox. |
|
|
26
|
+
| [`code-review`](code-review/) | build (reviewer), `pr-review.yaml` | Shared rubric: finding tiers (Critical/Important/Suggestions/Nits) and what-to-check. Referenced by both the branch-diff reviewer and the PR reviewer. |
|
|
27
|
+
| [`verify`](verify/) | `verify.yaml` | Test a behaviour claim as an investigator — CONFIRMED / REFUTED / INCONCLUSIVE with bash-captured evidence. |
|
|
28
|
+
| [`qa-test`](qa-test/) | `qa-test.yaml` | Run an automated QA flow against a CLI or locally-served app and report step-level pass/fail with evidence. |
|
|
29
|
+
| [`browser-qa`](browser-qa/) | `verify.yaml`, `qa-test.yaml`, `demo.yaml` | Drive a real headless browser against a locally-served web UI and capture screenshot (and, for `demo`, video) evidence. Gated to the docker QA image. |
|
|
30
|
+
| [`demo`](demo/) | `demo.yaml` | Record a short demo VIDEO of a PR/feature — drive the web UI, screen-record, and composite a titled, size-capped mp4 with ffmpeg. Gated to the docker QA image. |
|
|
31
|
+
|
|
32
|
+
`building` and `code-review` are **shared building blocks** — referenced by the
|
|
33
|
+
build-cycle prompts (`workflows/prompts/*.md`) and the PR skills so the install
|
|
34
|
+
gate and review rubric live in one place. They're staged alongside the phase's
|
|
35
|
+
primary skill/prompt and read on demand.
|
|
36
|
+
|
|
37
|
+
The live set is enforced at startup: `validateAssets()` (`src/workflows/loader.ts`)
|
|
38
|
+
resolves every workflow `skill:` reference and `CHAT_SKILL_NAMES`
|
|
39
|
+
(`src/engine/chat-skills.ts`). Adding a skill here does nothing until a phase or
|
|
40
|
+
the chat list references it.
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: browser-qa
|
|
3
|
+
description: Drive a real headless browser against a locally-served web UI and capture screenshot evidence, reporting step-level PASS/FAIL/BLOCKED with extracted DOM text and console errors. Use when a phase on the docker QA image must exercise a rendered UI and attach screenshots — it complements the text-evidence verify/qa-test skills.
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
tags: [browser, qa, screenshots, evidence]
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Browser QA
|
|
9
|
+
|
|
10
|
+
Drive a web UI with a real headless Chromium and report **step-level
|
|
11
|
+
pass/fail with evidence**: extracted DOM text, assertion results, console
|
|
12
|
+
errors, and screenshot files. The deliverable is a QA report — partial
|
|
13
|
+
coverage with documented failures is the expected output.
|
|
14
|
+
|
|
15
|
+
This skill is the **browser/screenshot path** that `verify` and `qa-test`
|
|
16
|
+
scope out to "a separate docker-gated capability". It only works on the
|
|
17
|
+
`lastlight-sandbox-qa:latest` docker image, which bakes in Playwright +
|
|
18
|
+
Chromium. You have bash + file tools but **no vision** — you reason over the
|
|
19
|
+
driver's JSON stdout (extracted text, assertion results, console errors).
|
|
20
|
+
The PNG screenshots are **human evidence only**; you never see them.
|
|
21
|
+
|
|
22
|
+
This skill uses the **building** skill for installing dependencies and
|
|
23
|
+
starting the repo's dev-server.
|
|
24
|
+
|
|
25
|
+
## When this applies
|
|
26
|
+
|
|
27
|
+
A phase running on the docker QA image whose target is a **web app** the
|
|
28
|
+
repo serves on `localhost` and where rendered-UI behaviour or visual
|
|
29
|
+
evidence matters. For a CLI or a curl-able API, use `qa-test`/`verify`
|
|
30
|
+
instead — don't launch a browser you don't need.
|
|
31
|
+
|
|
32
|
+
## FIRST — probe for a browser
|
|
33
|
+
|
|
34
|
+
The driver lives in this skill's bundle at
|
|
35
|
+
`<skill-dir>/scripts/agent-browser.mjs`. Resolve `<skill-dir>` from the
|
|
36
|
+
available-skills catalogue (it gives the absolute staged path of this
|
|
37
|
+
skill); do not assume a relative path from `$PWD` — the agent's cwd is the
|
|
38
|
+
checked-out repo, the bundle is a sibling.
|
|
39
|
+
|
|
40
|
+
Run the runtime probe before anything else:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
node <skill-dir>/scripts/agent-browser.mjs doctor
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
- Exit 0 with `{"ok":true,"chromium":"..."}` → Chromium is available; proceed.
|
|
47
|
+
- Exit non-zero (e.g. `playwright not available …`, or no Chromium — the lean
|
|
48
|
+
default image, or a gondolin run) → **DO NOT attempt browser QA.** Fall back
|
|
49
|
+
to the text path (`verify`/`qa-test` style: curl the dev-server, capture
|
|
50
|
+
stdout) and **say in your report that the browser path was unavailable** and
|
|
51
|
+
you used the text path.
|
|
52
|
+
|
|
53
|
+
## Start the app
|
|
54
|
+
|
|
55
|
+
Follow the **building** skill to install dependencies, then start the repo's
|
|
56
|
+
dev-server **in the background** and wait until it is listening on
|
|
57
|
+
`localhost:PORT` (poll with `curl` until it answers). Note the port — you
|
|
58
|
+
pass it as `--base-url`.
|
|
59
|
+
|
|
60
|
+
## Author a flow
|
|
61
|
+
|
|
62
|
+
Write a `flow.json` describing the QA flow. Shape:
|
|
63
|
+
|
|
64
|
+
```json
|
|
65
|
+
{
|
|
66
|
+
"baseUrl": "http://localhost:3000",
|
|
67
|
+
"viewport": { "width": 1280, "height": 800 },
|
|
68
|
+
"steps": [
|
|
69
|
+
{ "name": "home", "goto": "/" },
|
|
70
|
+
{ "click": "text=Login" },
|
|
71
|
+
{ "fill": ["#email", "a@b.com"] },
|
|
72
|
+
{ "type": ["#password", "hunter2"] },
|
|
73
|
+
{ "press": "Enter" },
|
|
74
|
+
{ "waitFor": "#dashboard" },
|
|
75
|
+
{ "assertText": "Welcome" },
|
|
76
|
+
{ "text": "h1" },
|
|
77
|
+
{ "screenshot": "after-login" }
|
|
78
|
+
]
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Step keys (a step may combine an action **plus** a trailing `screenshot`):
|
|
83
|
+
|
|
84
|
+
| Key | Value | Effect |
|
|
85
|
+
|--------------|----------------------|------------------------------------------------------------------|
|
|
86
|
+
| `goto` | path or URL | Navigate (resolved against `baseUrl`). **Failure is fatal.** |
|
|
87
|
+
| `click` | selector | Click the element. |
|
|
88
|
+
| `fill` | `[selector, value]` | Set an input's value. |
|
|
89
|
+
| `type` | `[selector, value]` | Type key-by-key into an input. |
|
|
90
|
+
| `press` | key (e.g. `Enter`) | Press a keyboard key. |
|
|
91
|
+
| `waitFor` | selector | Wait until the element is visible (~10s timeout). |
|
|
92
|
+
| `assertText` | string | Pass if the text is visible anywhere on the page; else step FAIL.|
|
|
93
|
+
| `text` | selector | Extract `textContent` into the step result (for you to read). |
|
|
94
|
+
| `screenshot` | basename | Write `<out-dir>/<basename>.png` (full page). |
|
|
95
|
+
|
|
96
|
+
The whole flow runs in **one Chromium session** — login state, cookies, and
|
|
97
|
+
navigation persist across steps.
|
|
98
|
+
|
|
99
|
+
## Run the flow
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
node <skill-dir>/scripts/agent-browser.mjs run flow.json \
|
|
103
|
+
--base-url http://localhost:PORT \
|
|
104
|
+
--out-dir <artifact-dir>
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
The driver prints a single JSON report:
|
|
108
|
+
|
|
109
|
+
```json
|
|
110
|
+
{ "ok": false,
|
|
111
|
+
"baseUrl": "http://localhost:3000",
|
|
112
|
+
"steps": [
|
|
113
|
+
{ "index": 0, "action": "goto", "ok": true, "ms": 412 },
|
|
114
|
+
{ "index": 6, "action": "assertText", "ok": false, "ms": 10003,
|
|
115
|
+
"error": "assertText not found: \"Welcome\"" },
|
|
116
|
+
{ "index": 7, "action": "text", "ok": true, "ms": 5, "text": "Dashboard" },
|
|
117
|
+
{ "index": 8, "action": "screenshot", "ok": true, "ms": 88,
|
|
118
|
+
"screenshot": "/…/after-login.png" }
|
|
119
|
+
],
|
|
120
|
+
"consoleErrors": ["Uncaught TypeError: …"],
|
|
121
|
+
"screenshots": ["/…/after-login.png"] }
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
- A step error (selector not found, assertion miss, timeout) is recorded
|
|
125
|
+
`ok:false` and the run **continues** — best-effort coverage. The exception
|
|
126
|
+
is a `goto` that fails: that is fatal and the remaining steps are skipped.
|
|
127
|
+
- `ok` at the top level is true only when **every** step passed. The process
|
|
128
|
+
exits 0 even when steps failed — judge from the JSON, not the exit code.
|
|
129
|
+
A non-zero exit means a fatal harness error (bad flow file, launch failure).
|
|
130
|
+
|
|
131
|
+
## Screenshots & artifacts
|
|
132
|
+
|
|
133
|
+
Pass `--out-dir <artifact-dir>` pointing at the run's artifact directory the
|
|
134
|
+
**prompt provides** (commonly the run's `.lastlight/<issueKey>/` dir,
|
|
135
|
+
referenced as the template variable the prompt passes in). Saving there lets
|
|
136
|
+
the harness **harvest** the PNGs into the dashboard Artifacts view. Reference
|
|
137
|
+
each screenshot path in your report.
|
|
138
|
+
|
|
139
|
+
Screenshot delivery requires **server-mode build-assets**. If the PNGs can't
|
|
140
|
+
be persisted (no artifact dir was provided, or build-assets is repo-mode),
|
|
141
|
+
still report the text/DOM observations and console errors, and say the
|
|
142
|
+
screenshots were **not retained**.
|
|
143
|
+
|
|
144
|
+
## Evidence rules
|
|
145
|
+
|
|
146
|
+
Mirror `verify`/`qa-test` — you are an investigator, not an advocate:
|
|
147
|
+
|
|
148
|
+
- **Never fake a pass.** Don't hardcode expected text or edit the code under
|
|
149
|
+
test to force a green step.
|
|
150
|
+
- **Report real failures.** A step that genuinely fails is a finding — record
|
|
151
|
+
it `FAIL` with the assertion/selector and the driver's error, and don't bury
|
|
152
|
+
it.
|
|
153
|
+
- **Treat `consoleErrors` and page errors as findings**, even if the visible
|
|
154
|
+
flow "worked" — surface them in the report.
|
|
155
|
+
- If the browser is unavailable (probe failed), fall back to text and say so —
|
|
156
|
+
don't pretend a UI step ran.
|
|
157
|
+
|
|
158
|
+
## Report
|
|
159
|
+
|
|
160
|
+
Produce your report as your **final message** — the workflow posts it for you
|
|
161
|
+
(don't `github_add_issue_comment` yourself; that would double-post).
|
|
162
|
+
|
|
163
|
+
**Be concise, and stay in the browser.** The deliverable is short: a one-line
|
|
164
|
+
environment summary, the results table, the screenshots, console errors, and a
|
|
165
|
+
one-line coverage note. Do **not** read or analyse the repo's source code (the
|
|
166
|
+
text pass already did) and do **not** narrate every step or dump extracted DOM
|
|
167
|
+
text — surface a `text`/assertion value only when it's the actual evidence for a
|
|
168
|
+
row (e.g. a FAIL).
|
|
169
|
+
|
|
170
|
+
```
|
|
171
|
+
## Browser QA: <target>
|
|
172
|
+
|
|
173
|
+
**Environment:** <branch / commit, package manager, how served, Chromium version>
|
|
174
|
+
|
|
175
|
+
### Results
|
|
176
|
+
| Step | Status | Evidence |
|
|
177
|
+
|------|--------|----------|
|
|
178
|
+
| <step description> | PASS / FAIL / BLOCKED | <terse evidence + inline screenshot if any> |
|
|
179
|
+
|
|
180
|
+
### Console errors
|
|
181
|
+
- <each consoleErrors / pageerror entry, or "none observed">
|
|
182
|
+
|
|
183
|
+
### Coverage
|
|
184
|
+
<one or two lines: what was driven, and anything not tested and why>
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
Reference screenshots **inline** as raw Markdown images when the prompt gives
|
|
188
|
+
you a public artifact base URL — write `` directly
|
|
189
|
+
in the report body (or table cell), **never** wrapped in backticks or a code
|
|
190
|
+
fence (inside a code span it renders as literal text, not an image). Otherwise
|
|
191
|
+
reference them by filename. Every step in the flow must
|
|
192
|
+
have a row and a result. Never report a flow as "passed" with steps you didn't
|
|
193
|
+
actually run — list them as untested instead.
|
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// agent-browser.mjs — bundled headless-browser driver for Tier B browser QA.
|
|
3
|
+
//
|
|
4
|
+
// CONTRACT
|
|
5
|
+
// --------
|
|
6
|
+
// Runs inside the `lastlight-sandbox-qa:latest` docker image, which bakes in
|
|
7
|
+
// Playwright + Chromium at PLAYWRIGHT_BROWSERS_PATH=/opt/playwright-browsers and
|
|
8
|
+
// makes the global `playwright` package resolvable via NODE_PATH. The calling
|
|
9
|
+
// agent has bash + file tools but NO vision: it reasons over this CLI's JSON
|
|
10
|
+
// stdout (extracted text, assertion results, console errors). The PNG
|
|
11
|
+
// screenshots are human evidence only — the agent never "sees" them.
|
|
12
|
+
//
|
|
13
|
+
// Subcommands:
|
|
14
|
+
// doctor
|
|
15
|
+
// Runtime probe. Launches headless Chromium (--no-sandbox), opens
|
|
16
|
+
// about:blank, closes. On success prints {"ok":true,"chromium":"<version>"}
|
|
17
|
+
// and exits 0; on ANY failure prints {"ok":false,"error":"..."} and exits 1.
|
|
18
|
+
// The skill runs this first to decide browser-vs-text.
|
|
19
|
+
//
|
|
20
|
+
// run <flow.json> [--base-url URL] [--out-dir DIR] [--record-dir DIR]
|
|
21
|
+
// Executes a FLOW in ONE Chromium session (state preserved across steps) and
|
|
22
|
+
// prints a single JSON report.
|
|
23
|
+
//
|
|
24
|
+
// With --record-dir DIR (or `"record": true` in the flow), the whole session
|
|
25
|
+
// is screen-recorded via Playwright's native recordVideo and saved to
|
|
26
|
+
// <record-dir>/session.webm (default: the out-dir). The saved path is
|
|
27
|
+
// reported as `video` in the JSON. Used by the `/demo` workflow, which then
|
|
28
|
+
// composites the raw webm into a titled mp4 with compose-demo.sh.
|
|
29
|
+
//
|
|
30
|
+
// Flow shape:
|
|
31
|
+
// { "baseUrl": "http://localhost:3000",
|
|
32
|
+
// "viewport": {"width":1280,"height":800},
|
|
33
|
+
// "steps": [
|
|
34
|
+
// {"name":"home", "goto":"/"},
|
|
35
|
+
// {"click":"text=Login"},
|
|
36
|
+
// {"fill":["#email","a@b.com"]},
|
|
37
|
+
// {"type":["#search","hello"]},
|
|
38
|
+
// {"press":"Enter"},
|
|
39
|
+
// {"waitFor":"#dashboard"},
|
|
40
|
+
// {"assertText":"Welcome"},
|
|
41
|
+
// {"text":"h1"},
|
|
42
|
+
// {"screenshot":"after-login"}
|
|
43
|
+
// ] }
|
|
44
|
+
// A step may combine an action plus a trailing `screenshot`.
|
|
45
|
+
//
|
|
46
|
+
// Per-step semantics:
|
|
47
|
+
// - A step error (selector not found, assertion fail, timeout) is recorded
|
|
48
|
+
// {ok:false} and the run CONTINUES (best-effort QA) — EXCEPT a `goto`
|
|
49
|
+
// that throws is FATAL: remaining steps are marked skipped.
|
|
50
|
+
// - assertText passes if the text is visible anywhere on the page; on miss
|
|
51
|
+
// the step is {ok:false} but the run continues.
|
|
52
|
+
// - text extracts a selector's textContent into the step result.
|
|
53
|
+
// - screenshot writes <out-dir>/<basename>.png (full page).
|
|
54
|
+
//
|
|
55
|
+
// Final stdout: one JSON object
|
|
56
|
+
// { ok, baseUrl, steps:[{index,action,ok,ms,text?,screenshot?,error?}],
|
|
57
|
+
// consoleErrors:[...], screenshots:[paths] }
|
|
58
|
+
// Exit 0 even when steps failed (the agent reads the JSON to judge). Exit
|
|
59
|
+
// non-zero only on a FATAL harness error (bad flow file, launch failure).
|
|
60
|
+
//
|
|
61
|
+
// Dependency-free apart from playwright.
|
|
62
|
+
|
|
63
|
+
import { readFileSync, mkdirSync } from 'node:fs';
|
|
64
|
+
import { resolve, join } from 'node:path';
|
|
65
|
+
import { createRequire } from 'node:module';
|
|
66
|
+
|
|
67
|
+
// This file is ESM (.mjs); playwright is a CJS package. createRequire gives us
|
|
68
|
+
// a `require` that resolves an absolute package-dir path (see loadPlaywright).
|
|
69
|
+
const require = createRequire(import.meta.url);
|
|
70
|
+
|
|
71
|
+
const WAIT_TIMEOUT = 10_000; // sane default for waitFor / assertText probes
|
|
72
|
+
|
|
73
|
+
function emit(obj) {
|
|
74
|
+
process.stdout.write(JSON.stringify(obj) + '\n');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async function loadPlaywright() {
|
|
78
|
+
// Resolve playwright via CJS `require`: it handles an absolute package-dir
|
|
79
|
+
// path ($LASTLIGHT_PLAYWRIGHT, baked into the QA image) by reading the
|
|
80
|
+
// package.json `main` — which ESM `import()` of a directory does NOT do — and
|
|
81
|
+
// it honours NODE_PATH for the bare-specifier fallback. playwright ships a CJS
|
|
82
|
+
// entry, so `require` returns { chromium, … } directly.
|
|
83
|
+
const candidates = [process.env.LASTLIGHT_PLAYWRIGHT, 'playwright'].filter(Boolean);
|
|
84
|
+
for (const spec of candidates) {
|
|
85
|
+
try {
|
|
86
|
+
return require(spec);
|
|
87
|
+
} catch {
|
|
88
|
+
// try the next candidate
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
emit({
|
|
92
|
+
ok: false,
|
|
93
|
+
error:
|
|
94
|
+
'playwright not available — browser QA needs the lastlight-sandbox-qa image',
|
|
95
|
+
});
|
|
96
|
+
process.exit(1);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async function doctor() {
|
|
100
|
+
const { chromium } = await loadPlaywright();
|
|
101
|
+
let browser;
|
|
102
|
+
try {
|
|
103
|
+
browser = await chromium.launch({ headless: true, args: ['--no-sandbox'] });
|
|
104
|
+
const page = await browser.newPage();
|
|
105
|
+
await page.goto('about:blank');
|
|
106
|
+
const version = browser.version();
|
|
107
|
+
await page.close();
|
|
108
|
+
emit({ ok: true, chromium: version });
|
|
109
|
+
process.exit(0);
|
|
110
|
+
} catch (err) {
|
|
111
|
+
emit({ ok: false, error: String(err && err.message ? err.message : err) });
|
|
112
|
+
process.exit(1);
|
|
113
|
+
} finally {
|
|
114
|
+
if (browser) await browser.close().catch(() => {});
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Resolve a goto target against the base URL. Absolute URLs pass through.
|
|
119
|
+
function resolveUrl(target, baseUrl) {
|
|
120
|
+
if (/^https?:\/\//i.test(target)) return target;
|
|
121
|
+
if (!baseUrl) return target;
|
|
122
|
+
return baseUrl.replace(/\/+$/, '') + '/' + String(target).replace(/^\/+/, '');
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Returns the action name for a step (for reporting) — first recognized key.
|
|
126
|
+
function actionOf(step) {
|
|
127
|
+
for (const k of [
|
|
128
|
+
'goto',
|
|
129
|
+
'click',
|
|
130
|
+
'fill',
|
|
131
|
+
'type',
|
|
132
|
+
'press',
|
|
133
|
+
'waitFor',
|
|
134
|
+
'assertText',
|
|
135
|
+
'text',
|
|
136
|
+
]) {
|
|
137
|
+
if (k in step) return k;
|
|
138
|
+
}
|
|
139
|
+
if ('screenshot' in step) return 'screenshot';
|
|
140
|
+
return 'noop';
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async function execStep(page, step, baseUrl, outDir, screenshots) {
|
|
144
|
+
const result = {};
|
|
145
|
+
let fatal = false;
|
|
146
|
+
|
|
147
|
+
if ('goto' in step) {
|
|
148
|
+
// Navigation failure is FATAL.
|
|
149
|
+
try {
|
|
150
|
+
await page.goto(resolveUrl(step.goto, baseUrl), {
|
|
151
|
+
waitUntil: 'load',
|
|
152
|
+
timeout: WAIT_TIMEOUT * 3,
|
|
153
|
+
});
|
|
154
|
+
} catch (err) {
|
|
155
|
+
throw Object.assign(new Error(`goto failed: ${err.message}`), {
|
|
156
|
+
fatal: true,
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
} else if ('click' in step) {
|
|
160
|
+
await page.click(step.click, { timeout: WAIT_TIMEOUT });
|
|
161
|
+
} else if ('fill' in step) {
|
|
162
|
+
const [sel, val] = step.fill;
|
|
163
|
+
await page.fill(sel, val, { timeout: WAIT_TIMEOUT });
|
|
164
|
+
} else if ('type' in step) {
|
|
165
|
+
const [sel, val] = step.type;
|
|
166
|
+
await page.locator(sel).first().pressSequentially(val, {
|
|
167
|
+
timeout: WAIT_TIMEOUT,
|
|
168
|
+
});
|
|
169
|
+
} else if ('press' in step) {
|
|
170
|
+
await page.keyboard.press(step.press);
|
|
171
|
+
} else if ('waitFor' in step) {
|
|
172
|
+
await page.locator(step.waitFor).first().waitFor({
|
|
173
|
+
state: 'visible',
|
|
174
|
+
timeout: WAIT_TIMEOUT,
|
|
175
|
+
});
|
|
176
|
+
} else if ('assertText' in step) {
|
|
177
|
+
// Pass if visible anywhere on the page; miss => step fails, run continues.
|
|
178
|
+
try {
|
|
179
|
+
await page
|
|
180
|
+
.getByText(step.assertText, { exact: false })
|
|
181
|
+
.first()
|
|
182
|
+
.waitFor({ state: 'visible', timeout: WAIT_TIMEOUT });
|
|
183
|
+
} catch {
|
|
184
|
+
throw new Error(`assertText not found: ${JSON.stringify(step.assertText)}`);
|
|
185
|
+
}
|
|
186
|
+
} else if ('text' in step) {
|
|
187
|
+
const txt = await page
|
|
188
|
+
.locator(step.text)
|
|
189
|
+
.first()
|
|
190
|
+
.textContent({ timeout: WAIT_TIMEOUT });
|
|
191
|
+
result.text = txt == null ? '' : txt.trim();
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// A trailing screenshot can ride along with any action (or stand alone).
|
|
195
|
+
if ('screenshot' in step) {
|
|
196
|
+
const base = String(step.screenshot).replace(/[^a-zA-Z0-9._-]/g, '_');
|
|
197
|
+
const path = join(outDir, `${base}.png`);
|
|
198
|
+
await page.screenshot({ path, fullPage: true });
|
|
199
|
+
result.screenshot = path;
|
|
200
|
+
screenshots.push(path);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return { result, fatal };
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
async function run(flowPath, baseUrlArg, outDirArg, recordDirArg) {
|
|
207
|
+
let flow;
|
|
208
|
+
try {
|
|
209
|
+
flow = JSON.parse(readFileSync(resolve(flowPath), 'utf8'));
|
|
210
|
+
} catch (err) {
|
|
211
|
+
emit({ ok: false, error: `cannot read flow file: ${err.message}` });
|
|
212
|
+
process.exit(1);
|
|
213
|
+
}
|
|
214
|
+
if (!flow || !Array.isArray(flow.steps)) {
|
|
215
|
+
emit({ ok: false, error: 'flow file must have a "steps" array' });
|
|
216
|
+
process.exit(1);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const baseUrl = baseUrlArg || flow.baseUrl || '';
|
|
220
|
+
const outDir = resolve(outDirArg || process.cwd());
|
|
221
|
+
// Record the session when --record-dir is passed OR the flow opts in with
|
|
222
|
+
// `"record": true`. The .webm lands in the record dir (default: out-dir).
|
|
223
|
+
const doRecord = !!recordDirArg || flow.record === true;
|
|
224
|
+
const videoDir = resolve(recordDirArg || outDir);
|
|
225
|
+
try {
|
|
226
|
+
mkdirSync(outDir, { recursive: true });
|
|
227
|
+
} catch {
|
|
228
|
+
/* best effort */
|
|
229
|
+
}
|
|
230
|
+
if (doRecord) {
|
|
231
|
+
try {
|
|
232
|
+
mkdirSync(videoDir, { recursive: true });
|
|
233
|
+
} catch {
|
|
234
|
+
/* best effort */
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const viewport = flow.viewport || { width: 1280, height: 800 };
|
|
239
|
+
const { chromium } = await loadPlaywright();
|
|
240
|
+
let browser;
|
|
241
|
+
const steps = [];
|
|
242
|
+
const screenshots = [];
|
|
243
|
+
const consoleErrors = [];
|
|
244
|
+
let videoPath = null;
|
|
245
|
+
|
|
246
|
+
try {
|
|
247
|
+
browser = await chromium.launch({ headless: true, args: ['--no-sandbox'] });
|
|
248
|
+
} catch (err) {
|
|
249
|
+
emit({
|
|
250
|
+
ok: false,
|
|
251
|
+
error: `browser launch failed: ${err && err.message ? err.message : err}`,
|
|
252
|
+
});
|
|
253
|
+
process.exit(1);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
try {
|
|
257
|
+
const context = await browser.newContext({
|
|
258
|
+
viewport,
|
|
259
|
+
// Playwright records video per-context; the size matches the viewport so
|
|
260
|
+
// the clip isn't letterboxed. The .webm is flushed on context.close().
|
|
261
|
+
...(doRecord ? { recordVideo: { dir: videoDir, size: viewport } } : {}),
|
|
262
|
+
});
|
|
263
|
+
const page = await context.newPage();
|
|
264
|
+
|
|
265
|
+
page.on('console', (msg) => {
|
|
266
|
+
if (msg.type() === 'error') consoleErrors.push(msg.text());
|
|
267
|
+
});
|
|
268
|
+
page.on('pageerror', (err) => {
|
|
269
|
+
consoleErrors.push(String(err && err.message ? err.message : err));
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
let fatalHit = false;
|
|
273
|
+
for (let i = 0; i < flow.steps.length; i++) {
|
|
274
|
+
const step = flow.steps[i];
|
|
275
|
+
const action = actionOf(step);
|
|
276
|
+
|
|
277
|
+
if (fatalHit) {
|
|
278
|
+
steps.push({ index: i, action, ok: false, ms: 0, error: 'skipped (prior fatal step)' });
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const started = Date.now();
|
|
283
|
+
try {
|
|
284
|
+
const { result } = await execStep(page, step, baseUrl, outDir, screenshots);
|
|
285
|
+
steps.push({ index: i, action, ok: true, ms: Date.now() - started, ...result });
|
|
286
|
+
} catch (err) {
|
|
287
|
+
const entry = {
|
|
288
|
+
index: i,
|
|
289
|
+
action,
|
|
290
|
+
ok: false,
|
|
291
|
+
ms: Date.now() - started,
|
|
292
|
+
error: String(err && err.message ? err.message : err),
|
|
293
|
+
};
|
|
294
|
+
steps.push(entry);
|
|
295
|
+
if (err && err.fatal) fatalHit = true; // navigation failure: skip the rest
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Finalize a recording (if any) BEFORE emitting: saving the video requires
|
|
300
|
+
// the context to close, which flushes the .webm to disk. A save failure is a
|
|
301
|
+
// reported finding, not a fatal run error.
|
|
302
|
+
if (doRecord) {
|
|
303
|
+
const video = page.video();
|
|
304
|
+
await context.close();
|
|
305
|
+
if (video) {
|
|
306
|
+
const target = join(videoDir, 'session.webm');
|
|
307
|
+
try {
|
|
308
|
+
await video.saveAs(target);
|
|
309
|
+
videoPath = target;
|
|
310
|
+
await video.delete().catch(() => {});
|
|
311
|
+
} catch (err) {
|
|
312
|
+
consoleErrors.push(
|
|
313
|
+
`video save failed: ${err && err.message ? err.message : err}`,
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
emit({
|
|
320
|
+
ok: steps.every((s) => s.ok),
|
|
321
|
+
baseUrl,
|
|
322
|
+
steps,
|
|
323
|
+
consoleErrors,
|
|
324
|
+
screenshots,
|
|
325
|
+
...(videoPath ? { video: videoPath } : {}),
|
|
326
|
+
});
|
|
327
|
+
process.exit(0);
|
|
328
|
+
} catch (err) {
|
|
329
|
+
// Unexpected harness error mid-run — still emit a JSON object.
|
|
330
|
+
emit({
|
|
331
|
+
ok: false,
|
|
332
|
+
error: String(err && err.message ? err.message : err),
|
|
333
|
+
baseUrl,
|
|
334
|
+
steps,
|
|
335
|
+
consoleErrors,
|
|
336
|
+
screenshots,
|
|
337
|
+
...(videoPath ? { video: videoPath } : {}),
|
|
338
|
+
});
|
|
339
|
+
process.exit(1);
|
|
340
|
+
} finally {
|
|
341
|
+
if (browser) await browser.close().catch(() => {});
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
async function main() {
|
|
346
|
+
const [cmd, ...rest] = process.argv.slice(2);
|
|
347
|
+
|
|
348
|
+
if (cmd === 'doctor') {
|
|
349
|
+
await doctor();
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
if (cmd === 'run') {
|
|
354
|
+
const positional = [];
|
|
355
|
+
let baseUrl;
|
|
356
|
+
let outDir;
|
|
357
|
+
let recordDir;
|
|
358
|
+
for (let i = 0; i < rest.length; i++) {
|
|
359
|
+
const a = rest[i];
|
|
360
|
+
if (a === '--base-url') baseUrl = rest[++i];
|
|
361
|
+
else if (a === '--out-dir') outDir = rest[++i];
|
|
362
|
+
else if (a === '--record-dir') recordDir = rest[++i];
|
|
363
|
+
else positional.push(a);
|
|
364
|
+
}
|
|
365
|
+
if (!positional[0]) {
|
|
366
|
+
emit({ ok: false, error: 'usage: agent-browser.mjs run <flow.json> [--base-url URL] [--out-dir DIR] [--record-dir DIR]' });
|
|
367
|
+
process.exit(1);
|
|
368
|
+
}
|
|
369
|
+
await run(positional[0], baseUrl, outDir, recordDir);
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
emit({ ok: false, error: 'usage: agent-browser.mjs <doctor|run> ...' });
|
|
374
|
+
process.exit(1);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
main().catch((err) => {
|
|
378
|
+
emit({ ok: false, error: String(err && err.message ? err.message : err) });
|
|
379
|
+
process.exit(1);
|
|
380
|
+
});
|