@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
package/dist/ui/files.js
ADDED
|
@@ -0,0 +1,492 @@
|
|
|
1
|
+
// Read-only filesystem browser for the desktop Files widget. The browser receives
|
|
2
|
+
// only metadata or redacted preview content; every request is contained inside a
|
|
3
|
+
// registered project root after realpath resolution.
|
|
4
|
+
import { lstat, opendir, open, readFile, realpath, stat, } from "node:fs/promises";
|
|
5
|
+
import { basename, dirname, extname, isAbsolute, join, parse as parsePath, posix as pathPosix, relative, resolve, } from "node:path";
|
|
6
|
+
import { compileIgnore, isIgnored } from "../workspace/ignore.js";
|
|
7
|
+
import { DENIED_MESSAGE, pathIsDenied } from "./files-deny.js";
|
|
8
|
+
import { errorBody } from "./routes.js";
|
|
9
|
+
const MAX_DIRECTORY_ENTRIES = 1_000;
|
|
10
|
+
const MAX_TEXT_PREVIEW_BYTES = 1_000_000;
|
|
11
|
+
const MAX_IMAGE_PREVIEW_BYTES = 3_000_000;
|
|
12
|
+
const MAX_IGNORED_SCAN_ENTRIES = 10_000;
|
|
13
|
+
class FilesError extends Error {
|
|
14
|
+
status;
|
|
15
|
+
code;
|
|
16
|
+
constructor(status, code, message) {
|
|
17
|
+
super(message);
|
|
18
|
+
this.status = status;
|
|
19
|
+
this.code = code;
|
|
20
|
+
this.name = "FilesError";
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function filesErrorResult(error) {
|
|
24
|
+
return { status: error.status, body: errorBody(error.code, error.message) };
|
|
25
|
+
}
|
|
26
|
+
async function runFilesHandler(work) {
|
|
27
|
+
try {
|
|
28
|
+
return await work();
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
if (error instanceof FilesError)
|
|
32
|
+
return filesErrorResult(error);
|
|
33
|
+
throw error;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
async function resolveDirectory(candidate) {
|
|
37
|
+
let resolved;
|
|
38
|
+
try {
|
|
39
|
+
resolved = await realpath(candidate);
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
throw new FilesError(400, "INVALID_DIRECTORY", "The directory does not exist.");
|
|
43
|
+
}
|
|
44
|
+
const info = await stat(resolved);
|
|
45
|
+
if (!info.isDirectory()) {
|
|
46
|
+
throw new FilesError(400, "INVALID_DIRECTORY", "The selected path must be a directory.");
|
|
47
|
+
}
|
|
48
|
+
return resolved;
|
|
49
|
+
}
|
|
50
|
+
function projectFor(store, projectId) {
|
|
51
|
+
return store.listProjects().find((project) => project.path === projectId);
|
|
52
|
+
}
|
|
53
|
+
function rootNameIsDenied(rootPath) {
|
|
54
|
+
return pathIsDenied(basename(rootPath));
|
|
55
|
+
}
|
|
56
|
+
async function resolveRoot(store, rootInput) {
|
|
57
|
+
if (rootInput === null || rootInput.trim().length === 0) {
|
|
58
|
+
throw new FilesError(400, "BAD_REQUEST", "The root query parameter is required.");
|
|
59
|
+
}
|
|
60
|
+
const project = projectFor(store, rootInput);
|
|
61
|
+
if (project === undefined) {
|
|
62
|
+
throw new FilesError(403, "WORKSPACE_NOT_REGISTERED", "The selected root is not a registered project.");
|
|
63
|
+
}
|
|
64
|
+
if (rootNameIsDenied(project.path)) {
|
|
65
|
+
throw new FilesError(403, "DENIED", DENIED_MESSAGE);
|
|
66
|
+
}
|
|
67
|
+
const realRoot = await resolveDirectory(project.path);
|
|
68
|
+
if (rootNameIsDenied(realRoot)) {
|
|
69
|
+
throw new FilesError(403, "DENIED", DENIED_MESSAGE);
|
|
70
|
+
}
|
|
71
|
+
return { root: project.path, realRoot };
|
|
72
|
+
}
|
|
73
|
+
function directoryRoots(projectRoot) {
|
|
74
|
+
return [{ label: "Project root", path: projectRoot }];
|
|
75
|
+
}
|
|
76
|
+
function parentPath(pathValue, projectRoot) {
|
|
77
|
+
if (pathValue === projectRoot)
|
|
78
|
+
return null;
|
|
79
|
+
const parsed = parsePath(pathValue);
|
|
80
|
+
return pathValue === parsed.root ? null : dirname(pathValue);
|
|
81
|
+
}
|
|
82
|
+
function normalizeRelativePath(pathInput) {
|
|
83
|
+
const raw = pathInput ?? "";
|
|
84
|
+
if (raw.includes("\0") || isAbsolute(raw)) {
|
|
85
|
+
throw new FilesError(400, "BAD_PATH", "The path must be relative to the selected root.");
|
|
86
|
+
}
|
|
87
|
+
const normalized = pathPosix.normalize(raw.replaceAll("\\", "/"));
|
|
88
|
+
if (normalized === ".")
|
|
89
|
+
return "";
|
|
90
|
+
if (normalized === ".." || normalized.startsWith("../")) {
|
|
91
|
+
throw new FilesError(400, "PATH_ESCAPE", "The requested path is outside the selected root.");
|
|
92
|
+
}
|
|
93
|
+
return normalized;
|
|
94
|
+
}
|
|
95
|
+
function nativePath(root, relativePath) {
|
|
96
|
+
if (relativePath.length === 0)
|
|
97
|
+
return root;
|
|
98
|
+
return resolve(root, ...relativePath.split("/").filter((part) => part.length > 0));
|
|
99
|
+
}
|
|
100
|
+
function isContained(root, target) {
|
|
101
|
+
const rootCmp = process.platform === "win32" ? root.toLowerCase() : root;
|
|
102
|
+
const targetCmp = process.platform === "win32" ? target.toLowerCase() : target;
|
|
103
|
+
const rel = relative(rootCmp, targetCmp);
|
|
104
|
+
return rel === "" || (!rel.startsWith("..") && !isAbsolute(rel));
|
|
105
|
+
}
|
|
106
|
+
function rootRelativePosixPath(root, target) {
|
|
107
|
+
const rel = relative(root, target);
|
|
108
|
+
return rel.replaceAll("\\", "/");
|
|
109
|
+
}
|
|
110
|
+
function normalizeDirectoryPath(pathInput, registeredRoot, realRoot) {
|
|
111
|
+
const raw = pathInput?.trim();
|
|
112
|
+
if (raw === undefined || raw.length === 0)
|
|
113
|
+
return realRoot;
|
|
114
|
+
if (raw.includes("\0")) {
|
|
115
|
+
throw new FilesError(400, "BAD_PATH", "The path must stay inside the selected project.");
|
|
116
|
+
}
|
|
117
|
+
const candidate = isAbsolute(raw) ? resolve(raw) : resolve(realRoot, raw);
|
|
118
|
+
if (!isContained(realRoot, candidate) && !isContained(registeredRoot, candidate)) {
|
|
119
|
+
throw new FilesError(403, "PATH_ESCAPE", "The requested path is outside the selected project.");
|
|
120
|
+
}
|
|
121
|
+
return candidate;
|
|
122
|
+
}
|
|
123
|
+
async function resolveDirectoryInsideRoot(store, rootInput, pathInput) {
|
|
124
|
+
const root = await resolveRoot(store, rootInput);
|
|
125
|
+
const candidate = normalizeDirectoryPath(pathInput, root.root, root.realRoot);
|
|
126
|
+
const pathValue = await resolveDirectory(candidate);
|
|
127
|
+
if (!isContained(root.realRoot, pathValue)) {
|
|
128
|
+
throw new FilesError(403, "PATH_ESCAPE", "The requested path is outside the selected project.");
|
|
129
|
+
}
|
|
130
|
+
const relativePath = rootRelativePosixPath(root.realRoot, pathValue);
|
|
131
|
+
if (pathIsDenied(relativePath)) {
|
|
132
|
+
throw new FilesError(403, "DENIED", DENIED_MESSAGE);
|
|
133
|
+
}
|
|
134
|
+
return { ...root, path: pathValue, relativePath };
|
|
135
|
+
}
|
|
136
|
+
async function resolveInsideRoot(store, rootInput, pathInput) {
|
|
137
|
+
const root = await resolveRoot(store, rootInput);
|
|
138
|
+
const relativePath = normalizeRelativePath(pathInput);
|
|
139
|
+
// Deny check runs BEFORE realpath so existence of a denied path is not
|
|
140
|
+
// observable via the 403/404 status-code difference. A non-existent denied
|
|
141
|
+
// path returns 403, identical to an existing denied path.
|
|
142
|
+
if (pathIsDenied(relativePath)) {
|
|
143
|
+
throw new FilesError(403, "DENIED", DENIED_MESSAGE);
|
|
144
|
+
}
|
|
145
|
+
const candidate = nativePath(root.realRoot, relativePath);
|
|
146
|
+
let target;
|
|
147
|
+
try {
|
|
148
|
+
target = await realpath(candidate);
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
throw new FilesError(404, "NOT_FOUND", "The requested path was not found.");
|
|
152
|
+
}
|
|
153
|
+
if (!isContained(root.realRoot, target)) {
|
|
154
|
+
throw new FilesError(403, "PATH_ESCAPE", "The requested path is outside the selected root.");
|
|
155
|
+
}
|
|
156
|
+
const targetRelativePath = rootRelativePosixPath(root.realRoot, target);
|
|
157
|
+
if (pathIsDenied(targetRelativePath)) {
|
|
158
|
+
throw new FilesError(403, "DENIED", DENIED_MESSAGE);
|
|
159
|
+
}
|
|
160
|
+
const linkStats = await lstat(candidate);
|
|
161
|
+
const targetStats = await stat(target);
|
|
162
|
+
return {
|
|
163
|
+
root: root.root,
|
|
164
|
+
realRoot: root.realRoot,
|
|
165
|
+
relativePath,
|
|
166
|
+
path: target,
|
|
167
|
+
stats: targetStats,
|
|
168
|
+
symlink: linkStats.isSymbolicLink(),
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
async function directoryEntries(root, pathValue) {
|
|
172
|
+
const entries = [];
|
|
173
|
+
const dir = await opendir(pathValue);
|
|
174
|
+
try {
|
|
175
|
+
for await (const entry of dir) {
|
|
176
|
+
if (!entry.isDirectory())
|
|
177
|
+
continue;
|
|
178
|
+
const entryPath = join(pathValue, entry.name);
|
|
179
|
+
const relativePath = rootRelativePosixPath(root, entryPath);
|
|
180
|
+
if (pathIsDenied(relativePath))
|
|
181
|
+
continue;
|
|
182
|
+
entries.push({ name: entry.name, path: entryPath });
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
finally {
|
|
186
|
+
await dir.close().catch(() => undefined);
|
|
187
|
+
}
|
|
188
|
+
return entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
189
|
+
}
|
|
190
|
+
export async function listFilesDirectories(store, rootInput, pathInput) {
|
|
191
|
+
const target = await resolveDirectoryInsideRoot(store, rootInput, pathInput);
|
|
192
|
+
return {
|
|
193
|
+
path: target.path,
|
|
194
|
+
parent: parentPath(target.path, target.realRoot),
|
|
195
|
+
entries: await directoryEntries(target.realRoot, target.path),
|
|
196
|
+
roots: directoryRoots(target.root),
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
function extensionOf(name) {
|
|
200
|
+
const lower = name.toLowerCase();
|
|
201
|
+
if (lower === "dockerfile")
|
|
202
|
+
return "dockerfile";
|
|
203
|
+
if (lower === ".env" || lower.startsWith(".env."))
|
|
204
|
+
return "env";
|
|
205
|
+
const ext = extname(lower).replace(/^\./u, "");
|
|
206
|
+
return ext.length > 0 ? ext : null;
|
|
207
|
+
}
|
|
208
|
+
async function classifyEntry(root, parentRelativePath, parentNativePath, entry) {
|
|
209
|
+
const childRelativePath = parentRelativePath.length === 0
|
|
210
|
+
? entry.name
|
|
211
|
+
: `${parentRelativePath}/${entry.name}`;
|
|
212
|
+
const entryPath = join(parentNativePath, entry.name);
|
|
213
|
+
const linkStats = await lstat(entryPath);
|
|
214
|
+
const symlink = linkStats.isSymbolicLink();
|
|
215
|
+
const base = {
|
|
216
|
+
name: entry.name,
|
|
217
|
+
path: childRelativePath,
|
|
218
|
+
sizeBytes: linkStats.size,
|
|
219
|
+
modifiedAt: linkStats.mtimeMs,
|
|
220
|
+
extension: extensionOf(entry.name),
|
|
221
|
+
symlink,
|
|
222
|
+
};
|
|
223
|
+
if (!symlink) {
|
|
224
|
+
const kind = linkStats.isDirectory() ? "directory" : "file";
|
|
225
|
+
return { ...base, kind, readable: true };
|
|
226
|
+
}
|
|
227
|
+
try {
|
|
228
|
+
const target = await realpath(entryPath);
|
|
229
|
+
const targetStats = await stat(target);
|
|
230
|
+
const contained = isContained(root, target);
|
|
231
|
+
const denied = contained && pathIsDenied(rootRelativePosixPath(root, target));
|
|
232
|
+
const kind = targetStats.isDirectory()
|
|
233
|
+
? "directory"
|
|
234
|
+
: targetStats.isFile()
|
|
235
|
+
? "file"
|
|
236
|
+
: "symlink";
|
|
237
|
+
return { ...base, kind, readable: contained && !denied };
|
|
238
|
+
}
|
|
239
|
+
catch {
|
|
240
|
+
return { ...base, kind: "symlink", readable: false };
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
function entryRank(entry) {
|
|
244
|
+
if (entry.kind === "directory")
|
|
245
|
+
return 0;
|
|
246
|
+
if (entry.kind === "file")
|
|
247
|
+
return 1;
|
|
248
|
+
return 2;
|
|
249
|
+
}
|
|
250
|
+
function childRelative(parentRelativePath, name) {
|
|
251
|
+
return parentRelativePath.length === 0 ? name : `${parentRelativePath}/${name}`;
|
|
252
|
+
}
|
|
253
|
+
// Best-effort: read the project root's `.gitignore` if present. Silent failure
|
|
254
|
+
// is intentional — `.gitignore` is tier-2 noise reduction, not a safety
|
|
255
|
+
// boundary (deny-list is tier 1). A missing/unreadable `.gitignore` is "no
|
|
256
|
+
// filter". No long-lived cache: the BFF is stateless across user-selected roots.
|
|
257
|
+
async function loadRootGitignore(rootPath) {
|
|
258
|
+
let raw;
|
|
259
|
+
try {
|
|
260
|
+
raw = await readFile(join(rootPath, ".gitignore"), "utf8");
|
|
261
|
+
}
|
|
262
|
+
catch {
|
|
263
|
+
return null;
|
|
264
|
+
}
|
|
265
|
+
const withoutBom = raw.charCodeAt(0) === 0xfeff ? raw.slice(1) : raw;
|
|
266
|
+
return compileIgnore(withoutBom.split("\n"));
|
|
267
|
+
}
|
|
268
|
+
function skipEntry(matcher, rel, isDir) {
|
|
269
|
+
if (pathIsDenied(rel))
|
|
270
|
+
return "denied";
|
|
271
|
+
return matcher !== null && isIgnored(matcher, rel, isDir) ? "ignored" : null;
|
|
272
|
+
}
|
|
273
|
+
async function listTreeEntries(root, relativePath, pathValue, matcher) {
|
|
274
|
+
const entries = [];
|
|
275
|
+
const dir = await opendir(pathValue);
|
|
276
|
+
let truncated = false;
|
|
277
|
+
let ignoredScans = 0;
|
|
278
|
+
try {
|
|
279
|
+
for await (const entry of dir) {
|
|
280
|
+
// Deny and .gitignore filtering happen BEFORE the truncation counter so
|
|
281
|
+
// a directory packed with denied entries (e.g. node_modules/**) cannot
|
|
282
|
+
// exhaust the 1000-entry budget and hide real files behind
|
|
283
|
+
// `truncated: true`. Deny is applied by the link name (the user only
|
|
284
|
+
// sees that name) so a symlink whose own name matches a deny pattern is
|
|
285
|
+
// denied even if its target does not — matches the workspace-layer
|
|
286
|
+
// semantics.
|
|
287
|
+
const rel = childRelative(relativePath, entry.name);
|
|
288
|
+
const skipReason = skipEntry(matcher, rel, entry.isDirectory());
|
|
289
|
+
if (skipReason === "denied")
|
|
290
|
+
continue;
|
|
291
|
+
if (skipReason === "ignored") {
|
|
292
|
+
ignoredScans += 1;
|
|
293
|
+
if (ignoredScans >= MAX_IGNORED_SCAN_ENTRIES) {
|
|
294
|
+
truncated = true;
|
|
295
|
+
break;
|
|
296
|
+
}
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
if (entries.length >= MAX_DIRECTORY_ENTRIES) {
|
|
300
|
+
truncated = true;
|
|
301
|
+
break;
|
|
302
|
+
}
|
|
303
|
+
entries.push(await classifyEntry(root, relativePath, pathValue, entry));
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
finally {
|
|
307
|
+
await dir.close().catch(() => undefined);
|
|
308
|
+
}
|
|
309
|
+
entries.sort((a, b) => entryRank(a) - entryRank(b) || a.name.localeCompare(b.name));
|
|
310
|
+
return { entries, truncated };
|
|
311
|
+
}
|
|
312
|
+
export async function readFilesTree(store, rootInput, pathInput) {
|
|
313
|
+
const target = await resolveInsideRoot(store, rootInput, pathInput);
|
|
314
|
+
if (!target.stats.isDirectory()) {
|
|
315
|
+
throw new FilesError(400, "NOT_DIRECTORY", "The requested path is not a directory.");
|
|
316
|
+
}
|
|
317
|
+
// Per-request: read the project root's `.gitignore` once. Best-effort noise
|
|
318
|
+
// reduction only; the matcher never relaxes the deny list. No long-lived
|
|
319
|
+
// cache — the BFF must stay stateless across user-selected roots.
|
|
320
|
+
const ignoreMatcher = await loadRootGitignore(target.realRoot);
|
|
321
|
+
const listed = await listTreeEntries(target.realRoot, target.relativePath, target.path, ignoreMatcher);
|
|
322
|
+
return {
|
|
323
|
+
root: target.root,
|
|
324
|
+
path: target.relativePath,
|
|
325
|
+
entries: listed.entries,
|
|
326
|
+
truncated: listed.truncated,
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
const IMAGE_MIME = {
|
|
330
|
+
png: "image/png",
|
|
331
|
+
jpg: "image/jpeg",
|
|
332
|
+
jpeg: "image/jpeg",
|
|
333
|
+
gif: "image/gif",
|
|
334
|
+
webp: "image/webp",
|
|
335
|
+
svg: "image/svg+xml",
|
|
336
|
+
};
|
|
337
|
+
const TEXT_EXTENSIONS = new Set([
|
|
338
|
+
"bash",
|
|
339
|
+
"c",
|
|
340
|
+
"cjs",
|
|
341
|
+
"css",
|
|
342
|
+
"csv",
|
|
343
|
+
"dockerfile",
|
|
344
|
+
"env",
|
|
345
|
+
"go",
|
|
346
|
+
"graphql",
|
|
347
|
+
"gql",
|
|
348
|
+
"gradle",
|
|
349
|
+
"html",
|
|
350
|
+
"java",
|
|
351
|
+
"js",
|
|
352
|
+
"json",
|
|
353
|
+
"jsx",
|
|
354
|
+
"kt",
|
|
355
|
+
"kts",
|
|
356
|
+
"md",
|
|
357
|
+
"mjs",
|
|
358
|
+
"properties",
|
|
359
|
+
"py",
|
|
360
|
+
"rs",
|
|
361
|
+
"sh",
|
|
362
|
+
"sql",
|
|
363
|
+
"toml",
|
|
364
|
+
"ts",
|
|
365
|
+
"tsx",
|
|
366
|
+
"txt",
|
|
367
|
+
"xml",
|
|
368
|
+
"yaml",
|
|
369
|
+
"yml",
|
|
370
|
+
]);
|
|
371
|
+
function mimeOf(extension) {
|
|
372
|
+
if (extension !== null && IMAGE_MIME[extension] !== undefined)
|
|
373
|
+
return IMAGE_MIME[extension];
|
|
374
|
+
if (extension === "json")
|
|
375
|
+
return "application/json";
|
|
376
|
+
if (extension === "md")
|
|
377
|
+
return "text/markdown";
|
|
378
|
+
if (extension === "html")
|
|
379
|
+
return "text/html";
|
|
380
|
+
if (extension === "css")
|
|
381
|
+
return "text/css";
|
|
382
|
+
if (isKnownTextExtension(extension))
|
|
383
|
+
return "text/plain";
|
|
384
|
+
return "application/octet-stream";
|
|
385
|
+
}
|
|
386
|
+
function isImageExtension(extension) {
|
|
387
|
+
return extension !== null && IMAGE_MIME[extension] !== undefined;
|
|
388
|
+
}
|
|
389
|
+
function isKnownTextExtension(extension) {
|
|
390
|
+
return extension !== null && TEXT_EXTENSIONS.has(extension);
|
|
391
|
+
}
|
|
392
|
+
function isLikelyUtf8Text(buffer) {
|
|
393
|
+
if (buffer.length === 0)
|
|
394
|
+
return true;
|
|
395
|
+
if (buffer.includes(0))
|
|
396
|
+
return false;
|
|
397
|
+
const decoded = buffer.toString("utf8");
|
|
398
|
+
if (decoded.includes("\uFFFD"))
|
|
399
|
+
return false;
|
|
400
|
+
let printable = 0;
|
|
401
|
+
for (const char of decoded) {
|
|
402
|
+
const code = char.charCodeAt(0);
|
|
403
|
+
if (code === 9 || code === 10 || code === 13 || code >= 32)
|
|
404
|
+
printable += 1;
|
|
405
|
+
}
|
|
406
|
+
return printable / decoded.length > 0.85;
|
|
407
|
+
}
|
|
408
|
+
async function readPrefix(pathValue, maxBytes) {
|
|
409
|
+
const file = await open(pathValue, "r");
|
|
410
|
+
try {
|
|
411
|
+
const buffer = Buffer.allocUnsafe(maxBytes + 1);
|
|
412
|
+
const result = await file.read(buffer, 0, maxBytes + 1, 0);
|
|
413
|
+
return {
|
|
414
|
+
buffer: buffer.subarray(0, Math.min(result.bytesRead, maxBytes)),
|
|
415
|
+
truncated: result.bytesRead > maxBytes,
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
finally {
|
|
419
|
+
await file.close();
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
function basePreview(target) {
|
|
423
|
+
const name = basename(target.relativePath);
|
|
424
|
+
const extension = extensionOf(name);
|
|
425
|
+
return {
|
|
426
|
+
root: target.root,
|
|
427
|
+
path: target.relativePath,
|
|
428
|
+
name,
|
|
429
|
+
sizeBytes: target.stats.size,
|
|
430
|
+
modifiedAt: target.stats.mtimeMs,
|
|
431
|
+
extension,
|
|
432
|
+
mime: mimeOf(extension),
|
|
433
|
+
symlink: target.symlink,
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
async function imagePreview(target, base) {
|
|
437
|
+
if (target.stats.size > MAX_IMAGE_PREVIEW_BYTES) {
|
|
438
|
+
return { ...base, kind: "binary", reason: "too_large", maxBytes: MAX_IMAGE_PREVIEW_BYTES };
|
|
439
|
+
}
|
|
440
|
+
const buffer = await readFile(target.path);
|
|
441
|
+
return {
|
|
442
|
+
...base,
|
|
443
|
+
kind: "image",
|
|
444
|
+
dataUrl: `data:${base.mime};base64,${buffer.toString("base64")}`,
|
|
445
|
+
maxBytes: MAX_IMAGE_PREVIEW_BYTES,
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
async function textPreview(target, base, redactor) {
|
|
449
|
+
const prefix = await readPrefix(target.path, MAX_TEXT_PREVIEW_BYTES);
|
|
450
|
+
const content = prefix.buffer.toString("utf8");
|
|
451
|
+
const redacted = redactor(content);
|
|
452
|
+
return {
|
|
453
|
+
...base,
|
|
454
|
+
kind: "text",
|
|
455
|
+
content: typeof redacted === "string" ? redacted : content,
|
|
456
|
+
truncated: prefix.truncated,
|
|
457
|
+
maxBytes: MAX_TEXT_PREVIEW_BYTES,
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
export async function readFilesPreview(store, rootInput, pathInput, redactor) {
|
|
461
|
+
const target = await resolveInsideRoot(store, rootInput, pathInput);
|
|
462
|
+
if (!target.stats.isFile()) {
|
|
463
|
+
throw new FilesError(400, "NOT_FILE", "The requested path is not a file.");
|
|
464
|
+
}
|
|
465
|
+
const base = basePreview(target);
|
|
466
|
+
if (isImageExtension(base.extension))
|
|
467
|
+
return imagePreview(target, base);
|
|
468
|
+
const prefix = await readPrefix(target.path, Math.min(target.stats.size, 4096));
|
|
469
|
+
if (isKnownTextExtension(base.extension) || isLikelyUtf8Text(prefix.buffer)) {
|
|
470
|
+
return textPreview(target, base, redactor);
|
|
471
|
+
}
|
|
472
|
+
return { ...base, kind: "binary", reason: "unsupported" };
|
|
473
|
+
}
|
|
474
|
+
export async function handleFilesDirectories(ctx, deps) {
|
|
475
|
+
return runFilesHandler(async () => {
|
|
476
|
+
const requestedRoot = ctx.url.searchParams.get("root");
|
|
477
|
+
const requestedPath = ctx.url.searchParams.get("path") ?? undefined;
|
|
478
|
+
return { status: 200, body: await listFilesDirectories(deps.store, requestedRoot, requestedPath) };
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
export async function handleFilesTree(ctx, deps) {
|
|
482
|
+
return runFilesHandler(async () => ({
|
|
483
|
+
status: 200,
|
|
484
|
+
body: await readFilesTree(deps.store, ctx.url.searchParams.get("root"), ctx.url.searchParams.get("path")),
|
|
485
|
+
}));
|
|
486
|
+
}
|
|
487
|
+
export async function handleFilesPreview(ctx, deps) {
|
|
488
|
+
return runFilesHandler(async () => ({
|
|
489
|
+
status: 200,
|
|
490
|
+
body: await readFilesPreview(deps.store, ctx.url.searchParams.get("root"), ctx.url.searchParams.get("path"), deps.redactor),
|
|
491
|
+
}));
|
|
492
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Security headers (ADR-0011 D5) applied to every BFF response. The CSP is precomputed once from
|
|
2
|
+
// the static export's inline-script hashes (see csp.ts) and reused for all responses.
|
|
3
|
+
// Headers set on every response regardless of route.
|
|
4
|
+
const BASE_SECURITY_HEADERS = {
|
|
5
|
+
"X-Content-Type-Options": "nosniff",
|
|
6
|
+
"X-Frame-Options": "DENY",
|
|
7
|
+
"Referrer-Policy": "no-referrer",
|
|
8
|
+
"Cross-Origin-Opener-Policy": "same-origin",
|
|
9
|
+
"Cross-Origin-Resource-Policy": "same-origin",
|
|
10
|
+
};
|
|
11
|
+
// Applies the CSP and the base security headers, plus `Cache-Control: no-store` for API responses
|
|
12
|
+
// (the contract requires it on every `/api/*` response; D5).
|
|
13
|
+
export function applySecurityHeaders(res, csp, isApiPath) {
|
|
14
|
+
res.setHeader("Content-Security-Policy", csp);
|
|
15
|
+
for (const [name, value] of Object.entries(BASE_SECURITY_HEADERS)) {
|
|
16
|
+
res.setHeader(name, value);
|
|
17
|
+
}
|
|
18
|
+
if (isApiPath) {
|
|
19
|
+
res.setHeader("Cache-Control", "no-store");
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// DNS-rebinding defense (ADR-0011 D5). The BFF binds 127.0.0.1 only and rejects any request whose
|
|
2
|
+
// `Host` header (or `Origin`, when present) does not name the loopback interface on the bound port.
|
|
3
|
+
// A rebinding attacker controls the victim's DNS but cannot forge the loopback host:port the
|
|
4
|
+
// browser sends, so this check blocks cross-origin access to the local server.
|
|
5
|
+
// Hostnames that legitimately resolve to the loopback interface.
|
|
6
|
+
const LOOPBACK_HOSTS = new Set(["127.0.0.1", "localhost", "[::1]", "::1"]);
|
|
7
|
+
// Parses a `host:port` authority into its host (lowercased) and optional port. IPv6 literals are
|
|
8
|
+
// kept in their bracketed form so they compare against LOOPBACK_HOSTS.
|
|
9
|
+
function splitAuthority(authority) {
|
|
10
|
+
const trimmed = authority.trim().toLowerCase();
|
|
11
|
+
if (trimmed.startsWith("[")) {
|
|
12
|
+
const close = trimmed.indexOf("]");
|
|
13
|
+
const host = close === -1 ? trimmed : trimmed.slice(0, close + 1);
|
|
14
|
+
const rest = close === -1 ? "" : trimmed.slice(close + 1);
|
|
15
|
+
const port = rest.startsWith(":") ? rest.slice(1) : undefined;
|
|
16
|
+
return { host, port };
|
|
17
|
+
}
|
|
18
|
+
const colon = trimmed.lastIndexOf(":");
|
|
19
|
+
if (colon === -1) {
|
|
20
|
+
return { host: trimmed, port: undefined };
|
|
21
|
+
}
|
|
22
|
+
return { host: trimmed.slice(0, colon), port: trimmed.slice(colon + 1) };
|
|
23
|
+
}
|
|
24
|
+
function isLoopbackAuthority(authority, expectedPort) {
|
|
25
|
+
const { host, port } = splitAuthority(authority);
|
|
26
|
+
if (!LOOPBACK_HOSTS.has(host)) {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
return port === String(expectedPort);
|
|
30
|
+
}
|
|
31
|
+
function originAuthority(origin) {
|
|
32
|
+
try {
|
|
33
|
+
const parsed = new URL(origin);
|
|
34
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
return parsed.host;
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
// A request is accepted only if its `Host` is a loopback authority on the bound port and, when an
|
|
44
|
+
// `Origin` is present, that origin is also loopback on the bound port. Opaque `Origin: null` is
|
|
45
|
+
// rejected because state-changing API routes are reachable from sandboxed/file origins otherwise.
|
|
46
|
+
// A missing `Host` is rejected.
|
|
47
|
+
export function isAllowedHost(req, expectedPort) {
|
|
48
|
+
const host = req.headers.host;
|
|
49
|
+
if (host === undefined || !isLoopbackAuthority(host, expectedPort)) {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
const origin = req.headers.origin;
|
|
53
|
+
if (origin === undefined) {
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
const authority = originAuthority(origin);
|
|
57
|
+
return authority !== undefined && isLoopbackAuthority(authority, expectedPort);
|
|
58
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export { createUiServer, DEFAULT_UI_PORT, UI_HOST, type UiServerDeps } from "./server.js";
|
|
2
|
+
export { buildCspHeader, extractInlineScriptHashes } from "./csp.js";
|
|
3
|
+
export { loadCspHeader } from "./load-csp.js";
|
|
4
|
+
export { applySecurityHeaders } from "./headers.js";
|
|
5
|
+
export { isAllowedHost } from "./host-check.js";
|
|
6
|
+
export { resolveContainedPath, serveFile } from "./static.js";
|
|
7
|
+
export { API_ROUTES, isApiPath, matchRoute, errorBody, STREAMING, type ApiError, type HandlerOutcome, type RouteContext, type RouteDefinition, type RouteHandler, type RouteMatch, type RouteResult, } from "./routes.js";
|
|
8
|
+
export { buildUiHandlerDeps, buildRedactor, type UiHandlerDeps, type BuildHandlerDepsOptions, type Redactor, type ModelPortFactory, } from "./deps.js";
|
|
9
|
+
export { createRunRegistry, ActiveRunLimitError, type RunRegistry, type RunRecord, type RunStatus, type AppliableSnapshot, } from "./runs.js";
|
|
10
|
+
export { QueueEventSink, type StreamEvent, type SseWriter } from "./sink.js";
|
|
11
|
+
export { parseRunRequest, type RunRequest, type RunKind } from "./run-request.js";
|
|
12
|
+
export { startRun, applyRun, type StartRunResult } from "./run-engine.js";
|
|
13
|
+
export { handleCreateRun, handleRunEvents, handleCancelRun, handleGetRun, handleApplyRun, } from "./run-handlers.js";
|
|
14
|
+
export { persistWorkflowEvidence, persistExplainEvidence, type EvidencePersistContext, type RunIdentity, } from "./evidence.js";
|
|
15
|
+
export { createInMemoryUiStore, createNodeUiStore, isProjectAvailable, resolveUiDbPath, runMigrations, SCHEMA_VERSION, UI_DB_DIRNAME, UI_DB_FILENAME, UiStoreError, validateProjectPath, type Chat, type ChatMessage, type ChatRole, type CreateChatOptions, type NewChatMessage, type Project, type UiStore, type UiStoreErrorCode, type UiStoreFactoryOptions, type UpdateChatPatch, type UpdateProjectPatch, type WorkflowStatus, } from "./store/index.js";
|
|
16
|
+
export { handleListProjects, handleCreateProject, handleUpdateProject, handleDeleteProject, handleListChats, handleCreateChat, handleUpdateChat, handleDeleteChat, handleListMessages, handleCreateMessage, } from "./store-handlers.js";
|
|
17
|
+
export { createTerminalExecutionManager, buildTerminalPolicySummary, listDirectories, type TerminalDirectoryListing, type TerminalExecutionInput, type TerminalExecutionManager, type TerminalExecutionResult, type TerminalEventEmitter, type TerminalEventEnvelope, type TerminalEventKind, type TerminalPolicySummary, } from "./terminal.js";
|
|
18
|
+
export { TerminalToolError, type TerminalErrorCode } from "./terminal-errors.js";
|
|
19
|
+
export { buildTerminalEvidenceEntry, appendTerminalEvidence, type TerminalEvidenceEntry, } from "./terminal-evidence.js";
|
|
20
|
+
export { listFilesDirectories, readFilesPreview, readFilesTree, type FilesDirectoryEntry, type FilesDirectoryListing, type FilesDirectoryRoot, type FilesEntryKind, type FilesPreviewResponse, type FilesTreeEntry, type FilesTreeResponse, } from "./files.js";
|
package/dist/ui/index.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Local UI BFF. The browser tier stays presentation-only: model, filesystem, PTY, and harness
|
|
2
|
+
// authority remain in the loopback Node process behind JSON, SSE, and token-scoped WebSocket seams.
|
|
3
|
+
export { createUiServer, DEFAULT_UI_PORT, UI_HOST } from "./server.js";
|
|
4
|
+
export { buildCspHeader, extractInlineScriptHashes } from "./csp.js";
|
|
5
|
+
export { loadCspHeader } from "./load-csp.js";
|
|
6
|
+
export { applySecurityHeaders } from "./headers.js";
|
|
7
|
+
export { isAllowedHost } from "./host-check.js";
|
|
8
|
+
export { resolveContainedPath, serveFile } from "./static.js";
|
|
9
|
+
export { API_ROUTES, isApiPath, matchRoute, errorBody, STREAMING, } from "./routes.js";
|
|
10
|
+
export { buildUiHandlerDeps, buildRedactor, } from "./deps.js";
|
|
11
|
+
export { createRunRegistry, ActiveRunLimitError, } from "./runs.js";
|
|
12
|
+
export { QueueEventSink } from "./sink.js";
|
|
13
|
+
export { parseRunRequest } from "./run-request.js";
|
|
14
|
+
export { startRun, applyRun } from "./run-engine.js";
|
|
15
|
+
export { handleCreateRun, handleRunEvents, handleCancelRun, handleGetRun, handleApplyRun, } from "./run-handlers.js";
|
|
16
|
+
export { persistWorkflowEvidence, persistExplainEvidence, } from "./evidence.js";
|
|
17
|
+
// ADR-0013 — UI-local SQLite persistence: ports, factories, and route handlers.
|
|
18
|
+
export { createInMemoryUiStore, createNodeUiStore, isProjectAvailable, resolveUiDbPath, runMigrations, SCHEMA_VERSION, UI_DB_DIRNAME, UI_DB_FILENAME, UiStoreError, validateProjectPath, } from "./store/index.js";
|
|
19
|
+
export { handleListProjects, handleCreateProject, handleUpdateProject, handleDeleteProject, handleListChats, handleCreateChat, handleUpdateChat, handleDeleteChat, handleListMessages, handleCreateMessage, } from "./store-handlers.js";
|
|
20
|
+
export { createTerminalExecutionManager, buildTerminalPolicySummary, listDirectories, } from "./terminal.js";
|
|
21
|
+
export { TerminalToolError } from "./terminal-errors.js";
|
|
22
|
+
export { buildTerminalEvidenceEntry, appendTerminalEvidence, } from "./terminal-evidence.js";
|
|
23
|
+
export { listFilesDirectories, readFilesPreview, readFilesTree, } from "./files.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function loadCspHeader(hashesFile: string): Promise<string>;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// Loads the precomputed inline-script CSP hashes emitted by the UI build step (build:ui writes
|
|
2
|
+
// `dist/ui/csp-hashes.json`) and folds them into the policy. When the file is absent or malformed,
|
|
3
|
+
// the policy is built with no hashes — `script-src 'self'` — which fails closed (inline scripts are
|
|
4
|
+
// blocked) rather than weakening the policy with `'unsafe-inline'`.
|
|
5
|
+
import { readFile } from "node:fs/promises";
|
|
6
|
+
import { buildCspHeader } from "./csp.js";
|
|
7
|
+
function parseHashes(raw) {
|
|
8
|
+
const parsed = JSON.parse(raw);
|
|
9
|
+
if (!Array.isArray(parsed)) {
|
|
10
|
+
return [];
|
|
11
|
+
}
|
|
12
|
+
return parsed.filter((entry) => typeof entry === "string");
|
|
13
|
+
}
|
|
14
|
+
export async function loadCspHeader(hashesFile) {
|
|
15
|
+
let raw;
|
|
16
|
+
try {
|
|
17
|
+
raw = await readFile(hashesFile, "utf8");
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return buildCspHeader([]);
|
|
21
|
+
}
|
|
22
|
+
try {
|
|
23
|
+
return buildCspHeader(parseHashes(raw));
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return buildCspHeader([]);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { RouteContext, RouteResult } from "./routes.js";
|
|
2
|
+
import type { UiHandlerDeps } from "./deps.js";
|
|
3
|
+
export declare function handleConfig(_ctx: RouteContext, deps: UiHandlerDeps): RouteResult;
|
|
4
|
+
export declare function handleModels(_ctx: RouteContext, deps: UiHandlerDeps): RouteResult;
|
|
5
|
+
export declare function handleWorkflows(): RouteResult;
|
|
6
|
+
export declare function handleWorkspace(ctx: RouteContext, deps: UiHandlerDeps): RouteResult;
|
|
7
|
+
export declare function handleEvidenceList(ctx: RouteContext, deps: UiHandlerDeps): RouteResult;
|
|
8
|
+
export declare function handleEvidenceDetail(ctx: RouteContext, deps: UiHandlerDeps): RouteResult;
|