@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,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Makes the selected data source's Python driver available before the connect
|
|
3
|
+
* turn starts, rather than letting the agent discover it is missing.
|
|
4
|
+
*
|
|
5
|
+
* wren ships connectors for twenty-one sources but installs almost none of
|
|
6
|
+
* their drivers: each lives behind an optional extra. Picking a source whose
|
|
7
|
+
* extra is absent used to fail deep inside the turn with whatever the connector
|
|
8
|
+
* happened to raise — a bare `ModuleNotFoundError` for most, and for Oracle an
|
|
9
|
+
* `AttributeError: 'NoneType' object has no attribute 'connect'`, since that
|
|
10
|
+
* module binds its driver to `None` on ImportError.
|
|
11
|
+
*
|
|
12
|
+
* Provisioning happens here, in the BFF, and not in the agent's turn: the
|
|
13
|
+
* agent-run `pip install` that the onboarding skill's Preflight used to perform
|
|
14
|
+
* was removed precisely because it could consume the whole turn. A deterministic
|
|
15
|
+
* step costs no turn budget, is testable, and does not depend on the model
|
|
16
|
+
* choosing to run it.
|
|
17
|
+
*/
|
|
18
|
+
import { execFile } from "node:child_process";
|
|
19
|
+
import path from "node:path";
|
|
20
|
+
import { realpathSync, statSync } from "node:fs";
|
|
21
|
+
/**
|
|
22
|
+
* Sources that need no extra: their dependencies are wren's core ones. The four
|
|
23
|
+
* file sources and `duckdb` all route to wren's duckdb connector, whose driver
|
|
24
|
+
* is a core dependency; `datafusion` runs in-process on wren-core. Note wren's
|
|
25
|
+
* own `_INSTALL_EXTRA` maps the file sources to a `duckdb` extra that does not
|
|
26
|
+
* exist in its pyproject — harmless there because nothing needs installing,
|
|
27
|
+
* but a reason not to derive the extra name blindly.
|
|
28
|
+
*/
|
|
29
|
+
const NO_EXTRA_SOURCES = new Set(["duckdb", "local_file", "s3_file", "minio_file", "gcs_file", "datafusion", "connection_url"]);
|
|
30
|
+
/** Sources whose driver ships under a differently-named extra (wren's `_INSTALL_EXTRA`). */
|
|
31
|
+
const EXTRA_ALIASES = { doris: "mysql", canner: "postgres" };
|
|
32
|
+
/** The driver extras wren's pyproject actually declares. */
|
|
33
|
+
const DRIVER_EXTRAS = new Set([
|
|
34
|
+
"postgres", "mysql", "bigquery", "snowflake", "clickhouse", "trino",
|
|
35
|
+
"mssql", "databricks", "redshift", "spark", "athena", "oracle",
|
|
36
|
+
]);
|
|
37
|
+
/** The extra a source's driver ships under, or undefined when it needs none. */
|
|
38
|
+
export function driverExtraFor(sourceKey) {
|
|
39
|
+
if (NO_EXTRA_SOURCES.has(sourceKey))
|
|
40
|
+
return undefined;
|
|
41
|
+
const extra = EXTRA_ALIASES[sourceKey] ?? sourceKey;
|
|
42
|
+
return DRIVER_EXTRAS.has(extra) ? extra : undefined;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* The interpreter of the wren install on PATH. `wren` is a console script inside
|
|
46
|
+
* its environment's `bin/`, so its realpath's sibling `python` is that
|
|
47
|
+
* environment — the one whose site-packages a connector import will search.
|
|
48
|
+
*/
|
|
49
|
+
export function resolveWrenVenvPython() {
|
|
50
|
+
const found = process.env["PATH"]
|
|
51
|
+
?.split(path.delimiter)
|
|
52
|
+
.map((dir) => path.join(dir, "wren"))
|
|
53
|
+
.find((candidate) => {
|
|
54
|
+
try {
|
|
55
|
+
return statSync(candidate).isFile();
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
if (found === undefined)
|
|
62
|
+
return undefined;
|
|
63
|
+
try {
|
|
64
|
+
const interpreter = path.join(path.dirname(realpathSync(found)), "python");
|
|
65
|
+
const stat = statSync(interpreter);
|
|
66
|
+
return stat.isFile() && (stat.mode & 0o111) !== 0 ? interpreter : undefined;
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function runUv(args) {
|
|
73
|
+
return new Promise((resolve) => {
|
|
74
|
+
execFile("uv", args, { maxBuffer: 32 * 1024 * 1024 }, (error, stdout, stderr) => {
|
|
75
|
+
resolve({ stdout, stderr, error: error });
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Installs the source's driver extra into the wren environment. Idempotent:
|
|
81
|
+
* `uv pip install` on an already-satisfied extra makes no changes, so this is
|
|
82
|
+
* safe to run before every connect turn without probing first.
|
|
83
|
+
*
|
|
84
|
+
* Installs the extra (`wrenai[postgres]`) rather than the driver package
|
|
85
|
+
* directly, so the pins come from wren rather than from a second list here.
|
|
86
|
+
* uv resolves the already-installed editable `wrenai` as satisfying itself, so
|
|
87
|
+
* this adds the driver without disturbing a local editable checkout.
|
|
88
|
+
*/
|
|
89
|
+
export async function provisionSourceDriver(sourceKey, options = {}) {
|
|
90
|
+
const extra = driverExtraFor(sourceKey);
|
|
91
|
+
if (extra === undefined)
|
|
92
|
+
return { status: "not_needed" };
|
|
93
|
+
const interpreter = resolveWrenVenvPython();
|
|
94
|
+
if (interpreter === undefined) {
|
|
95
|
+
return { status: "unavailable", extra, detail: "could not locate the wren environment's interpreter from PATH" };
|
|
96
|
+
}
|
|
97
|
+
const run = options.run ?? runUv;
|
|
98
|
+
const { stdout, stderr, error } = await run(["pip", "install", "--python", interpreter, `wrenai[${extra}]`]);
|
|
99
|
+
if (error !== null) {
|
|
100
|
+
const detail = error.code === "ENOENT" ? "uv is not on PATH" : (stderr.trim() || error.message);
|
|
101
|
+
return { status: error.code === "ENOENT" ? "unavailable" : "failed", extra, detail };
|
|
102
|
+
}
|
|
103
|
+
// uv prints "Installed N packages" only when it changed something; an
|
|
104
|
+
// already-satisfied extra makes no changes.
|
|
105
|
+
const changed = /^\s*(Installed|Prepared)\s+\d+\s+package/m.test(stdout) || /^\s*\+ /m.test(stdout);
|
|
106
|
+
return changed ? { status: "installed", extra } : { status: "ready", extra };
|
|
107
|
+
}
|
|
108
|
+
/** One line describing the outcome for the composed prompt — verifiable, never a promise. */
|
|
109
|
+
export function driverProvisionNote(sourceKey, result) {
|
|
110
|
+
switch (result.status) {
|
|
111
|
+
case "not_needed":
|
|
112
|
+
return `The "${sourceKey}" connector needs no optional driver: its dependencies ship with wren.`;
|
|
113
|
+
case "installed":
|
|
114
|
+
return `The "${sourceKey}" driver (wren's "${result.extra}" extra) was just installed into the wren environment.`;
|
|
115
|
+
case "ready":
|
|
116
|
+
return `The "${sourceKey}" driver (wren's "${result.extra}" extra) is present in the wren environment.`;
|
|
117
|
+
default:
|
|
118
|
+
// Say what is actually known. The agent can check this in one command, so
|
|
119
|
+
// claiming the driver is available when provisioning did not succeed would
|
|
120
|
+
// be disproved immediately.
|
|
121
|
+
return (`The "${sourceKey}" driver (wren's "${result.extra}" extra) could NOT be provisioned: ${result.detail ?? "unknown reason"}. ` +
|
|
122
|
+
`Do not attempt to install it yourself. If a wren command fails because the driver is missing, stop and report exactly that — do not work around it.`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Serves the built SPA (`vite build`'s `dist/`) from the same Hono app as
|
|
3
|
+
* the BFF's own `/api/*` routes, so a published build can run as one
|
|
4
|
+
* process on one port instead of a separate static host in front of the
|
|
5
|
+
* BFF. Mounting is opt-in: `server/app.ts`'s `createApp` only calls
|
|
6
|
+
* `mountSpaFallback` when `TurnDeps.staticDir` is wired, and `server/bin.ts`
|
|
7
|
+
* (the only place that wires real production values) only wires it when
|
|
8
|
+
* `<staticDir>/index.html` actually exists — a dev boot with no `dist/`
|
|
9
|
+
* never touches this module, so the vite-proxied dev flow is unaffected.
|
|
10
|
+
*
|
|
11
|
+
* MUST be mounted last, after every `/api/*` route `createApp` registers.
|
|
12
|
+
* Hono resolves an overlapping match by registration order — whichever
|
|
13
|
+
* matching, non-`next()`-calling handler was registered first wins — so a
|
|
14
|
+
* catch-all mounted any earlier would shadow real routes (worst case: an
|
|
15
|
+
* SSE route that still "connects" but silently never streams an event).
|
|
16
|
+
* Both middlewares below additionally check the path themselves rather
|
|
17
|
+
* than relying on mount order alone, so an `/api/*` request that somehow
|
|
18
|
+
* reaches this module (no earlier route matched it) still 404s instead of
|
|
19
|
+
* getting HTML.
|
|
20
|
+
*/
|
|
21
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
22
|
+
import path from "node:path";
|
|
23
|
+
import { serveStatic } from "@hono/node-server/serve-static";
|
|
24
|
+
function isApiPath(pathname) {
|
|
25
|
+
return pathname === "/api" || pathname.startsWith("/api/");
|
|
26
|
+
}
|
|
27
|
+
export function mountSpaFallback(app, staticDir) {
|
|
28
|
+
// Real files (including `/` -> `index.html`, handled internally by
|
|
29
|
+
// `serveStatic`) are served as-is. `/api/*` is excluded up front so a
|
|
30
|
+
// request under it is never resolved against `staticDir` at all.
|
|
31
|
+
app.use("*", async (c, next) => {
|
|
32
|
+
if (isApiPath(c.req.path))
|
|
33
|
+
return next();
|
|
34
|
+
return serveStatic({ root: staticDir })(c, next);
|
|
35
|
+
});
|
|
36
|
+
// Anything serveStatic didn't find. Three cases:
|
|
37
|
+
// - `/api/*` with no matching route: a real 404, never HTML.
|
|
38
|
+
// - `/assets/...` (Vite's own hashed-bundle convention) with no matching
|
|
39
|
+
// file: a real 404 — a missing/renamed build asset must not silently
|
|
40
|
+
// resolve to `index.html`.
|
|
41
|
+
// - anything else: a client-side route; serve `index.html` so the SPA's
|
|
42
|
+
// own router can take over.
|
|
43
|
+
app.get("*", (c) => {
|
|
44
|
+
if (isApiPath(c.req.path) || c.req.path.startsWith("/assets/"))
|
|
45
|
+
return c.notFound();
|
|
46
|
+
const indexPath = path.join(staticDir, "index.html");
|
|
47
|
+
if (!existsSync(indexPath))
|
|
48
|
+
return c.notFound();
|
|
49
|
+
return c.html(readFileSync(indexPath, "utf8"));
|
|
50
|
+
});
|
|
51
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { SubscriptionModelCatalog, SubscriptionProvider } from "./wire-types.js";
|
|
2
|
+
export interface SubscriptionModelCatalogOptions {
|
|
3
|
+
readonly agentSdkBin?: string;
|
|
4
|
+
readonly codexLocalBin?: string;
|
|
5
|
+
readonly codexHome?: string;
|
|
6
|
+
readonly codexBin?: string;
|
|
7
|
+
/** The live project identity used by the runtime, when one is bound. */
|
|
8
|
+
readonly getUserProject: () => string | undefined;
|
|
9
|
+
/** Hermetic test seam. It receives no credentials and returns parsed JSON only. */
|
|
10
|
+
readonly execute?: (provider: SubscriptionProvider) => Promise<unknown>;
|
|
11
|
+
readonly now?: () => number;
|
|
12
|
+
}
|
|
13
|
+
/** Parses a provider payload into the public allowlist, intentionally discarding every other field. */
|
|
14
|
+
export declare function sanitizeSubscriptionModelCatalog(provider: SubscriptionProvider, payload: unknown): SubscriptionModelCatalog;
|
|
15
|
+
/**
|
|
16
|
+
* Runs a provider-owned command with bounded output and process-group cleanup.
|
|
17
|
+
* The test-only `onSpawn` hook observes only a PID; it never crosses the BFF
|
|
18
|
+
* wire boundary and lets the regression suite prove a TERM-ignoring child is
|
|
19
|
+
* eventually removed by the group KILL fallback.
|
|
20
|
+
*/
|
|
21
|
+
export declare function spawnJson(command: string, args: readonly string[], options: {
|
|
22
|
+
readonly env: NodeJS.ProcessEnv;
|
|
23
|
+
readonly cwd?: string;
|
|
24
|
+
readonly onSpawn?: (pid: number) => void;
|
|
25
|
+
}): Promise<unknown>;
|
|
26
|
+
/** Builds the BFF seam and owns a small provider-keyed process-memory TTL cache. */
|
|
27
|
+
export declare function createSubscriptionModelCatalog(options: SubscriptionModelCatalogOptions): (provider: SubscriptionProvider, refresh: boolean) => Promise<SubscriptionModelCatalog>;
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { resolveAgentSdkCli } from "../harness/index.js";
|
|
3
|
+
import { resolveCodexLocalCli } from "../harness/route/codex-local-cli.js";
|
|
4
|
+
const CATALOG_TIMEOUT_MS = 15_000;
|
|
5
|
+
const CATALOG_CACHE_MS = 30_000;
|
|
6
|
+
const MAX_OUTPUT_BYTES = 1024 * 1024;
|
|
7
|
+
const TERMINATION_GRACE_MS = 1_000;
|
|
8
|
+
function unavailable(provider, code, retryable = true) {
|
|
9
|
+
return { version: 1, status: "unavailable", provider, code, retryable };
|
|
10
|
+
}
|
|
11
|
+
function record(value) {
|
|
12
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : undefined;
|
|
13
|
+
}
|
|
14
|
+
function nonEmptyString(value) {
|
|
15
|
+
return typeof value === "string" && value.trim().length > 0 ? value : undefined;
|
|
16
|
+
}
|
|
17
|
+
function entry(value) {
|
|
18
|
+
const candidate = record(value);
|
|
19
|
+
const model = candidate && nonEmptyString(candidate.model);
|
|
20
|
+
const displayName = candidate && nonEmptyString(candidate.displayName);
|
|
21
|
+
if (!candidate || !model || !displayName)
|
|
22
|
+
return undefined;
|
|
23
|
+
const description = typeof candidate.description === "string" ? candidate.description : undefined;
|
|
24
|
+
const reasoningEfforts = Array.isArray(candidate.reasoningEfforts)
|
|
25
|
+
? candidate.reasoningEfforts.map((effort) => {
|
|
26
|
+
const item = record(effort);
|
|
27
|
+
const value = item && nonEmptyString(item.value);
|
|
28
|
+
const effortDisplayName = item && nonEmptyString(item.displayName);
|
|
29
|
+
if (!item || !value || !effortDisplayName)
|
|
30
|
+
return undefined;
|
|
31
|
+
return {
|
|
32
|
+
value,
|
|
33
|
+
displayName: effortDisplayName,
|
|
34
|
+
...(typeof item.description === "string" ? { description: item.description } : {}),
|
|
35
|
+
};
|
|
36
|
+
}).filter((item) => item !== undefined)
|
|
37
|
+
: undefined;
|
|
38
|
+
return {
|
|
39
|
+
model,
|
|
40
|
+
displayName,
|
|
41
|
+
...(description !== undefined ? { description } : {}),
|
|
42
|
+
...(typeof candidate.isDefault === "boolean" ? { isDefault: candidate.isDefault } : {}),
|
|
43
|
+
...(reasoningEfforts && reasoningEfforts.length > 0 ? { reasoningEfforts } : {}),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
/** Parses a provider payload into the public allowlist, intentionally discarding every other field. */
|
|
47
|
+
export function sanitizeSubscriptionModelCatalog(provider, payload) {
|
|
48
|
+
const result = record(payload);
|
|
49
|
+
if (!result || result.version !== 1 || result.provider !== provider)
|
|
50
|
+
return unavailable(provider, "protocol_error", false);
|
|
51
|
+
if (result.status === "unavailable") {
|
|
52
|
+
const code = result.code;
|
|
53
|
+
if (code !== "not_authenticated" && code !== "runtime_unavailable" && code !== "timeout" && code !== "protocol_error") {
|
|
54
|
+
return unavailable(provider, "protocol_error", false);
|
|
55
|
+
}
|
|
56
|
+
return unavailable(provider, code, typeof result.retryable === "boolean" ? result.retryable : true);
|
|
57
|
+
}
|
|
58
|
+
if (result.status !== "ready" || !Array.isArray(result.models))
|
|
59
|
+
return unavailable(provider, "protocol_error", false);
|
|
60
|
+
const models = result.models.map(entry);
|
|
61
|
+
if (models.some((model) => model === undefined))
|
|
62
|
+
return unavailable(provider, "protocol_error", false);
|
|
63
|
+
return { version: 1, status: "ready", provider, models: models };
|
|
64
|
+
}
|
|
65
|
+
async function runProviderCommand(provider, options) {
|
|
66
|
+
const cli = provider === "claude"
|
|
67
|
+
? await resolveAgentSdkCli(options.agentSdkBin)
|
|
68
|
+
: await resolveCodexLocalCli(options.codexLocalBin);
|
|
69
|
+
// `list-models` is intentionally a provider-owned, no-turn command. Its
|
|
70
|
+
// documented flags mirror the active runtime's project/Codex identity; the
|
|
71
|
+
// process output still crosses this module only after allowlist validation.
|
|
72
|
+
const project = options.getUserProject();
|
|
73
|
+
const args = [
|
|
74
|
+
...cli.prefixArgs,
|
|
75
|
+
"list-models",
|
|
76
|
+
...(project ? ["--project", project] : []),
|
|
77
|
+
...(provider === "codex" && options.codexHome ? ["--codex-home", options.codexHome] : []),
|
|
78
|
+
...(provider === "codex" && options.codexBin ? ["--codex-bin", options.codexBin] : []),
|
|
79
|
+
"--timeout",
|
|
80
|
+
String(CATALOG_TIMEOUT_MS),
|
|
81
|
+
];
|
|
82
|
+
return spawnJson(cli.command, args, { env: process.env });
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Runs a provider-owned command with bounded output and process-group cleanup.
|
|
86
|
+
* The test-only `onSpawn` hook observes only a PID; it never crosses the BFF
|
|
87
|
+
* wire boundary and lets the regression suite prove a TERM-ignoring child is
|
|
88
|
+
* eventually removed by the group KILL fallback.
|
|
89
|
+
*/
|
|
90
|
+
export function spawnJson(command, args, options) {
|
|
91
|
+
return new Promise((resolve, reject) => {
|
|
92
|
+
const child = spawn(command, [...args], {
|
|
93
|
+
env: options.env,
|
|
94
|
+
...(options.cwd !== undefined ? { cwd: options.cwd } : {}),
|
|
95
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
96
|
+
detached: process.platform !== "win32",
|
|
97
|
+
});
|
|
98
|
+
let stdout = "";
|
|
99
|
+
let bytes = 0;
|
|
100
|
+
let settled = false;
|
|
101
|
+
let timedOut = false;
|
|
102
|
+
let cleanupStarted = false;
|
|
103
|
+
const signalProcessGroup = (signal) => {
|
|
104
|
+
if (child.pid !== undefined && process.platform !== "win32") {
|
|
105
|
+
try {
|
|
106
|
+
process.kill(-child.pid, signal);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
catch { /* direct-child fallback */ }
|
|
110
|
+
}
|
|
111
|
+
child.kill(signal);
|
|
112
|
+
};
|
|
113
|
+
const cleanup = () => {
|
|
114
|
+
if (cleanupStarted)
|
|
115
|
+
return;
|
|
116
|
+
cleanupStarted = true;
|
|
117
|
+
signalProcessGroup("SIGTERM");
|
|
118
|
+
// Never clear this fallback on an early caller settlement: the command
|
|
119
|
+
// may ignore TERM (or have descendants) after the BFF has returned its
|
|
120
|
+
// sanitized failure. KILLing the detached group prevents a leak.
|
|
121
|
+
setTimeout(() => signalProcessGroup("SIGKILL"), TERMINATION_GRACE_MS).unref();
|
|
122
|
+
};
|
|
123
|
+
const settle = (action) => {
|
|
124
|
+
if (settled)
|
|
125
|
+
return;
|
|
126
|
+
settled = true;
|
|
127
|
+
clearTimeout(timer);
|
|
128
|
+
action();
|
|
129
|
+
};
|
|
130
|
+
const abort = (error) => {
|
|
131
|
+
cleanup();
|
|
132
|
+
settle(() => reject(new Error(error)));
|
|
133
|
+
};
|
|
134
|
+
if (child.pid !== undefined)
|
|
135
|
+
options.onSpawn?.(child.pid);
|
|
136
|
+
const timer = setTimeout(() => {
|
|
137
|
+
timedOut = true;
|
|
138
|
+
abort("timeout");
|
|
139
|
+
}, CATALOG_TIMEOUT_MS);
|
|
140
|
+
child.stdout.on("data", (chunk) => {
|
|
141
|
+
if (settled)
|
|
142
|
+
return;
|
|
143
|
+
bytes += chunk.length;
|
|
144
|
+
if (bytes > MAX_OUTPUT_BYTES) {
|
|
145
|
+
abort("protocol_error");
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
stdout += chunk.toString("utf8");
|
|
149
|
+
});
|
|
150
|
+
// Drain stderr without retaining it: provider details must never cross the BFF boundary.
|
|
151
|
+
child.stderr.resume();
|
|
152
|
+
child.on("error", () => settle(() => reject(new Error("runtime_unavailable"))));
|
|
153
|
+
child.on("close", (code) => settle(() => {
|
|
154
|
+
if (timedOut) {
|
|
155
|
+
reject(new Error("timeout"));
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
if (code !== 0) {
|
|
159
|
+
reject(new Error("runtime_unavailable"));
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
try {
|
|
163
|
+
resolve(JSON.parse(stdout));
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
reject(new Error("protocol_error"));
|
|
167
|
+
}
|
|
168
|
+
}));
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
/** Builds the BFF seam and owns a small provider-keyed process-memory TTL cache. */
|
|
172
|
+
export function createSubscriptionModelCatalog(options) {
|
|
173
|
+
const cache = new Map();
|
|
174
|
+
const latestRequest = new Map();
|
|
175
|
+
const now = options.now ?? Date.now;
|
|
176
|
+
return async (provider, refresh) => {
|
|
177
|
+
const cached = cache.get(provider);
|
|
178
|
+
if (!refresh && cached && cached.expiresAt > now())
|
|
179
|
+
return cached.result;
|
|
180
|
+
const requestId = (latestRequest.get(provider) ?? 0) + 1;
|
|
181
|
+
latestRequest.set(provider, requestId);
|
|
182
|
+
const writeCacheIfCurrent = (result) => {
|
|
183
|
+
// A refresh is authoritative over every request that preceded it. An
|
|
184
|
+
// older promise may still resolve for its original caller, but cannot
|
|
185
|
+
// replace the provider cache used by subsequent reads.
|
|
186
|
+
if (latestRequest.get(provider) === requestId) {
|
|
187
|
+
cache.set(provider, { expiresAt: now() + CATALOG_CACHE_MS, result });
|
|
188
|
+
}
|
|
189
|
+
return result;
|
|
190
|
+
};
|
|
191
|
+
try {
|
|
192
|
+
const raw = await (options.execute ? options.execute(provider) : runProviderCommand(provider, options));
|
|
193
|
+
const result = sanitizeSubscriptionModelCatalog(provider, raw);
|
|
194
|
+
return writeCacheIfCurrent(result);
|
|
195
|
+
}
|
|
196
|
+
catch (error) {
|
|
197
|
+
const message = error instanceof Error ? error.message : "runtime_unavailable";
|
|
198
|
+
const code = message === "timeout" ? "timeout" : message === "protocol_error" ? "protocol_error" : "runtime_unavailable";
|
|
199
|
+
const result = unavailable(provider, code, code !== "protocol_error");
|
|
200
|
+
return writeCacheIfCurrent(result);
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
}
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import type { AuthChoice, Bundle, LoginProbe, RouteOptions, RouteResult, SetupStepRunner } from "../harness/index.js";
|
|
2
|
+
import type { SubscriptionModelCatalog, SubscriptionProvider } from "./wire-types.js";
|
|
3
|
+
import { type Store } from "./db.js";
|
|
4
|
+
import type { EnrichmentApprovalProvider, EnrichmentApplyRunner, EnrichmentRunner } from "./enrichment.js";
|
|
5
|
+
import type { verifyProposal } from "./enrichment-verify.js";
|
|
6
|
+
import type { EnrichmentBinding } from "./enrichment.js";
|
|
7
|
+
import type { InteractiveTarget, InteractiveTargetReadiness, InteractiveTerminalSession, PreparedInteractiveHandoff } from "./interactive-terminal.js";
|
|
8
|
+
import type { NativeSessionService } from "./native-sessions.js";
|
|
9
|
+
import type { NativeArtifactService } from "./native-artifacts.js";
|
|
10
|
+
import type { NativePurpose } from "./native-dispatch-registry.js";
|
|
11
|
+
import type { AskSessionData, ClarifyEvent, PublishedEvent, PublishScope, SavedEvent, SetupMode, SseFrame, UnsavedEvent } from "./wire-types.js";
|
|
12
|
+
export declare class SessionNotFoundError extends Error {
|
|
13
|
+
readonly sessionId: string;
|
|
14
|
+
constructor(sessionId: string);
|
|
15
|
+
}
|
|
16
|
+
export declare class TurnNotFoundError extends Error {
|
|
17
|
+
readonly turnId: string;
|
|
18
|
+
constructor(turnId: string);
|
|
19
|
+
}
|
|
20
|
+
export declare class ArtifactNotFoundError extends Error {
|
|
21
|
+
readonly artifactId: string;
|
|
22
|
+
constructor(artifactId: string);
|
|
23
|
+
}
|
|
24
|
+
export interface TurnDeps {
|
|
25
|
+
readonly store: Store;
|
|
26
|
+
readonly route: (options: RouteOptions) => Promise<RouteResult>;
|
|
27
|
+
/** A native vendor TUI host, deliberately independent from Ask/SSE. */
|
|
28
|
+
readonly startInteractiveTerminal?: (input: {
|
|
29
|
+
readonly target: InteractiveTarget;
|
|
30
|
+
readonly binding: EnrichmentBinding;
|
|
31
|
+
}) => Promise<InteractiveTerminalSession>;
|
|
32
|
+
readonly getInteractiveTerminal?: (id: string) => InteractiveTerminalSession | undefined;
|
|
33
|
+
/** Revokes compatibility terminals outside the durable NativeSessionService. */
|
|
34
|
+
readonly revokeInteractiveTerminals?: () => void;
|
|
35
|
+
readonly interactiveTerminalReadiness?: () => Promise<Record<InteractiveTarget, InteractiveTargetReadiness>>;
|
|
36
|
+
readonly prepareInteractiveTerminal?: (input: {
|
|
37
|
+
readonly target: InteractiveTarget;
|
|
38
|
+
readonly binding: EnrichmentBinding;
|
|
39
|
+
}) => Promise<PreparedInteractiveHandoff>;
|
|
40
|
+
/** Durable native Sessions control plane; intentionally distinct from Ask. */
|
|
41
|
+
/**
|
|
42
|
+
* Refutes a drafted enrichment proposal before it is offered for approval.
|
|
43
|
+
* Injected rather than imported so a test can exercise the surrounding flow
|
|
44
|
+
* without a real project on disk — and, more importantly, so the production
|
|
45
|
+
* default is the real ladder rather than something a caller can weaken.
|
|
46
|
+
*/
|
|
47
|
+
readonly verifyEnrichmentProposal?: typeof verifyProposal;
|
|
48
|
+
readonly nativeSessions?: NativeSessionService;
|
|
49
|
+
/** Session-scoped MCP persistence service; never used by Ask publish routes. */
|
|
50
|
+
readonly nativeArtifacts?: NativeArtifactService;
|
|
51
|
+
readonly baseRouteOptions: Omit<RouteOptions, "question" | "onEvent">;
|
|
52
|
+
/**
|
|
53
|
+
* Ask/compile-bind only: compiles+loads the analysis profile's bundle.
|
|
54
|
+
* Harness purpose introspection uses `describeHarnessBundle` below. Optional so
|
|
55
|
+
* every other route and every existing test's `TurnDeps` literal is
|
|
56
|
+
* unaffected — `server/bin.ts` wires the real `describeBundle` (from
|
|
57
|
+
* `../harness/index.js`); a `TurnDeps` without it simply can't serve
|
|
58
|
+
* `/api/harness` (that route reports a 500 instead of throwing).
|
|
59
|
+
*/
|
|
60
|
+
readonly describeBundle?: (options: Omit<RouteOptions, "question" | "onEvent">) => Promise<Bundle>;
|
|
61
|
+
/**
|
|
62
|
+
* Harness-only bundle description for one closed native purpose. The
|
|
63
|
+
* purpose is parsed by the BFF from its read-only query vocabulary; this
|
|
64
|
+
* dependency resolves the corresponding server-owned profile source. It is
|
|
65
|
+
* intentionally distinct from native-session IR/launch wiring.
|
|
66
|
+
*/
|
|
67
|
+
readonly describeHarnessBundle?: (purpose: NativePurpose, options: Omit<RouteOptions, "question" | "onEvent">) => Promise<Bundle>;
|
|
68
|
+
/**
|
|
69
|
+
* Compiles the canonical post-bind profile without rebinding its context and
|
|
70
|
+
* returns its declared tier names. Unlike `describeBundle`, this seam is
|
|
71
|
+
* intentionally auth-independent and works before a user project is bound.
|
|
72
|
+
*/
|
|
73
|
+
readonly getRuntimeTierNames?: () => Promise<readonly string[]>;
|
|
74
|
+
/**
|
|
75
|
+
* Dispatches a setup-wizard turn (`connect` / `connect_resume` — see
|
|
76
|
+
* `TurnRow.setupStepKey`) via `harness/setup/runner.ts`'s `SetupStepRunner`
|
|
77
|
+
* seam, instead of the normal `route()` path. Optional so every existing
|
|
78
|
+
* test's `TurnDeps` literal is unaffected; a setup turn dispatched against
|
|
79
|
+
* a `TurnDeps` without this wired resolves as an error turn with a clear
|
|
80
|
+
* "not configured" message rather than throwing.
|
|
81
|
+
*/
|
|
82
|
+
readonly setupRunner?: SetupStepRunner;
|
|
83
|
+
/**
|
|
84
|
+
* Resolves which `SetupStepRunner` a setup turn should dispatch through,
|
|
85
|
+
* given the auth choice actually in effect (Claude subscription, Codex
|
|
86
|
+
* subscription, or non-subscription — the same selection `server/bin.ts`
|
|
87
|
+
* uses at boot). Optional so every existing test's `TurnDeps` literal is
|
|
88
|
+
* unaffected: when absent, `resolveSetupRunner` falls back to the fixed
|
|
89
|
+
* `setupRunner` above (the pre-live-auth-choice behavior). Wired by
|
|
90
|
+
* `server/bin.ts` so a setup turn dispatches through the runner matching
|
|
91
|
+
* whatever auth mode is ACTUALLY bound at run time, not the one resolved at
|
|
92
|
+
* boot.
|
|
93
|
+
*/
|
|
94
|
+
setupRunnerFor?(choice: AuthChoice): SetupStepRunner;
|
|
95
|
+
/**
|
|
96
|
+
* The workspace root new wren projects are scaffolded under
|
|
97
|
+
* (`WREN_HARNESS_WORKSPACE_ROOT`) — the `root` half of
|
|
98
|
+
* `parseSetupTerminal`'s `{root, name}` context, and `SetupStepRunOptions.workspaceRoot`.
|
|
99
|
+
* Optional for the same reason as `setupRunner` above.
|
|
100
|
+
*/
|
|
101
|
+
readonly workspaceRoot?: string;
|
|
102
|
+
/**
|
|
103
|
+
* Binds (or rebinds) the process's single active wren project — the
|
|
104
|
+
* bootstrap mode's mutable project binding (see `server/bin.ts`). Optional:
|
|
105
|
+
* a `TurnDeps` built with a fixed `baseRouteOptions.userProject` (every
|
|
106
|
+
* existing test, and any non-bootstrap boot) simply has no rebinding
|
|
107
|
+
* capability, which is fine — `resolveUserProject` falls back to the fixed
|
|
108
|
+
* `baseRouteOptions.userProject` when this isn't wired.
|
|
109
|
+
*/
|
|
110
|
+
bindProject?(dir: string): void;
|
|
111
|
+
/**
|
|
112
|
+
* Reads the process's currently bound project, if any. Optional; when
|
|
113
|
+
* absent, `resolveUserProject` reads `baseRouteOptions.userProject` instead
|
|
114
|
+
* (the pre-bootstrap-mode, single-fixed-project behavior).
|
|
115
|
+
*/
|
|
116
|
+
getUserProject?(): string | undefined;
|
|
117
|
+
/**
|
|
118
|
+
* Clears the bootstrap project binding (back to unbound), for the setup
|
|
119
|
+
* wizard's "Reset setup" action. Optional and symmetric with `bindProject`:
|
|
120
|
+
* a fixed-project (non-bootstrap) `TurnDeps` has no binding to clear.
|
|
121
|
+
*/
|
|
122
|
+
unbindProject?(): void;
|
|
123
|
+
/**
|
|
124
|
+
* Reads the process's currently bound auth choice, if a live binding is
|
|
125
|
+
* wired — the auth-choice mirror of `getUserProject`. Optional; when
|
|
126
|
+
* absent, `resolveAuthChoice` reads `baseRouteOptions.authChoice` instead
|
|
127
|
+
* (the pre-live-auth-choice, boot-fixed behavior every existing test's
|
|
128
|
+
* `TurnDeps` literal still gets).
|
|
129
|
+
*/
|
|
130
|
+
getAuthChoice?(): AuthChoice;
|
|
131
|
+
/**
|
|
132
|
+
* Rebinds the process's live auth choice — the auth-choice mirror of
|
|
133
|
+
* `bindProject`. Wired by `server/bin.ts`; `PUT /api/config/runtime`
|
|
134
|
+
* (`server/app.ts`) calls this once a candidate `AuthChoice` has passed the
|
|
135
|
+
* compliance gate, so every later `route()`/setup dispatch reads the live
|
|
136
|
+
* binding through `resolveAuthChoice`/`effectiveRouteOptions` instead of the
|
|
137
|
+
* boot-fixed `baseRouteOptions.authChoice`.
|
|
138
|
+
*/
|
|
139
|
+
setAuthChoice?(choice: AuthChoice): void;
|
|
140
|
+
/** Injectable, boolean-only subscription login probe used by config UI/API. */
|
|
141
|
+
readonly loginProbe?: LoginProbe;
|
|
142
|
+
/** Provider-owned, already-sanitized model discovery for the Setup form. */
|
|
143
|
+
readonly listSubscriptionModels?: (provider: SubscriptionProvider, refresh: boolean) => Promise<SubscriptionModelCatalog>;
|
|
144
|
+
/** Optional post-bind enrichment seams; absence is an intentional fail-closed runtime wall. */
|
|
145
|
+
readonly enrichmentRunner?: EnrichmentRunner;
|
|
146
|
+
readonly enrichmentApplyRunner?: EnrichmentApplyRunner;
|
|
147
|
+
readonly enrichmentApprovalProvider?: EnrichmentApprovalProvider;
|
|
148
|
+
/**
|
|
149
|
+
* Points every subsequent `wren` CLI subprocess (via `harness/exec/local.ts`'s
|
|
150
|
+
* `execFile` and `server/adopt.ts`'s own `execWren`, both of which inherit
|
|
151
|
+
* `process.env` verbatim) at the right `WREN_HOME` for the wizard's chosen
|
|
152
|
+
* entry path: `"create"` anchors it to `<workspaceRoot>/.wren` so a
|
|
153
|
+
* scaffolded project's `wren profile add` writes into a fresh,
|
|
154
|
+
* workspace-scoped `profiles.yml` instead of the operator's real
|
|
155
|
+
* `~/.wren/profiles.yml`; `"adopt"` (or `undefined`, e.g.
|
|
156
|
+
* after "Reset setup") restores the real baseline `WREN_HOME` the process
|
|
157
|
+
* booted with, since adopt is strictly read-only against global state and
|
|
158
|
+
* must keep resolving the operator's own profiles. Optional so every
|
|
159
|
+
* existing test's `TurnDeps` literal is unaffected — a `TurnDeps` without
|
|
160
|
+
* this wired just leaves `process.env.WREN_HOME` untouched (the
|
|
161
|
+
* pre-isolation behavior). Wired by `server/bin.ts`; called from
|
|
162
|
+
* `server/app.ts`'s `dispatchConnectTurn` (create) and `POST
|
|
163
|
+
* /api/setup/adopt` (adopt) — the two single choke points every actual
|
|
164
|
+
* `wren` invocation for each mode passes through — plus `POST
|
|
165
|
+
* /api/setup/reset` (restores baseline) so a mode change mid-boot (pick
|
|
166
|
+
* create, reset, pick adopt, or the reverse) can never leave a stale
|
|
167
|
+
* `WREN_HOME` from the previous choice in effect.
|
|
168
|
+
*/
|
|
169
|
+
setWrenHomeForSetupMode?(mode: SetupMode | undefined): void;
|
|
170
|
+
/**
|
|
171
|
+
* Absolute path to a built SPA (`vite build`'s `dist/`, containing
|
|
172
|
+
* `index.html`) to serve from this same process, same origin as every
|
|
173
|
+
* `/api/*` route below. Optional so every existing test's `TurnDeps`
|
|
174
|
+
* literal is unaffected: when absent, `server/app.ts`'s `createApp` never
|
|
175
|
+
* mounts the SPA fallback and the BFF behaves exactly as it does today
|
|
176
|
+
* (no static serving at all — the dev flow's vite proxy on a separate
|
|
177
|
+
* port is the only way the UI is served). `server/bin.ts` wires this only
|
|
178
|
+
* when `<staticDir>/index.html` actually exists, so a dev boot with no
|
|
179
|
+
* `dist/` present leaves this unset.
|
|
180
|
+
*/
|
|
181
|
+
readonly staticDir?: string;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Thrown by `effectiveRouteOptions` when no wren project is bound yet
|
|
185
|
+
* (bootstrap-mode, before the setup wizard's bind step). Routes that require
|
|
186
|
+
* a bound project (`server/app.ts`'s 409 guard) check `isProjectBound` first
|
|
187
|
+
* and never actually let this escape in normal operation — it exists mainly
|
|
188
|
+
* so a caller that forgets the guard fails loudly instead of silently
|
|
189
|
+
* compiling against `undefined`.
|
|
190
|
+
*/
|
|
191
|
+
/** Shared 409 message for every route that requires a bound project (`server/app.ts`'s guard) — single source of truth so the guard and `ProjectNotBoundError` never drift apart. */
|
|
192
|
+
export declare const PROJECT_NOT_BOUND_MESSAGE = "no wren project is bound yet \u2014 complete the setup wizard's connect/bind steps first";
|
|
193
|
+
export declare class ProjectNotBoundError extends Error {
|
|
194
|
+
constructor();
|
|
195
|
+
}
|
|
196
|
+
/** Whether a wren project is currently bound (always true for a `TurnDeps` without `getUserProject` wired — the pre-bootstrap-mode case). */
|
|
197
|
+
export declare function isProjectBound(deps: TurnDeps): boolean;
|
|
198
|
+
/** Resolves the currently-bound project directory, preferring the mutable `getUserProject` binding over the fixed `baseRouteOptions.userProject`. */
|
|
199
|
+
export declare function resolveUserProject(deps: TurnDeps): string | undefined;
|
|
200
|
+
/** Resolves the auth choice actually in effect, preferring the mutable `getAuthChoice` binding over the fixed `baseRouteOptions.authChoice` — the auth-choice mirror of `resolveUserProject`. */
|
|
201
|
+
export declare function resolveAuthChoice(deps: TurnDeps): AuthChoice;
|
|
202
|
+
/** Resolves which `SetupStepRunner` a setup turn should dispatch through, given the auth choice actually in effect. Falls back to the fixed `deps.setupRunner` when `setupRunnerFor` isn't wired (pre-live-auth-choice behavior). */
|
|
203
|
+
export declare function resolveSetupRunner(deps: TurnDeps): SetupStepRunner | undefined;
|
|
204
|
+
/**
|
|
205
|
+
* `deps.baseRouteOptions` with `userProject`/`authChoice` re-resolved through
|
|
206
|
+
* their mutable bindings at call time. By default this throws while unbound;
|
|
207
|
+
* the read-only bootstrap Harness descriptor is the one deliberate exception
|
|
208
|
+
* and receives an empty project value that its raw-profile path never reads.
|
|
209
|
+
*/
|
|
210
|
+
export declare function effectiveRouteOptions(deps: TurnDeps, options?: {
|
|
211
|
+
readonly allowUnbound?: boolean;
|
|
212
|
+
}): Omit<RouteOptions, "question" | "onEvent">;
|
|
213
|
+
/** Evicts `deps`'s memoized compiled-bundle-agent-ids entry (see `bundleAgentInfoCache` below) — called by `bindProject` so a turn after a (re)bind recompiles against the newly-bound project instead of replaying a stale/empty cache entry. */
|
|
214
|
+
export declare function invalidateBundleAgentIdsCache(deps: TurnDeps): void;
|
|
215
|
+
export interface PostTurnResult {
|
|
216
|
+
readonly turnId: string;
|
|
217
|
+
readonly clarify?: ClarifyEvent;
|
|
218
|
+
}
|
|
219
|
+
/** POST /api/sessions/:id/turns — persists the user event, runs the D1 clarify pre-flight, and either short-circuits with a clarify prompt or prepares a turn for streaming. */
|
|
220
|
+
export declare function postTurn(deps: TurnDeps, sessionId: string, question: string): Promise<PostTurnResult>;
|
|
221
|
+
/** GET (SSE) /api/sessions/:id/stream?turn=:turnId — replays a resolved turn, or executes+streams a pending one. */
|
|
222
|
+
export declare function streamTurn(deps: TurnDeps, sessionId: string, turnId: string, emit: (frame: SseFrame) => Promise<void>, signal?: AbortSignal): Promise<void>;
|
|
223
|
+
/** GET /api/sessions/:id — the Ask page's session snapshot: full event log + the latest turn's work log. */
|
|
224
|
+
export declare function getAskSessionData(store: Store, sessionId: string): AskSessionData;
|
|
225
|
+
/** POST /api/sessions/:id/artifacts/:artifactId/publish */
|
|
226
|
+
export declare function publishArtifactForSession(store: Store, sessionId: string, artifactId: string, scope: PublishScope): PublishedEvent;
|
|
227
|
+
/**
|
|
228
|
+
* POST /api/sessions/:id/artifacts/:artifactId/save — promotes an
|
|
229
|
+
* auto-created artifact onto the Artifacts page. Idempotent: a repeat call
|
|
230
|
+
* on an already-saved artifact returns the same `savedAt` (via
|
|
231
|
+
* `store.saveArtifact`'s own idempotency) and does NOT insert a second
|
|
232
|
+
* `SavedEvent` into the session's event log — otherwise every no-op re-save
|
|
233
|
+
* would grow the persisted history with duplicate events.
|
|
234
|
+
*/
|
|
235
|
+
export declare function saveArtifactForSession(store: Store, sessionId: string, artifactId: string): SavedEvent;
|
|
236
|
+
/**
|
|
237
|
+
* POST /api/sessions/:id/artifacts/:artifactId/unsave — the
|
|
238
|
+
* mirror of `saveArtifactForSession`: unpins an artifact from the Artifacts
|
|
239
|
+
* page. The artifact row and its envelope file are left exactly as they are
|
|
240
|
+
* — only `saved_at` clears — so the artifact remains re-saveable. Idempotent:
|
|
241
|
+
* a repeat call on an already-unsaved artifact does NOT append a second
|
|
242
|
+
* `UnsavedEvent` into the session's event log, for the same reason
|
|
243
|
+
* `saveArtifactForSession` guards its own repeat calls.
|
|
244
|
+
*
|
|
245
|
+
* Appends an `UnsavedEvent` rather than deleting the earlier `SavedEvent` —
|
|
246
|
+
* the event log is append-only/replayed (see `getAskSessionData`), so
|
|
247
|
+
* "unsaved" is recorded as a newer fact, never as erasing an older one.
|
|
248
|
+
*/
|
|
249
|
+
export declare function unsaveArtifactForSession(store: Store, sessionId: string, artifactId: string): UnsavedEvent;
|