@oscharko-dev/keiko 0.1.0-beta.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/LICENSE +202 -0
- package/NOTICE +7 -0
- package/README.md +621 -0
- package/TRADEMARKS.md +41 -0
- package/dist/audit/aggregate.d.ts +5 -0
- package/dist/audit/aggregate.js +25 -0
- package/dist/audit/build.d.ts +2 -0
- package/dist/audit/build.js +224 -0
- package/dist/audit/errors.d.ts +25 -0
- package/dist/audit/errors.js +39 -0
- package/dist/audit/index-api.d.ts +14 -0
- package/dist/audit/index-api.js +131 -0
- package/dist/audit/index.d.ts +12 -0
- package/dist/audit/index.js +17 -0
- package/dist/audit/persist.d.ts +8 -0
- package/dist/audit/persist.js +40 -0
- package/dist/audit/redaction.d.ts +3 -0
- package/dist/audit/redaction.js +61 -0
- package/dist/audit/report.d.ts +18 -0
- package/dist/audit/report.js +50 -0
- package/dist/audit/retention.d.ts +3 -0
- package/dist/audit/retention.js +95 -0
- package/dist/audit/runid.d.ts +1 -0
- package/dist/audit/runid.js +29 -0
- package/dist/audit/side-file.d.ts +12 -0
- package/dist/audit/side-file.js +82 -0
- package/dist/audit/store.d.ts +12 -0
- package/dist/audit/store.js +198 -0
- package/dist/audit/types.d.ts +188 -0
- package/dist/audit/types.js +8 -0
- package/dist/audit/workflow-evidence.d.ts +27 -0
- package/dist/audit/workflow-evidence.js +145 -0
- package/dist/cli/context.d.ts +2 -0
- package/dist/cli/context.js +102 -0
- package/dist/cli/evaluate.d.ts +7 -0
- package/dist/cli/evaluate.js +207 -0
- package/dist/cli/evidence.d.ts +8 -0
- package/dist/cli/evidence.js +88 -0
- package/dist/cli/gateway-config.d.ts +10 -0
- package/dist/cli/gateway-config.js +12 -0
- package/dist/cli/gen-tests.d.ts +7 -0
- package/dist/cli/gen-tests.js +208 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +14 -0
- package/dist/cli/investigate.d.ts +8 -0
- package/dist/cli/investigate.js +242 -0
- package/dist/cli/models.d.ts +3 -0
- package/dist/cli/models.js +64 -0
- package/dist/cli/run.d.ts +7 -0
- package/dist/cli/run.js +187 -0
- package/dist/cli/runner.d.ts +6 -0
- package/dist/cli/runner.js +83 -0
- package/dist/cli/ui.d.ts +31 -0
- package/dist/cli/ui.js +240 -0
- package/dist/cli/verify.d.ts +2 -0
- package/dist/cli/verify.js +103 -0
- package/dist/evaluations/fixtures/bug-investigation/happy-path.d.ts +2 -0
- package/dist/evaluations/fixtures/bug-investigation/happy-path.js +66 -0
- package/dist/evaluations/fixtures/bug-investigation/investigation-only.d.ts +2 -0
- package/dist/evaluations/fixtures/bug-investigation/investigation-only.js +39 -0
- package/dist/evaluations/fixtures/bug-investigation/unsafe-action.d.ts +2 -0
- package/dist/evaluations/fixtures/bug-investigation/unsafe-action.js +37 -0
- package/dist/evaluations/fixtures/index.d.ts +7 -0
- package/dist/evaluations/fixtures/index.js +35 -0
- package/dist/evaluations/fixtures/support.d.ts +5 -0
- package/dist/evaluations/fixtures/support.js +42 -0
- package/dist/evaluations/fixtures/unit-tests/happy-path.d.ts +2 -0
- package/dist/evaluations/fixtures/unit-tests/happy-path.js +40 -0
- package/dist/evaluations/fixtures/unit-tests/retry-then-accept.d.ts +2 -0
- package/dist/evaluations/fixtures/unit-tests/retry-then-accept.js +39 -0
- package/dist/evaluations/fixtures/unit-tests/unsafe-action.d.ts +2 -0
- package/dist/evaluations/fixtures/unit-tests/unsafe-action.js +32 -0
- package/dist/evaluations/index.d.ts +12 -0
- package/dist/evaluations/index.js +12 -0
- package/dist/evaluations/manifest-check.d.ts +1 -0
- package/dist/evaluations/manifest-check.js +48 -0
- package/dist/evaluations/model-provider.d.ts +12 -0
- package/dist/evaluations/model-provider.js +26 -0
- package/dist/evaluations/render.d.ts +2 -0
- package/dist/evaluations/render.js +59 -0
- package/dist/evaluations/runner-support.d.ts +27 -0
- package/dist/evaluations/runner-support.js +163 -0
- package/dist/evaluations/runner.d.ts +20 -0
- package/dist/evaluations/runner.js +174 -0
- package/dist/evaluations/scorer.d.ts +14 -0
- package/dist/evaluations/scorer.js +131 -0
- package/dist/evaluations/scripted-model.d.ts +6 -0
- package/dist/evaluations/scripted-model.js +26 -0
- package/dist/evaluations/surface-parity.d.ts +2 -0
- package/dist/evaluations/surface-parity.js +184 -0
- package/dist/evaluations/types.d.ts +74 -0
- package/dist/evaluations/types.js +16 -0
- package/dist/gateway/capabilities.d.ts +11 -0
- package/dist/gateway/capabilities.data.d.ts +2 -0
- package/dist/gateway/capabilities.data.js +203 -0
- package/dist/gateway/capabilities.js +41 -0
- package/dist/gateway/config.d.ts +15 -0
- package/dist/gateway/config.js +154 -0
- package/dist/gateway/errors.d.ts +72 -0
- package/dist/gateway/errors.js +82 -0
- package/dist/gateway/gateway.d.ts +19 -0
- package/dist/gateway/gateway.js +94 -0
- package/dist/gateway/index.d.ts +10 -0
- package/dist/gateway/index.js +11 -0
- package/dist/gateway/model-selection.d.ts +9 -0
- package/dist/gateway/model-selection.js +36 -0
- package/dist/gateway/normalize.d.ts +7 -0
- package/dist/gateway/normalize.js +93 -0
- package/dist/gateway/openai-adapter.d.ts +20 -0
- package/dist/gateway/openai-adapter.js +263 -0
- package/dist/gateway/redaction.d.ts +1 -0
- package/dist/gateway/redaction.js +51 -0
- package/dist/gateway/resilience.d.ts +24 -0
- package/dist/gateway/resilience.js +166 -0
- package/dist/gateway/types.d.ts +108 -0
- package/dist/gateway/types.js +2 -0
- package/dist/harness/adapters.d.ts +23 -0
- package/dist/harness/adapters.js +38 -0
- package/dist/harness/context.d.ts +33 -0
- package/dist/harness/context.js +21 -0
- package/dist/harness/emitter.d.ts +15 -0
- package/dist/harness/emitter.js +72 -0
- package/dist/harness/errors.d.ts +21 -0
- package/dist/harness/errors.js +39 -0
- package/dist/harness/executor.d.ts +3 -0
- package/dist/harness/executor.js +211 -0
- package/dist/harness/fingerprint.d.ts +6 -0
- package/dist/harness/fingerprint.js +43 -0
- package/dist/harness/index.d.ts +9 -0
- package/dist/harness/index.js +13 -0
- package/dist/harness/loop.d.ts +3 -0
- package/dist/harness/loop.js +159 -0
- package/dist/harness/patcher.d.ts +4 -0
- package/dist/harness/patcher.js +49 -0
- package/dist/harness/planner.d.ts +3 -0
- package/dist/harness/planner.js +21 -0
- package/dist/harness/ports.d.ts +61 -0
- package/dist/harness/ports.js +4 -0
- package/dist/harness/session.d.ts +25 -0
- package/dist/harness/session.js +116 -0
- package/dist/harness/sinks.d.ts +30 -0
- package/dist/harness/sinks.js +72 -0
- package/dist/harness/tasks/explain-plan.d.ts +3 -0
- package/dist/harness/tasks/explain-plan.js +29 -0
- package/dist/harness/tasks/generate-unit-tests.d.ts +3 -0
- package/dist/harness/tasks/generate-unit-tests.js +28 -0
- package/dist/harness/tasks/investigate-bug.d.ts +3 -0
- package/dist/harness/tasks/investigate-bug.js +31 -0
- package/dist/harness/tasks/policy.d.ts +11 -0
- package/dist/harness/tasks/policy.js +22 -0
- package/dist/harness/tasks/verify.d.ts +3 -0
- package/dist/harness/tasks/verify.js +16 -0
- package/dist/harness/types.d.ts +270 -0
- package/dist/harness/types.js +33 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +36 -0
- package/dist/sdk/index.d.ts +9 -0
- package/dist/sdk/index.js +37 -0
- package/dist/sdk/run-agent.d.ts +16 -0
- package/dist/sdk/run-agent.js +56 -0
- package/dist/tools/browser/cdp-client.d.ts +35 -0
- package/dist/tools/browser/cdp-client.js +218 -0
- package/dist/tools/browser/errors.d.ts +25 -0
- package/dist/tools/browser/errors.js +55 -0
- package/dist/tools/browser/index.d.ts +5 -0
- package/dist/tools/browser/index.js +6 -0
- package/dist/tools/browser/session.d.ts +44 -0
- package/dist/tools/browser/session.js +748 -0
- package/dist/tools/browser/types.d.ts +48 -0
- package/dist/tools/browser/types.js +2 -0
- package/dist/tools/browser/validators.d.ts +5 -0
- package/dist/tools/browser/validators.js +97 -0
- package/dist/tools/errors.d.ts +59 -0
- package/dist/tools/errors.js +94 -0
- package/dist/tools/exec.d.ts +42 -0
- package/dist/tools/exec.js +327 -0
- package/dist/tools/index.d.ts +11 -0
- package/dist/tools/index.js +14 -0
- package/dist/tools/patch-content.d.ts +10 -0
- package/dist/tools/patch-content.js +126 -0
- package/dist/tools/patch-normalize.d.ts +1 -0
- package/dist/tools/patch-normalize.js +80 -0
- package/dist/tools/patch-parse.d.ts +8 -0
- package/dist/tools/patch-parse.js +201 -0
- package/dist/tools/patch.d.ts +18 -0
- package/dist/tools/patch.js +403 -0
- package/dist/tools/registry.d.ts +36 -0
- package/dist/tools/registry.js +231 -0
- package/dist/tools/sandbox.d.ts +8 -0
- package/dist/tools/sandbox.js +121 -0
- package/dist/tools/schemas.d.ts +2 -0
- package/dist/tools/schemas.js +51 -0
- package/dist/tools/terminal-policy.d.ts +9 -0
- package/dist/tools/terminal-policy.js +313 -0
- package/dist/tools/types.d.ts +99 -0
- package/dist/tools/types.js +103 -0
- package/dist/tools/writer.d.ts +7 -0
- package/dist/tools/writer.js +20 -0
- package/dist/ui/browser.d.ts +10 -0
- package/dist/ui/browser.js +231 -0
- package/dist/ui/chat-handlers.d.ts +4 -0
- package/dist/ui/chat-handlers.js +281 -0
- package/dist/ui/csp-hashes.json +17 -0
- package/dist/ui/csp.d.ts +2 -0
- package/dist/ui/csp.js +66 -0
- package/dist/ui/deps.d.ts +34 -0
- package/dist/ui/deps.js +137 -0
- package/dist/ui/evidence.d.ts +27 -0
- package/dist/ui/evidence.js +142 -0
- package/dist/ui/files-deny.d.ts +2 -0
- package/dist/ui/files-deny.js +12 -0
- package/dist/ui/files.d.ts +65 -0
- package/dist/ui/files.js +492 -0
- package/dist/ui/headers.d.ts +2 -0
- package/dist/ui/headers.js +21 -0
- package/dist/ui/host-check.d.ts +2 -0
- package/dist/ui/host-check.js +58 -0
- package/dist/ui/index.d.ts +20 -0
- package/dist/ui/index.js +23 -0
- package/dist/ui/load-csp.d.ts +1 -0
- package/dist/ui/load-csp.js +28 -0
- package/dist/ui/read-handlers.d.ts +8 -0
- package/dist/ui/read-handlers.js +247 -0
- package/dist/ui/routes.d.ts +36 -0
- package/dist/ui/routes.js +129 -0
- package/dist/ui/run-engine.d.ts +20 -0
- package/dist/ui/run-engine.js +345 -0
- package/dist/ui/run-handlers.d.ts +8 -0
- package/dist/ui/run-handlers.js +431 -0
- package/dist/ui/run-request.d.ts +13 -0
- package/dist/ui/run-request.js +219 -0
- package/dist/ui/runs.d.ts +43 -0
- package/dist/ui/runs.js +92 -0
- package/dist/ui/server.d.ts +11 -0
- package/dist/ui/server.js +143 -0
- package/dist/ui/sink.d.ts +27 -0
- package/dist/ui/sink.js +80 -0
- package/dist/ui/sse.d.ts +7 -0
- package/dist/ui/sse.js +27 -0
- package/dist/ui/static/404.html +1 -0
- package/dist/ui/static/_next/static/ca-A01hy9W98aRvMZKdAw/_buildManifest.js +1 -0
- package/dist/ui/static/_next/static/ca-A01hy9W98aRvMZKdAw/_ssgManifest.js +1 -0
- package/dist/ui/static/_next/static/chunks/255-d47fd57964443afe.js +1 -0
- package/dist/ui/static/_next/static/chunks/4-be1fef693af8e088.js +1 -0
- package/dist/ui/static/_next/static/chunks/4bd1b696-c023c6e3521b1417.js +1 -0
- package/dist/ui/static/_next/static/chunks/app/_not-found/page-75825b09bcecad97.js +1 -0
- package/dist/ui/static/_next/static/chunks/app/launch/page-9c86a13c29884245.js +1 -0
- package/dist/ui/static/_next/static/chunks/app/layout-bdea63fe87947d50.js +1 -0
- package/dist/ui/static/_next/static/chunks/app/page-4168c12c68b7a853.js +1 -0
- package/dist/ui/static/_next/static/chunks/framework-a6e0b7e30f98059a.js +1 -0
- package/dist/ui/static/_next/static/chunks/main-778a50aebff02192.js +1 -0
- package/dist/ui/static/_next/static/chunks/main-app-30679af7240d63e9.js +1 -0
- package/dist/ui/static/_next/static/chunks/pages/_app-7d307437aca18ad4.js +1 -0
- package/dist/ui/static/_next/static/chunks/pages/_error-cb2a52f75f2162e2.js +1 -0
- package/dist/ui/static/_next/static/chunks/polyfills-42372ed130431b0a.js +1 -0
- package/dist/ui/static/_next/static/chunks/webpack-4a462cecab786e93.js +1 -0
- package/dist/ui/static/_next/static/css/be7cb54d5c5673b6.css +1 -0
- package/dist/ui/static/assets/editors/goland.svg +35 -0
- package/dist/ui/static/assets/editors/intellij.svg +39 -0
- package/dist/ui/static/assets/editors/pycharm.svg +58 -0
- package/dist/ui/static/assets/editors/rustrover.svg +19 -0
- package/dist/ui/static/assets/editors/vscode.svg +1 -0
- package/dist/ui/static/assets/editors/webstorm.svg +21 -0
- package/dist/ui/static/assets/icons/anthropic.svg +1 -0
- package/dist/ui/static/assets/icons/brave.svg +1 -0
- package/dist/ui/static/assets/icons/css3.svg +1 -0
- package/dist/ui/static/assets/icons/docker.svg +1 -0
- package/dist/ui/static/assets/icons/git.svg +1 -0
- package/dist/ui/static/assets/icons/github.svg +1 -0
- package/dist/ui/static/assets/icons/go.svg +1 -0
- package/dist/ui/static/assets/icons/gradle.svg +1 -0
- package/dist/ui/static/assets/icons/grafana.svg +1 -0
- package/dist/ui/static/assets/icons/graphql.svg +1 -0
- package/dist/ui/static/assets/icons/html5.svg +1 -0
- package/dist/ui/static/assets/icons/image.svg +1 -0
- package/dist/ui/static/assets/icons/java.svg +1 -0
- package/dist/ui/static/assets/icons/javascript.svg +1 -0
- package/dist/ui/static/assets/icons/json.svg +1 -0
- package/dist/ui/static/assets/icons/kafka.svg +1 -0
- package/dist/ui/static/assets/icons/kubernetes.svg +1 -0
- package/dist/ui/static/assets/icons/linear.svg +1 -0
- package/dist/ui/static/assets/icons/markdown.svg +1 -0
- package/dist/ui/static/assets/icons/nginx.svg +1 -0
- package/dist/ui/static/assets/icons/nodejs.svg +1 -0
- package/dist/ui/static/assets/icons/notion.svg +1 -0
- package/dist/ui/static/assets/icons/openai.svg +1 -0
- package/dist/ui/static/assets/icons/playwright.svg +1 -0
- package/dist/ui/static/assets/icons/postgresql.svg +1 -0
- package/dist/ui/static/assets/icons/prometheus.svg +1 -0
- package/dist/ui/static/assets/icons/properties.svg +1 -0
- package/dist/ui/static/assets/icons/puppeteer.svg +1 -0
- package/dist/ui/static/assets/icons/python.svg +1 -0
- package/dist/ui/static/assets/icons/react.svg +1 -0
- package/dist/ui/static/assets/icons/redis.svg +1 -0
- package/dist/ui/static/assets/icons/rust.svg +1 -0
- package/dist/ui/static/assets/icons/sentry.svg +1 -0
- package/dist/ui/static/assets/icons/slack.svg +1 -0
- package/dist/ui/static/assets/icons/spring.svg +1 -0
- package/dist/ui/static/assets/icons/typescript.svg +1 -0
- package/dist/ui/static/assets/icons/upstash.svg +1 -0
- package/dist/ui/static/assets/icons/yaml.svg +1 -0
- package/dist/ui/static/assets/keiko-logo.svg +10 -0
- package/dist/ui/static/index.html +1 -0
- package/dist/ui/static/index.txt +19 -0
- package/dist/ui/static/keiko-logo.svg +10 -0
- package/dist/ui/static/launch.html +1 -0
- package/dist/ui/static/launch.txt +19 -0
- package/dist/ui/static.d.ts +3 -0
- package/dist/ui/static.js +72 -0
- package/dist/ui/store/chats.d.ts +14 -0
- package/dist/ui/store/chats.js +110 -0
- package/dist/ui/store/db.d.ts +6 -0
- package/dist/ui/store/db.js +182 -0
- package/dist/ui/store/errors.d.ts +12 -0
- package/dist/ui/store/errors.js +30 -0
- package/dist/ui/store/index.d.ts +6 -0
- package/dist/ui/store/index.js +6 -0
- package/dist/ui/store/messages.d.ts +5 -0
- package/dist/ui/store/messages.js +137 -0
- package/dist/ui/store/paths.d.ts +4 -0
- package/dist/ui/store/paths.js +69 -0
- package/dist/ui/store/projects.d.ts +7 -0
- package/dist/ui/store/projects.js +61 -0
- package/dist/ui/store/schema.d.ts +3 -0
- package/dist/ui/store/schema.js +77 -0
- package/dist/ui/store/types.d.ts +80 -0
- package/dist/ui/store/types.js +3 -0
- package/dist/ui/store/validation.d.ts +4 -0
- package/dist/ui/store/validation.js +72 -0
- package/dist/ui/store-handlers.d.ts +16 -0
- package/dist/ui/store-handlers.js +465 -0
- package/dist/ui/terminal-errors.d.ts +21 -0
- package/dist/ui/terminal-errors.js +45 -0
- package/dist/ui/terminal-evidence.d.ts +20 -0
- package/dist/ui/terminal-evidence.js +65 -0
- package/dist/ui/terminal-routes.d.ts +9 -0
- package/dist/ui/terminal-routes.js +219 -0
- package/dist/ui/terminal.d.ts +67 -0
- package/dist/ui/terminal.js +835 -0
- package/dist/verification/classify.d.ts +10 -0
- package/dist/verification/classify.js +53 -0
- package/dist/verification/detect.d.ts +4 -0
- package/dist/verification/detect.js +81 -0
- package/dist/verification/errors.d.ts +11 -0
- package/dist/verification/errors.js +21 -0
- package/dist/verification/index.d.ts +17 -0
- package/dist/verification/index.js +13 -0
- package/dist/verification/limits.d.ts +3 -0
- package/dist/verification/limits.js +40 -0
- package/dist/verification/monitor.d.ts +4 -0
- package/dist/verification/monitor.js +58 -0
- package/dist/verification/orchestrator.d.ts +16 -0
- package/dist/verification/orchestrator.js +363 -0
- package/dist/verification/plan.d.ts +9 -0
- package/dist/verification/plan.js +125 -0
- package/dist/verification/summary.d.ts +40 -0
- package/dist/verification/summary.js +67 -0
- package/dist/verification/types.d.ts +63 -0
- package/dist/verification/types.js +13 -0
- package/dist/workflows/bug-investigation/context.d.ts +7 -0
- package/dist/workflows/bug-investigation/context.js +119 -0
- package/dist/workflows/bug-investigation/descriptor.d.ts +3 -0
- package/dist/workflows/bug-investigation/descriptor.js +46 -0
- package/dist/workflows/bug-investigation/emit.d.ts +12 -0
- package/dist/workflows/bug-investigation/emit.js +35 -0
- package/dist/workflows/bug-investigation/events.d.ts +81 -0
- package/dist/workflows/bug-investigation/events.js +9 -0
- package/dist/workflows/bug-investigation/failure-parse.d.ts +3 -0
- package/dist/workflows/bug-investigation/failure-parse.js +154 -0
- package/dist/workflows/bug-investigation/guard.d.ts +2 -0
- package/dist/workflows/bug-investigation/guard.js +69 -0
- package/dist/workflows/bug-investigation/index.d.ts +7 -0
- package/dist/workflows/bug-investigation/index.js +13 -0
- package/dist/workflows/bug-investigation/internal.d.ts +37 -0
- package/dist/workflows/bug-investigation/internal.js +64 -0
- package/dist/workflows/bug-investigation/model-loop.d.ts +4 -0
- package/dist/workflows/bug-investigation/model-loop.js +223 -0
- package/dist/workflows/bug-investigation/parse.d.ts +3 -0
- package/dist/workflows/bug-investigation/parse.js +123 -0
- package/dist/workflows/bug-investigation/prompt.d.ts +4 -0
- package/dist/workflows/bug-investigation/prompt.js +107 -0
- package/dist/workflows/bug-investigation/report.d.ts +23 -0
- package/dist/workflows/bug-investigation/report.js +151 -0
- package/dist/workflows/bug-investigation/stages.d.ts +13 -0
- package/dist/workflows/bug-investigation/stages.js +242 -0
- package/dist/workflows/bug-investigation/types.d.ts +91 -0
- package/dist/workflows/bug-investigation/types.js +14 -0
- package/dist/workflows/bug-investigation/verify-stage.d.ts +10 -0
- package/dist/workflows/bug-investigation/verify-stage.js +91 -0
- package/dist/workflows/bug-investigation/workflow.d.ts +2 -0
- package/dist/workflows/bug-investigation/workflow.js +74 -0
- package/dist/workflows/descriptor.d.ts +20 -0
- package/dist/workflows/descriptor.js +8 -0
- package/dist/workflows/index.d.ts +3 -0
- package/dist/workflows/index.js +2 -0
- package/dist/workflows/unit-tests/context.d.ts +7 -0
- package/dist/workflows/unit-tests/context.js +129 -0
- package/dist/workflows/unit-tests/conventions.d.ts +4 -0
- package/dist/workflows/unit-tests/conventions.js +87 -0
- package/dist/workflows/unit-tests/descriptor.d.ts +4 -0
- package/dist/workflows/unit-tests/descriptor.js +43 -0
- package/dist/workflows/unit-tests/emit.d.ts +12 -0
- package/dist/workflows/unit-tests/emit.js +35 -0
- package/dist/workflows/unit-tests/events.d.ts +78 -0
- package/dist/workflows/unit-tests/events.js +7 -0
- package/dist/workflows/unit-tests/index.d.ts +6 -0
- package/dist/workflows/unit-tests/index.js +10 -0
- package/dist/workflows/unit-tests/internal.d.ts +35 -0
- package/dist/workflows/unit-tests/internal.js +43 -0
- package/dist/workflows/unit-tests/model-loop.d.ts +4 -0
- package/dist/workflows/unit-tests/model-loop.js +95 -0
- package/dist/workflows/unit-tests/parse.d.ts +6 -0
- package/dist/workflows/unit-tests/parse.js +68 -0
- package/dist/workflows/unit-tests/prompt.d.ts +4 -0
- package/dist/workflows/unit-tests/prompt.js +71 -0
- package/dist/workflows/unit-tests/report.d.ts +21 -0
- package/dist/workflows/unit-tests/report.js +90 -0
- package/dist/workflows/unit-tests/stages.d.ts +9 -0
- package/dist/workflows/unit-tests/stages.js +155 -0
- package/dist/workflows/unit-tests/types.d.ts +70 -0
- package/dist/workflows/unit-tests/types.js +11 -0
- package/dist/workflows/unit-tests/verify-stage.d.ts +9 -0
- package/dist/workflows/unit-tests/verify-stage.js +56 -0
- package/dist/workflows/unit-tests/workflow.d.ts +2 -0
- package/dist/workflows/unit-tests/workflow.js +58 -0
- package/dist/workspace/contextPack.d.ts +9 -0
- package/dist/workspace/contextPack.js +94 -0
- package/dist/workspace/detect.d.ts +3 -0
- package/dist/workspace/detect.js +135 -0
- package/dist/workspace/discovery.d.ts +9 -0
- package/dist/workspace/discovery.js +167 -0
- package/dist/workspace/errors.d.ts +39 -0
- package/dist/workspace/errors.js +66 -0
- package/dist/workspace/fs.d.ts +21 -0
- package/dist/workspace/fs.js +36 -0
- package/dist/workspace/ignore.d.ts +14 -0
- package/dist/workspace/ignore.js +176 -0
- package/dist/workspace/index.d.ts +11 -0
- package/dist/workspace/index.js +13 -0
- package/dist/workspace/paths.d.ts +2 -0
- package/dist/workspace/paths.js +38 -0
- package/dist/workspace/realpath.d.ts +7 -0
- package/dist/workspace/realpath.js +72 -0
- package/dist/workspace/retrieval.d.ts +9 -0
- package/dist/workspace/retrieval.js +74 -0
- package/dist/workspace/summary.d.ts +3 -0
- package/dist/workspace/summary.js +54 -0
- package/dist/workspace/types.d.ts +103 -0
- package/dist/workspace/types.js +27 -0
- package/package.json +58 -0
|
@@ -0,0 +1,835 @@
|
|
|
1
|
+
// ADR-0018 — bounded, permitted-command execution surface for the UI terminal. Replaces the
|
|
2
|
+
// previous PTY surface with a synchronous `runCommand` per HTTP request. The allowlist
|
|
3
|
+
// (TERMINAL_COMMAND_RULES) plus the existing ADR-0006 sandbox boundary form the trust model;
|
|
4
|
+
// nothing new is invented here.
|
|
5
|
+
//
|
|
6
|
+
// Reuse (UNCHANGED):
|
|
7
|
+
// • runCommand from src/tools/exec.ts (sandbox env, no-shell, cwd realpath, output cap, abort)
|
|
8
|
+
// • EvidenceStore from src/audit/store.ts (atomic O_EXCL + realpath-contained write)
|
|
9
|
+
// • deepRedactStrings from src/audit/redaction.ts (Layer-2 redact-before-persist)
|
|
10
|
+
// • ProjectStore from src/ui/store/** (projectId → workspaceRoot)
|
|
11
|
+
//
|
|
12
|
+
// New (bounded composition):
|
|
13
|
+
// • TerminalExecutionManager: execute(input) / abort(executionId) / subscribe(handler).
|
|
14
|
+
// • In-memory Map<executionId, InFlight> capped at MAX_CONCURRENT_EXECUTIONS = 8 (D9).
|
|
15
|
+
// • SSE-source observer pattern mirroring the browser tool (no HarnessEvent envelope).
|
|
16
|
+
// • Directory picker preserved from the previous PTY module, anchored at the project root.
|
|
17
|
+
import { randomUUID } from "node:crypto";
|
|
18
|
+
import { readdir, realpath, stat } from "node:fs/promises";
|
|
19
|
+
import { dirname, isAbsolute, join, parse as parsePath, resolve as resolvePath, } from "node:path";
|
|
20
|
+
import { CommandCancelledError, CommandDeniedError, CommandTimeoutError, } from "../tools/errors.js";
|
|
21
|
+
import { nodeSpawnFn, runCommand } from "../tools/exec.js";
|
|
22
|
+
import { isTerminalCommandAllowed, TERMINAL_COMMAND_RULES, } from "../tools/terminal-policy.js";
|
|
23
|
+
import { isWithinWorkspace, resolveWithinWorkspace } from "../workspace/paths.js";
|
|
24
|
+
import { PathDeniedError } from "../workspace/errors.js";
|
|
25
|
+
import { isDenied } from "../workspace/ignore.js";
|
|
26
|
+
import { nodeWorkspaceFs } from "../workspace/fs.js";
|
|
27
|
+
import { containedRealPathInfo } from "../workspace/realpath.js";
|
|
28
|
+
import { DEFAULT_SANDBOX_POLICY } from "../tools/types.js";
|
|
29
|
+
import { appendTerminalEvidence, buildTerminalEvidenceEntry, } from "./terminal-evidence.js";
|
|
30
|
+
import { TerminalToolError } from "./terminal-errors.js";
|
|
31
|
+
const MAX_CONCURRENT_EXECUTIONS = 8;
|
|
32
|
+
const MIN_TIMEOUT_MS = 1_000;
|
|
33
|
+
function defaultRedactor(input) {
|
|
34
|
+
return input;
|
|
35
|
+
}
|
|
36
|
+
function projectFor(store, projectId) {
|
|
37
|
+
for (const project of store.listProjects()) {
|
|
38
|
+
if (project.path === projectId) {
|
|
39
|
+
return project;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
// Tier-2 cwd containment (ADR-0018 D2 project-scoped pre-check). The requested cwd must resolve
|
|
45
|
+
// lexically inside the project root before we hand it to `runCommand`, which then re-checks via
|
|
46
|
+
// realpath/deny-list (Tier 1). A path traversal is denied here; a symlink escape is denied there.
|
|
47
|
+
function assertCwdInsideProject(projectRoot, requested) {
|
|
48
|
+
const candidate = requested === undefined || requested.length === 0 ? "." : requested;
|
|
49
|
+
let lexical;
|
|
50
|
+
try {
|
|
51
|
+
lexical = resolveWithinWorkspace(projectRoot, candidate);
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
throw new TerminalToolError("CWD_OUTSIDE_PROJECT", "Working directory is outside the selected project.");
|
|
55
|
+
}
|
|
56
|
+
if (!isWithinWorkspace(projectRoot, lexical)) {
|
|
57
|
+
throw new TerminalToolError("CWD_OUTSIDE_PROJECT", "Working directory is outside the selected project.");
|
|
58
|
+
}
|
|
59
|
+
return lexical;
|
|
60
|
+
}
|
|
61
|
+
function projectRootOrThrow(project) {
|
|
62
|
+
try {
|
|
63
|
+
return realpathSyncCompat(project.path);
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
throw new TerminalToolError("PROJECT_NOT_FOUND", "Project root path could not be resolved.");
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function realpathSyncCompat(pathValue) {
|
|
70
|
+
return nodeWorkspaceFs.realPath(pathValue);
|
|
71
|
+
}
|
|
72
|
+
function requestIdPayload(input) {
|
|
73
|
+
return input.requestId === undefined ? {} : { requestId: input.requestId };
|
|
74
|
+
}
|
|
75
|
+
function assertOperandInsideProject(ctx, operand) {
|
|
76
|
+
if (operand.length === 0 || operand === "-") {
|
|
77
|
+
throw new TerminalToolError("COMMAND_DENIED", "Command operands must stay inside the selected project.");
|
|
78
|
+
}
|
|
79
|
+
let lexical;
|
|
80
|
+
try {
|
|
81
|
+
const candidate = isAbsolute(operand) ? resolvePath(operand) : resolvePath(ctx.cwd, operand);
|
|
82
|
+
lexical = resolveWithinWorkspace(ctx.projectRoot, candidate);
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
throw new TerminalToolError("CWD_OUTSIDE_PROJECT", "Command operand is outside the selected project.");
|
|
86
|
+
}
|
|
87
|
+
const lexicalRelative = lexical.slice(ctx.projectRoot.length).replace(/^[/\\]/, "");
|
|
88
|
+
if (isDenied(lexicalRelative)) {
|
|
89
|
+
throw new TerminalToolError("CWD_DENIED", "Command operand is denied by policy.");
|
|
90
|
+
}
|
|
91
|
+
try {
|
|
92
|
+
const info = containedRealPathInfo(ctx.fs, ctx.projectRoot, lexical);
|
|
93
|
+
if (isDenied(info.realRelative)) {
|
|
94
|
+
throw new TerminalToolError("CWD_DENIED", "Command operand is denied by policy.");
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
if (error instanceof TerminalToolError)
|
|
99
|
+
throw error;
|
|
100
|
+
throw new TerminalToolError("CWD_OUTSIDE_PROJECT", "Command operand is outside the selected project.");
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
function isOptionTerminator(arg) {
|
|
104
|
+
return arg === "--";
|
|
105
|
+
}
|
|
106
|
+
function isFlag(arg, afterTerminator) {
|
|
107
|
+
return !afterTerminator && arg.startsWith("-") && arg !== "-";
|
|
108
|
+
}
|
|
109
|
+
function flagName(arg) {
|
|
110
|
+
const equalsIndex = arg.indexOf("=");
|
|
111
|
+
return equalsIndex === -1 ? arg : arg.slice(0, equalsIndex);
|
|
112
|
+
}
|
|
113
|
+
function equalsValueFor(arg, flags) {
|
|
114
|
+
const equalsIndex = arg.indexOf("=");
|
|
115
|
+
if (equalsIndex === -1)
|
|
116
|
+
return undefined;
|
|
117
|
+
const flag = arg.slice(0, equalsIndex);
|
|
118
|
+
return flags.has(flag) ? arg.slice(equalsIndex + 1) : undefined;
|
|
119
|
+
}
|
|
120
|
+
function shortInlineValueFor(arg, flags) {
|
|
121
|
+
for (const flag of flags) {
|
|
122
|
+
if (flag.startsWith("--") || flag.length !== 2)
|
|
123
|
+
continue;
|
|
124
|
+
if (arg.startsWith(flag) && arg.length > flag.length)
|
|
125
|
+
return arg.slice(flag.length);
|
|
126
|
+
}
|
|
127
|
+
return undefined;
|
|
128
|
+
}
|
|
129
|
+
function inlineValueFor(arg, flags) {
|
|
130
|
+
return equalsValueFor(arg, flags) ?? shortInlineValueFor(arg, flags);
|
|
131
|
+
}
|
|
132
|
+
function isSeparatedValueFlag(arg, flags) {
|
|
133
|
+
return !arg.includes("=") && flags.has(arg);
|
|
134
|
+
}
|
|
135
|
+
function consumePendingOperandValue(ctx, state, arg) {
|
|
136
|
+
if (state.pending === undefined)
|
|
137
|
+
return false;
|
|
138
|
+
const pending = state.pending;
|
|
139
|
+
state.pending = undefined;
|
|
140
|
+
if (pending === "path")
|
|
141
|
+
assertOperandInsideProject(ctx, arg);
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
function consumeValueFlag(ctx, state, arg, pathFlags, scalarFlags) {
|
|
145
|
+
const pathValue = inlineValueFor(arg, pathFlags);
|
|
146
|
+
if (pathValue !== undefined) {
|
|
147
|
+
assertOperandInsideProject(ctx, pathValue);
|
|
148
|
+
return true;
|
|
149
|
+
}
|
|
150
|
+
if (isSeparatedValueFlag(arg, pathFlags)) {
|
|
151
|
+
state.pending = "path";
|
|
152
|
+
return true;
|
|
153
|
+
}
|
|
154
|
+
if (inlineValueFor(arg, scalarFlags) !== undefined)
|
|
155
|
+
return true;
|
|
156
|
+
if (isSeparatedValueFlag(arg, scalarFlags)) {
|
|
157
|
+
state.pending = "scalar";
|
|
158
|
+
return true;
|
|
159
|
+
}
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
function validatePathOperands(ctx, args, scalarFlags, pathFlags = FROZEN_EMPTY_SET) {
|
|
163
|
+
const state = { afterTerminator: false, pending: undefined };
|
|
164
|
+
for (const arg of args) {
|
|
165
|
+
if (consumePendingOperandValue(ctx, state, arg))
|
|
166
|
+
continue;
|
|
167
|
+
if (!state.afterTerminator && isOptionTerminator(arg)) {
|
|
168
|
+
state.afterTerminator = true;
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
if (!state.afterTerminator && consumeValueFlag(ctx, state, arg, pathFlags, scalarFlags)) {
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
if (isFlag(arg, state.afterTerminator))
|
|
175
|
+
continue;
|
|
176
|
+
assertOperandInsideProject(ctx, arg);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
const FROZEN_EMPTY_SET = new Set();
|
|
180
|
+
const HEAD_TAIL_SCALAR_FLAGS = new Set(["-n", "-c", "--lines", "--bytes"]);
|
|
181
|
+
const LS_SCALAR_FLAGS = new Set([
|
|
182
|
+
"-w",
|
|
183
|
+
"--width",
|
|
184
|
+
"--block-size",
|
|
185
|
+
"--color",
|
|
186
|
+
"--format",
|
|
187
|
+
"--time",
|
|
188
|
+
"--sort",
|
|
189
|
+
"--ignore",
|
|
190
|
+
"--hide",
|
|
191
|
+
"--indicator-style",
|
|
192
|
+
"--quoting-style",
|
|
193
|
+
"--tabsize",
|
|
194
|
+
]);
|
|
195
|
+
const TREE_SCALAR_FLAGS = new Set([
|
|
196
|
+
"-L",
|
|
197
|
+
"-I",
|
|
198
|
+
"-P",
|
|
199
|
+
"--charset",
|
|
200
|
+
"--filelimit",
|
|
201
|
+
"--timefmt",
|
|
202
|
+
"--sort",
|
|
203
|
+
]);
|
|
204
|
+
const GREP_PATTERN_FILE_FLAGS = new Set(["-f", "--file"]);
|
|
205
|
+
const GREP_PATH_VALUE_FLAGS = new Set(["--exclude-from"]);
|
|
206
|
+
const GREP_SCALAR_VALUE_FLAGS = new Set([
|
|
207
|
+
"-A",
|
|
208
|
+
"-B",
|
|
209
|
+
"-C",
|
|
210
|
+
"-D",
|
|
211
|
+
"-d",
|
|
212
|
+
"-m",
|
|
213
|
+
"--after-context",
|
|
214
|
+
"--before-context",
|
|
215
|
+
"--binary-files",
|
|
216
|
+
"--color",
|
|
217
|
+
"--context",
|
|
218
|
+
"--devices",
|
|
219
|
+
"--directories",
|
|
220
|
+
"--label",
|
|
221
|
+
"--max-count",
|
|
222
|
+
]);
|
|
223
|
+
function shortClusterEndsWithFlag(arg, flag) {
|
|
224
|
+
return arg.startsWith("-") && !arg.startsWith("--") && arg.length > 2 && arg.endsWith(flag.slice(1));
|
|
225
|
+
}
|
|
226
|
+
function consumePendingGrepOperand(ctx, state, arg) {
|
|
227
|
+
if (state.nextPattern) {
|
|
228
|
+
state.nextPattern = false;
|
|
229
|
+
state.expectPattern = false;
|
|
230
|
+
return true;
|
|
231
|
+
}
|
|
232
|
+
if (state.nextPath) {
|
|
233
|
+
state.nextPath = false;
|
|
234
|
+
assertOperandInsideProject(ctx, arg);
|
|
235
|
+
if (state.nextPathProvidesPattern)
|
|
236
|
+
state.expectPattern = false;
|
|
237
|
+
state.nextPathProvidesPattern = false;
|
|
238
|
+
return true;
|
|
239
|
+
}
|
|
240
|
+
if (state.nextScalar) {
|
|
241
|
+
state.nextScalar = false;
|
|
242
|
+
return true;
|
|
243
|
+
}
|
|
244
|
+
return false;
|
|
245
|
+
}
|
|
246
|
+
function consumeGrepPatternFlag(state, arg) {
|
|
247
|
+
if (arg === "-e" || arg === "--regexp") {
|
|
248
|
+
state.nextPattern = true;
|
|
249
|
+
return true;
|
|
250
|
+
}
|
|
251
|
+
if (arg.startsWith("--regexp=") || (arg.startsWith("-e") && arg.length > 2)) {
|
|
252
|
+
state.expectPattern = false;
|
|
253
|
+
return true;
|
|
254
|
+
}
|
|
255
|
+
return false;
|
|
256
|
+
}
|
|
257
|
+
function consumeGrepFileFlag(ctx, state, arg) {
|
|
258
|
+
if (arg === "-f" || arg === "--file") {
|
|
259
|
+
state.nextPath = true;
|
|
260
|
+
state.nextPathProvidesPattern = true;
|
|
261
|
+
return true;
|
|
262
|
+
}
|
|
263
|
+
if (isSeparatedValueFlag(arg, GREP_PATTERN_FILE_FLAGS) || shortClusterEndsWithFlag(arg, "-f")) {
|
|
264
|
+
state.nextPath = true;
|
|
265
|
+
state.nextPathProvidesPattern = true;
|
|
266
|
+
return true;
|
|
267
|
+
}
|
|
268
|
+
const patternFile = inlineValueFor(arg, GREP_PATTERN_FILE_FLAGS);
|
|
269
|
+
if (patternFile !== undefined) {
|
|
270
|
+
assertOperandInsideProject(ctx, patternFile);
|
|
271
|
+
state.expectPattern = false;
|
|
272
|
+
return true;
|
|
273
|
+
}
|
|
274
|
+
if (isSeparatedValueFlag(arg, GREP_PATH_VALUE_FLAGS)) {
|
|
275
|
+
state.nextPath = true;
|
|
276
|
+
state.nextPathProvidesPattern = false;
|
|
277
|
+
return true;
|
|
278
|
+
}
|
|
279
|
+
const value = inlineValueFor(arg, GREP_PATH_VALUE_FLAGS);
|
|
280
|
+
if (value !== undefined) {
|
|
281
|
+
assertOperandInsideProject(ctx, value);
|
|
282
|
+
return true;
|
|
283
|
+
}
|
|
284
|
+
return false;
|
|
285
|
+
}
|
|
286
|
+
function consumeGrepScalarFlag(state, arg) {
|
|
287
|
+
if (inlineValueFor(arg, GREP_SCALAR_VALUE_FLAGS) !== undefined)
|
|
288
|
+
return true;
|
|
289
|
+
if (isSeparatedValueFlag(arg, GREP_SCALAR_VALUE_FLAGS)) {
|
|
290
|
+
state.nextScalar = true;
|
|
291
|
+
return true;
|
|
292
|
+
}
|
|
293
|
+
return false;
|
|
294
|
+
}
|
|
295
|
+
function consumeGrepFlag(ctx, state, arg) {
|
|
296
|
+
if (state.afterTerminator)
|
|
297
|
+
return false;
|
|
298
|
+
if (isOptionTerminator(arg)) {
|
|
299
|
+
state.afterTerminator = true;
|
|
300
|
+
return true;
|
|
301
|
+
}
|
|
302
|
+
if (consumeGrepPatternFlag(state, arg))
|
|
303
|
+
return true;
|
|
304
|
+
if (consumeGrepFileFlag(ctx, state, arg))
|
|
305
|
+
return true;
|
|
306
|
+
if (consumeGrepScalarFlag(state, arg))
|
|
307
|
+
return true;
|
|
308
|
+
return isFlag(arg, false);
|
|
309
|
+
}
|
|
310
|
+
function consumeGrepPositional(ctx, state, arg) {
|
|
311
|
+
if (state.expectPattern) {
|
|
312
|
+
state.expectPattern = false;
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
assertOperandInsideProject(ctx, arg);
|
|
316
|
+
}
|
|
317
|
+
function validateGrepOperands(ctx, args) {
|
|
318
|
+
const state = {
|
|
319
|
+
afterTerminator: false,
|
|
320
|
+
expectPattern: true,
|
|
321
|
+
nextPattern: false,
|
|
322
|
+
nextPath: false,
|
|
323
|
+
nextPathProvidesPattern: false,
|
|
324
|
+
nextScalar: false,
|
|
325
|
+
};
|
|
326
|
+
for (const arg of args) {
|
|
327
|
+
if (consumePendingGrepOperand(ctx, state, arg))
|
|
328
|
+
continue;
|
|
329
|
+
if (consumeGrepFlag(ctx, state, arg))
|
|
330
|
+
continue;
|
|
331
|
+
consumeGrepPositional(ctx, state, arg);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
const FIND_ROOT_OPTIONS = new Set(["-H", "-L", "-P", "-E", "-X", "-d", "-s", "-x"]);
|
|
335
|
+
const FIND_PATH_VALUE_FLAGS = new Set([
|
|
336
|
+
"-anewer",
|
|
337
|
+
"-cnewer",
|
|
338
|
+
"-f",
|
|
339
|
+
"-newer",
|
|
340
|
+
"-samefile",
|
|
341
|
+
]);
|
|
342
|
+
const FIND_SCALAR_VALUE_FLAGS = new Set([
|
|
343
|
+
"-amin",
|
|
344
|
+
"-atime",
|
|
345
|
+
"-cmin",
|
|
346
|
+
"-context",
|
|
347
|
+
"-ctime",
|
|
348
|
+
"-flags",
|
|
349
|
+
"-fstype",
|
|
350
|
+
"-gid",
|
|
351
|
+
"-group",
|
|
352
|
+
"-ilname",
|
|
353
|
+
"-iname",
|
|
354
|
+
"-inum",
|
|
355
|
+
"-ipath",
|
|
356
|
+
"-iregex",
|
|
357
|
+
"-links",
|
|
358
|
+
"-lname",
|
|
359
|
+
"-maxdepth",
|
|
360
|
+
"-mindepth",
|
|
361
|
+
"-mmin",
|
|
362
|
+
"-mtime",
|
|
363
|
+
"-name",
|
|
364
|
+
"-path",
|
|
365
|
+
"-perm",
|
|
366
|
+
"-printf",
|
|
367
|
+
"-regex",
|
|
368
|
+
"-regextype",
|
|
369
|
+
"-size",
|
|
370
|
+
"-type",
|
|
371
|
+
"-uid",
|
|
372
|
+
"-used",
|
|
373
|
+
"-user",
|
|
374
|
+
"-xtype",
|
|
375
|
+
]);
|
|
376
|
+
const FIND_EXPRESSION_OPERATORS = new Set(["!", "(", ")", ",", "-and", "-or"]);
|
|
377
|
+
function newerFlagValueKind(flag) {
|
|
378
|
+
if (!flag.startsWith("-newer"))
|
|
379
|
+
return undefined;
|
|
380
|
+
if (flag === "-newer")
|
|
381
|
+
return "path";
|
|
382
|
+
if (flag.length !== "-newerXY".length)
|
|
383
|
+
return undefined;
|
|
384
|
+
return flag.endsWith("t") ? "scalar" : "path";
|
|
385
|
+
}
|
|
386
|
+
function findValueKind(arg) {
|
|
387
|
+
const flag = flagName(arg);
|
|
388
|
+
if (FIND_PATH_VALUE_FLAGS.has(flag))
|
|
389
|
+
return "path";
|
|
390
|
+
if (FIND_SCALAR_VALUE_FLAGS.has(flag))
|
|
391
|
+
return "scalar";
|
|
392
|
+
return newerFlagValueKind(flag);
|
|
393
|
+
}
|
|
394
|
+
function consumeFindValueFlag(state, arg) {
|
|
395
|
+
const kind = findValueKind(arg);
|
|
396
|
+
if (kind === undefined)
|
|
397
|
+
return false;
|
|
398
|
+
state.pending = kind;
|
|
399
|
+
return true;
|
|
400
|
+
}
|
|
401
|
+
function startsFindExpression(arg) {
|
|
402
|
+
return arg.startsWith("-") || FIND_EXPRESSION_OPERATORS.has(arg);
|
|
403
|
+
}
|
|
404
|
+
function validateFindOperands(ctx, args) {
|
|
405
|
+
const state = { afterTerminator: false, pending: undefined };
|
|
406
|
+
let expressionStarted = false;
|
|
407
|
+
for (const arg of args) {
|
|
408
|
+
if (consumePendingOperandValue(ctx, state, arg))
|
|
409
|
+
continue;
|
|
410
|
+
if (!expressionStarted && arg === "-f") {
|
|
411
|
+
state.pending = "path";
|
|
412
|
+
continue;
|
|
413
|
+
}
|
|
414
|
+
if (!expressionStarted && !startsFindExpression(arg)) {
|
|
415
|
+
assertOperandInsideProject(ctx, arg);
|
|
416
|
+
continue;
|
|
417
|
+
}
|
|
418
|
+
if (!expressionStarted && FIND_ROOT_OPTIONS.has(arg))
|
|
419
|
+
continue;
|
|
420
|
+
expressionStarted = true;
|
|
421
|
+
if (consumeFindValueFlag(state, arg))
|
|
422
|
+
continue;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
function validateCommandOperands(projectRoot, cwd, input, fs) {
|
|
426
|
+
const ctx = { fs, projectRoot, cwd };
|
|
427
|
+
switch (input.command) {
|
|
428
|
+
case "cat":
|
|
429
|
+
case "wc":
|
|
430
|
+
validatePathOperands(ctx, input.args, FROZEN_EMPTY_SET);
|
|
431
|
+
break;
|
|
432
|
+
case "head":
|
|
433
|
+
case "tail":
|
|
434
|
+
validatePathOperands(ctx, input.args, HEAD_TAIL_SCALAR_FLAGS);
|
|
435
|
+
break;
|
|
436
|
+
case "ls":
|
|
437
|
+
validatePathOperands(ctx, input.args, LS_SCALAR_FLAGS);
|
|
438
|
+
break;
|
|
439
|
+
case "tree":
|
|
440
|
+
validatePathOperands(ctx, input.args, TREE_SCALAR_FLAGS);
|
|
441
|
+
break;
|
|
442
|
+
case "grep":
|
|
443
|
+
validateGrepOperands(ctx, input.args);
|
|
444
|
+
break;
|
|
445
|
+
case "find":
|
|
446
|
+
validateFindOperands(ctx, input.args);
|
|
447
|
+
break;
|
|
448
|
+
default:
|
|
449
|
+
break;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
function clampTimeout(requested, ceiling) {
|
|
453
|
+
if (requested === undefined || !Number.isFinite(requested)) {
|
|
454
|
+
return ceiling;
|
|
455
|
+
}
|
|
456
|
+
const rounded = Math.round(requested);
|
|
457
|
+
if (rounded <= MIN_TIMEOUT_MS)
|
|
458
|
+
return MIN_TIMEOUT_MS;
|
|
459
|
+
if (rounded >= ceiling)
|
|
460
|
+
return ceiling;
|
|
461
|
+
return rounded;
|
|
462
|
+
}
|
|
463
|
+
class TerminalExecutionManagerImpl {
|
|
464
|
+
store;
|
|
465
|
+
evidenceStore;
|
|
466
|
+
policy;
|
|
467
|
+
processEnv;
|
|
468
|
+
redactor;
|
|
469
|
+
runDeps;
|
|
470
|
+
now;
|
|
471
|
+
executions = new Map();
|
|
472
|
+
subscribers = new Set();
|
|
473
|
+
constructor(opts) {
|
|
474
|
+
this.store = opts.store;
|
|
475
|
+
this.evidenceStore = opts.evidenceStore;
|
|
476
|
+
this.policy = opts.policy ?? DEFAULT_SANDBOX_POLICY;
|
|
477
|
+
this.processEnv = opts.processEnv ?? process.env;
|
|
478
|
+
this.redactor = opts.redactor ?? defaultRedactor;
|
|
479
|
+
this.runDeps = opts.runDeps ?? {};
|
|
480
|
+
this.now = opts.now ?? Date.now;
|
|
481
|
+
}
|
|
482
|
+
inFlightCount = () => this.executions.size;
|
|
483
|
+
subscribe = (listener) => {
|
|
484
|
+
this.subscribers.add(listener);
|
|
485
|
+
return () => {
|
|
486
|
+
this.subscribers.delete(listener);
|
|
487
|
+
};
|
|
488
|
+
};
|
|
489
|
+
abort = (executionId) => {
|
|
490
|
+
const entry = this.executions.get(executionId);
|
|
491
|
+
if (entry === undefined)
|
|
492
|
+
return false;
|
|
493
|
+
entry.cancelledByUser = true;
|
|
494
|
+
entry.controller.abort();
|
|
495
|
+
return true;
|
|
496
|
+
};
|
|
497
|
+
execute = async (input) => {
|
|
498
|
+
const project = projectFor(this.store, input.projectId);
|
|
499
|
+
if (project === undefined) {
|
|
500
|
+
throw new TerminalToolError("PROJECT_NOT_FOUND", "Project not found.");
|
|
501
|
+
}
|
|
502
|
+
const decision = isTerminalCommandAllowed(input.command, input.args);
|
|
503
|
+
if (!decision.allowed) {
|
|
504
|
+
throw new TerminalToolError("COMMAND_DENIED", "Command is not in the allowlist.");
|
|
505
|
+
}
|
|
506
|
+
if (this.executions.size >= MAX_CONCURRENT_EXECUTIONS) {
|
|
507
|
+
throw new TerminalToolError("EXECUTION_LIMIT_EXCEEDED", "Too many in-flight terminal executions.");
|
|
508
|
+
}
|
|
509
|
+
const projectRoot = projectRootOrThrow(project);
|
|
510
|
+
const cwd = assertCwdInsideProject(projectRoot, input.cwd);
|
|
511
|
+
validateCommandOperands(projectRoot, cwd, input, this.runDeps.fs ?? nodeWorkspaceFs);
|
|
512
|
+
return this.runExecution(projectRoot, cwd, input);
|
|
513
|
+
};
|
|
514
|
+
async runExecution(projectRoot, cwd, input) {
|
|
515
|
+
const executionId = randomUUID();
|
|
516
|
+
const controller = new AbortController();
|
|
517
|
+
const entry = {
|
|
518
|
+
controller,
|
|
519
|
+
projectId: input.projectId,
|
|
520
|
+
cancelledByUser: false,
|
|
521
|
+
};
|
|
522
|
+
this.executions.set(executionId, entry);
|
|
523
|
+
const startedAt = this.now();
|
|
524
|
+
this.emitStarted(executionId, input, startedAt);
|
|
525
|
+
try {
|
|
526
|
+
return await this.invokeRunCommand(executionId, projectRoot, cwd, input, entry, startedAt);
|
|
527
|
+
}
|
|
528
|
+
finally {
|
|
529
|
+
this.executions.delete(executionId);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
// Keep runCommand on the same terminal policy table used by the BFF pre-check. Layer 2 above
|
|
533
|
+
// covers operand containment, while runCommand still owns the spawn boundary, cwd realpath check,
|
|
534
|
+
// executable resolution, sandbox env, timeout, and output cap.
|
|
535
|
+
buildRunDepsFor(projectRoot) {
|
|
536
|
+
const workspace = {
|
|
537
|
+
root: projectRoot,
|
|
538
|
+
name: undefined,
|
|
539
|
+
version: undefined,
|
|
540
|
+
testFramework: "unknown",
|
|
541
|
+
sourceDirs: [],
|
|
542
|
+
testDirs: [],
|
|
543
|
+
languages: [],
|
|
544
|
+
ignoreLines: [],
|
|
545
|
+
};
|
|
546
|
+
return {
|
|
547
|
+
workspace,
|
|
548
|
+
policy: this.policy,
|
|
549
|
+
commandRules: this.runDeps.commandRules ?? TERMINAL_COMMAND_RULES,
|
|
550
|
+
spawn: this.runDeps.spawn ?? nodeSpawnFn,
|
|
551
|
+
processEnv: this.processEnv,
|
|
552
|
+
now: this.runDeps.now ?? this.now,
|
|
553
|
+
...(this.runDeps.resolveExecutable === undefined
|
|
554
|
+
? {}
|
|
555
|
+
: { resolveExecutable: this.runDeps.resolveExecutable }),
|
|
556
|
+
...(this.runDeps.fs === undefined ? {} : { fs: this.runDeps.fs }),
|
|
557
|
+
...(this.runDeps.home === undefined ? {} : { home: this.runDeps.home }),
|
|
558
|
+
};
|
|
559
|
+
}
|
|
560
|
+
async invokeRunCommand(executionId, projectRoot, cwd, input, entry, startedAt) {
|
|
561
|
+
const deps = this.buildRunDepsFor(projectRoot);
|
|
562
|
+
const timeoutMs = clampTimeout(input.timeoutMs, this.policy.defaultTimeoutMs);
|
|
563
|
+
let result;
|
|
564
|
+
try {
|
|
565
|
+
result = await runCommand({
|
|
566
|
+
command: input.command,
|
|
567
|
+
args: input.args,
|
|
568
|
+
cwd,
|
|
569
|
+
timeoutMs,
|
|
570
|
+
signal: entry.controller.signal,
|
|
571
|
+
}, deps);
|
|
572
|
+
}
|
|
573
|
+
catch (error) {
|
|
574
|
+
this.recordFailure(executionId, input, entry, error, startedAt);
|
|
575
|
+
throw this.mapError(error, entry);
|
|
576
|
+
}
|
|
577
|
+
return this.handleSuccess(executionId, input, result, startedAt);
|
|
578
|
+
}
|
|
579
|
+
handleSuccess(executionId, input, result, startedAt) {
|
|
580
|
+
const stdoutBytes = Buffer.byteLength(result.stdout, "utf8");
|
|
581
|
+
const stderrBytes = Buffer.byteLength(result.stderr, "utf8");
|
|
582
|
+
const counts = {
|
|
583
|
+
exitCode: result.exitCode,
|
|
584
|
+
signal: null,
|
|
585
|
+
durationMs: result.durationMs,
|
|
586
|
+
timedOut: result.timedOut,
|
|
587
|
+
truncated: result.truncated,
|
|
588
|
+
stdoutBytes,
|
|
589
|
+
stderrBytes,
|
|
590
|
+
startedAt,
|
|
591
|
+
};
|
|
592
|
+
this.persistEntryOrEmitFailure(executionId, input, counts);
|
|
593
|
+
this.emit({
|
|
594
|
+
kind: "execution-completed",
|
|
595
|
+
executionId,
|
|
596
|
+
payload: {
|
|
597
|
+
exitCode: result.exitCode,
|
|
598
|
+
durationMs: result.durationMs,
|
|
599
|
+
truncated: result.truncated,
|
|
600
|
+
timedOut: result.timedOut,
|
|
601
|
+
stdoutByteLength: stdoutBytes,
|
|
602
|
+
stderrByteLength: stderrBytes,
|
|
603
|
+
...requestIdPayload(input),
|
|
604
|
+
},
|
|
605
|
+
});
|
|
606
|
+
return {
|
|
607
|
+
executionId,
|
|
608
|
+
exitCode: result.exitCode,
|
|
609
|
+
stdout: result.stdout,
|
|
610
|
+
stderr: result.stderr,
|
|
611
|
+
durationMs: result.durationMs,
|
|
612
|
+
truncated: result.truncated,
|
|
613
|
+
timedOut: result.timedOut,
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
recordFailure(executionId, input, entry, error, startedAt) {
|
|
617
|
+
const cancelled = error instanceof CommandCancelledError || entry.cancelledByUser;
|
|
618
|
+
const counts = {
|
|
619
|
+
exitCode: null,
|
|
620
|
+
signal: cancelled ? "SIGTERM" : null,
|
|
621
|
+
durationMs: this.now() - startedAt,
|
|
622
|
+
timedOut: error instanceof CommandTimeoutError,
|
|
623
|
+
truncated: false,
|
|
624
|
+
stdoutBytes: 0,
|
|
625
|
+
stderrBytes: 0,
|
|
626
|
+
startedAt,
|
|
627
|
+
};
|
|
628
|
+
this.persistEntryOrEmitFailure(executionId, input, counts);
|
|
629
|
+
if (cancelled) {
|
|
630
|
+
this.emit({
|
|
631
|
+
kind: "execution-cancelled",
|
|
632
|
+
executionId,
|
|
633
|
+
payload: requestIdPayload(input),
|
|
634
|
+
});
|
|
635
|
+
return;
|
|
636
|
+
}
|
|
637
|
+
// ADR-0018 D7: timeout is a "completed with timedOut=true" outcome, not a failure.
|
|
638
|
+
if (error instanceof CommandTimeoutError) {
|
|
639
|
+
this.emit({
|
|
640
|
+
kind: "execution-completed",
|
|
641
|
+
executionId,
|
|
642
|
+
payload: {
|
|
643
|
+
exitCode: null,
|
|
644
|
+
durationMs: counts.durationMs,
|
|
645
|
+
truncated: false,
|
|
646
|
+
timedOut: true,
|
|
647
|
+
stdoutByteLength: 0,
|
|
648
|
+
stderrByteLength: 0,
|
|
649
|
+
...requestIdPayload(input),
|
|
650
|
+
},
|
|
651
|
+
});
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
const mapped = this.mapError(error, entry);
|
|
655
|
+
this.emit({
|
|
656
|
+
kind: "execution-failed",
|
|
657
|
+
executionId,
|
|
658
|
+
payload: { code: mapped.code, message: mapped.message, ...requestIdPayload(input) },
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
persistEntry(executionId, input, counts) {
|
|
662
|
+
if (this.evidenceStore === undefined) {
|
|
663
|
+
throw new TerminalToolError("EVIDENCE_WRITE_FAILED", "Terminal evidence store is unavailable.");
|
|
664
|
+
}
|
|
665
|
+
const entry = buildTerminalEvidenceEntry({
|
|
666
|
+
executionId,
|
|
667
|
+
projectId: input.projectId,
|
|
668
|
+
command: input.command,
|
|
669
|
+
argCount: input.args.length,
|
|
670
|
+
exitCode: counts.exitCode,
|
|
671
|
+
signal: counts.signal,
|
|
672
|
+
durationMs: counts.durationMs,
|
|
673
|
+
timedOut: counts.timedOut,
|
|
674
|
+
truncated: counts.truncated,
|
|
675
|
+
stdoutBytes: counts.stdoutBytes,
|
|
676
|
+
stderrBytes: counts.stderrBytes,
|
|
677
|
+
startedAt: counts.startedAt,
|
|
678
|
+
});
|
|
679
|
+
try {
|
|
680
|
+
appendTerminalEvidence(this.evidenceStore, entry, this.redactor);
|
|
681
|
+
}
|
|
682
|
+
catch {
|
|
683
|
+
throw new TerminalToolError("EVIDENCE_WRITE_FAILED", "Terminal evidence could not be written.");
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
persistEntryOrEmitFailure(executionId, input, counts) {
|
|
687
|
+
try {
|
|
688
|
+
this.persistEntry(executionId, input, counts);
|
|
689
|
+
}
|
|
690
|
+
catch (error) {
|
|
691
|
+
const mapped = error instanceof TerminalToolError
|
|
692
|
+
? error
|
|
693
|
+
: new TerminalToolError("EVIDENCE_WRITE_FAILED", "Terminal evidence could not be written.");
|
|
694
|
+
this.emit({
|
|
695
|
+
kind: "execution-failed",
|
|
696
|
+
executionId,
|
|
697
|
+
payload: { code: mapped.code, message: mapped.message, ...requestIdPayload(input) },
|
|
698
|
+
});
|
|
699
|
+
throw mapped;
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
mapError(error, entry) {
|
|
703
|
+
if (error instanceof TerminalToolError)
|
|
704
|
+
return error;
|
|
705
|
+
if (error instanceof CommandTimeoutError) {
|
|
706
|
+
return new TerminalToolError("TIMEOUT", "Command timed out.");
|
|
707
|
+
}
|
|
708
|
+
if (error instanceof CommandCancelledError || entry.cancelledByUser) {
|
|
709
|
+
return new TerminalToolError("CANCELLED", "Command was cancelled.");
|
|
710
|
+
}
|
|
711
|
+
if (error instanceof PathDeniedError) {
|
|
712
|
+
return new TerminalToolError("CWD_DENIED", "Working directory is denied by policy.");
|
|
713
|
+
}
|
|
714
|
+
if (error instanceof CommandDeniedError) {
|
|
715
|
+
return this.mapCommandDenied(error);
|
|
716
|
+
}
|
|
717
|
+
return new TerminalToolError("INTERNAL", "Command execution failed.");
|
|
718
|
+
}
|
|
719
|
+
mapCommandDenied(error) {
|
|
720
|
+
if (error.message.includes("not found on PATH")) {
|
|
721
|
+
return new TerminalToolError("EXECUTABLE_NOT_FOUND", "Command executable not found on PATH.");
|
|
722
|
+
}
|
|
723
|
+
return new TerminalToolError("COMMAND_DENIED", "Command is not in the allowlist.");
|
|
724
|
+
}
|
|
725
|
+
emitStarted(executionId, input, startedAt) {
|
|
726
|
+
this.emit({
|
|
727
|
+
kind: "execution-started",
|
|
728
|
+
executionId,
|
|
729
|
+
payload: {
|
|
730
|
+
projectId: input.projectId,
|
|
731
|
+
command: input.command,
|
|
732
|
+
argCount: input.args.length,
|
|
733
|
+
startedAt,
|
|
734
|
+
...requestIdPayload(input),
|
|
735
|
+
},
|
|
736
|
+
});
|
|
737
|
+
}
|
|
738
|
+
emit(event) {
|
|
739
|
+
for (const listener of [...this.subscribers]) {
|
|
740
|
+
try {
|
|
741
|
+
listener(event);
|
|
742
|
+
}
|
|
743
|
+
catch {
|
|
744
|
+
// A subscriber throwing must not stop fan-out (matches the browser tool pattern).
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
export function createTerminalExecutionManager(opts) {
|
|
750
|
+
return new TerminalExecutionManagerImpl(opts);
|
|
751
|
+
}
|
|
752
|
+
// ─── Policy summary (GET /api/terminal/policy) ───────────────────────────────────
|
|
753
|
+
// A6 — Derived from TERMINAL_COMMAND_RULES so the policy and the summary stay in sync.
|
|
754
|
+
// Materialized once at module load so the GET handler is O(1) and the public surface is a
|
|
755
|
+
// frozen list — a test that compares against this exact set locks the deny-by-default invariant.
|
|
756
|
+
const ALLOWED_COMMAND_NAMES = Object.freeze([...TERMINAL_COMMAND_RULES.map((r) => r.executable)].sort());
|
|
757
|
+
export function buildTerminalPolicySummary(policy = DEFAULT_SANDBOX_POLICY) {
|
|
758
|
+
return {
|
|
759
|
+
commands: ALLOWED_COMMAND_NAMES,
|
|
760
|
+
limits: {
|
|
761
|
+
maxOutputBytes: policy.maxOutputBytes,
|
|
762
|
+
defaultTimeoutMs: policy.defaultTimeoutMs,
|
|
763
|
+
},
|
|
764
|
+
};
|
|
765
|
+
}
|
|
766
|
+
// ─── Directory picker (anchored at the project root — A3 containment) ────────────
|
|
767
|
+
function parentPath(pathValue, projectRoot) {
|
|
768
|
+
// Do not let parent navigation escape the project root.
|
|
769
|
+
if (pathValue === projectRoot)
|
|
770
|
+
return null;
|
|
771
|
+
const parsed = parsePath(pathValue);
|
|
772
|
+
return pathValue === parsed.root ? null : dirname(pathValue);
|
|
773
|
+
}
|
|
774
|
+
// A3 — Normalise the client-supplied path to an absolute path. Relative paths are resolved
|
|
775
|
+
// against `projectRoot`. Absolute paths are kept as-is; realpath containment is enforced
|
|
776
|
+
// in `resolveDirectory` after both sides are realpath'd (handles macOS /tmp → /private/tmp).
|
|
777
|
+
function normalizeClientPath(pathInput, projectRoot) {
|
|
778
|
+
const raw = pathInput?.trim();
|
|
779
|
+
if (raw === undefined || raw.length === 0) {
|
|
780
|
+
return projectRoot;
|
|
781
|
+
}
|
|
782
|
+
return isAbsolute(raw) ? raw : resolvePath(projectRoot, raw);
|
|
783
|
+
}
|
|
784
|
+
async function resolveDirectory(candidate, projectRoot) {
|
|
785
|
+
let resolved;
|
|
786
|
+
try {
|
|
787
|
+
resolved = await realpath(candidate);
|
|
788
|
+
}
|
|
789
|
+
catch {
|
|
790
|
+
throw new TerminalToolError("BAD_REQUEST", "The working directory does not exist.");
|
|
791
|
+
}
|
|
792
|
+
// Realpath containment check — catches symlink escapes (Tier 2 of ADR-0018 D2).
|
|
793
|
+
if (!isWithinWorkspace(projectRoot, resolved)) {
|
|
794
|
+
throw new TerminalToolError("CWD_OUTSIDE_PROJECT", "Working directory is outside the selected project.");
|
|
795
|
+
}
|
|
796
|
+
const info = await stat(resolved);
|
|
797
|
+
if (!info.isDirectory()) {
|
|
798
|
+
throw new TerminalToolError("BAD_REQUEST", "The working directory must be a directory.");
|
|
799
|
+
}
|
|
800
|
+
return resolved;
|
|
801
|
+
}
|
|
802
|
+
export async function listDirectories(store, projectId, pathInput) {
|
|
803
|
+
const project = projectFor(store, projectId);
|
|
804
|
+
if (project === undefined) {
|
|
805
|
+
throw new TerminalToolError("PROJECT_NOT_FOUND", "Project not found.");
|
|
806
|
+
}
|
|
807
|
+
const projectRootRaw = project.path;
|
|
808
|
+
// Resolve the project root to its real path first so that comparisons on macOS (where /tmp
|
|
809
|
+
// is a symlink to /private/tmp) don't false-positive as escapes.
|
|
810
|
+
let projectRoot;
|
|
811
|
+
try {
|
|
812
|
+
projectRoot = await realpath(projectRootRaw);
|
|
813
|
+
}
|
|
814
|
+
catch {
|
|
815
|
+
throw new TerminalToolError("PROJECT_NOT_FOUND", "Project root path could not be resolved.");
|
|
816
|
+
}
|
|
817
|
+
const lexical = normalizeClientPath(pathInput, projectRoot);
|
|
818
|
+
const pathValue = await resolveDirectory(lexical, projectRoot);
|
|
819
|
+
const entries = await readdir(pathValue, { withFileTypes: true });
|
|
820
|
+
const dirs = entries
|
|
821
|
+
.filter((entry) => entry.isDirectory())
|
|
822
|
+
.map((entry) => ({ name: entry.name, path: join(pathValue, entry.name) }))
|
|
823
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
824
|
+
// A3 — roots contains only the project root. Home and FS-root are no longer exposed because
|
|
825
|
+
// they could be outside the project boundary. The UI cwd picker shows only project-scoped paths.
|
|
826
|
+
const roots = [
|
|
827
|
+
{ label: "Project root", path: projectRoot },
|
|
828
|
+
];
|
|
829
|
+
return {
|
|
830
|
+
path: pathValue,
|
|
831
|
+
parent: parentPath(pathValue, projectRoot),
|
|
832
|
+
entries: dirs,
|
|
833
|
+
roots,
|
|
834
|
+
};
|
|
835
|
+
}
|