@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,72 @@
|
|
|
1
|
+
// Path-traversal-safe static file serving of the exported UI (ADR-0011 D5/D6). The BFF serves only
|
|
2
|
+
// files contained within `dist/ui/static/`. The request path is decoded, normalized, and resolved
|
|
3
|
+
// against the static root; the resolved path must remain inside the root or the request is refused.
|
|
4
|
+
// There is no shell and no user-controlled path beyond the contained root.
|
|
5
|
+
import { createReadStream } from "node:fs";
|
|
6
|
+
import { lstat } from "node:fs/promises";
|
|
7
|
+
import { join, normalize, resolve, sep, extname } from "node:path";
|
|
8
|
+
const CONTENT_TYPES = {
|
|
9
|
+
".html": "text/html; charset=utf-8",
|
|
10
|
+
".js": "text/javascript; charset=utf-8",
|
|
11
|
+
".css": "text/css; charset=utf-8",
|
|
12
|
+
".json": "application/json; charset=utf-8",
|
|
13
|
+
".txt": "text/plain; charset=utf-8",
|
|
14
|
+
".svg": "image/svg+xml",
|
|
15
|
+
".png": "image/png",
|
|
16
|
+
".jpg": "image/jpeg",
|
|
17
|
+
".jpeg": "image/jpeg",
|
|
18
|
+
".gif": "image/gif",
|
|
19
|
+
".webp": "image/webp",
|
|
20
|
+
".ico": "image/x-icon",
|
|
21
|
+
".woff": "font/woff",
|
|
22
|
+
".woff2": "font/woff2",
|
|
23
|
+
".map": "application/json; charset=utf-8",
|
|
24
|
+
};
|
|
25
|
+
function contentTypeFor(filePath) {
|
|
26
|
+
return CONTENT_TYPES[extname(filePath).toLowerCase()] ?? "application/octet-stream";
|
|
27
|
+
}
|
|
28
|
+
// Resolves a URL pathname to an absolute path strictly contained within `root`, or `undefined` when
|
|
29
|
+
// the request escapes the root (traversal) or cannot be decoded. Containment is enforced on the
|
|
30
|
+
// resolved, normalized path: it must equal `root` or start with `root + sep`.
|
|
31
|
+
export function resolveContainedPath(root, pathname) {
|
|
32
|
+
let decoded;
|
|
33
|
+
try {
|
|
34
|
+
decoded = decodeURIComponent(pathname);
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
if (decoded.includes("\0")) {
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
// Containment is enforced below on the resolved path, so traversal segments cannot escape the
|
|
43
|
+
// root regardless of their position; `normalize` only collapses redundant separators here.
|
|
44
|
+
const candidate = resolve(join(root, normalize(decoded)));
|
|
45
|
+
const containedRoot = resolve(root);
|
|
46
|
+
if (candidate !== containedRoot && !candidate.startsWith(containedRoot + sep)) {
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
return candidate;
|
|
50
|
+
}
|
|
51
|
+
// Streams the file at `filePath` with the correct content type. Returns false when the path is not
|
|
52
|
+
// a regular file (caller then falls back to the SPA index or a 404). Uses `lstat` (not `stat`) so a
|
|
53
|
+
// symlink planted in the static root is NOT followed: a regular file is served, a symlink is refused
|
|
54
|
+
// even when it points back inside the root — matching the audit store's never-follow-a-symlink rule
|
|
55
|
+
// (defense in depth; the export pipeline emits only regular files).
|
|
56
|
+
export async function serveFile(res, filePath) {
|
|
57
|
+
let info;
|
|
58
|
+
try {
|
|
59
|
+
info = await lstat(filePath);
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
if (info.isSymbolicLink() || !info.isFile()) {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
res.statusCode = 200;
|
|
68
|
+
res.setHeader("Content-Type", contentTypeFor(filePath));
|
|
69
|
+
res.setHeader("Content-Length", info.size);
|
|
70
|
+
createReadStream(filePath).pipe(res);
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { DatabaseSync } from "node:sqlite";
|
|
2
|
+
import type { Chat, CreateChatOptions, UpdateChatPatch } from "./types.js";
|
|
3
|
+
export declare function listChats(db: DatabaseSync, projectPath: string): readonly Chat[];
|
|
4
|
+
export declare function insertChat(db: DatabaseSync, args: {
|
|
5
|
+
readonly id: string;
|
|
6
|
+
readonly projectPath: string;
|
|
7
|
+
readonly title: string;
|
|
8
|
+
readonly selectedModel: string;
|
|
9
|
+
readonly opts: CreateChatOptions | undefined;
|
|
10
|
+
readonly now: number;
|
|
11
|
+
}): Chat;
|
|
12
|
+
export declare function updateChat(db: DatabaseSync, id: string, patch: UpdateChatPatch, now: number): Chat;
|
|
13
|
+
export declare function deleteChat(db: DatabaseSync, id: string): void;
|
|
14
|
+
export declare function touchChat(db: DatabaseSync, id: string, now: number): void;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
// ADR-0013 — chats CRUD scoped to a project. Parameterized SQL only.
|
|
2
|
+
import { invalidRequest, notFound } from "./errors.js";
|
|
3
|
+
const MAX_SELECTED_MODEL_LEN = 160;
|
|
4
|
+
const SELECTED_MODEL_RE = /^[A-Za-z0-9][A-Za-z0-9._/\- ]*$/;
|
|
5
|
+
const FORBIDDEN_SELECTED_MODEL_TERMS = [
|
|
6
|
+
"apiKey",
|
|
7
|
+
"api_key",
|
|
8
|
+
"baseUrl",
|
|
9
|
+
"base_url",
|
|
10
|
+
"provider",
|
|
11
|
+
"deployment",
|
|
12
|
+
"endpoint",
|
|
13
|
+
"secret",
|
|
14
|
+
"token",
|
|
15
|
+
"credential",
|
|
16
|
+
];
|
|
17
|
+
function rowToChat(row) {
|
|
18
|
+
const status = row.status === null ? undefined : row.status;
|
|
19
|
+
return {
|
|
20
|
+
id: row.id,
|
|
21
|
+
projectPath: row.project_path,
|
|
22
|
+
title: row.title,
|
|
23
|
+
selectedModel: row.selected_model,
|
|
24
|
+
branchLabel: row.branch_label ?? undefined,
|
|
25
|
+
status,
|
|
26
|
+
createdAt: row.created_at,
|
|
27
|
+
updatedAt: row.updated_at,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
const SQL_LIST = "SELECT id, project_path, title, selected_model, branch_label, status, created_at, updated_at FROM chats WHERE project_path = ? ORDER BY created_at ASC";
|
|
31
|
+
const SQL_INSERT = `
|
|
32
|
+
INSERT INTO chats (id, project_path, title, selected_model, branch_label, status, created_at, updated_at)
|
|
33
|
+
VALUES (?, ?, ?, ?, ?, NULL, ?, ?)
|
|
34
|
+
RETURNING id, project_path, title, selected_model, branch_label, status, created_at, updated_at
|
|
35
|
+
`;
|
|
36
|
+
const SQL_UPDATE = `
|
|
37
|
+
UPDATE chats SET
|
|
38
|
+
title = COALESCE(?, title),
|
|
39
|
+
selected_model = COALESCE(?, selected_model),
|
|
40
|
+
branch_label = COALESCE(?, branch_label),
|
|
41
|
+
status = COALESCE(?, status),
|
|
42
|
+
updated_at = ?
|
|
43
|
+
WHERE id = ?
|
|
44
|
+
RETURNING id, project_path, title, selected_model, branch_label, status, created_at, updated_at
|
|
45
|
+
`;
|
|
46
|
+
const SQL_DELETE = "DELETE FROM chats WHERE id = ?";
|
|
47
|
+
const SQL_PROJECT_EXISTS = "SELECT 1 FROM projects WHERE path = ?";
|
|
48
|
+
const SQL_TOUCH = "UPDATE chats SET updated_at = ? WHERE id = ?";
|
|
49
|
+
function validateSelectedModel(value) {
|
|
50
|
+
if (value.length === 0)
|
|
51
|
+
throw invalidRequest("selectedModel is required.");
|
|
52
|
+
if (value.length > MAX_SELECTED_MODEL_LEN || !SELECTED_MODEL_RE.test(value)) {
|
|
53
|
+
throw invalidRequest("selectedModel must be a registry id.");
|
|
54
|
+
}
|
|
55
|
+
const lower = value.toLowerCase();
|
|
56
|
+
if (value.includes("://") ||
|
|
57
|
+
value.trim().startsWith("{") ||
|
|
58
|
+
FORBIDDEN_SELECTED_MODEL_TERMS.some((term) => lower.includes(term.toLowerCase()))) {
|
|
59
|
+
throw invalidRequest("selectedModel must be a registry id.");
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
export function listChats(db, projectPath) {
|
|
63
|
+
return db.prepare(SQL_LIST).all(projectPath).map(rowToChat);
|
|
64
|
+
}
|
|
65
|
+
export function insertChat(db, args) {
|
|
66
|
+
if (args.title.length === 0)
|
|
67
|
+
throw invalidRequest("Title is required.");
|
|
68
|
+
validateSelectedModel(args.selectedModel);
|
|
69
|
+
const projectExists = db.prepare(SQL_PROJECT_EXISTS).get(args.projectPath) !== undefined;
|
|
70
|
+
if (!projectExists)
|
|
71
|
+
throw notFound("Project");
|
|
72
|
+
const branch = args.opts?.branchLabel ?? null;
|
|
73
|
+
const row = db
|
|
74
|
+
.prepare(SQL_INSERT)
|
|
75
|
+
.get(args.id, args.projectPath, args.title, args.selectedModel, branch, args.now, args.now);
|
|
76
|
+
return rowToChat(row);
|
|
77
|
+
}
|
|
78
|
+
const VALID_CHAT_STATUSES = new Set(["open", "closed"]);
|
|
79
|
+
function validateChatPatch(patch) {
|
|
80
|
+
// Runtime defense: handlers may pass widened (unknown) input cast to UpdateChatPatch.
|
|
81
|
+
const raw = patch.status;
|
|
82
|
+
if (raw !== undefined && (typeof raw !== "string" || !VALID_CHAT_STATUSES.has(raw))) {
|
|
83
|
+
throw invalidRequest("Invalid status.");
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
export function updateChat(db, id, patch, now) {
|
|
87
|
+
validateChatPatch(patch);
|
|
88
|
+
if (patch.selectedModel !== undefined)
|
|
89
|
+
validateSelectedModel(patch.selectedModel);
|
|
90
|
+
const titleParam = patch.title ?? null;
|
|
91
|
+
const modelParam = patch.selectedModel ?? null;
|
|
92
|
+
const branchParam = patch.branchLabel ?? null;
|
|
93
|
+
const statusParam = patch.status ?? null;
|
|
94
|
+
const row = db
|
|
95
|
+
.prepare(SQL_UPDATE)
|
|
96
|
+
.get(titleParam, modelParam, branchParam, statusParam, now, id);
|
|
97
|
+
if (row === undefined)
|
|
98
|
+
throw notFound("Chat");
|
|
99
|
+
return rowToChat(row);
|
|
100
|
+
}
|
|
101
|
+
export function deleteChat(db, id) {
|
|
102
|
+
const info = db.prepare(SQL_DELETE).run(id);
|
|
103
|
+
if (info.changes === 0)
|
|
104
|
+
throw notFound("Chat");
|
|
105
|
+
}
|
|
106
|
+
export function touchChat(db, id, now) {
|
|
107
|
+
const info = db.prepare(SQL_TOUCH).run(now, id);
|
|
108
|
+
if (info.changes === 0)
|
|
109
|
+
throw notFound("Chat");
|
|
110
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { UiStore, UiStoreFactoryOptions } from "./types.js";
|
|
2
|
+
export declare function isProjectAvailable(project: {
|
|
3
|
+
readonly path: string;
|
|
4
|
+
}): boolean;
|
|
5
|
+
export declare function createInMemoryUiStore(opts?: UiStoreFactoryOptions): UiStore;
|
|
6
|
+
export declare function createNodeUiStore(dbPath: string, opts?: UiStoreFactoryOptions): UiStore;
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
// ADR-0013 D3/D8/D9 — DB lifecycle, factories, and the public UiStore wiring. The synchronous
|
|
2
|
+
// `node:sqlite` DatabaseSync drives both factories; the node adapter adds directory creation,
|
|
3
|
+
// 0o700/0o600 permission hardening (Unix), and reopen-safe migrations.
|
|
4
|
+
import { DatabaseSync } from "node:sqlite";
|
|
5
|
+
import { chmodSync, existsSync, mkdirSync, renameSync, statSync } from "node:fs";
|
|
6
|
+
import { dirname } from "node:path";
|
|
7
|
+
import { randomUUID } from "node:crypto";
|
|
8
|
+
import { runMigrations } from "./schema.js";
|
|
9
|
+
import { deleteProject as sqlDeleteProject, getProject as sqlGetProject, listProjects as sqlListProjects, updateProject as sqlUpdateProject, upsertProject as sqlUpsertProject, } from "./projects.js";
|
|
10
|
+
import { deleteChat as sqlDeleteChat, insertChat as sqlInsertChat, listChats as sqlListChats, touchChat as sqlTouchChat, updateChat as sqlUpdateChat, } from "./chats.js";
|
|
11
|
+
import { insertMessage as sqlInsertMessage, listMessages as sqlListMessages, updateMessage as sqlUpdateMessage, } from "./messages.js";
|
|
12
|
+
import { validateProjectPath } from "./validation.js";
|
|
13
|
+
import { basename } from "node:path";
|
|
14
|
+
import { invalidRequest } from "./errors.js";
|
|
15
|
+
const DEFAULT_REDACT = (s) => s;
|
|
16
|
+
// Returns whether a project's directory currently exists and is a directory. Derived availability
|
|
17
|
+
// (ADR-0013 D5): the store never deletes a row because the path went missing; the UI surfaces this.
|
|
18
|
+
export function isProjectAvailable(project) {
|
|
19
|
+
try {
|
|
20
|
+
return statSync(project.path).isDirectory();
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function resolveOptions(opts) {
|
|
27
|
+
return {
|
|
28
|
+
now: opts?.now ?? (() => Date.now()),
|
|
29
|
+
newId: opts?.newId ?? randomUUID,
|
|
30
|
+
redactString: opts?.redactString ?? DEFAULT_REDACT,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
function deriveProjectName(explicit, path) {
|
|
34
|
+
if (explicit === undefined)
|
|
35
|
+
return basename(path);
|
|
36
|
+
if (explicit.length === 0)
|
|
37
|
+
throw invalidRequest("Name must not be empty.");
|
|
38
|
+
return explicit;
|
|
39
|
+
}
|
|
40
|
+
function createChatRecord(db, options, projectPath, title, selectedModel, opts) {
|
|
41
|
+
const project = sqlGetProject(db, projectPath);
|
|
42
|
+
if (project !== undefined && !isProjectAvailable(project)) {
|
|
43
|
+
throw invalidRequest("Project path is unavailable.");
|
|
44
|
+
}
|
|
45
|
+
return sqlInsertChat(db, {
|
|
46
|
+
id: options.newId(),
|
|
47
|
+
projectPath,
|
|
48
|
+
title,
|
|
49
|
+
selectedModel,
|
|
50
|
+
opts,
|
|
51
|
+
now: options.now(),
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
function createMessageRecord(db, options, msg) {
|
|
55
|
+
return sqlInsertMessage(db, options.newId(), msg, options.redactString);
|
|
56
|
+
}
|
|
57
|
+
function createProjectRecord(db, options, path, name) {
|
|
58
|
+
const normalized = validateProjectPath(path, { mustExist: true });
|
|
59
|
+
const resolvedName = deriveProjectName(name, normalized);
|
|
60
|
+
return sqlUpsertProject(db, normalized, resolvedName, name !== undefined, options.now());
|
|
61
|
+
}
|
|
62
|
+
function updateProjectRecord(db, options, path, patch) {
|
|
63
|
+
const normalized = validateProjectPath(path, { mustExist: false });
|
|
64
|
+
return sqlUpdateProject(db, normalized, patch, options.now());
|
|
65
|
+
}
|
|
66
|
+
function deleteProjectRecord(db, path) {
|
|
67
|
+
const normalized = validateProjectPath(path, { mustExist: false });
|
|
68
|
+
sqlDeleteProject(db, normalized);
|
|
69
|
+
}
|
|
70
|
+
function createMessageBatch(db, options, messages) {
|
|
71
|
+
if (messages.length === 0) {
|
|
72
|
+
throw invalidRequest("At least one message is required.");
|
|
73
|
+
}
|
|
74
|
+
db.exec("BEGIN");
|
|
75
|
+
try {
|
|
76
|
+
const created = messages.map((msg) => createMessageRecord(db, options, msg));
|
|
77
|
+
for (const chatId of new Set(messages.map((msg) => msg.chatId))) {
|
|
78
|
+
sqlTouchChat(db, chatId, options.now());
|
|
79
|
+
}
|
|
80
|
+
db.exec("COMMIT");
|
|
81
|
+
return created;
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
db.exec("ROLLBACK");
|
|
85
|
+
throw error;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
function buildStore(db, options) {
|
|
89
|
+
return {
|
|
90
|
+
listProjects: () => sqlListProjects(db),
|
|
91
|
+
createProject: (path, name) => createProjectRecord(db, options, path, name),
|
|
92
|
+
updateProject: (path, patch) => updateProjectRecord(db, options, path, patch),
|
|
93
|
+
deleteProject: (path) => {
|
|
94
|
+
deleteProjectRecord(db, path);
|
|
95
|
+
},
|
|
96
|
+
listChats: (projectPath) => sqlListChats(db, projectPath),
|
|
97
|
+
createChat: (projectPath, title, selectedModel, opts) => createChatRecord(db, options, projectPath, title, selectedModel, opts),
|
|
98
|
+
updateChat: (id, patch) => sqlUpdateChat(db, id, patch, options.now()),
|
|
99
|
+
deleteChat: (id) => {
|
|
100
|
+
sqlDeleteChat(db, id);
|
|
101
|
+
},
|
|
102
|
+
listMessages: (chatId) => sqlListMessages(db, chatId),
|
|
103
|
+
createMessage: (msg) => {
|
|
104
|
+
const message = createMessageRecord(db, options, msg);
|
|
105
|
+
sqlTouchChat(db, msg.chatId, options.now());
|
|
106
|
+
return message;
|
|
107
|
+
},
|
|
108
|
+
createMessages: (messages) => createMessageBatch(db, options, messages),
|
|
109
|
+
updateMessage: (id, patch) => sqlUpdateMessage(db, id, patch, options.redactString),
|
|
110
|
+
close: () => {
|
|
111
|
+
db.close();
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
function quarantineCorruptDb(target) {
|
|
116
|
+
const ts = new Date().toISOString().replace(/[:.]/g, "-");
|
|
117
|
+
renameSync(target, `${target}.corrupt.${ts}`);
|
|
118
|
+
for (const sidecar of [`${target}-wal`, `${target}-shm`]) {
|
|
119
|
+
if (existsSync(sidecar)) {
|
|
120
|
+
renameSync(sidecar, `${sidecar}.corrupt.${ts}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
function preparedDatabase(target) {
|
|
125
|
+
const db = new DatabaseSync(target);
|
|
126
|
+
db.exec("PRAGMA foreign_keys = ON");
|
|
127
|
+
return db;
|
|
128
|
+
}
|
|
129
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
130
|
+
// In-memory factory (tests)
|
|
131
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
132
|
+
export function createInMemoryUiStore(opts) {
|
|
133
|
+
const db = preparedDatabase(":memory:");
|
|
134
|
+
runMigrations(db);
|
|
135
|
+
return buildStore(db, resolveOptions(opts));
|
|
136
|
+
}
|
|
137
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
138
|
+
// Node on-disk factory
|
|
139
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
140
|
+
function ensureDirHardened(dir) {
|
|
141
|
+
if (!existsSync(dir)) {
|
|
142
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
143
|
+
}
|
|
144
|
+
if (process.platform !== "win32") {
|
|
145
|
+
try {
|
|
146
|
+
chmodSync(dir, 0o700);
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
// best-effort; leave existing perms if owner change is unavailable
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
function chmodIfPresent(path, mode) {
|
|
154
|
+
if (process.platform === "win32")
|
|
155
|
+
return;
|
|
156
|
+
try {
|
|
157
|
+
chmodSync(path, mode);
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
// file may not exist yet (WAL/-shm sidecars); best-effort
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
export function createNodeUiStore(dbPath, opts) {
|
|
164
|
+
ensureDirHardened(dirname(dbPath));
|
|
165
|
+
let db = preparedDatabase(dbPath);
|
|
166
|
+
try {
|
|
167
|
+
db.exec("PRAGMA journal_mode = WAL");
|
|
168
|
+
runMigrations(db);
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
// Corrupt DB: quarantine (rename to .corrupt.<iso>) and open a fresh one.
|
|
172
|
+
db.close();
|
|
173
|
+
quarantineCorruptDb(dbPath);
|
|
174
|
+
db = preparedDatabase(dbPath);
|
|
175
|
+
db.exec("PRAGMA journal_mode = WAL");
|
|
176
|
+
runMigrations(db);
|
|
177
|
+
}
|
|
178
|
+
chmodIfPresent(dbPath, 0o600);
|
|
179
|
+
chmodIfPresent(`${dbPath}-wal`, 0o600);
|
|
180
|
+
chmodIfPresent(`${dbPath}-shm`, 0o600);
|
|
181
|
+
return buildStore(db, resolveOptions(opts));
|
|
182
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type UiStoreErrorCode = "invalid_path" | "path_not_directory" | "path_not_found" | "project_exists" | "not_found" | "invalid_request" | "internal";
|
|
2
|
+
export declare class UiStoreError extends Error {
|
|
3
|
+
readonly code: UiStoreErrorCode;
|
|
4
|
+
readonly status: number;
|
|
5
|
+
constructor(code: UiStoreErrorCode, message: string, status: number);
|
|
6
|
+
}
|
|
7
|
+
export declare function invalidPath(message: string): UiStoreError;
|
|
8
|
+
export declare function pathNotDirectory(): UiStoreError;
|
|
9
|
+
export declare function pathNotFound(): UiStoreError;
|
|
10
|
+
export declare function notFound(entity: string): UiStoreError;
|
|
11
|
+
export declare function invalidRequest(message: string): UiStoreError;
|
|
12
|
+
export declare function projectExists(): UiStoreError;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// ADR-0013 — Typed errors with stable codes. NEVER include the raw path, SQL text, or system error
|
|
2
|
+
// strings in `.message` — these surface to the BFF error envelope and must be safe to log/expose.
|
|
3
|
+
export class UiStoreError extends Error {
|
|
4
|
+
code;
|
|
5
|
+
status;
|
|
6
|
+
constructor(code, message, status) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.name = "UiStoreError";
|
|
9
|
+
this.code = code;
|
|
10
|
+
this.status = status;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export function invalidPath(message) {
|
|
14
|
+
return new UiStoreError("invalid_path", message, 400);
|
|
15
|
+
}
|
|
16
|
+
export function pathNotDirectory() {
|
|
17
|
+
return new UiStoreError("path_not_directory", "The path is not a directory.", 400);
|
|
18
|
+
}
|
|
19
|
+
export function pathNotFound() {
|
|
20
|
+
return new UiStoreError("path_not_found", "The path does not exist.", 400);
|
|
21
|
+
}
|
|
22
|
+
export function notFound(entity) {
|
|
23
|
+
return new UiStoreError("not_found", `${entity} not found.`, 404);
|
|
24
|
+
}
|
|
25
|
+
export function invalidRequest(message) {
|
|
26
|
+
return new UiStoreError("invalid_request", message, 400);
|
|
27
|
+
}
|
|
28
|
+
export function projectExists() {
|
|
29
|
+
return new UiStoreError("project_exists", "Project already registered.", 409);
|
|
30
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export type { Chat, ChatMessage, ChatRole, CreateChatOptions, NewChatMessage, Project, UiStore, UiStoreFactoryOptions, UpdateChatMessagePatch, UpdateChatPatch, UpdateProjectPatch, WorkflowStatus, } from "./types.js";
|
|
2
|
+
export { UiStoreError, type UiStoreErrorCode, invalidPath, invalidRequest, notFound, pathNotDirectory, pathNotFound, projectExists, } from "./errors.js";
|
|
3
|
+
export { validateProjectPath, type ValidateProjectPathOptions } from "./validation.js";
|
|
4
|
+
export { assertUiDbOutsideProject, resolveUiDbPath, UI_DB_FILENAME, UI_DB_DIRNAME, } from "./paths.js";
|
|
5
|
+
export { runMigrations, SCHEMA_VERSION } from "./schema.js";
|
|
6
|
+
export { createInMemoryUiStore, createNodeUiStore, isProjectAvailable } from "./db.js";
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// ADR-0013 — UI-local SQLite persistence layer. Barrel re-exporting the public seams.
|
|
2
|
+
export { UiStoreError, invalidPath, invalidRequest, notFound, pathNotDirectory, pathNotFound, projectExists, } from "./errors.js";
|
|
3
|
+
export { validateProjectPath } from "./validation.js";
|
|
4
|
+
export { assertUiDbOutsideProject, resolveUiDbPath, UI_DB_FILENAME, UI_DB_DIRNAME, } from "./paths.js";
|
|
5
|
+
export { runMigrations, SCHEMA_VERSION } from "./schema.js";
|
|
6
|
+
export { createInMemoryUiStore, createNodeUiStore, isProjectAvailable } from "./db.js";
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { DatabaseSync } from "node:sqlite";
|
|
2
|
+
import type { ChatMessage, NewChatMessage, UpdateChatMessagePatch } from "./types.js";
|
|
3
|
+
export declare function listMessages(db: DatabaseSync, chatId: string): readonly ChatMessage[];
|
|
4
|
+
export declare function insertMessage(db: DatabaseSync, id: string, msg: NewChatMessage, redactString: (s: string) => string): ChatMessage;
|
|
5
|
+
export declare function updateMessage(db: DatabaseSync, id: string, patch: UpdateChatMessagePatch, redactString: (s: string) => string): ChatMessage;
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
// ADR-0013 — chat_messages CRUD. shortResult is redacted+truncated to ≤ MAX_SHORT_RESULT before persist.
|
|
2
|
+
// Issue #66 adds:
|
|
3
|
+
// - `cancelled` to the accepted workflow status set (parity with src/ui/runs.ts RunStatus).
|
|
4
|
+
// - `task_type` column read/write so non-workflow runs (verify/explain-plan) can be labelled.
|
|
5
|
+
// - updateMessage(): partial PATCH on the row, re-using the existing redact+truncate path.
|
|
6
|
+
import { invalidRequest, notFound } from "./errors.js";
|
|
7
|
+
const MAX_SHORT_RESULT = 200;
|
|
8
|
+
const MAX_TASK_TYPE = 64;
|
|
9
|
+
// Constrained to a-z, digits, and a single inner `-` so the label remains URL-safe and survives
|
|
10
|
+
// a SQL round-trip. Identical to the rule the BFF descriptors use for taskType identifiers.
|
|
11
|
+
const TASK_TYPE_RE = /^[a-z][a-z0-9-]*$/;
|
|
12
|
+
const ROLES = new Set(["user", "assistant", "system"]);
|
|
13
|
+
const STATUSES = new Set([
|
|
14
|
+
"pending",
|
|
15
|
+
"running",
|
|
16
|
+
"completed",
|
|
17
|
+
"failed",
|
|
18
|
+
"cancelled",
|
|
19
|
+
]);
|
|
20
|
+
function rowToMessage(row) {
|
|
21
|
+
return {
|
|
22
|
+
id: row.id,
|
|
23
|
+
chatId: row.chat_id,
|
|
24
|
+
role: row.role,
|
|
25
|
+
content: row.content,
|
|
26
|
+
timestamp: row.timestamp,
|
|
27
|
+
runId: row.run_id ?? undefined,
|
|
28
|
+
workflowId: row.workflow_id ?? undefined,
|
|
29
|
+
workflowStatus: (row.workflow_status ?? undefined),
|
|
30
|
+
shortResult: row.short_result ?? undefined,
|
|
31
|
+
taskType: row.task_type ?? undefined,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
const COLUMNS = "id, chat_id, role, content, timestamp, run_id, workflow_id, workflow_status, short_result, task_type";
|
|
35
|
+
const SQL_LIST = `SELECT ${COLUMNS} FROM chat_messages WHERE chat_id = ? ORDER BY timestamp ASC, id ASC`;
|
|
36
|
+
const SQL_CHAT_EXISTS = "SELECT 1 FROM chats WHERE id = ?";
|
|
37
|
+
const SQL_INSERT = `
|
|
38
|
+
INSERT INTO chat_messages
|
|
39
|
+
(id, chat_id, role, content, timestamp, run_id, workflow_id, workflow_status, short_result, task_type)
|
|
40
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
41
|
+
RETURNING ${COLUMNS}
|
|
42
|
+
`;
|
|
43
|
+
function validateTaskType(value) {
|
|
44
|
+
if (value.length === 0 || value.length > MAX_TASK_TYPE || !TASK_TYPE_RE.test(value)) {
|
|
45
|
+
throw invalidRequest("Invalid taskType.");
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function hasRunSummaryFields(msg) {
|
|
49
|
+
return (msg.runId !== undefined ||
|
|
50
|
+
msg.workflowId !== undefined ||
|
|
51
|
+
msg.workflowStatus !== undefined ||
|
|
52
|
+
msg.shortResult !== undefined ||
|
|
53
|
+
msg.taskType !== undefined);
|
|
54
|
+
}
|
|
55
|
+
function validateRunIdentifiers(msg) {
|
|
56
|
+
if (msg.runId?.length === 0) {
|
|
57
|
+
throw invalidRequest("runId is required for run summaries.");
|
|
58
|
+
}
|
|
59
|
+
if (msg.workflowId?.length === 0) {
|
|
60
|
+
throw invalidRequest("workflowId must be non-empty.");
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
function validateRunSummaryScope(msg) {
|
|
64
|
+
if (hasRunSummaryFields(msg) && (msg.role !== "system" || msg.runId === undefined)) {
|
|
65
|
+
throw invalidRequest("Run summary fields require a system message with runId.");
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function validateMessage(msg) {
|
|
69
|
+
if (!ROLES.has(msg.role))
|
|
70
|
+
throw invalidRequest("Invalid role.");
|
|
71
|
+
if (msg.content.length === 0)
|
|
72
|
+
throw invalidRequest("Content is required.");
|
|
73
|
+
validateRunIdentifiers(msg);
|
|
74
|
+
validateRunSummaryScope(msg);
|
|
75
|
+
if (msg.workflowStatus !== undefined && !STATUSES.has(msg.workflowStatus)) {
|
|
76
|
+
throw invalidRequest("Invalid workflowStatus.");
|
|
77
|
+
}
|
|
78
|
+
if (msg.taskType !== undefined)
|
|
79
|
+
validateTaskType(msg.taskType);
|
|
80
|
+
}
|
|
81
|
+
function processShortResult(raw, redactString) {
|
|
82
|
+
if (raw === undefined)
|
|
83
|
+
return null;
|
|
84
|
+
const redacted = redactString(raw);
|
|
85
|
+
return redacted.length > MAX_SHORT_RESULT ? redacted.slice(0, MAX_SHORT_RESULT) : redacted;
|
|
86
|
+
}
|
|
87
|
+
export function listMessages(db, chatId) {
|
|
88
|
+
return db.prepare(SQL_LIST).all(chatId).map(rowToMessage);
|
|
89
|
+
}
|
|
90
|
+
export function insertMessage(db, id, msg, redactString) {
|
|
91
|
+
validateMessage(msg);
|
|
92
|
+
const chatExists = db.prepare(SQL_CHAT_EXISTS).get(msg.chatId) !== undefined;
|
|
93
|
+
if (!chatExists)
|
|
94
|
+
throw notFound("Chat");
|
|
95
|
+
const shortResult = processShortResult(msg.shortResult, redactString);
|
|
96
|
+
const row = db
|
|
97
|
+
.prepare(SQL_INSERT)
|
|
98
|
+
.get(id, msg.chatId, msg.role, msg.content, msg.timestamp, msg.runId ?? null, msg.workflowId ?? null, msg.workflowStatus ?? null, shortResult, msg.taskType ?? null);
|
|
99
|
+
return rowToMessage(row);
|
|
100
|
+
}
|
|
101
|
+
// Issue #66 — Partial PATCH on a system run-summary message. Builds a dynamic SET clause from the
|
|
102
|
+
// supplied fields so absent fields are not overwritten. shortResult goes through the existing
|
|
103
|
+
// redact+truncate pipeline. workflowStatus and taskType are validated before SQL is built. An
|
|
104
|
+
// empty patch is an invalid_request — the route surface guards this earlier, but the store layer
|
|
105
|
+
// also fails-closed.
|
|
106
|
+
export function updateMessage(db, id, patch, redactString) {
|
|
107
|
+
const sets = [];
|
|
108
|
+
const args = [];
|
|
109
|
+
if (patch.workflowStatus !== undefined) {
|
|
110
|
+
if (!STATUSES.has(patch.workflowStatus))
|
|
111
|
+
throw invalidRequest("Invalid workflowStatus.");
|
|
112
|
+
sets.push("workflow_status = ?");
|
|
113
|
+
args.push(patch.workflowStatus);
|
|
114
|
+
}
|
|
115
|
+
if (patch.shortResult !== undefined) {
|
|
116
|
+
sets.push("short_result = ?");
|
|
117
|
+
args.push(processShortResult(patch.shortResult, redactString));
|
|
118
|
+
}
|
|
119
|
+
if (patch.taskType !== undefined) {
|
|
120
|
+
validateTaskType(patch.taskType);
|
|
121
|
+
sets.push("task_type = ?");
|
|
122
|
+
args.push(patch.taskType);
|
|
123
|
+
}
|
|
124
|
+
if (sets.length === 0) {
|
|
125
|
+
throw invalidRequest("PATCH body must include at least one updatable field.");
|
|
126
|
+
}
|
|
127
|
+
const sql = `
|
|
128
|
+
UPDATE chat_messages
|
|
129
|
+
SET ${sets.join(", ")}
|
|
130
|
+
WHERE id = ? AND role = 'system' AND run_id IS NOT NULL AND length(run_id) > 0
|
|
131
|
+
RETURNING ${COLUMNS}
|
|
132
|
+
`;
|
|
133
|
+
const row = db.prepare(sql).get(...args, id);
|
|
134
|
+
if (row === undefined)
|
|
135
|
+
throw notFound("Message");
|
|
136
|
+
return rowToMessage(row);
|
|
137
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare const UI_DB_FILENAME = "keiko-ui.db";
|
|
2
|
+
export declare const UI_DB_DIRNAME = ".keiko";
|
|
3
|
+
export declare function resolveUiDbPath(explicit: string | undefined, env: Readonly<Record<string, string | undefined>>): string;
|
|
4
|
+
export declare function assertUiDbOutsideProject(uiDbPath: string | undefined, projectPath: string): void;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// ADR-0013 D4 — resolveUiDbPath precedence (mirrors resolveEvidenceDir):
|
|
2
|
+
// explicit option → KEIKO_UI_DATA_DIR/keiko-ui.db → homedir()/.keiko/keiko-ui.db.
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { existsSync, lstatSync } from "node:fs";
|
|
5
|
+
import { dirname, isAbsolute, join, normalize, parse, resolve, sep } from "node:path";
|
|
6
|
+
import { invalidRequest } from "./errors.js";
|
|
7
|
+
export const UI_DB_FILENAME = "keiko-ui.db";
|
|
8
|
+
export const UI_DB_DIRNAME = ".keiko";
|
|
9
|
+
function isInsideCurrentWorkingDirectory(path) {
|
|
10
|
+
const cwd = resolve(process.cwd());
|
|
11
|
+
const resolved = resolve(path);
|
|
12
|
+
return resolved === cwd || resolved.startsWith(`${cwd}${sep}`);
|
|
13
|
+
}
|
|
14
|
+
function hasSymlinkAncestor(path) {
|
|
15
|
+
let current = dirname(path);
|
|
16
|
+
const root = parse(current).root;
|
|
17
|
+
while (current !== root) {
|
|
18
|
+
if (existsSync(current)) {
|
|
19
|
+
return lstatSync(current).isSymbolicLink();
|
|
20
|
+
}
|
|
21
|
+
current = dirname(current);
|
|
22
|
+
}
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
function resolveConfiguredPath(path, label) {
|
|
26
|
+
if (!isAbsolute(path)) {
|
|
27
|
+
throw invalidRequest(`${label} must be absolute.`);
|
|
28
|
+
}
|
|
29
|
+
const resolved = normalize(path);
|
|
30
|
+
if (isInsideCurrentWorkingDirectory(resolved)) {
|
|
31
|
+
throw invalidRequest(`${label} must not be inside the current workspace.`);
|
|
32
|
+
}
|
|
33
|
+
if (existsSync(resolved) && lstatSync(resolved).isSymbolicLink()) {
|
|
34
|
+
throw invalidRequest(`${label} must not be a symlink.`);
|
|
35
|
+
}
|
|
36
|
+
if (hasSymlinkAncestor(resolved)) {
|
|
37
|
+
throw invalidRequest(`${label} must not be inside a symlinked directory.`);
|
|
38
|
+
}
|
|
39
|
+
return resolved;
|
|
40
|
+
}
|
|
41
|
+
function containsPath(parent, child) {
|
|
42
|
+
const resolvedParent = resolve(parent);
|
|
43
|
+
const resolvedChild = resolve(child);
|
|
44
|
+
return resolvedChild === resolvedParent || resolvedChild.startsWith(`${resolvedParent}${sep}`);
|
|
45
|
+
}
|
|
46
|
+
export function resolveUiDbPath(explicit, env) {
|
|
47
|
+
if (explicit !== undefined && explicit.length > 0) {
|
|
48
|
+
return resolveConfiguredPath(explicit, "UI database path");
|
|
49
|
+
}
|
|
50
|
+
const dir = env.KEIKO_UI_DATA_DIR;
|
|
51
|
+
if (dir !== undefined && dir.length > 0) {
|
|
52
|
+
return join(resolveConfiguredPath(dir, "KEIKO_UI_DATA_DIR"), UI_DB_FILENAME);
|
|
53
|
+
}
|
|
54
|
+
return join(homedir(), UI_DB_DIRNAME, UI_DB_FILENAME);
|
|
55
|
+
}
|
|
56
|
+
export function assertUiDbOutsideProject(uiDbPath, projectPath) {
|
|
57
|
+
if (uiDbPath === undefined || uiDbPath.length === 0) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
const resolvedDbPath = resolve(uiDbPath);
|
|
61
|
+
const resolvedDbDir = dirname(resolvedDbPath);
|
|
62
|
+
const resolvedProject = resolve(projectPath);
|
|
63
|
+
if (containsPath(resolvedProject, resolvedDbPath)) {
|
|
64
|
+
throw invalidRequest("UI database path must not be inside a selected project.");
|
|
65
|
+
}
|
|
66
|
+
if (containsPath(resolvedDbDir, resolvedProject)) {
|
|
67
|
+
throw invalidRequest("Selected projects must not be inside the UI database directory.");
|
|
68
|
+
}
|
|
69
|
+
}
|