@wrenai/genbi 0.0.1
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 +201 -0
- package/README.md +141 -0
- package/bin/genbi.mjs +31 -0
- package/dist/assets/echarts-LTFC-ysd.js +40 -0
- package/dist/assets/index-Bo--QMUN.js +441 -0
- package/dist/assets/index-Xs4q7pNo.css +1 -0
- package/dist/index.html +14 -0
- package/dist/wrenai_logo.png +0 -0
- package/dist/wrenai_logo_white.png +0 -0
- package/dist-server/harness/auth/detect.d.ts +18 -0
- package/dist-server/harness/auth/detect.js +37 -0
- package/dist-server/harness/auth/index.d.ts +4 -0
- package/dist-server/harness/auth/index.js +2 -0
- package/dist-server/harness/auth/probe.d.ts +18 -0
- package/dist-server/harness/auth/probe.js +52 -0
- package/dist-server/harness/auth/types.d.ts +42 -0
- package/dist-server/harness/auth/types.js +9 -0
- package/dist-server/harness/bundle/loader.d.ts +32 -0
- package/dist-server/harness/bundle/loader.js +43 -0
- package/dist-server/harness/bundle/schema.d.ts +199 -0
- package/dist-server/harness/bundle/schema.js +127 -0
- package/dist-server/harness/bundle/version.d.ts +10 -0
- package/dist-server/harness/bundle/version.js +43 -0
- package/dist-server/harness/capability/gate.d.ts +6 -0
- package/dist-server/harness/capability/gate.js +29 -0
- package/dist-server/harness/capability/registry.d.ts +6 -0
- package/dist-server/harness/capability/registry.js +17 -0
- package/dist-server/harness/cli-args.d.ts +123 -0
- package/dist-server/harness/cli-args.js +247 -0
- package/dist-server/harness/cli.d.ts +2 -0
- package/dist-server/harness/cli.js +151 -0
- package/dist-server/harness/compile/cache.d.ts +23 -0
- package/dist-server/harness/compile/cache.js +109 -0
- package/dist-server/harness/compile/compose-profile.d.ts +34 -0
- package/dist-server/harness/compile/compose-profile.js +95 -0
- package/dist-server/harness/compile/errors.d.ts +22 -0
- package/dist-server/harness/compile/errors.js +41 -0
- package/dist-server/harness/compile/fingerprint.d.ts +22 -0
- package/dist-server/harness/compile/fingerprint.js +84 -0
- package/dist-server/harness/compile/index.d.ts +8 -0
- package/dist-server/harness/compile/index.js +7 -0
- package/dist-server/harness/compile/pipeline.d.ts +66 -0
- package/dist-server/harness/compile/pipeline.js +176 -0
- package/dist-server/harness/compile/resolve-binary.d.ts +55 -0
- package/dist-server/harness/compile/resolve-binary.js +133 -0
- package/dist-server/harness/compile/types.d.ts +107 -0
- package/dist-server/harness/compile/types.js +1 -0
- package/dist-server/harness/compile/warble-identity.d.ts +12 -0
- package/dist-server/harness/compile/warble-identity.js +33 -0
- package/dist-server/harness/compliance/gate.d.ts +35 -0
- package/dist-server/harness/compliance/gate.js +48 -0
- package/dist-server/harness/compliance/index.d.ts +2 -0
- package/dist-server/harness/compliance/index.js +1 -0
- package/dist-server/harness/compliance/types.d.ts +16 -0
- package/dist-server/harness/compliance/types.js +1 -0
- package/dist-server/harness/events/emitter.d.ts +34 -0
- package/dist-server/harness/events/emitter.js +25 -0
- package/dist-server/harness/events/index.d.ts +4 -0
- package/dist-server/harness/events/index.js +2 -0
- package/dist-server/harness/events/sse.d.ts +20 -0
- package/dist-server/harness/events/sse.js +48 -0
- package/dist-server/harness/events/types.d.ts +131 -0
- package/dist-server/harness/events/types.js +1 -0
- package/dist-server/harness/exec/errors.d.ts +16 -0
- package/dist-server/harness/exec/errors.js +29 -0
- package/dist-server/harness/exec/index.d.ts +5 -0
- package/dist-server/harness/exec/index.js +2 -0
- package/dist-server/harness/exec/local.d.ts +32 -0
- package/dist-server/harness/exec/local.js +134 -0
- package/dist-server/harness/exec/policy.d.ts +12 -0
- package/dist-server/harness/exec/policy.js +1 -0
- package/dist-server/harness/exec/types.d.ts +63 -0
- package/dist-server/harness/exec/types.js +1 -0
- package/dist-server/harness/guardrails/index.d.ts +2 -0
- package/dist-server/harness/guardrails/index.js +1 -0
- package/dist-server/harness/guardrails/policy.d.ts +45 -0
- package/dist-server/harness/guardrails/policy.js +57 -0
- package/dist-server/harness/index.d.ts +37 -0
- package/dist-server/harness/index.js +19 -0
- package/dist-server/harness/loop/errors.d.ts +53 -0
- package/dist-server/harness/loop/errors.js +72 -0
- package/dist-server/harness/loop/executor.d.ts +123 -0
- package/dist-server/harness/loop/executor.js +380 -0
- package/dist-server/harness/loop/guard.d.ts +16 -0
- package/dist-server/harness/loop/guard.js +1 -0
- package/dist-server/harness/loop/index.d.ts +5 -0
- package/dist-server/harness/loop/index.js +3 -0
- package/dist-server/harness/native-producer/index.d.ts +109 -0
- package/dist-server/harness/native-producer/index.js +302 -0
- package/dist-server/harness/npm-package-resolve.d.ts +28 -0
- package/dist-server/harness/npm-package-resolve.js +47 -0
- package/dist-server/harness/providers/adapters/anthropic.d.ts +7 -0
- package/dist-server/harness/providers/adapters/anthropic.js +6 -0
- package/dist-server/harness/providers/adapters/mock.d.ts +4 -0
- package/dist-server/harness/providers/adapters/mock.js +5 -0
- package/dist-server/harness/providers/adapters/openai-compatible.d.ts +10 -0
- package/dist-server/harness/providers/adapters/openai-compatible.js +10 -0
- package/dist-server/harness/providers/binding.d.ts +23 -0
- package/dist-server/harness/providers/binding.js +13 -0
- package/dist-server/harness/providers/errors.d.ts +6 -0
- package/dist-server/harness/providers/errors.js +12 -0
- package/dist-server/harness/providers/index.d.ts +18 -0
- package/dist-server/harness/providers/index.js +22 -0
- package/dist-server/harness/providers/registry.d.ts +16 -0
- package/dist-server/harness/providers/registry.js +24 -0
- package/dist-server/harness/render/envelope.d.ts +145 -0
- package/dist-server/harness/render/envelope.js +428 -0
- package/dist-server/harness/render/errors.d.ts +33 -0
- package/dist-server/harness/render/errors.js +46 -0
- package/dist-server/harness/render/index.d.ts +5 -0
- package/dist-server/harness/render/index.js +3 -0
- package/dist-server/harness/render/validate.d.ts +13 -0
- package/dist-server/harness/render/validate.js +88 -0
- package/dist-server/harness/route/adapter-spec.d.ts +40 -0
- package/dist-server/harness/route/adapter-spec.js +61 -0
- package/dist-server/harness/route/agent-sdk-cli.d.ts +30 -0
- package/dist-server/harness/route/agent-sdk-cli.js +86 -0
- package/dist-server/harness/route/agent-sdk-manifest.d.ts +31 -0
- package/dist-server/harness/route/agent-sdk-manifest.js +56 -0
- package/dist-server/harness/route/artifact-content.d.ts +53 -0
- package/dist-server/harness/route/artifact-content.js +83 -0
- package/dist-server/harness/route/chat-event-mapper.d.ts +95 -0
- package/dist-server/harness/route/chat-event-mapper.js +164 -0
- package/dist-server/harness/route/codex-ask-events.d.ts +25 -0
- package/dist-server/harness/route/codex-ask-events.js +395 -0
- package/dist-server/harness/route/codex-ask.d.ts +15 -0
- package/dist-server/harness/route/codex-ask.js +242 -0
- package/dist-server/harness/route/codex-local-cli.d.ts +20 -0
- package/dist-server/harness/route/codex-local-cli.js +78 -0
- package/dist-server/harness/route/codex-local-manifest.d.ts +22 -0
- package/dist-server/harness/route/codex-local-manifest.js +180 -0
- package/dist-server/harness/route/describe.d.ts +54 -0
- package/dist-server/harness/route/describe.js +84 -0
- package/dist-server/harness/route/dispatched.d.ts +138 -0
- package/dist-server/harness/route/dispatched.js +339 -0
- package/dist-server/harness/route/in-process.d.ts +43 -0
- package/dist-server/harness/route/in-process.js +106 -0
- package/dist-server/harness/route/index.d.ts +19 -0
- package/dist-server/harness/route/index.js +12 -0
- package/dist-server/harness/route/profile-source.d.ts +37 -0
- package/dist-server/harness/route/profile-source.js +76 -0
- package/dist-server/harness/route/route.d.ts +33 -0
- package/dist-server/harness/route/route.js +114 -0
- package/dist-server/harness/route/tier-binding.d.ts +36 -0
- package/dist-server/harness/route/tier-binding.js +63 -0
- package/dist-server/harness/route/types.d.ts +286 -0
- package/dist-server/harness/route/types.js +1 -0
- package/dist-server/harness/session/errors.d.ts +4 -0
- package/dist-server/harness/session/errors.js +7 -0
- package/dist-server/harness/session/gate.d.ts +11 -0
- package/dist-server/harness/session/gate.js +13 -0
- package/dist-server/harness/session/index.d.ts +5 -0
- package/dist-server/harness/session/index.js +3 -0
- package/dist-server/harness/session/run.d.ts +60 -0
- package/dist-server/harness/session/run.js +333 -0
- package/dist-server/harness/session/types.d.ts +29 -0
- package/dist-server/harness/session/types.js +1 -0
- package/dist-server/harness/setup/codex-events.d.ts +12 -0
- package/dist-server/harness/setup/codex-events.js +108 -0
- package/dist-server/harness/setup/runner.d.ts +497 -0
- package/dist-server/harness/setup/runner.js +1314 -0
- package/dist-server/harness/tools/errors.d.ts +107 -0
- package/dist-server/harness/tools/errors.js +141 -0
- package/dist-server/harness/tools/index.d.ts +13 -0
- package/dist-server/harness/tools/index.js +8 -0
- package/dist-server/harness/tools/mcp.d.ts +26 -0
- package/dist-server/harness/tools/mcp.js +15 -0
- package/dist-server/harness/tools/native.d.ts +179 -0
- package/dist-server/harness/tools/native.js +322 -0
- package/dist-server/harness/tools/parse.d.ts +14 -0
- package/dist-server/harness/tools/parse.js +18 -0
- package/dist-server/harness/tools/resolve-wren-binary.d.ts +12 -0
- package/dist-server/harness/tools/resolve-wren-binary.js +21 -0
- package/dist-server/harness/tools/resolve.d.ts +51 -0
- package/dist-server/harness/tools/resolve.js +79 -0
- package/dist-server/harness/tools/setup-native.d.ts +84 -0
- package/dist-server/harness/tools/setup-native.js +353 -0
- package/dist-server/harness/tools/wiring.d.ts +10 -0
- package/dist-server/harness/tools/wiring.js +16 -0
- package/dist-server/providers/setup.provider.yaml +31 -0
- package/dist-server/providers/wren.provider.yaml +33 -0
- package/dist-server/server/adopt.d.ts +87 -0
- package/dist-server/server/adopt.js +263 -0
- package/dist-server/server/app.d.ts +6 -0
- package/dist-server/server/app.js +2521 -0
- package/dist-server/server/auth-choice.d.ts +16 -0
- package/dist-server/server/auth-choice.js +36 -0
- package/dist-server/server/bin.d.ts +2 -0
- package/dist-server/server/bin.js +601 -0
- package/dist-server/server/bootstrap-project-binding.d.ts +31 -0
- package/dist-server/server/bootstrap-project-binding.js +71 -0
- package/dist-server/server/clarify.d.ts +10 -0
- package/dist-server/server/clarify.js +17 -0
- package/dist-server/server/codex-setup-mcp.d.ts +2 -0
- package/dist-server/server/codex-setup-mcp.js +147 -0
- package/dist-server/server/compose.d.ts +89 -0
- package/dist-server/server/compose.js +271 -0
- package/dist-server/server/conn-config.d.ts +30 -0
- package/dist-server/server/conn-config.js +253 -0
- package/dist-server/server/context-files.d.ts +12 -0
- package/dist-server/server/context-files.js +117 -0
- package/dist-server/server/context-map.d.ts +30 -0
- package/dist-server/server/context-map.js +95 -0
- package/dist-server/server/context-source.d.ts +68 -0
- package/dist-server/server/context-source.js +85 -0
- package/dist-server/server/db.d.ts +862 -0
- package/dist-server/server/db.js +1839 -0
- package/dist-server/server/enrichment-runner.d.ts +184 -0
- package/dist-server/server/enrichment-runner.js +213 -0
- package/dist-server/server/enrichment-verify.d.ts +73 -0
- package/dist-server/server/enrichment-verify.js +290 -0
- package/dist-server/server/enrichment.d.ts +232 -0
- package/dist-server/server/enrichment.js +199 -0
- package/dist-server/server/env-detect.d.ts +8 -0
- package/dist-server/server/env-detect.js +17 -0
- package/dist-server/server/env-field-hints.d.ts +46 -0
- package/dist-server/server/env-field-hints.js +48 -0
- package/dist-server/server/fold.d.ts +133 -0
- package/dist-server/server/fold.js +407 -0
- package/dist-server/server/harness-profile-sources.d.ts +3 -0
- package/dist-server/server/harness-profile-sources.js +24 -0
- package/dist-server/server/harness.d.ts +19 -0
- package/dist-server/server/harness.js +439 -0
- package/dist-server/server/impact.d.ts +18 -0
- package/dist-server/server/impact.js +85 -0
- package/dist-server/server/interactive-terminal.d.ts +175 -0
- package/dist-server/server/interactive-terminal.js +336 -0
- package/dist-server/server/native-artifacts.d.ts +1004 -0
- package/dist-server/server/native-artifacts.js +564 -0
- package/dist-server/server/native-dispatch-registry.d.ts +42 -0
- package/dist-server/server/native-dispatch-registry.js +45 -0
- package/dist-server/server/native-producer-fence.d.ts +14 -0
- package/dist-server/server/native-producer-fence.js +48 -0
- package/dist-server/server/native-session-resume.d.ts +6 -0
- package/dist-server/server/native-session-resume.js +103 -0
- package/dist-server/server/native-session-workspace.d.ts +40 -0
- package/dist-server/server/native-session-workspace.js +175 -0
- package/dist-server/server/native-sessions.d.ts +374 -0
- package/dist-server/server/native-sessions.js +1517 -0
- package/dist-server/server/native-wren-runtime.d.ts +20 -0
- package/dist-server/server/native-wren-runtime.js +259 -0
- package/dist-server/server/node-pty-host.d.ts +31 -0
- package/dist-server/server/node-pty-host.js +100 -0
- package/dist-server/server/route-intent.d.ts +40 -0
- package/dist-server/server/route-intent.js +23 -0
- package/dist-server/server/runtime-binding.d.ts +52 -0
- package/dist-server/server/runtime-binding.js +258 -0
- package/dist-server/server/source-catalog.d.ts +84 -0
- package/dist-server/server/source-catalog.js +221 -0
- package/dist-server/server/source-driver.d.ts +35 -0
- package/dist-server/server/source-driver.js +124 -0
- package/dist-server/server/spa.d.ts +2 -0
- package/dist-server/server/spa.js +51 -0
- package/dist-server/server/subscription-model-catalog.d.ts +27 -0
- package/dist-server/server/subscription-model-catalog.js +203 -0
- package/dist-server/server/turn.d.ts +249 -0
- package/dist-server/server/turn.js +1074 -0
- package/dist-server/server/wire-types.d.ts +799 -0
- package/dist-server/server/wire-types.js +1 -0
- package/dist-server/server/wren-home.d.ts +19 -0
- package/dist-server/server/wren-home.js +33 -0
- package/dist-server/server/wren-profiles.d.ts +43 -0
- package/dist-server/server/wren-profiles.js +123 -0
- package/package.json +93 -0
- package/profiles/genbi-default/context/binding.yml +9 -0
- package/profiles/genbi-default/ir.golden.json +506 -0
- package/profiles/genbi-default/profile.yml +16 -0
- package/profiles/genbi-enrich-context/components/apply_enrichment/component.yml +42 -0
- package/profiles/genbi-enrich-context/components/draft_enrichment/component.yml +30 -0
- package/profiles/genbi-enrich-context/components/draft_enrichment/steps/draft.md +34 -0
- package/profiles/genbi-enrich-context/components/inspect_context/component.yml +32 -0
- package/profiles/genbi-enrich-context/components/inspect_context/steps/inspect.md +13 -0
- package/profiles/genbi-enrich-context/context/binding.yml +4 -0
- package/profiles/genbi-enrich-context/ir.golden.json +302 -0
- package/profiles/genbi-enrich-context/profile.yml +15 -0
- package/profiles/genbi-monitor/context/binding.yml +7 -0
- package/profiles/genbi-monitor/ir.golden.json +188 -0
- package/profiles/genbi-monitor/profile.yml +27 -0
- package/profiles/genbi-setup/components/build_context/component.yml +44 -0
- package/profiles/genbi-setup/components/build_context/steps/build.md +14 -0
- package/profiles/genbi-setup/components/connect_source/component.yml +51 -0
- package/profiles/genbi-setup/components/connect_source/steps/connect.md +23 -0
- package/profiles/genbi-setup/context/binding.yml +8 -0
- package/profiles/genbi-setup/ir.golden.json +140 -0
- package/profiles/genbi-setup/profile.yml +18 -0
- package/profiles/genbi-setup/raw/schema.json +5 -0
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
import { existsSync, realpathSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { tool } from "ai";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { SetupCommandDeniedError, SetupExecCwdScopeError, SetupExecutionInputError, SetupWriteScopeError } from "./errors.js";
|
|
6
|
+
/**
|
|
7
|
+
* `setup_execution` (native realization): the ONE native tool
|
|
8
|
+
* `connect_source` and `build_context` both bind to (see
|
|
9
|
+
* `providers/setup.provider.yaml`) — a scope-limited exec + write pair
|
|
10
|
+
* mirroring the `setup_execution` guardrail's semantics on the dispatched
|
|
11
|
+
* (claude-agent-sdk) side of the same onboarding flow, so an api-key/in-process
|
|
12
|
+
* session can drive the same setup components under an equivalent, not
|
|
13
|
+
* merely similarly-named, permission boundary.
|
|
14
|
+
*
|
|
15
|
+
* DESTRUCTIVE/REDIRECTION below are copied VERBATIM from warble's
|
|
16
|
+
* claude-agent-sdk dispatcher (`dispatcher/claude-agent-sdk/src/guardrails.ts`,
|
|
17
|
+
* `GuardConfig.setupScope` branch) rather than reinvented — the brief for
|
|
18
|
+
* this work is explicit that a hand-rolled second copy that drifts from the
|
|
19
|
+
* original is an exploitable gap, not a stylistic choice. All three of
|
|
20
|
+
* DESTRUCTIVE/REDIRECTION/the dotenv-read pair below are checked FIRST and
|
|
21
|
+
* unconditionally, before any other permission logic, exactly as that
|
|
22
|
+
* dispatcher orders its own two, and are never relaxed because the scope is a
|
|
23
|
+
* setup scope.
|
|
24
|
+
*
|
|
25
|
+
* The dotenv-read pair (DOTENV_READER_COMMANDS + DOTENV_PATH) is a
|
|
26
|
+
* genbi-side addition neither DESTRUCTIVE nor REDIRECTION covers: the setup
|
|
27
|
+
* credential design (see `credentialBoundary` in `server/compose.ts` and the
|
|
28
|
+
* onboarding skill) writes an EMPTY `.env` template and never reads it back —
|
|
29
|
+
* but nothing enforced that until this pair was added. Observed live (real
|
|
30
|
+
* `gpt-4.1`, in-process): the agent ran `cat <project>/.env` through this same
|
|
31
|
+
* `exec` action, it succeeded (neither DESTRUCTIVE nor REDIRECTION matches a
|
|
32
|
+
* plain read), and the full stdout — a connection string, or for
|
|
33
|
+
* postgres/snowflake/bigquery, a password/API-key/service-account value — was
|
|
34
|
+
* handed back to the model AND persisted verbatim into the BFF's
|
|
35
|
+
* `turns.trace_json` (see `redactSetupExecutionOutput` below, the persistence-side
|
|
36
|
+
* companion to this pair). A counterpart change belongs in warble's
|
|
37
|
+
* claude-agent-sdk dispatcher (same file/branch as DESTRUCTIVE/REDIRECTION
|
|
38
|
+
* above) so the dispatched side of this same setup scope gets the identical
|
|
39
|
+
* guard — kept in the same shape and same position here so that copy can
|
|
40
|
+
* mirror it byte-for-byte.
|
|
41
|
+
*/
|
|
42
|
+
const DESTRUCTIVE = /\b(rm|sudo|dd|mkfs|shutdown|reboot|kill|chmod|chown|mv|cp)\b/;
|
|
43
|
+
/** Shell output redirection — an artifact/warehouse write escape (same rationale/regex as the dispatcher side). */
|
|
44
|
+
const REDIRECTION = /(^|[^>])>>?[^>]/;
|
|
45
|
+
/**
|
|
46
|
+
* Reader commands that can print a file's contents: `cat`, `head`, `tail`,
|
|
47
|
+
* `less`, `more`, `od`, `xxd`, `strings`, `grep`, `awk`, `sed`. Matched as a
|
|
48
|
+
* whole word (`\b`) so a lookalike substring inside another word — `cat`
|
|
49
|
+
* inside "concatenate", `sed` inside "used", `od` inside "produce" — never
|
|
50
|
+
* matches.
|
|
51
|
+
*/
|
|
52
|
+
const DOTENV_READER_COMMANDS = /\b(cat|head|tail|less|more|od|xxd|strings|grep|awk|sed)\b/;
|
|
53
|
+
/**
|
|
54
|
+
* A `.env`/`.env.<suffix>` path token (`.env`, `project/.env`,
|
|
55
|
+
* `.env.local`, `.env.production`, …), matched precisely: the literal
|
|
56
|
+
* `.env` must be preceded by start-of-string/whitespace/quote/`/`/`=` and
|
|
57
|
+
* followed by end-of-string/whitespace/quote/`/`, with an optional
|
|
58
|
+
* `.<suffix>` in between. This is what keeps a file named `.environment`
|
|
59
|
+
* (no boundary right after `.env` — the next character is `i`, not one of
|
|
60
|
+
* the above) and a bare directory named `env/` (no leading dot at all) from
|
|
61
|
+
* tripping the match, even though both contain the substring "env" — both
|
|
62
|
+
* are exercised in `test/setup-native.test.ts`.
|
|
63
|
+
*/
|
|
64
|
+
const DOTENV_PATH = /(^|[\s"'/=])\.env(\.[\w.-]+)?(?=$|[\s"'/])/;
|
|
65
|
+
// A setup exec may legitimately perform a cold schema discovery or context
|
|
66
|
+
// build, so this is a generous hang guard rather than a performance target.
|
|
67
|
+
// It matches the existing warble compile/dispatch ceiling. In-process previously
|
|
68
|
+
// supplied no timeout at all, allowing one interactive or deadlocked command
|
|
69
|
+
// to leave the setup turn (and its explicit recovery retry) running forever.
|
|
70
|
+
const SETUP_EXEC_TIMEOUT_MS = 2 * 60 * 1000;
|
|
71
|
+
/**
|
|
72
|
+
* Whether `command`'s TEXT references a dotenv-shaped path at all —
|
|
73
|
+
* independent of whether it's paired with a known reader command. Used both
|
|
74
|
+
* by `assertCommandAllowed` (paired with `DOTENV_READER_COMMANDS`, below) and
|
|
75
|
+
* by `redactSetupExecutionOutput` (unpaired — see that function's doc
|
|
76
|
+
* comment for why the persistence-side check is deliberately broader).
|
|
77
|
+
*/
|
|
78
|
+
function commandReferencesDotenvPath(command) {
|
|
79
|
+
return DOTENV_PATH.test(command);
|
|
80
|
+
}
|
|
81
|
+
export const SETUP_EXECUTION_TOOL_NAME = "setup_execution";
|
|
82
|
+
/**
|
|
83
|
+
* A single flat top-level object, NOT a `z.discriminatedUnion` — a
|
|
84
|
+
* discriminated union serializes (via `z.toJSONSchema`) to a schema whose
|
|
85
|
+
* TOP level is `{"oneOf": [...]}` with no top-level `"type": "object"`, which
|
|
86
|
+
* both OpenAI's function-calling API and Anthropic's `input_schema` reject
|
|
87
|
+
* outright (confirmed against a real OpenAI `gpt-4.1` call; see
|
|
88
|
+
* `test/native-tool-schema-shape.test.ts` for the offline, no-API-key-spent
|
|
89
|
+
* regression coverage). `command`/`path`/`content` are therefore all
|
|
90
|
+
* optional here — the per-action required-field combination a discriminated
|
|
91
|
+
* union would enforce at the schema level is instead checked in
|
|
92
|
+
* `createSetupExecutionTool`'s `execute`, before any side effect.
|
|
93
|
+
*
|
|
94
|
+
* Flattening the schema drops the model-facing pairing a discriminated union
|
|
95
|
+
* used to encode for free (`exec` needs `command`; `write` needs `path` +
|
|
96
|
+
* `content`) — nothing about the field TYPES says which action requires
|
|
97
|
+
* which field. Since in-process has no turn budget (unlike dispatched's fixed
|
|
98
|
+
* `--max-turns` cap on setup), an under-described schema makes that pairing
|
|
99
|
+
* discoverable only by the model guessing wrong and reading a thrown
|
|
100
|
+
* `SetupExecutionInputError` back — unbounded trial-and-error on the user's
|
|
101
|
+
* own API spend. The `.describe()` calls below and the tool `description` in
|
|
102
|
+
* `createSetupExecutionTool` spell the pairing out explicitly so the model
|
|
103
|
+
* can satisfy it on the first call; the `execute`-time checks remain as the
|
|
104
|
+
* enforced (not just described) guarantee.
|
|
105
|
+
*/
|
|
106
|
+
export const setupExecutionInputSchema = z.object({
|
|
107
|
+
action: z.enum(["exec", "write"]).describe('Which operation to perform: "exec" runs a shell command; "write" writes a file.'),
|
|
108
|
+
command: z.string().optional().describe('Required when action is "exec": the shell command to run. Not used for "write".'),
|
|
109
|
+
cwd: z
|
|
110
|
+
.string()
|
|
111
|
+
.optional()
|
|
112
|
+
.describe('Optional for both actions: use this directory instead of the workspace root as the command cwd or the base for a relative write path (e.g. the project directory). Absolute or relative to the workspace root, and must resolve within it. Defaults to the workspace root when omitted. For exec, use this instead of "cd <dir> && ..." chaining.'),
|
|
113
|
+
path: z
|
|
114
|
+
.string()
|
|
115
|
+
.optional()
|
|
116
|
+
.describe('Required when action is "write": the file path to write. A relative path is resolved from cwd when supplied, otherwise from the setup workspace root; an absolute path is allowed only within that root. Not used for "exec".'),
|
|
117
|
+
content: z.string().optional().describe('Required when action is "write": the file content to write. Not used for "exec".'),
|
|
118
|
+
});
|
|
119
|
+
/**
|
|
120
|
+
* Rejects a command matching the destructive/redirection/dotenv-read
|
|
121
|
+
* denylist BEFORE it is ever handed to `ExecutionEnv.exec` — a rejection
|
|
122
|
+
* here has run zero subprocess, exactly like the dispatcher-side
|
|
123
|
+
* `canUseTool` handler that denies before invoking the tool at all.
|
|
124
|
+
*
|
|
125
|
+
* The dotenv-read check is checked FIRST (see this module's top doc comment)
|
|
126
|
+
* and requires BOTH `DOTENV_READER_COMMANDS` and `commandReferencesDotenvPath`
|
|
127
|
+
* to match — either alone is over- or under-broad: `DOTENV_READER_COMMANDS`
|
|
128
|
+
* alone would deny an unrelated `cat notes.txt`; `commandReferencesDotenvPath`
|
|
129
|
+
* alone would deny `wren project show .env-example-model` (a path that merely
|
|
130
|
+
* mentions ".env" as a substring of something else, not a read of it) or a
|
|
131
|
+
* legitimate write-scoped `action: "write"` call — though that case can't
|
|
132
|
+
* actually reach here, since `assertCommandAllowed` is only ever called from
|
|
133
|
+
* the `exec` branch, never `write` (see `createSetupExecutionTool` below) —
|
|
134
|
+
* the empty `.env` template write this whole boundary exists to protect stays
|
|
135
|
+
* unaffected by construction, not merely by this check's shape.
|
|
136
|
+
*
|
|
137
|
+
* KNOWN GAPS (a command-text mitigation, not a shell parser — see the exec
|
|
138
|
+
* denylist's overall doc comment): shell indirection that never spells
|
|
139
|
+
* ".env" in the same command string (a variable populated from a
|
|
140
|
+
* base64/hex-decoded literal, or a wrapper script written in a PRIOR call
|
|
141
|
+
* that itself references .env); a reader not in `DOTENV_READER_COMMANDS`
|
|
142
|
+
* (`hexdump`, `wc`, a `python`/`node`/`perl` one-liner, `source .env`/`.
|
|
143
|
+
* .env` sourcing it into the shell's own environment then echoing a
|
|
144
|
+
* variable back); and a recursive/glob read that never names the file
|
|
145
|
+
* literally (`grep -r PASSWORD .`). None of these are caught here.
|
|
146
|
+
*/
|
|
147
|
+
function assertCommandAllowed(command) {
|
|
148
|
+
if (DOTENV_READER_COMMANDS.test(command) && commandReferencesDotenvPath(command)) {
|
|
149
|
+
throw new SetupCommandDeniedError(command, "matches the dotenv-read denylist");
|
|
150
|
+
}
|
|
151
|
+
if (DESTRUCTIVE.test(command)) {
|
|
152
|
+
throw new SetupCommandDeniedError(command, "matches the destructive-command denylist");
|
|
153
|
+
}
|
|
154
|
+
if (REDIRECTION.test(command)) {
|
|
155
|
+
throw new SetupCommandDeniedError(command, "matches the shell-redirection denylist");
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Redacts `stdout`/`stderr` from an exec result whenever `command`'s TEXT
|
|
160
|
+
* references a dotenv path — the persistence-side companion to
|
|
161
|
+
* `assertCommandAllowed`. Applied INSIDE this tool's own `execute`, not
|
|
162
|
+
* downstream in `harness/loop/executor.ts`, so there is exactly one choke
|
|
163
|
+
* point for this tool: the redacted shape is both what the model sees in the
|
|
164
|
+
* tool-loop AND what `executor.ts` later persists verbatim into
|
|
165
|
+
* `turns.trace_json` (it persists whatever this tool returns as
|
|
166
|
+
* `part.output`, unmodified — see `reportToolCallOutcomes`/
|
|
167
|
+
* `summarizeToolOutput`, which only bound length, not content). There is no
|
|
168
|
+
* second "does this look like a dotenv path" check downstream that could
|
|
169
|
+
* drift from this one.
|
|
170
|
+
*
|
|
171
|
+
* Deliberately UNPAIRED with `DOTENV_READER_COMMANDS` (unlike
|
|
172
|
+
* `assertCommandAllowed`'s check): every KNOWN reader is already denied
|
|
173
|
+
* outright before it runs, so a command only ever reaches this function
|
|
174
|
+
* having already executed — either because it doesn't reference a dotenv
|
|
175
|
+
* path at all (no-op), or because it references one via a reader NOT in
|
|
176
|
+
* `DOTENV_READER_COMMANDS` (the exact "KNOWN GAPS" listed on
|
|
177
|
+
* `assertCommandAllowed`: `hexdump`, `wc`, a `python`/`node`/`perl`
|
|
178
|
+
* one-liner, `source .env`/`. .env` shell-sourcing then echoing a variable,
|
|
179
|
+
* `grep -r` without naming the file literally). Redacting on the broader,
|
|
180
|
+
* unpaired check is defense in depth for exactly the readers the paired
|
|
181
|
+
* check in `assertCommandAllowed` can't enumerate.
|
|
182
|
+
*
|
|
183
|
+
* Deliberately NOT a blanket "omit all setup stdout": that would destroy the
|
|
184
|
+
* exact debuggability this defect (and others) were diagnosed with —
|
|
185
|
+
* reading real commands/outputs back out of `turns.trace_json` in SQLite.
|
|
186
|
+
* Scoping the redaction to commands whose text references a dotenv path
|
|
187
|
+
* keeps every other setup command's output intact for that diagnosis while
|
|
188
|
+
* closing the credential leak. `exitCode`/`notFound`/`timedOut`/
|
|
189
|
+
* `maxBufferExceeded` are left untouched too — only the two fields that can
|
|
190
|
+
* actually CONTAIN file contents are ever replaced, so "did the command
|
|
191
|
+
* succeed/timeout/get truncated" stays diagnosable even for a redacted call.
|
|
192
|
+
*
|
|
193
|
+
* KNOWN GAP: this only redacts the current call's OWN stdout/stderr. A
|
|
194
|
+
* command that reads `.env` indirectly and echoes its contents back through
|
|
195
|
+
* some other channel this tool doesn't see (e.g. a value smuggled into a
|
|
196
|
+
* LATER command's argument list, or into a file this tool then reads without
|
|
197
|
+
* the path itself mentioning "env") is not caught here — a mitigation on the
|
|
198
|
+
* common path, not an airtight seal.
|
|
199
|
+
*/
|
|
200
|
+
function redactSetupExecutionOutput(command, result) {
|
|
201
|
+
if (!commandReferencesDotenvPath(command)) {
|
|
202
|
+
return result;
|
|
203
|
+
}
|
|
204
|
+
const REDACTED = "[redacted: command references a dotenv path]";
|
|
205
|
+
return {
|
|
206
|
+
stdout: result.stdout.length > 0 ? REDACTED : result.stdout,
|
|
207
|
+
stderr: result.stderr.length > 0 ? REDACTED : result.stderr,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Resolves `requestedPath` against `root` and rejects anything that escapes
|
|
212
|
+
* it, via `path.relative` — NEVER a bare `startsWith` (a root of `/x/a` must
|
|
213
|
+
* not admit a sibling `/x/a-evil`; `path.relative("/x/a", "/x/a-evil")` is
|
|
214
|
+
* `"../a-evil"`, which the `..`-prefix check below catches, whereas
|
|
215
|
+
* `"/x/a-evil".startsWith("/x/a")` would wrongly admit it). Mirrors
|
|
216
|
+
* `harness/route/artifact-content.ts`'s `escapesRoot` exactly.
|
|
217
|
+
*/
|
|
218
|
+
function escapesRoot(root, target) {
|
|
219
|
+
const relative = path.relative(root, target);
|
|
220
|
+
return relative.startsWith("..") || path.isAbsolute(relative);
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Realpath-resolves the nearest EXISTING ancestor of `target` and re-appends
|
|
224
|
+
* whatever tail components don't exist yet, rather than realpath-ing
|
|
225
|
+
* `target` itself — a write's target commonly doesn't exist yet, and
|
|
226
|
+
* `realpathSync` throws ENOENT on a nonexistent path. Only the existing
|
|
227
|
+
* ancestor chain can possibly BE a symlink, so resolving it (and leaving the
|
|
228
|
+
* not-yet-existing tail literal) is sufficient to catch a symlink escape
|
|
229
|
+
* without requiring the target to already exist. Mirrors
|
|
230
|
+
* `resolveArtifactContent`'s two-phase (nominal, then realpath) containment
|
|
231
|
+
* pattern in `harness/route/artifact-content.ts`, adapted for a write whose
|
|
232
|
+
* target is usually new.
|
|
233
|
+
*/
|
|
234
|
+
function realpathNearestExisting(target) {
|
|
235
|
+
let candidate = target;
|
|
236
|
+
const tail = [];
|
|
237
|
+
while (!existsSync(candidate)) {
|
|
238
|
+
const parent = path.dirname(candidate);
|
|
239
|
+
if (parent === candidate)
|
|
240
|
+
break; // reached the filesystem root without finding anything existing
|
|
241
|
+
tail.unshift(path.basename(candidate));
|
|
242
|
+
candidate = parent;
|
|
243
|
+
}
|
|
244
|
+
const realAncestor = realpathSync(candidate);
|
|
245
|
+
return tail.length > 0 ? path.join(realAncestor, ...tail) : realAncestor;
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Two-phase containment check shared by the `write` action's path scope and
|
|
249
|
+
* the `exec` action's optional `cwd` override (see {@link resolveExecCwd}):
|
|
250
|
+
* (1) a nominal, zero-filesystem-access `path.relative` check against
|
|
251
|
+
* `workspaceRoot`, which rejects an out-of-scope path (including the
|
|
252
|
+
* sibling-prefix case) before touching disk at all; (2) a realpath-based
|
|
253
|
+
* re-check (see {@link realpathNearestExisting}) that catches a symlink
|
|
254
|
+
* escape a nominal check alone would miss. Either phase failing throws
|
|
255
|
+
* whatever error `makeError` constructs, with zero side effects — nothing is
|
|
256
|
+
* written or spawned. Returns the resolved absolute path on success, so a
|
|
257
|
+
* caller that needs it (the exec cwd) doesn't have to resolve it a second
|
|
258
|
+
* time.
|
|
259
|
+
*/
|
|
260
|
+
function assertWithinScope(workspaceRoot, requestedPath, makeError) {
|
|
261
|
+
const nominalTarget = path.isAbsolute(requestedPath) ? path.resolve(requestedPath) : path.resolve(workspaceRoot, requestedPath);
|
|
262
|
+
if (escapesRoot(workspaceRoot, nominalTarget)) {
|
|
263
|
+
throw makeError(requestedPath, workspaceRoot);
|
|
264
|
+
}
|
|
265
|
+
const realRoot = realpathNearestExisting(workspaceRoot);
|
|
266
|
+
const realTarget = realpathNearestExisting(nominalTarget);
|
|
267
|
+
if (escapesRoot(realRoot, realTarget)) {
|
|
268
|
+
throw makeError(requestedPath, workspaceRoot);
|
|
269
|
+
}
|
|
270
|
+
return nominalTarget;
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Resolves a write target from the same optional cwd an exec can use. A
|
|
274
|
+
* relative path is based at cwd (or workspaceRoot when omitted); an absolute
|
|
275
|
+
* path remains absolute. Both cwd itself and the final target pass through
|
|
276
|
+
* the existing nominal + realpath workspace containment check, so adding a
|
|
277
|
+
* project-relative convenience cannot broaden the write scope.
|
|
278
|
+
*/
|
|
279
|
+
function resolveWritePath(workspaceRoot, requestedPath, requestedCwd) {
|
|
280
|
+
const makeError = (requested, scope) => new SetupWriteScopeError(requested, scope);
|
|
281
|
+
const baseDir = requestedCwd === undefined ? workspaceRoot : assertWithinScope(workspaceRoot, requestedCwd, makeError);
|
|
282
|
+
const target = path.isAbsolute(requestedPath) ? requestedPath : path.resolve(baseDir, requestedPath);
|
|
283
|
+
return assertWithinScope(workspaceRoot, target, makeError);
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Resolves the `exec` action's effective subprocess `cwd`: `workspaceRoot`
|
|
287
|
+
* itself when `requestedCwd` is unset (the unchanged default), otherwise
|
|
288
|
+
* `requestedCwd` run through the same two-phase containment check as the
|
|
289
|
+
* write path (see {@link assertWithinScope}), throwing
|
|
290
|
+
* {@link SetupExecCwdScopeError} on escape. A model can therefore narrow the
|
|
291
|
+
* exec cwd to any directory within `workspaceRoot` (e.g. the project
|
|
292
|
+
* directory), but never broaden it beyond that root.
|
|
293
|
+
*/
|
|
294
|
+
function resolveExecCwd(workspaceRoot, requestedCwd) {
|
|
295
|
+
if (requestedCwd === undefined) {
|
|
296
|
+
return workspaceRoot;
|
|
297
|
+
}
|
|
298
|
+
return assertWithinScope(workspaceRoot, requestedCwd, (requestedCwd, scope) => new SetupExecCwdScopeError(requestedCwd, scope));
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Builds the combined `setup_execution` tool: `action: "exec"` runs a shell
|
|
302
|
+
* command via `/bin/sh -c`, cwd defaulting to `workspaceRoot` but narrowable
|
|
303
|
+
* per-call via the optional `cwd` field (see {@link resolveExecCwd} — always
|
|
304
|
+
* contained within `workspaceRoot`, never able to escape it), broadened
|
|
305
|
+
* beyond a single fixed binary the way the dispatcher-side `setup_execution`
|
|
306
|
+
* guardrail broadens Bash — but still subject to the same denylist, checked
|
|
307
|
+
* first, never relaxed. `action: "write"` writes a file relative to the same
|
|
308
|
+
* optional cwd (workspaceRoot by default), scoped to `workspaceRoot` by the
|
|
309
|
+
* two-phase containment check above, then delegates the actual write to
|
|
310
|
+
* `env.writeFile` (which applies its OWN, nominal-only
|
|
311
|
+
* `policy.artifactWriteScope` check — this tool's pre-flight check is
|
|
312
|
+
* additional, not a replacement, since `env.writeFile` has no realpath
|
|
313
|
+
* re-check of its own; see `harness/exec/local.ts`).
|
|
314
|
+
*/
|
|
315
|
+
export async function executeSetupExecution(input, options) {
|
|
316
|
+
const { env, policy, workspaceRoot } = options;
|
|
317
|
+
if (input.action === "exec") {
|
|
318
|
+
if (input.command === undefined) {
|
|
319
|
+
throw new SetupExecutionInputError("exec", "command");
|
|
320
|
+
}
|
|
321
|
+
assertCommandAllowed(input.command);
|
|
322
|
+
const cwd = resolveExecCwd(workspaceRoot, input.cwd);
|
|
323
|
+
const result = await env.exec({ mode: "write", command: "/bin/sh", args: ["-c", input.command], cwd, timeoutMs: SETUP_EXEC_TIMEOUT_MS }, policy);
|
|
324
|
+
const { stdout, stderr } = redactSetupExecutionOutput(input.command, result);
|
|
325
|
+
return {
|
|
326
|
+
exitCode: result.exitCode,
|
|
327
|
+
stdout,
|
|
328
|
+
stderr,
|
|
329
|
+
...(result.notFound !== undefined ? { notFound: result.notFound } : {}),
|
|
330
|
+
...(result.timedOut !== undefined ? { timedOut: result.timedOut } : {}),
|
|
331
|
+
...(result.maxBufferExceeded !== undefined ? { maxBufferExceeded: result.maxBufferExceeded } : {}),
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
if (input.path === undefined)
|
|
335
|
+
throw new SetupExecutionInputError("write", "path");
|
|
336
|
+
if (input.content === undefined)
|
|
337
|
+
throw new SetupExecutionInputError("write", "content");
|
|
338
|
+
const writePath = resolveWritePath(workspaceRoot, input.path, input.cwd);
|
|
339
|
+
await env.writeFile(writePath, input.content, policy);
|
|
340
|
+
return { written: true, path: writePath, bytes: input.content.length };
|
|
341
|
+
}
|
|
342
|
+
export function createSetupExecutionTool(options) {
|
|
343
|
+
return tool({
|
|
344
|
+
description: "Run a shell command or write a file within the setup workspace, subject to a destructive/redirection " +
|
|
345
|
+
'command denylist and a workspace-root scope. Two call shapes: {action: "exec", command, cwd?} runs a ' +
|
|
346
|
+
"shell command (command is required; cwd is optional, defaults to the workspace root, and must resolve " +
|
|
347
|
+
'within it — path/content are unused); {action: "write", path, content, cwd?} writes a file (path and content ' +
|
|
348
|
+
"are both required; a relative path uses cwd when supplied, otherwise the workspace root; command is unused). A call missing the field(s) its action requires, or " +
|
|
349
|
+
"supplying a cwd/path that escapes the workspace root, is rejected before anything runs or is written.",
|
|
350
|
+
inputSchema: setupExecutionInputSchema,
|
|
351
|
+
execute: (input) => executeSetupExecution(input, options),
|
|
352
|
+
});
|
|
353
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ToolSet } from "ai";
|
|
2
|
+
import type { Agent } from "../bundle/schema.js";
|
|
3
|
+
import { type ResolveToolsContext } from "./resolve.js";
|
|
4
|
+
/**
|
|
5
|
+
* Thin wiring showing `resolveTools`'s output feeding a run: resolves
|
|
6
|
+
* `agent.tools[]` into a `ToolSet`, hands it to `fn` (typically a call into
|
|
7
|
+
* `executeAgent` from `../loop/index.js`), and always tears down whatever
|
|
8
|
+
* MCP clients were opened — even if `fn` throws.
|
|
9
|
+
*/
|
|
10
|
+
export declare function withResolvedTools<T>(agent: Agent, ctx: ResolveToolsContext, fn: (tools: ToolSet) => Promise<T>): Promise<T>;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { resolveTools } from "./resolve.js";
|
|
2
|
+
/**
|
|
3
|
+
* Thin wiring showing `resolveTools`'s output feeding a run: resolves
|
|
4
|
+
* `agent.tools[]` into a `ToolSet`, hands it to `fn` (typically a call into
|
|
5
|
+
* `executeAgent` from `../loop/index.js`), and always tears down whatever
|
|
6
|
+
* MCP clients were opened — even if `fn` throws.
|
|
7
|
+
*/
|
|
8
|
+
export async function withResolvedTools(agent, ctx, fn) {
|
|
9
|
+
const resolved = await resolveTools(agent, ctx);
|
|
10
|
+
try {
|
|
11
|
+
return await fn(resolved.tools);
|
|
12
|
+
}
|
|
13
|
+
finally {
|
|
14
|
+
await resolved.close();
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# setup capability-provider fragment (NATIVE realization) — supplies the genbi-setup profile's
|
|
2
|
+
# onboarding capabilities (`source_connect`, `context_build`) via the harness's scope-limited
|
|
3
|
+
# `setup_execution` native tool, for `warble dispatch --provider <this file> --target vercel`
|
|
4
|
+
# against the pre-committed `warble/genbi-setup/ir.golden.json`.
|
|
5
|
+
#
|
|
6
|
+
# Mirrors wren.provider.yaml's shape exactly. Unlike that fragment (which binds each capability to
|
|
7
|
+
# its own dedicated tool), both capabilities here share ONE native tool — `connect_source` and
|
|
8
|
+
# `build_context` are sibling halves of the same guarded-Bash-and-scoped-write lifecycle
|
|
9
|
+
# (`setup_execution`, see the two component.yml files), so a single tool with an
|
|
10
|
+
# exec/write action discriminator covers both rather than duplicating the guardrail plumbing twice.
|
|
11
|
+
fragment_version: "0.1"
|
|
12
|
+
provider: setup
|
|
13
|
+
engine: vercel
|
|
14
|
+
capabilities:
|
|
15
|
+
source_connect:
|
|
16
|
+
outcome: native
|
|
17
|
+
via: setup-execution
|
|
18
|
+
provided_by: runtime
|
|
19
|
+
criticality: required
|
|
20
|
+
context_build:
|
|
21
|
+
outcome: native
|
|
22
|
+
via: setup-execution
|
|
23
|
+
provided_by: runtime
|
|
24
|
+
criticality: required
|
|
25
|
+
tools:
|
|
26
|
+
source_connect:
|
|
27
|
+
name: setup_execution
|
|
28
|
+
source: native
|
|
29
|
+
context_build:
|
|
30
|
+
name: setup_execution
|
|
31
|
+
source: native
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# wren capability-provider fragment (NATIVE realization) — supplies the genbi-default profile's
|
|
2
|
+
# analytical domain capabilities via the harness's native `wren` CLI tools (query, semantic
|
|
3
|
+
# introspection, dashboard build), for `warble dispatch --provider <this file> --target vercel`.
|
|
4
|
+
# Lives outside the warble repo — warble itself stays product-neutral / zero-wren.
|
|
5
|
+
fragment_version: "0.1"
|
|
6
|
+
provider: wren
|
|
7
|
+
engine: vercel
|
|
8
|
+
capabilities:
|
|
9
|
+
sql_execution:read_only:
|
|
10
|
+
outcome: native
|
|
11
|
+
via: wren-cli
|
|
12
|
+
provided_by: runtime
|
|
13
|
+
criticality: required
|
|
14
|
+
semantic_introspection:
|
|
15
|
+
outcome: realize-via
|
|
16
|
+
via: wren-cli
|
|
17
|
+
provided_by: runtime
|
|
18
|
+
criticality: required
|
|
19
|
+
genbi_build:
|
|
20
|
+
outcome: native
|
|
21
|
+
via: wren-cli
|
|
22
|
+
provided_by: runtime
|
|
23
|
+
criticality: required
|
|
24
|
+
tools:
|
|
25
|
+
sql_execution:read_only:
|
|
26
|
+
name: query
|
|
27
|
+
source: native
|
|
28
|
+
semantic_introspection:
|
|
29
|
+
name: semantic_introspect
|
|
30
|
+
source: native
|
|
31
|
+
genbi_build:
|
|
32
|
+
name: build_dashboard
|
|
33
|
+
source: native
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { type ProfileCandidate } from "./wren-profiles.js";
|
|
2
|
+
export type { ProfileCandidate };
|
|
3
|
+
export interface AdoptVerifyOk {
|
|
4
|
+
readonly status: "ok";
|
|
5
|
+
/** Whether `target/mdl.json` already exists — `false` routes the caller to `needs_decision(build_context)` instead of binding immediately. */
|
|
6
|
+
readonly hasMdl: boolean;
|
|
7
|
+
readonly sourceType: string;
|
|
8
|
+
}
|
|
9
|
+
export interface AdoptVerifyNeedsProfile {
|
|
10
|
+
readonly status: "needs_profile";
|
|
11
|
+
readonly sourceType: string;
|
|
12
|
+
/** At least one entry — zero candidates is reported as `AdoptVerifyError` instead. Ranked: project-dir-name match first, then the global `active` profile, then the rest. */
|
|
13
|
+
readonly candidates: readonly ProfileCandidate[];
|
|
14
|
+
}
|
|
15
|
+
export interface AdoptVerifyError {
|
|
16
|
+
readonly status: "error";
|
|
17
|
+
/** Names exactly which check failed — surfaced verbatim as `SetupAdoptResponse.message`. */
|
|
18
|
+
readonly message: string;
|
|
19
|
+
}
|
|
20
|
+
export type AdoptVerifyResult = AdoptVerifyOk | AdoptVerifyNeedsProfile | AdoptVerifyError;
|
|
21
|
+
/**
|
|
22
|
+
* Verifies an existing wren project directory is adoptable: exists, is a
|
|
23
|
+
* directory, has a `wren_project.yml` with a `data_source:` in
|
|
24
|
+
* `supportedSourceTypes`. If it also has a `profile:` pin, that profile's
|
|
25
|
+
* connection must pass `wren context validate`'s live smoke query. If it has
|
|
26
|
+
* no pin, looks for a compatible profile to propose (see the module doc
|
|
27
|
+
* comment's three-state contract). Never crashes the caller: a missing
|
|
28
|
+
* `wren` binary (ENOENT) is reported the same way as any other verification
|
|
29
|
+
* failure, not thrown.
|
|
30
|
+
*/
|
|
31
|
+
export declare function verifyAdoptProject(projectPath: string, options: {
|
|
32
|
+
readonly supportedSourceTypes: ReadonlySet<string>;
|
|
33
|
+
}): Promise<AdoptVerifyResult>;
|
|
34
|
+
/**
|
|
35
|
+
* Writes a durable `profile:` (+ `data_source:`) pin into an adopted
|
|
36
|
+
* project's `wren_project.yml` by shelling out to `wren context set-profile
|
|
37
|
+
* <name>` in the project directory — reuses the CLI's own bind logic rather
|
|
38
|
+
* than hand-writing YAML here, so the pin ends up byte-for-byte what a user
|
|
39
|
+
* running the command themselves would get. Called by the adopt route when
|
|
40
|
+
* the caller resolves a `needs_profile` checkpoint by re-POSTing with a
|
|
41
|
+
* chosen `profile` name, before re-running `verifyAdoptProject` (which will
|
|
42
|
+
* now see the pin and proceed to the live smoke check).
|
|
43
|
+
*/
|
|
44
|
+
export declare function runSetProfile(projectPath: string, profileName: string): Promise<{
|
|
45
|
+
ok: true;
|
|
46
|
+
} | {
|
|
47
|
+
ok: false;
|
|
48
|
+
message: string;
|
|
49
|
+
}>;
|
|
50
|
+
/**
|
|
51
|
+
* Confirms `profileName` is actually one of the candidates `verifyAdoptProject`
|
|
52
|
+
* would have offered for this project — i.e. it exists in `~/.wren/profiles.yml`
|
|
53
|
+
* and its `datasource:` matches the project's `data_source:`. The route must
|
|
54
|
+
* call this before `runSetProfile`: a client-supplied `profile` that skipped
|
|
55
|
+
* this check would let `wren context set-profile` durably rewrite
|
|
56
|
+
* `data_source:` to whatever the chosen profile declares (see
|
|
57
|
+
* `context_cli.py`'s `set-profile`), with no compatibility check of its own.
|
|
58
|
+
* Recomputes the candidate list from disk rather than trusting a list the
|
|
59
|
+
* caller might have seen in an earlier response, so a hand-crafted request
|
|
60
|
+
* naming an incompatible or nonexistent profile is rejected here with zero
|
|
61
|
+
* mutation.
|
|
62
|
+
*/
|
|
63
|
+
export declare function validateChosenProfile(projectPath: string, profileName: string, options: {
|
|
64
|
+
readonly supportedSourceTypes: ReadonlySet<string>;
|
|
65
|
+
}): Promise<{
|
|
66
|
+
ok: true;
|
|
67
|
+
} | {
|
|
68
|
+
ok: false;
|
|
69
|
+
message: string;
|
|
70
|
+
}>;
|
|
71
|
+
/**
|
|
72
|
+
* Applies a client-chosen profile to an adopted project and re-verifies it,
|
|
73
|
+
* rolling back on any failure so the project never ends up durably worse off
|
|
74
|
+
* than before the call: `validateChosenProfile` rejects an incompatible or
|
|
75
|
+
* unknown profile before anything is written; if that passes but the
|
|
76
|
+
* subsequent `wren context validate` (inside `verifyAdoptProject`) still
|
|
77
|
+
* fails — dead host, bad credentials, a moved duckdb file — the pre-call
|
|
78
|
+
* `wren_project.yml` bytes are restored so the project falls back to its
|
|
79
|
+
* previous no-pin state. That matters because `save_project_config` drops
|
|
80
|
+
* YAML comments on rewrite, so the ORIGINAL bytes are restored rather than
|
|
81
|
+
* re-serialized, and because restoring to no-pin means the next adopt call
|
|
82
|
+
* naturally re-offers the candidate list (`needs_profile`) instead of
|
|
83
|
+
* re-validating a permanently broken pin forever.
|
|
84
|
+
*/
|
|
85
|
+
export declare function adoptWithChosenProfile(projectPath: string, profileName: string, options: {
|
|
86
|
+
readonly supportedSourceTypes: ReadonlySet<string>;
|
|
87
|
+
}): Promise<AdoptVerifyResult>;
|