@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,71 @@
|
|
|
1
|
+
import { existsSync, realpathSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { resolveProjectIdentity } from "./enrichment.js";
|
|
4
|
+
function escapes(root, target) {
|
|
5
|
+
const relative = path.relative(root, target);
|
|
6
|
+
return relative.startsWith("..") || path.isAbsolute(relative);
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Recover the in-memory project binding after a BFF restart.
|
|
10
|
+
*
|
|
11
|
+
* `bindProject` already persists an identity-fenced enrichment binding when
|
|
12
|
+
* Setup accepts a project. The process-local `boundProject` pointer used by the
|
|
13
|
+
* remaining Setup routes must be reconstructed from that durable proof, not
|
|
14
|
+
* from the setup form alone: the form is user input and a stale or replaced
|
|
15
|
+
* directory must remain unbound.
|
|
16
|
+
*
|
|
17
|
+
* The wizard's two entry paths leave different durable evidence, so recovery
|
|
18
|
+
* branches on the persisted `setup.mode` rather than applying one set of
|
|
19
|
+
* checks to both:
|
|
20
|
+
*
|
|
21
|
+
* - `"create"` scaffolds INTO the workspace root, and its connect turn writes
|
|
22
|
+
* the `.wren-validated` sentinel only after `wren profile add` genuinely
|
|
23
|
+
* validates the connection. Both facts are load-bearing evidence there, so
|
|
24
|
+
* that path keeps the workspace fence and the sentinel check.
|
|
25
|
+
* - `"adopt"` takes a project the user already has, anywhere on disk. Neither
|
|
26
|
+
* piece of evidence exists for it — containment is false by construction and
|
|
27
|
+
* the sentinel belongs to a flow that never ran. What stands in for them is
|
|
28
|
+
* that `bindProject` is reached only after adopt verification succeeds
|
|
29
|
+
* (`POST /api/setup/adopt`), so a *persisted binding at all* is that flow's
|
|
30
|
+
* equivalent proof.
|
|
31
|
+
*
|
|
32
|
+
* Identity equality is required on both paths and is what actually fences the
|
|
33
|
+
* adopt case: `resolveProjectIdentity` resolves device+inode, so a symlink
|
|
34
|
+
* repointed at another directory — or a different directory moved into the
|
|
35
|
+
* recorded path — no longer matches and stays unbound.
|
|
36
|
+
*/
|
|
37
|
+
export function recoverBootstrapProjectBinding(store, workspaceRoot) {
|
|
38
|
+
const stored = store.getEnrichmentBinding();
|
|
39
|
+
if (!stored)
|
|
40
|
+
return undefined;
|
|
41
|
+
try {
|
|
42
|
+
if (store.getSetupMode() === "adopt") {
|
|
43
|
+
const identity = resolveProjectIdentity(stored.path);
|
|
44
|
+
if (identity.path !== stored.path || identity.identity !== stored.identity)
|
|
45
|
+
return undefined;
|
|
46
|
+
if (!existsSync(path.join(identity.path, "wren_project.yml")))
|
|
47
|
+
return undefined;
|
|
48
|
+
return identity.path;
|
|
49
|
+
}
|
|
50
|
+
const form = store.getSetupConnectForm();
|
|
51
|
+
if (!form)
|
|
52
|
+
return undefined;
|
|
53
|
+
const canonicalWorkspace = realpathSync(workspaceRoot);
|
|
54
|
+
const requested = path.resolve(workspaceRoot, form.projectName);
|
|
55
|
+
if (escapes(path.resolve(workspaceRoot), requested))
|
|
56
|
+
return undefined;
|
|
57
|
+
const identity = resolveProjectIdentity(requested);
|
|
58
|
+
if (escapes(canonicalWorkspace, identity.path))
|
|
59
|
+
return undefined;
|
|
60
|
+
if (identity.path !== stored.path || identity.identity !== stored.identity)
|
|
61
|
+
return undefined;
|
|
62
|
+
if (!existsSync(path.join(identity.path, "wren_project.yml")))
|
|
63
|
+
return undefined;
|
|
64
|
+
if (!existsSync(path.join(identity.path, ".wren-validated")))
|
|
65
|
+
return undefined;
|
|
66
|
+
return identity.path;
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Heuristic clarify pre-flight. No LLM call: a plain regex
|
|
3
|
+
* classifier that flags an ambiguous comparative question with no time
|
|
4
|
+
* qualifier and proposes a small set of time-range chips. Deliberately
|
|
5
|
+
* simple and fixed — do not "improve" the heuristic here.
|
|
6
|
+
*/
|
|
7
|
+
export declare function classifyClarify(question: string): {
|
|
8
|
+
prompt: string;
|
|
9
|
+
chips: string[];
|
|
10
|
+
} | null;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Heuristic clarify pre-flight. No LLM call: a plain regex
|
|
3
|
+
* classifier that flags an ambiguous comparative question with no time
|
|
4
|
+
* qualifier and proposes a small set of time-range chips. Deliberately
|
|
5
|
+
* simple and fixed — do not "improve" the heuristic here.
|
|
6
|
+
*/
|
|
7
|
+
export function classifyClarify(question) {
|
|
8
|
+
const q = question.trim();
|
|
9
|
+
if (!q)
|
|
10
|
+
return null;
|
|
11
|
+
const hasAmbiguousComparative = /\b(which|compare|vs\.?|versus|better)\b/i.test(q);
|
|
12
|
+
const hasTimeQualifier = /\b(today|yesterday|this (week|month|quarter|year)|last \d+|ytd|year to date|\d{4})\b/i.test(q);
|
|
13
|
+
if (hasAmbiguousComparative && !hasTimeQualifier) {
|
|
14
|
+
return { prompt: "Which time range should I use?", chips: ["This month", "This quarter", "Last 12 months"] };
|
|
15
|
+
}
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createInterface } from "node:readline";
|
|
3
|
+
import { open } from "node:fs/promises";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { parseArgs } from "node:util";
|
|
6
|
+
import { createLocalExecutionEnv } from "../harness/exec/index.js";
|
|
7
|
+
import { executeSetupExecution, setupExecutionInputSchema, SETUP_EXECUTION_TOOL_NAME, } from "../harness/tools/index.js";
|
|
8
|
+
const inputSchema = {
|
|
9
|
+
type: "object",
|
|
10
|
+
properties: {
|
|
11
|
+
action: { type: "string", enum: ["exec", "write"] },
|
|
12
|
+
command: { type: "string" },
|
|
13
|
+
cwd: { type: "string" },
|
|
14
|
+
path: { type: "string" },
|
|
15
|
+
content: { type: "string" },
|
|
16
|
+
},
|
|
17
|
+
required: ["action"],
|
|
18
|
+
additionalProperties: false,
|
|
19
|
+
};
|
|
20
|
+
const { values } = parseArgs({
|
|
21
|
+
options: { "workspace-root": { type: "string" }, "trace-path": { type: "string" } },
|
|
22
|
+
});
|
|
23
|
+
const workspaceRoot = values["workspace-root"];
|
|
24
|
+
if (!workspaceRoot || !path.isAbsolute(workspaceRoot)) {
|
|
25
|
+
process.stderr.write("codex setup MCP requires an absolute --workspace-root\n");
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
const resolvedWorkspaceRoot = workspaceRoot;
|
|
29
|
+
const tracePath = values["trace-path"];
|
|
30
|
+
if (tracePath !== undefined && !path.isAbsolute(tracePath)) {
|
|
31
|
+
process.stderr.write("codex setup MCP requires an absolute --trace-path\n");
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
const traceHandle = tracePath ? await open(tracePath, "a") : undefined;
|
|
35
|
+
const env = createLocalExecutionEnv({ rootDir: resolvedWorkspaceRoot });
|
|
36
|
+
const policy = { readOnly: false, artifactWriteScope: "." };
|
|
37
|
+
function send(id, result) {
|
|
38
|
+
process.stdout.write(`${JSON.stringify({ jsonrpc: "2.0", id, result })}\n`);
|
|
39
|
+
}
|
|
40
|
+
function sendError(id, code, message) {
|
|
41
|
+
process.stdout.write(`${JSON.stringify({ jsonrpc: "2.0", id, error: { code, message } })}\n`);
|
|
42
|
+
}
|
|
43
|
+
function boundedDetail(value) {
|
|
44
|
+
const text = JSON.stringify(value) ?? "null";
|
|
45
|
+
return text.length <= 200 ? text : `${text.slice(0, 199)}…`;
|
|
46
|
+
}
|
|
47
|
+
async function appendTrace(input, result) {
|
|
48
|
+
if (!traceHandle)
|
|
49
|
+
return;
|
|
50
|
+
const safeInput = {
|
|
51
|
+
action: input.action,
|
|
52
|
+
...(input.command !== undefined ? { command: input.command } : {}),
|
|
53
|
+
...(input.cwd !== undefined ? { cwd: input.cwd } : {}),
|
|
54
|
+
...(input.path !== undefined ? { path: input.path } : {}),
|
|
55
|
+
};
|
|
56
|
+
await traceHandle.appendFile(`${JSON.stringify({ input: safeInput, detail: boundedDetail(result) })}\n`, "utf8");
|
|
57
|
+
}
|
|
58
|
+
async function handle(request) {
|
|
59
|
+
if (request.id === undefined)
|
|
60
|
+
return;
|
|
61
|
+
switch (request.method) {
|
|
62
|
+
case "initialize":
|
|
63
|
+
send(request.id, {
|
|
64
|
+
protocolVersion: typeof request.params?.["protocolVersion"] === "string"
|
|
65
|
+
? request.params["protocolVersion"]
|
|
66
|
+
: "2025-03-26",
|
|
67
|
+
capabilities: { tools: { listChanged: false } },
|
|
68
|
+
serverInfo: { name: "wren-genbi-setup", version: "0.1.0" },
|
|
69
|
+
});
|
|
70
|
+
return;
|
|
71
|
+
case "ping":
|
|
72
|
+
send(request.id, {});
|
|
73
|
+
return;
|
|
74
|
+
case "tools/list":
|
|
75
|
+
send(request.id, {
|
|
76
|
+
tools: [
|
|
77
|
+
{
|
|
78
|
+
name: SETUP_EXECUTION_TOOL_NAME,
|
|
79
|
+
description: "Run a guarded command or write a file within the setup workspace. exec requires command; write requires path and content; cwd is optional and must stay inside the workspace.",
|
|
80
|
+
inputSchema,
|
|
81
|
+
},
|
|
82
|
+
],
|
|
83
|
+
});
|
|
84
|
+
return;
|
|
85
|
+
case "tools/call": {
|
|
86
|
+
const name = request.params?.["name"];
|
|
87
|
+
if (name !== SETUP_EXECUTION_TOOL_NAME) {
|
|
88
|
+
sendError(request.id, -32602, "unknown setup tool");
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
const parsed = setupExecutionInputSchema.safeParse(request.params?.["arguments"] ?? {});
|
|
92
|
+
if (!parsed.success) {
|
|
93
|
+
send(request.id, {
|
|
94
|
+
isError: true,
|
|
95
|
+
content: [{ type: "text", text: "invalid setup_execution input" }],
|
|
96
|
+
});
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
try {
|
|
100
|
+
const result = await executeSetupExecution(parsed.data, { env, policy, workspaceRoot: resolvedWorkspaceRoot });
|
|
101
|
+
await appendTrace(parsed.data, result);
|
|
102
|
+
send(request.id, { content: [{ type: "text", text: JSON.stringify(result) }] });
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
await appendTrace(parsed.data, { error: "setup tool failed" });
|
|
106
|
+
send(request.id, {
|
|
107
|
+
isError: true,
|
|
108
|
+
content: [{ type: "text", text: error instanceof Error ? error.message : "setup tool failed" }],
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
default:
|
|
114
|
+
sendError(request.id, -32601, "method not found");
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
const lines = createInterface({ input: process.stdin, crlfDelay: Infinity });
|
|
118
|
+
const pendingRequests = new Set();
|
|
119
|
+
let inputClosed = false;
|
|
120
|
+
function closeTraceWhenIdle() {
|
|
121
|
+
if (inputClosed && pendingRequests.size === 0)
|
|
122
|
+
void traceHandle?.close();
|
|
123
|
+
}
|
|
124
|
+
lines.on("line", (line) => {
|
|
125
|
+
if (!line.trim())
|
|
126
|
+
return;
|
|
127
|
+
let request;
|
|
128
|
+
try {
|
|
129
|
+
request = JSON.parse(line);
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
const pending = handle(request).catch((error) => {
|
|
135
|
+
if (request.id !== undefined)
|
|
136
|
+
sendError(request.id, -32603, error instanceof Error ? error.message : "internal error");
|
|
137
|
+
});
|
|
138
|
+
pendingRequests.add(pending);
|
|
139
|
+
void pending.finally(() => {
|
|
140
|
+
pendingRequests.delete(pending);
|
|
141
|
+
closeTraceWhenIdle();
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
lines.on("close", () => {
|
|
145
|
+
inputClosed = true;
|
|
146
|
+
closeTraceWhenIdle();
|
|
147
|
+
});
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import type { ContextLifecyclePrefix } from "../harness/index.js";
|
|
2
|
+
/**
|
|
3
|
+
* Multi-turn context composition. Bounded to the last 5
|
|
4
|
+
* resolved turns in the session (see `Store.listRecentResolvedTurns`), using
|
|
5
|
+
* only the verbatim prior question and the prior answer's *summary* — never
|
|
6
|
+
* the full `RenderEnvelope` — to keep the composed input small and to avoid
|
|
7
|
+
* leaking rich block structure into a plain-text agent prompt.
|
|
8
|
+
*/
|
|
9
|
+
export interface PriorTurnForCompose {
|
|
10
|
+
readonly question: string;
|
|
11
|
+
readonly answerSummary: string | undefined;
|
|
12
|
+
}
|
|
13
|
+
export declare function composeInput(priorTurns: readonly PriorTurnForCompose[], question: string): string;
|
|
14
|
+
/**
|
|
15
|
+
* A follow-up that resolves a pending clarify (see `Store.updateSessionStatus`'s
|
|
16
|
+
* `awaiting_clarify` state) combines the original stored question with the
|
|
17
|
+
* short follow-up text into one effective question, which is then fed into
|
|
18
|
+
* `composeInput` as `question` like any other turn. The caller is
|
|
19
|
+
* responsible for clearing `pending_question` once this is consumed.
|
|
20
|
+
*/
|
|
21
|
+
export declare function composeClarifyFollowUp(pendingQuestion: string, followUp: string): string;
|
|
22
|
+
/** Which turn of the agentic setup/connect flow is being dispatched (see `harness/setup/runner.ts`). */
|
|
23
|
+
export type SetupStepKey = "connect" | "connect_resume" | "context";
|
|
24
|
+
/** Values sourced from the setup wizard's connect form. Never a secret — see `composeSetupPrompt`'s doc comment. */
|
|
25
|
+
export interface SetupFormValues {
|
|
26
|
+
readonly projectName: string;
|
|
27
|
+
readonly sourceType: string;
|
|
28
|
+
readonly workspaceRoot: string;
|
|
29
|
+
}
|
|
30
|
+
export declare function zeroMdlSchemaDiscoveryInstruction(sourceType?: string): string;
|
|
31
|
+
/**
|
|
32
|
+
* Composes the single-line prompt for a setup/connect turn dispatched via
|
|
33
|
+
* `DispatchedSetupRunner` (the `connect_source` warble component). Single-line
|
|
34
|
+
* because dispatched's `chat` subcommand is a line-per-turn stdin protocol —
|
|
35
|
+
* `buildAgentSdkChatArgs` rejects a question containing a newline.
|
|
36
|
+
*
|
|
37
|
+
* Credential boundary (load-bearing): this prompt
|
|
38
|
+
* carries only the source TYPE plus the project name and workspace root —
|
|
39
|
+
* NEVER a credential value. It explicitly instructs the agent to never ask
|
|
40
|
+
* for or print any credential value, and to write only an EMPTY `.env`
|
|
41
|
+
* template for the user to fill out-of-band.
|
|
42
|
+
*
|
|
43
|
+
* Terminal contract: the prompt itself is where the
|
|
44
|
+
* SETUP_STATUS contract is defined — warble's `connect_source` component
|
|
45
|
+
* carries no terminal-status convention of its own. The composed prompt
|
|
46
|
+
* instructs the agent to end its final message with exactly one line of the
|
|
47
|
+
* form `SETUP_STATUS: ok|needs_input|error` followed by a one-line human
|
|
48
|
+
* reason; `parseSetupTerminal` (`harness/setup/runner.ts`) parses the LAST such
|
|
49
|
+
* line out of the turn's finalText.
|
|
50
|
+
*/
|
|
51
|
+
export declare function composeSetupPrompt(stepKey: SetupStepKey, formValues: SetupFormValues, options?: {
|
|
52
|
+
readonly resumeFromDisk?: boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Plan A session resume: the caller is dispatching this turn with
|
|
55
|
+
* `DispatchedOptions.resumeSessionId` set, so the agent-sdk conversation itself
|
|
56
|
+
* (not just the on-disk project state) already carries everything the
|
|
57
|
+
* prior attempt read/listed/fetched. When set, the `context` step composes
|
|
58
|
+
* a short "continue in this same conversation" nudge instead of
|
|
59
|
+
* `resumeFromDisk`'s disk-inventory prompt — there is no re-orientation
|
|
60
|
+
* left to compensate for, so no inventory needs restating. Takes
|
|
61
|
+
* precedence over `resumeFromDisk` if both are set (callers should only
|
|
62
|
+
* ever set one).
|
|
63
|
+
*/
|
|
64
|
+
readonly resumeSession?: boolean;
|
|
65
|
+
/**
|
|
66
|
+
* One bounded recovery after a context turn completed without recognised
|
|
67
|
+
* schema discovery. This takes precedence over the disk-resume wording:
|
|
68
|
+
* the recovery must discover the schema before it can finish, even if a
|
|
69
|
+
* prior turn left partial files behind.
|
|
70
|
+
*/
|
|
71
|
+
readonly schemaDiscoveryRecovery?: boolean;
|
|
72
|
+
/** Host-verified ordered progress from matching earlier context attempts. */
|
|
73
|
+
readonly contextLifecycleRecovery?: ContextLifecyclePrefix;
|
|
74
|
+
/**
|
|
75
|
+
* What the host actually did about this source's Python driver, from
|
|
76
|
+
* `provisionSourceDriver`. Stated verbatim rather than assumed: this prompt
|
|
77
|
+
* used to assert the driver was "already available — there is nothing to
|
|
78
|
+
* install", which is false for most of wren's connectors and is disprovable
|
|
79
|
+
* by the agent in a single command.
|
|
80
|
+
*/
|
|
81
|
+
readonly driverNote?: string;
|
|
82
|
+
/**
|
|
83
|
+
* The exact `.env` variable names the user's chosen connection shape needs.
|
|
84
|
+
* Several sources publish more than one shape — BigQuery dataset vs
|
|
85
|
+
* project, Redshift password vs IAM — and with nothing naming the choice
|
|
86
|
+
* the agent picked one, so the credential form differed between runs.
|
|
87
|
+
*/
|
|
88
|
+
readonly variantFields?: readonly string[];
|
|
89
|
+
}): string;
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import { readdirSync, readFileSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
export function composeInput(priorTurns, question) {
|
|
4
|
+
const bounded = priorTurns.slice(-5);
|
|
5
|
+
const pairs = bounded.map((turn) => `User: ${turn.question}\nAssistant: ${turn.answerSummary ?? "(no answer)"}`);
|
|
6
|
+
return [...pairs, question].join("\n\n");
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* A follow-up that resolves a pending clarify (see `Store.updateSessionStatus`'s
|
|
10
|
+
* `awaiting_clarify` state) combines the original stored question with the
|
|
11
|
+
* short follow-up text into one effective question, which is then fed into
|
|
12
|
+
* `composeInput` as `question` like any other turn. The caller is
|
|
13
|
+
* responsible for clearing `pending_question` once this is consumed.
|
|
14
|
+
*/
|
|
15
|
+
export function composeClarifyFollowUp(pendingQuestion, followUp) {
|
|
16
|
+
return `${pendingQuestion} (${followUp})`;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Canonical schema discovery for a connected project before its first MDL
|
|
20
|
+
* build. Kept shared with the explicit corrective-retry composer so a failed
|
|
21
|
+
* pre-MDL `wren --sql` attempt cannot be prompted to repeat the same command.
|
|
22
|
+
*/
|
|
23
|
+
/** wren's file-backed sources: their `url` is a directory, not a database. */
|
|
24
|
+
const FILE_BACKED_SOURCES = new Set(["duckdb", "local_file", "s3_file", "minio_file", "gcs_file"]);
|
|
25
|
+
export function zeroMdlSchemaDiscoveryInstruction(sourceType) {
|
|
26
|
+
return (`Before the first usable MDL exists, do NOT run "wren --sql" for schema discovery: that command queries through the semantic layer and requires target/mdl.json first. ` +
|
|
27
|
+
`Use the project-pinned Wren profile and connector directly instead, without reading or printing .env or the expanded profile. From the bound project directory run this safe metadata query shape (you may add another metadata query in the same shape if needed): ` +
|
|
28
|
+
`WREN_PYTHON="$(sed -n '1s/^#!//p' "$(command -v wren)")"; "$WREN_PYTHON" -c "from pathlib import Path; from wren.profile import resolve_profile_for_project, expand_profile_secrets; from wren.model.data_source import DataSource; from wren.connector import get_connector; _, p = resolve_profile_for_project(Path.cwd(), strict=True); ds = DataSource(p.pop(\'datasource\')); c = get_connector(ds, ds.get_connection_info(expand_profile_secrets(p))); print(c.query(\\"SELECT table_catalog, table_schema, table_name, column_name, ordinal_position, data_type, is_nullable FROM information_schema.columns WHERE table_schema NOT IN (\'information_schema\', \'pg_catalog\') ORDER BY table_catalog, table_schema, table_name, ordinal_position\\").to_pylist()); c.close()". ` +
|
|
29
|
+
// `information_schema` is not universal: BigQuery exposes it only as
|
|
30
|
+
// `<dataset>.INFORMATION_SCHEMA.COLUMNS`, and Oracle has no
|
|
31
|
+
// `information_schema` at all. Presenting one literal SQL as the shape for
|
|
32
|
+
// every connector would send the agent to run a query that cannot parse,
|
|
33
|
+
// and the recipe used to say "run exactly this".
|
|
34
|
+
`That query shape is written for connectors that expose a flat "information_schema.columns"; the read-only, metadata-only, no-credentials rules are what matter, not the literal SQL. ` +
|
|
35
|
+
`If this connector's catalog is shaped differently, keep the same command scaffold and substitute that source's own metadata catalog rather than forcing this one. ` +
|
|
36
|
+
// Only for the file-backed sources. Sent to every project before this, it
|
|
37
|
+
// put DuckDB advice — and `DUCKDB_URL` — in front of an agent connecting to
|
|
38
|
+
// Postgres.
|
|
39
|
+
(sourceType === undefined || FILE_BACKED_SOURCES.has(sourceType)
|
|
40
|
+
? `For DuckDB/local-file profiles specifically, DUCKDB_URL is a DIRECTORY containing one or more .duckdb files; never pass that directory to duckdb.connect as though it were one database file. The Wren connector performs the required read-only attachment of every .duckdb file in that directory. `
|
|
41
|
+
: ""));
|
|
42
|
+
}
|
|
43
|
+
/** Model subdirectory names already written under `<projectDir>/models` (one per model — see `wren generate-mdl`'s scaffold shape). Empty if `models/` doesn't exist yet, e.g. before the first model is written. */
|
|
44
|
+
function listModelNames(projectDir) {
|
|
45
|
+
try {
|
|
46
|
+
return readdirSync(path.join(projectDir, "models"), { withFileTypes: true })
|
|
47
|
+
.filter((entry) => entry.isDirectory())
|
|
48
|
+
.map((entry) => entry.name)
|
|
49
|
+
.sort();
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return [];
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/** Cube names already written under `<projectDir>/cubes` — either a `<name>/metadata.yml` subdirectory or a flat `<name>.yml` file (both shapes occur in practice). Empty if `cubes/` doesn't exist yet. */
|
|
56
|
+
function listCubeNames(projectDir) {
|
|
57
|
+
try {
|
|
58
|
+
return readdirSync(path.join(projectDir, "cubes"), { withFileTypes: true })
|
|
59
|
+
.map((entry) => (entry.isDirectory() ? entry.name : entry.name.replace(/\.ya?ml$/i, "")))
|
|
60
|
+
.sort();
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return [];
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Counts top-level relationship entries already declared in
|
|
68
|
+
* `<projectDir>/relationships.yml` via a plain `- name:` line scan, rather
|
|
69
|
+
* than pulling in a YAML parser dependency for what is only a progress hint
|
|
70
|
+
* in the resume prompt (never a source of truth the agent is told to trust
|
|
71
|
+
* blindly). A commented-out example block (every line prefixed with `#`, the
|
|
72
|
+
* shape `wren context init` scaffolds) never matches, since the pattern
|
|
73
|
+
* anchors on a bare `-` at the start of the line. Returns `undefined` when
|
|
74
|
+
* the file doesn't exist at all, distinct from `0` for an existing-but-empty
|
|
75
|
+
* list, so the caller can omit the sentence entirely instead of asserting
|
|
76
|
+
* "0 relationships" about a project that hasn't reached that step yet.
|
|
77
|
+
*/
|
|
78
|
+
function countRelationships(projectDir) {
|
|
79
|
+
try {
|
|
80
|
+
const text = readFileSync(path.join(projectDir, "relationships.yml"), "utf-8");
|
|
81
|
+
return (text.match(/^\s*-\s*name:/gm) ?? []).length;
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Builds the "already done" inventory sentence for the resume
|
|
89
|
+
* prompt (`composeSetupPrompt`'s `resumeFromDisk` branch) — read straight off
|
|
90
|
+
* disk at compose time, not from any agent self-report, so a resumed turn can
|
|
91
|
+
* be told exactly what already exists instead of re-discovering it via `ls`
|
|
92
|
+
* and re-reading files.
|
|
93
|
+
*/
|
|
94
|
+
function describeResumeInventory(projectDir) {
|
|
95
|
+
const modelNames = listModelNames(projectDir);
|
|
96
|
+
const cubeNames = listCubeNames(projectDir);
|
|
97
|
+
const relationshipCount = countRelationships(projectDir);
|
|
98
|
+
const parts = [
|
|
99
|
+
modelNames.length > 0
|
|
100
|
+
? `${modelNames.length} model(s) already written under "${projectDir}/models": ${modelNames.join(", ")}.`
|
|
101
|
+
: `no model directories exist yet under "${projectDir}/models".`,
|
|
102
|
+
];
|
|
103
|
+
if (cubeNames.length > 0) {
|
|
104
|
+
parts.push(`${cubeNames.length} cube(s) already defined under "${projectDir}/cubes": ${cubeNames.join(", ")}.`);
|
|
105
|
+
}
|
|
106
|
+
if (relationshipCount !== undefined && relationshipCount > 0) {
|
|
107
|
+
parts.push(`${relationshipCount} relationship(s) already declared in "${projectDir}/relationships.yml".`);
|
|
108
|
+
}
|
|
109
|
+
return parts.join(" ");
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Composes the single-line prompt for a setup/connect turn dispatched via
|
|
113
|
+
* `DispatchedSetupRunner` (the `connect_source` warble component). Single-line
|
|
114
|
+
* because dispatched's `chat` subcommand is a line-per-turn stdin protocol —
|
|
115
|
+
* `buildAgentSdkChatArgs` rejects a question containing a newline.
|
|
116
|
+
*
|
|
117
|
+
* Credential boundary (load-bearing): this prompt
|
|
118
|
+
* carries only the source TYPE plus the project name and workspace root —
|
|
119
|
+
* NEVER a credential value. It explicitly instructs the agent to never ask
|
|
120
|
+
* for or print any credential value, and to write only an EMPTY `.env`
|
|
121
|
+
* template for the user to fill out-of-band.
|
|
122
|
+
*
|
|
123
|
+
* Terminal contract: the prompt itself is where the
|
|
124
|
+
* SETUP_STATUS contract is defined — warble's `connect_source` component
|
|
125
|
+
* carries no terminal-status convention of its own. The composed prompt
|
|
126
|
+
* instructs the agent to end its final message with exactly one line of the
|
|
127
|
+
* form `SETUP_STATUS: ok|needs_input|error` followed by a one-line human
|
|
128
|
+
* reason; `parseSetupTerminal` (`harness/setup/runner.ts`) parses the LAST such
|
|
129
|
+
* line out of the turn's finalText.
|
|
130
|
+
*/
|
|
131
|
+
export function composeSetupPrompt(stepKey, formValues, options) {
|
|
132
|
+
const { projectName, sourceType, workspaceRoot } = formValues;
|
|
133
|
+
const projectDir = path.join(workspaceRoot, projectName);
|
|
134
|
+
const credentialBoundary = "Never ask for, print, or read credential values (passwords, API keys, tokens, connection secrets, etc).";
|
|
135
|
+
const emptyCredentialTemplateBoundary = "Write only an empty .env template with the required variable names and no values; the user fills it in themselves, out-of-band, and you must never read its values back.";
|
|
136
|
+
const terminalContract = "End your final message with exactly one line of the form 'SETUP_STATUS: ok' or 'SETUP_STATUS: needs_input' or 'SETUP_STATUS: error', " +
|
|
137
|
+
"followed on the same line by a short human-readable reason after a dash, e.g. 'SETUP_STATUS: needs_input - waiting for the user to fill in .env'.";
|
|
138
|
+
// Parametrized over the skill name so the `context` step can point the agent at
|
|
139
|
+
// `generate-mdl` instead of `onboarding`, while every step still shares the exact same
|
|
140
|
+
// no-redirection/project-relative rule (the sandbox constraint itself never changes).
|
|
141
|
+
// `skipSkillFetch` is set only by the `context` step's `resumeFromDisk` branch — the prior
|
|
142
|
+
// attempt already fetched this same skill, so re-fetching it here would be exactly the kind
|
|
143
|
+
// of rediscovery this resume path exists to avoid; the sandbox rule itself still applies.
|
|
144
|
+
//
|
|
145
|
+
// Only shell REDIRECTION (no 2>&1, no >, no >>) is actually enforced — on both dispatch
|
|
146
|
+
// paths (in-process's setup-native.ts and dispatched's warble dispatcher guardrail share the same
|
|
147
|
+
// DESTRUCTIVE/REDIRECTION denylist; neither blocks pipes or ||/&& chaining). The prior
|
|
148
|
+
// wording here falsely claimed the sandbox blocks those too, which a real agent disproved by
|
|
149
|
+
// running `cd <projectDir> && wren profile add ...` — the only way that step could succeed,
|
|
150
|
+
// since the exec action's cwd is otherwise fixed to the workspace root and some wren
|
|
151
|
+
// subcommands (`wren profile add`) have no path/project flag of their own. This rule now
|
|
152
|
+
// says only what's true: project steps are rooted at their project either by
|
|
153
|
+
// a turn-level binding or, where the backend exposes one, a per-action cwd.
|
|
154
|
+
const bashDisciplineFor = (skillName, disciplineOptions) => {
|
|
155
|
+
const sandboxRule = "When you run wren CLI commands, invoke each one directly as a single plain command with NO shell redirection (no 2>&1, no >, no >>) — the sandbox blocks that and the call will fail. " +
|
|
156
|
+
"For a project step, execution is rooted at the project directory either by a turn-level binding or, where the backend exposes it, a per-action cwd field. Do not prepend the project name to a project-relative path or use 'cd <dir> && ...' chaining. " +
|
|
157
|
+
"pipes and ||/&& chaining are not specifically blocked, but are unnecessary and should be avoided.";
|
|
158
|
+
return disciplineOptions?.skipSkillFetch
|
|
159
|
+
? sandboxRule
|
|
160
|
+
: `${sandboxRule} Start by fetching the skill with 'wren skills get ${skillName}' (plain, no redirection) and follow it.`;
|
|
161
|
+
};
|
|
162
|
+
if (stepKey === "connect") {
|
|
163
|
+
return (`Follow the wren onboarding skill to scaffold a new wren project named "${projectName}" for a "${sourceType}" data source, ` +
|
|
164
|
+
`under the workspace root "${workspaceRoot}". The wren CLI is already installed and on PATH. ` +
|
|
165
|
+
`${options?.driverNote ?? ""} The host manages the Python environment, so skip the skill's Preflight section entirely and skip Step 2's "pip install" sub-step; ` +
|
|
166
|
+
`do not run pip install and do not spend turns checking the Python environment. Whenever the skill has you run "wren context init", always pass ` +
|
|
167
|
+
`"--path \"${projectDir}\" --empty" explicitly — never run "wren context init" bare, since the exec action's cwd defaults to the workspace root and a ` +
|
|
168
|
+
`bare call would scaffold the project into the workspace root itself instead of "${projectDir}". Go straight to running that "wren context init" command — it creates the project directory and writes its wren_project.yml for you; ` +
|
|
169
|
+
`never hand-write or edit wren_project.yml yourself, before or after running it. Then write an empty .env template ` +
|
|
170
|
+
`for the "${sourceType}" connection's required variables — do not fill in any values. ` +
|
|
171
|
+
(options?.variantFields !== undefined && options.variantFields.length > 0
|
|
172
|
+
? `The user has chosen this connection's shape, so the template must contain exactly these variable names and no others: ${options.variantFields.join(", ")}. `
|
|
173
|
+
: "") +
|
|
174
|
+
`Then stop and wait for the user to fill in the .env file. ` +
|
|
175
|
+
`${bashDisciplineFor("onboarding")} ${credentialBoundary} ${emptyCredentialTemplateBoundary} ${terminalContract}`);
|
|
176
|
+
}
|
|
177
|
+
if (stepKey === "context") {
|
|
178
|
+
// No credential boundary here (unlike connect/connect_resume): this step never touches
|
|
179
|
+
// secret values — the project's .env was already filled in and validated by the prior
|
|
180
|
+
// connect_resume turn, and generate-mdl only discovers schema + writes MDL YAML/JSON.
|
|
181
|
+
//
|
|
182
|
+
// Resume path: after an `error_max_turns` checkpoint, the "continue" decision
|
|
183
|
+
// re-dispatches this same step with `resumeFromDisk: true` so the agent picks up the
|
|
184
|
+
// partially-written models already on disk instead of re-scaffolding from zero.
|
|
185
|
+
//
|
|
186
|
+
// The resumed agent was observed burning its (fresh) turn budget re-orienting
|
|
187
|
+
// from scratch — re-`ls`-ing the project dir, re-reading files, re-fetching skills it
|
|
188
|
+
// already had — instead of picking up where the prior attempt left off. So this branch
|
|
189
|
+
// now reads the project dir at COMPOSE TIME (not the agent, and not the agent's
|
|
190
|
+
// self-report) to hand it a concrete "already done" inventory, tells it the generate-mdl/
|
|
191
|
+
// enrich-context skills were already fetched, and points it straight at finishing rather
|
|
192
|
+
// than discovery. Every downstream instruction (build/validate/measure/stop) is still
|
|
193
|
+
// shared byte-for-byte with the normal path so the existing prompt tests stay valid.
|
|
194
|
+
//
|
|
195
|
+
// Plan A (`resumeSession`): when the caller is resuming the SAME agent-sdk conversation
|
|
196
|
+
// (not just replaying disk state), there is nothing left to compensate for — the agent's
|
|
197
|
+
// own context already has the inventory, so this composes a short continuation nudge
|
|
198
|
+
// instead of restating `describeResumeInventory`'s findings at all.
|
|
199
|
+
const recoveryOpening = (() => {
|
|
200
|
+
switch (options?.contextLifecycleRecovery) {
|
|
201
|
+
case "discovery":
|
|
202
|
+
return `Host verification already established schema discovery for the wren project "${projectName}" at "${projectDir}". Do NOT repeat discovery; the exact remaining ordered operations are: run "wren context validate" successfully, THEN run "wren context build" successfully. `;
|
|
203
|
+
case "validate":
|
|
204
|
+
return `Host verification already established schema discovery and "wren context validate" for the wren project "${projectName}" at "${projectDir}". Do NOT repeat either; the exact remaining operation is: run "wren context build" successfully and produce a nonempty target/mdl.json. `;
|
|
205
|
+
case "build":
|
|
206
|
+
return `Host verification already established schema discovery, "wren context validate", and "wren context build" for the wren project "${projectName}" at "${projectDir}". Do not redo lifecycle commands; inspect the host-required target/mdl.json artifact and report honestly if it is unavailable. `;
|
|
207
|
+
default:
|
|
208
|
+
return undefined;
|
|
209
|
+
}
|
|
210
|
+
})();
|
|
211
|
+
const opening = recoveryOpening ?? (options?.schemaDiscoveryRecovery
|
|
212
|
+
? options.resumeSession
|
|
213
|
+
? `Continue this same conversation with the required schema-discovery correction for the wren project "${projectName}" at "${projectDir}". `
|
|
214
|
+
: `A prior attempt to generate the MDL for the wren project "${projectName}" at "${projectDir}" did not complete recognized schema discovery. `
|
|
215
|
+
: options?.resumeSession
|
|
216
|
+
? `Continue this same conversation exactly where you left off before you ran out of turns on generating the MDL for the wren project "${projectName}" at "${projectDir}" — you already have full context, so do not re-list directories, do not re-read files you've already read, and do not re-fetch any skill you already fetched. Pick up straight from where you stopped and keep finishing the generate-mdl work. `
|
|
217
|
+
: options?.resumeFromDisk
|
|
218
|
+
? `A previous attempt to generate the MDL for the wren project "${projectName}" at "${projectDir}" ran out of turns partway through. ` +
|
|
219
|
+
`Already done, read directly off disk — trust this instead of re-checking it yourself: ${describeResumeInventory(projectDir)} ` +
|
|
220
|
+
`You already fetched the generate-mdl skill (and, if used, the enrich-context skill) in the previous attempt — do NOT run "wren skills get" for them again. ` +
|
|
221
|
+
`Do NOT delete or re-scaffold any existing model, and do NOT run "wren context init" again. ` +
|
|
222
|
+
`Skip re-orientation entirely: do not re-list directories or re-read files just to confirm the inventory above, and do not start over from schema discovery — go straight to finishing. Write only whatever model, measure, cube, or relationship is still genuinely missing (if anything), then build and validate as described below and stop. `
|
|
223
|
+
: `Follow the wren generate-mdl skill to generate the MDL for the wren project "${projectName}" ` +
|
|
224
|
+
`at "${projectDir}". `);
|
|
225
|
+
// A real agent turn was observed running "wren generate-mdl" as if it were a CLI
|
|
226
|
+
// subcommand — it isn't; "generate-mdl" only names a skill DOCUMENT, fetched via
|
|
227
|
+
// "wren skills get generate-mdl". This note is a prompt-wording mitigation only
|
|
228
|
+
// (probabilistic, not a guarantee the model will heed it) — worded so the resume
|
|
229
|
+
// paths, which already fetched the skill, don't repeat the fetch instruction (that
|
|
230
|
+
// would contradict their own "do NOT re-run wren skills get" guidance above).
|
|
231
|
+
const generateMdlNote = options?.resumeFromDisk === true || options?.resumeSession === true || recoveryOpening !== undefined
|
|
232
|
+
? `Reminder: "generate-mdl" is a skill document you already fetched, not a runnable CLI subcommand — there is no "wren generate-mdl" command. `
|
|
233
|
+
: `Note: "generate-mdl" names a skill DOCUMENT, not a runnable CLI subcommand — the only way to obtain it is "wren skills get generate-mdl"; there is no "wren generate-mdl" command, and running it will fail with a CLI usage/error rather than doing anything. `;
|
|
234
|
+
const schemaDiscoveryRecovery = options?.schemaDiscoveryRecovery && recoveryOpening === undefined
|
|
235
|
+
? `Corrective requirement: before writing any model/cube/measure or treating "wren context build" or "wren context validate" as finalization, run a real schema-discovery command from the generate-mdl skill against the connected source and make sure it succeeds. The prior result did not establish that the source is empty; it established only that the workflow skipped successful discovery. If discovery fails, report that command/tool failure honestly and stop. `
|
|
236
|
+
: "";
|
|
237
|
+
const zeroMdlDiscovery = recoveryOpening === undefined ? zeroMdlSchemaDiscoveryInstruction(sourceType) : "";
|
|
238
|
+
return (opening +
|
|
239
|
+
generateMdlNote +
|
|
240
|
+
schemaDiscoveryRecovery +
|
|
241
|
+
zeroMdlDiscovery +
|
|
242
|
+
`This project step is rooted at "${projectDir}" by its turn-level binding or, where exposed, a per-action cwd. Run every wren CLI command in this step — schema-introspection commands, "wren context build", and "wren context validate" — relative to that project directory. The wren CLI's .env auto-discovery only checks the process's current working directory and completely ignores --path, so a command run from the wrong directory will report this project's connection variables as unset even though "${projectDir}/.env" is filled in correctly — that is NOT a real connection failure. ` +
|
|
243
|
+
(options?.contextLifecycleRecovery === "discovery"
|
|
244
|
+
? `The remaining ordered operations are mandatory: run "wren context validate" successfully, THEN run "wren context build" successfully (the build compiles the model YAML into target/mdl.json, which must contain at least one model). `
|
|
245
|
+
: options?.contextLifecycleRecovery === "validate"
|
|
246
|
+
? `The remaining ordered operation is mandatory: run "wren context build" successfully and produce target/mdl.json with at least one model. `
|
|
247
|
+
: options?.contextLifecycleRecovery === "build"
|
|
248
|
+
? `Do not run discovery, validate, or build again unless the host artifact check below shows the already-built target/mdl.json is unavailable; report that failure honestly instead. `
|
|
249
|
+
: `After successful schema discovery and schema-derived model generation, you MUST run "wren context validate" successfully, THEN run "wren context build" successfully (the build compiles the model YAML into target/mdl.json, which must contain at least one model). `) +
|
|
250
|
+
`The project is already scaffolded and connected: do NOT run "wren context init" (wren_project.yml already exists), and do NOT attempt to delete any file or directory (deletion is blocked by the sandbox). ` +
|
|
251
|
+
`If a placeholder model such as "example" already exists on disk from a prior scaffold, leave it in place rather than trying to delete it. ` +
|
|
252
|
+
`Never hand-write a placeholder, seed, sample, or otherwise invented model, cube, or metadata file just to make "wren context build" succeed — every model, column, and measure in the MDL must come from something you actually introspected from the connected warehouse, never from imagination. If schema introspection yields no tables, or a connection variable still reports as unset despite running from the project directory, STOP: do not write any model/cube/metadata content, and end the turn with SETUP_STATUS: error (or SETUP_STATUS: needs_input if the user needs to act, e.g. fix a bad credential) explaining exactly why — never fabricate content just to satisfy the build. ` +
|
|
253
|
+
`Cubes and measures are optional enrichment, not Setup completion requirements. Do not fetch enrich-context or create a cube/measure solely to complete this step. ` +
|
|
254
|
+
`Only after that successful validate-then-build sequence and a nonempty target/mdl.json with at least one model, STOP: report the model count and SETUP_STATUS: ok in your final message. Do NOT run further query/enrichment/cleanup loops after a successful build. ` +
|
|
255
|
+
`${bashDisciplineFor("generate-mdl", { skipSkillFetch: options?.resumeFromDisk === true || options?.resumeSession === true || recoveryOpening !== undefined })} ${terminalContract}`);
|
|
256
|
+
}
|
|
257
|
+
return (`The GenBI host has already accepted and persisted the user's credential form for the wren project "${projectName}" at "${projectDir}"; treat that credential handoff as a verified host fact, but no connection profile exists for it yet. ` +
|
|
258
|
+
`Do not inspect .env in any form: do not cat, sed, cut, grep, head, tail, awk, list its keys, test its contents, or ask setup_execution to read it. Build only placeholder field references from "wren docs connection-info ${sourceType}" and let "wren profile add" perform connection validation from the project directory. ` +
|
|
259
|
+
`The project is already scaffolded — do NOT run "wren context init" again. ` +
|
|
260
|
+
`This project's data source is "${sourceType}" — the connection profile you create MUST declare "datasource: ${sourceType}" and use exactly the fields "wren docs connection-info ${sourceType}" reports for it. ` +
|
|
261
|
+
`The onboarding skill's own connection-profile worked example is written for postgres and only applies as-is when "${sourceType}" IS postgres — for any other data source, do NOT follow that worked example verbatim; use it only as a template shape and substitute "${sourceType}"'s own fields throughout. ` +
|
|
262
|
+
`Follow the onboarding skill's connection-profile step (the step right after the project is scaffolded and its .env is filled in) to create that profile FIRST: ` +
|
|
263
|
+
`it tells you the scratch file to write — a placeholder for every field, never a value read out of .env — and the exact "wren profile add" command to run. ` +
|
|
264
|
+
`This project step is rooted at "${projectDir}" by its turn-level binding or, where exposed, a per-action cwd. Write that scratch file at the project-relative path exactly "conn.profile.yml"; do not prepend the workspace path or project name to that path. ` +
|
|
265
|
+
`Do NOT add "--activate" to that command: this profile belongs to "${projectDir}" alone, and "wren profile add" already pins a pinless project to the profile it just created regardless of --activate, which is everything this project's own commands need — omitting --activate just avoids also repointing the operator's unrelated global default profile at this one project. ` +
|
|
266
|
+
`"wren profile add" has no project-path flag of its own, so run it from the already-bound project directory; where the backend supports it, set the exec action's cwd field to "${projectDir}". Never "cd ${projectDir} && ..." chaining. ` +
|
|
267
|
+
`Only once that command succeeds should you consider the connection validated, and report whether it succeeded. ` +
|
|
268
|
+
`Only if validation genuinely succeeds, create the sentinel at the project-relative path exactly ".wren-validated" (empty content, no shell redirection; set a per-action cwd to "${projectDir}" only where supported) ` +
|
|
269
|
+
`as independent proof of a successful validation — do not create it if validation failed or you are unsure. ` +
|
|
270
|
+
`${bashDisciplineFor("onboarding")} ${credentialBoundary} ${terminalContract}`);
|
|
271
|
+
}
|