@wrenai/genbi 0.0.3 → 0.0.5
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/README.md +65 -3
- package/dist/assets/{echarts-LTFC-ysd.js → echarts-DYM4hjvk.js} +2 -2
- package/dist/assets/index-A_Wrq7Ey.css +1 -0
- package/dist/assets/{index-Bo--QMUN.js → index-DsH129tV.js} +18 -17
- package/dist/index.html +2 -2
- package/dist-server/harness/bundle/schema.d.ts +38 -0
- package/dist-server/harness/bundle/schema.js +18 -10
- package/dist-server/harness/bundle/version.js +1 -1
- package/dist-server/harness/cli-args.d.ts +10 -2
- package/dist-server/harness/cli-args.js +24 -8
- package/dist-server/harness/cli.js +75 -9
- package/dist-server/harness/compile/cache.js +4 -4
- package/dist-server/harness/compile/compose-profile.d.ts +51 -14
- package/dist-server/harness/compile/compose-profile.js +85 -19
- package/dist-server/harness/compile/context-loader.d.ts +56 -0
- package/dist-server/harness/compile/context-loader.js +237 -0
- package/dist-server/harness/compile/errors.d.ts +19 -0
- package/dist-server/harness/compile/errors.js +35 -0
- package/dist-server/harness/compile/index.d.ts +4 -3
- package/dist-server/harness/compile/index.js +4 -3
- package/dist-server/harness/compile/pipeline.d.ts +15 -7
- package/dist-server/harness/compile/pipeline.js +62 -14
- package/dist-server/harness/compile/resolve-binary.d.ts +2 -2
- package/dist-server/harness/compile/resolve-binary.js +13 -3
- package/dist-server/harness/compile/types.d.ts +29 -0
- package/dist-server/harness/compile/warble-identity.d.ts +11 -0
- package/dist-server/harness/compile/warble-identity.js +13 -0
- package/dist-server/harness/components/ai-step.d.ts +4 -0
- package/dist-server/harness/components/ai-step.js +40 -0
- package/dist-server/harness/components/broker.d.ts +60 -0
- package/dist-server/harness/components/broker.js +117 -0
- package/dist-server/harness/components/capability-card.d.ts +98 -0
- package/dist-server/harness/components/capability-card.js +169 -0
- package/dist-server/harness/components/context.d.ts +2 -0
- package/dist-server/harness/components/context.js +30 -0
- package/dist-server/harness/components/display.d.ts +6 -0
- package/dist-server/harness/components/display.js +29 -0
- package/dist-server/harness/components/egress-judge.d.ts +12 -0
- package/dist-server/harness/components/egress-judge.js +27 -0
- package/dist-server/harness/components/egress.d.ts +142 -0
- package/dist-server/harness/components/egress.js +469 -0
- package/dist-server/harness/components/in-process.d.ts +7 -0
- package/dist-server/harness/components/in-process.js +245 -0
- package/dist-server/harness/components/normalize.d.ts +35 -0
- package/dist-server/harness/components/normalize.js +245 -0
- package/dist-server/harness/components/plan.d.ts +31 -0
- package/dist-server/harness/components/plan.js +184 -0
- package/dist-server/harness/components/report.d.ts +207 -0
- package/dist-server/harness/components/report.js +309 -0
- package/dist-server/harness/components/runner.d.ts +193 -0
- package/dist-server/harness/components/runner.js +492 -0
- package/dist-server/harness/components/semantic-guards.d.ts +20 -0
- package/dist-server/harness/components/semantic-guards.js +42 -0
- package/dist-server/harness/components/terminal-json.d.ts +18 -0
- package/dist-server/harness/components/terminal-json.js +82 -0
- package/dist-server/harness/components/usage.d.ts +14 -0
- package/dist-server/harness/components/usage.js +19 -0
- package/dist-server/harness/components/wren-access.d.ts +29 -0
- package/dist-server/harness/components/wren-access.js +233 -0
- package/dist-server/harness/components/zone-dry-run.d.ts +43 -0
- package/dist-server/harness/components/zone-dry-run.js +107 -0
- package/dist-server/harness/components/zone-gate.d.ts +76 -0
- package/dist-server/harness/components/zone-gate.js +144 -0
- package/dist-server/harness/events/index.d.ts +1 -1
- package/dist-server/harness/events/types.d.ts +52 -0
- package/dist-server/harness/index.d.ts +9 -2
- package/dist-server/harness/index.js +5 -1
- package/dist-server/harness/loop/executor.d.ts +1 -0
- package/dist-server/harness/loop/executor.js +37 -15
- package/dist-server/harness/npm-package-resolve.d.ts +2 -0
- package/dist-server/harness/npm-package-resolve.js +16 -4
- package/dist-server/harness/providers/adapters/openai-compatible.d.ts +13 -0
- package/dist-server/harness/providers/adapters/openai-compatible.js +17 -0
- package/dist-server/harness/providers/adapters/openai.d.ts +7 -0
- package/dist-server/harness/providers/adapters/openai.js +6 -0
- package/dist-server/harness/providers/adapters/pi-ai.d.ts +48 -0
- package/dist-server/harness/providers/adapters/pi-ai.js +518 -0
- package/dist-server/harness/providers/binding.d.ts +45 -4
- package/dist-server/harness/providers/binding.js +44 -8
- package/dist-server/harness/providers/index.d.ts +9 -3
- package/dist-server/harness/providers/index.js +10 -3
- package/dist-server/harness/providers/zone.d.ts +96 -0
- package/dist-server/harness/providers/zone.js +112 -0
- package/dist-server/harness/render/answering-query.d.ts +29 -0
- package/dist-server/harness/render/answering-query.js +62 -0
- package/dist-server/harness/render/envelope.d.ts +6 -0
- package/dist-server/harness/render/envelope.js +10 -3
- package/dist-server/harness/route/adapter-spec.d.ts +8 -9
- package/dist-server/harness/route/adapter-spec.js +13 -14
- package/dist-server/harness/route/codex-ask-events.js +2 -1
- package/dist-server/harness/route/codex-ask.d.ts +4 -0
- package/dist-server/harness/route/codex-ask.js +69 -30
- package/dist-server/harness/route/codex-host-query.d.ts +52 -0
- package/dist-server/harness/route/codex-host-query.js +191 -0
- package/dist-server/harness/route/codex-local-manifest.d.ts +9 -1
- package/dist-server/harness/route/codex-local-manifest.js +96 -15
- package/dist-server/harness/route/codex-wren-proxy.d.ts +33 -0
- package/dist-server/harness/route/codex-wren-proxy.js +227 -0
- package/dist-server/harness/route/describe.js +45 -6
- package/dist-server/harness/route/in-process.d.ts +22 -1
- package/dist-server/harness/route/in-process.js +54 -4
- package/dist-server/harness/route/route.js +5 -2
- package/dist-server/harness/route/types.d.ts +30 -1
- package/dist-server/harness/session/run.js +30 -10
- package/dist-server/harness/setup/runner.d.ts +2 -0
- package/dist-server/harness/setup/runner.js +38 -17
- package/dist-server/harness/setup/yaml-scalar.d.ts +23 -0
- package/dist-server/harness/setup/yaml-scalar.js +62 -0
- package/dist-server/server/adopt.js +1 -17
- package/dist-server/server/app.d.ts +1 -0
- package/dist-server/server/app.js +163 -14
- package/dist-server/server/auth-choice.d.ts +13 -1
- package/dist-server/server/auth-choice.js +66 -1
- package/dist-server/server/bin.js +197 -26
- package/dist-server/server/codex-local-grounding.d.ts +18 -0
- package/dist-server/server/codex-local-grounding.js +92 -0
- package/dist-server/server/codex-native-session.d.ts +25 -0
- package/dist-server/server/codex-native-session.js +1 -0
- package/dist-server/server/codex-runtime-composition.d.ts +34 -0
- package/dist-server/server/codex-runtime-composition.js +250 -0
- package/dist-server/server/codex-structured-ask.d.ts +23 -0
- package/dist-server/server/codex-structured-ask.js +102 -0
- package/dist-server/server/conn-config.js +4 -16
- package/dist-server/server/conversation-socket.d.ts +16 -0
- package/dist-server/server/conversation-socket.js +65 -0
- package/dist-server/server/db.d.ts +46 -0
- package/dist-server/server/db.js +88 -9
- package/dist-server/server/env-detect.d.ts +6 -0
- package/dist-server/server/env-detect.js +7 -0
- package/dist-server/server/fold.d.ts +22 -4
- package/dist-server/server/fold.js +37 -5
- package/dist-server/server/harness.d.ts +2 -2
- package/dist-server/server/harness.js +33 -7
- package/dist-server/server/interactive-terminal.d.ts +10 -6
- package/dist-server/server/interactive-terminal.js +33 -8
- package/dist-server/server/managed-wren-runtime.d.ts +116 -0
- package/dist-server/server/managed-wren-runtime.js +670 -0
- package/dist-server/server/native-artifacts.d.ts +23 -7
- package/dist-server/server/native-artifacts.js +43 -12
- package/dist-server/server/native-component-context.d.ts +19 -0
- package/dist-server/server/native-component-context.js +76 -0
- package/dist-server/server/native-component-preparation.d.ts +20 -0
- package/dist-server/server/native-component-preparation.js +129 -0
- package/dist-server/server/native-component-runtime.d.ts +36 -0
- package/dist-server/server/native-component-runtime.js +92 -0
- package/dist-server/server/native-components.d.ts +84 -0
- package/dist-server/server/native-components.js +160 -0
- package/dist-server/server/native-dispatch-process.d.ts +8 -0
- package/dist-server/server/native-dispatch-process.js +65 -0
- package/dist-server/server/native-runtime-spec.d.ts +89 -0
- package/dist-server/server/native-runtime-spec.js +169 -0
- package/dist-server/server/native-sessions.d.ts +172 -19
- package/dist-server/server/native-sessions.js +870 -251
- package/dist-server/server/native-shutdown.d.ts +6 -0
- package/dist-server/server/native-shutdown.js +28 -0
- package/dist-server/server/native-wren-home.d.ts +18 -0
- package/dist-server/server/native-wren-home.js +262 -0
- package/dist-server/server/node-pty-host.d.ts +2 -1
- package/dist-server/server/node-pty-host.js +3 -2
- package/dist-server/server/profile-registry.d.ts +139 -0
- package/dist-server/server/profile-registry.js +620 -0
- package/dist-server/server/runtime-binding.d.ts +4 -1
- package/dist-server/server/runtime-binding.js +50 -2
- package/dist-server/server/runtime-host/claude-component-step.d.ts +25 -0
- package/dist-server/server/runtime-host/claude-component-step.js +186 -0
- package/dist-server/server/runtime-host/codex-app-server.d.ts +75 -0
- package/dist-server/server/runtime-host/codex-app-server.js +239 -0
- package/dist-server/server/runtime-host/codex-compatibility.d.ts +110 -0
- package/dist-server/server/runtime-host/codex-compatibility.js +162 -0
- package/dist-server/server/runtime-host/codex-component-step.d.ts +19 -0
- package/dist-server/server/runtime-host/codex-component-step.js +308 -0
- package/dist-server/server/runtime-host/codex-conversation.d.ts +109 -0
- package/dist-server/server/runtime-host/codex-conversation.js +295 -0
- package/dist-server/server/runtime-host/codex-direct-tools.d.ts +23 -0
- package/dist-server/server/runtime-host/codex-direct-tools.js +59 -0
- package/dist-server/server/runtime-host/codex-events.d.ts +647 -0
- package/dist-server/server/runtime-host/codex-events.js +66 -0
- package/dist-server/server/runtime-host/codex-policy.d.ts +16 -0
- package/dist-server/server/runtime-host/codex-policy.js +151 -0
- package/dist-server/server/runtime-host/codex-process.d.ts +8 -0
- package/dist-server/server/runtime-host/codex-process.js +96 -0
- package/dist-server/server/runtime-host/codex-rpc.d.ts +47 -0
- package/dist-server/server/runtime-host/codex-rpc.js +182 -0
- package/dist-server/server/runtime-host/codex-session.d.ts +74 -0
- package/dist-server/server/runtime-host/codex-session.js +492 -0
- package/dist-server/server/runtime-host/codex-terminal-protocol.d.ts +35 -0
- package/dist-server/server/runtime-host/codex-terminal-protocol.js +243 -0
- package/dist-server/server/runtime-host/codex-terminal.d.ts +16 -0
- package/dist-server/server/runtime-host/codex-terminal.js +120 -0
- package/dist-server/server/runtime-host/index.d.ts +5 -0
- package/dist-server/server/runtime-host/index.js +3 -0
- package/dist-server/server/runtime-host/local.d.ts +29 -0
- package/dist-server/server/runtime-host/local.js +90 -0
- package/dist-server/server/runtime-host/policy.d.ts +8 -0
- package/dist-server/server/runtime-host/policy.js +78 -0
- package/dist-server/server/runtime-host/types.d.ts +52 -0
- package/dist-server/server/runtime-host/types.js +62 -0
- package/dist-server/server/structured-query-evidence.d.ts +7 -0
- package/dist-server/server/structured-query-evidence.js +47 -0
- package/dist-server/server/structured-runtime.d.ts +52 -0
- package/dist-server/server/structured-runtime.js +130 -0
- package/dist-server/server/structured-scope.d.ts +4 -0
- package/dist-server/server/structured-scope.js +12 -0
- package/dist-server/server/turn.d.ts +6 -1
- package/dist-server/server/turn.js +82 -25
- package/dist-server/server/wire-types.d.ts +67 -3
- package/dist-server/server/wren-profiles.js +1 -1
- package/managed-wren/README.md +60 -0
- package/managed-wren/dependency-wheels.json +282 -0
- package/managed-wren/manifest.json +373 -0
- package/managed-wren/release-inputs.json +45 -0
- package/managed-wren/runtime-tree.cjs +115 -0
- package/managed-wren/source-inputs.json +40 -0
- package/package.json +15 -6
- package/profiles/genbi-default/context/binding.yml +17 -7
- package/profiles/genbi-default/context/context.json +146 -0
- package/profiles/genbi-default/ir.golden.json +39 -43
- package/profiles/genbi-default/profile.yml +31 -0
- package/profiles/genbi-enrich-context/context/binding.yml +14 -3
- package/profiles/genbi-enrich-context/context/context.json +146 -0
- package/profiles/genbi-enrich-context/ir.golden.json +4 -5
- package/profiles/genbi-monitor/context/binding.yml +13 -5
- package/profiles/genbi-monitor/context/context.json +146 -0
- package/profiles/genbi-monitor/ir.golden.json +13 -7
- package/profiles/genbi-report/context/binding.yml +19 -0
- package/profiles/genbi-report/context/context.json +223 -0
- package/profiles/genbi-report/ir.golden.json +328 -0
- package/profiles/genbi-report/profile.yml +38 -0
- package/profiles/genbi-setup/ir.golden.json +3 -1
- package/dist/assets/index-Xs4q7pNo.css +0 -1
|
@@ -1,13 +1,16 @@
|
|
|
1
|
+
import { GATEWAY_API_KEY_ENV } from "./env-detect.js";
|
|
1
2
|
/**
|
|
2
3
|
* Maps the wizard's persisted `RuntimeSettings` to the harness-level `AuthChoice` that should
|
|
3
4
|
* actually be dispatched with. `RuntimeSettings.authMode` is the wire/persisted vocabulary
|
|
4
|
-
* (`"subscription" | "byo" | "local"`); `AuthChoice.mode` is the harness vocabulary
|
|
5
|
+
* (`"subscription" | "byo" | "local" | "gateway"`); `AuthChoice.mode` is the harness vocabulary
|
|
5
6
|
* (`"subscription" | "api-key" | "local" | "gateway"`) — `"byo"` maps to `"api-key"`.
|
|
6
7
|
*
|
|
7
8
|
* For the `openai-compatible` adapter, the BFF reads `OPENAI_API_KEY` from its own process env
|
|
8
9
|
* and injects it explicitly as `config.apiKey` — that adapter has no env self-read of its own.
|
|
9
10
|
* For `anthropic`, `config.apiKey` is deliberately omitted so the adapter self-reads
|
|
10
11
|
* `ANTHROPIC_API_KEY` from the process env itself (see `harness/providers/adapters/anthropic.ts`).
|
|
12
|
+
* For `gateway` (the `pi-ai` adapter), the BFF reads `GENBI_GATEWAY_API_KEY` and injects it as
|
|
13
|
+
* `config.apiKey` the same way — pi-ai never reads env vars or credential files on its own.
|
|
11
14
|
* The key value is never read into `RuntimeSettings`, SQLite, or any log — only referenced here,
|
|
12
15
|
* transiently, to build the in-memory `AuthChoice` passed straight to the adapter factory.
|
|
13
16
|
*/
|
|
@@ -17,6 +20,19 @@ export function toAuthChoiceFromRuntimeSettings(settings) {
|
|
|
17
20
|
return { mode: "subscription", provider: settings.subscriptionProvider ?? "claude" };
|
|
18
21
|
case "local":
|
|
19
22
|
return { mode: "local" };
|
|
23
|
+
case "gateway": {
|
|
24
|
+
const config = {};
|
|
25
|
+
if (settings.gatewayProvider !== undefined)
|
|
26
|
+
config["provider"] = settings.gatewayProvider;
|
|
27
|
+
if (settings.gatewayModel !== undefined)
|
|
28
|
+
config["model"] = settings.gatewayModel;
|
|
29
|
+
if (settings.gatewayBaseURL?.trim())
|
|
30
|
+
config["baseUrl"] = settings.gatewayBaseURL;
|
|
31
|
+
const apiKey = process.env[GATEWAY_API_KEY_ENV];
|
|
32
|
+
if (apiKey?.trim())
|
|
33
|
+
config["apiKey"] = apiKey;
|
|
34
|
+
return { mode: "gateway", config };
|
|
35
|
+
}
|
|
20
36
|
case "byo": {
|
|
21
37
|
const adapter = settings.apiKeyAdapter ?? "anthropic";
|
|
22
38
|
if (adapter === "openai-compatible") {
|
|
@@ -34,3 +50,52 @@ export function toAuthChoiceFromRuntimeSettings(settings) {
|
|
|
34
50
|
}
|
|
35
51
|
}
|
|
36
52
|
}
|
|
53
|
+
const RUNTIME_AUTH_MODES = new Set(["subscription", "byo", "local", "gateway"]);
|
|
54
|
+
/** True for the wire `authMode` values `toAuthChoiceFromRuntimeSettings` can map. */
|
|
55
|
+
export function isRuntimeAuthMode(value) {
|
|
56
|
+
return typeof value === "string" && RUNTIME_AUTH_MODES.has(value);
|
|
57
|
+
}
|
|
58
|
+
function stringField(config, key) {
|
|
59
|
+
const value = config?.[key];
|
|
60
|
+
return typeof value === "string" && value.trim().length > 0 ? value : undefined;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* The inverse of `toAuthChoiceFromRuntimeSettings`, for reporting the auth the BFF booted with
|
|
64
|
+
* (`WREN_HARNESS_MODE` / `MODEL` / `ENDPOINT` / flags) before the user has saved a runtime.
|
|
65
|
+
* Overlays only the auth-selection fields onto `base`; tier rows, deployment and hybrid stay the
|
|
66
|
+
* store's. Credentials never appear in the result. A boot adapter the wizard cannot represent
|
|
67
|
+
* leaves `base` untouched.
|
|
68
|
+
*/
|
|
69
|
+
export function runtimeSettingsFromAuthChoice(choice, base) {
|
|
70
|
+
switch (choice.mode) {
|
|
71
|
+
case "subscription":
|
|
72
|
+
return { ...base, authMode: "subscription", subscriptionProvider: choice.provider };
|
|
73
|
+
case "local":
|
|
74
|
+
return { ...base, authMode: "local" };
|
|
75
|
+
case "gateway": {
|
|
76
|
+
const provider = stringField(choice.config, "provider");
|
|
77
|
+
const model = stringField(choice.config, "model");
|
|
78
|
+
const baseURL = stringField(choice.config, "baseUrl");
|
|
79
|
+
return {
|
|
80
|
+
...base,
|
|
81
|
+
authMode: "gateway",
|
|
82
|
+
...(provider !== undefined ? { gatewayProvider: provider } : {}),
|
|
83
|
+
...(model !== undefined ? { gatewayModel: model } : {}),
|
|
84
|
+
...(baseURL !== undefined ? { gatewayBaseURL: baseURL } : {}),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
case "api-key": {
|
|
88
|
+
if (choice.adapter !== "anthropic" && choice.adapter !== "openai-compatible")
|
|
89
|
+
return base;
|
|
90
|
+
const model = stringField(choice.config, "model");
|
|
91
|
+
const baseURL = choice.adapter === "openai-compatible" ? stringField(choice.config, "baseURL") : undefined;
|
|
92
|
+
return {
|
|
93
|
+
...base,
|
|
94
|
+
authMode: "byo",
|
|
95
|
+
apiKeyAdapter: choice.adapter,
|
|
96
|
+
...(model !== undefined ? { apiKeyModel: model } : {}),
|
|
97
|
+
...(baseURL !== undefined ? { apiKeyBaseURL: baseURL } : {}),
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { createCodexRuntimeComposition, readCodexBootConfiguration } from "./codex-runtime-composition.js";
|
|
3
|
+
import { createCodexStructuredAdapter } from "./codex-structured-ask.js";
|
|
4
|
+
import { StructuredRuntime } from "./structured-runtime.js";
|
|
5
|
+
import { shutdownNativeResources } from "./native-shutdown.js";
|
|
2
6
|
/**
|
|
3
7
|
* The BFF's process entrypoint. This is the ONLY file
|
|
4
8
|
* in `server/` that wires real production values (env vars, a real `Store`
|
|
@@ -30,15 +34,21 @@
|
|
|
30
34
|
* clear 500 at dispatch time instead.
|
|
31
35
|
* WREN_HARNESS_PROFILE profileSource; default resolveDefaultProfileSource()
|
|
32
36
|
* WREN_HARNESS_MODE subscription|api-key|local|gateway (auth mode)
|
|
33
|
-
* WREN_HARNESS_PROVIDER subscription provider: claude|codex
|
|
37
|
+
* WREN_HARNESS_PROVIDER subscription provider: claude|codex; gateway mode: pi-ai provider id
|
|
34
38
|
* WREN_HARNESS_ADAPTER api-key adapter name
|
|
35
39
|
* WREN_HARNESS_API_KEY api-key credential
|
|
36
40
|
* WREN_HARNESS_MODEL model override
|
|
37
|
-
* WREN_HARNESS_ENDPOINT local
|
|
41
|
+
* WREN_HARNESS_ENDPOINT local endpoint URL; gateway mode: optional custom base URL
|
|
42
|
+
* GENBI_GATEWAY_API_KEY gateway-mode credential for the selected pi-ai provider. Read by
|
|
43
|
+
* the BFF and passed per request; never persisted, and pi-ai's own
|
|
44
|
+
* provider env vars / credential files are never consulted
|
|
38
45
|
* WREN_HARNESS_WARBLE_BIN path to the warble CLI binary
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
* the
|
|
46
|
+
* WREN_HARNESS_CONTEXT_LOADER_BIN
|
|
47
|
+
* path to the wren-context-loader binary, which renders the bound
|
|
48
|
+
* wren project into the prepared-context document a `kind: prepared`
|
|
49
|
+
* binding reads. Falls back to this repo's own Rust build; resolution
|
|
50
|
+
* loud-fails rather than degrading to Warble's built-in MDL adapter
|
|
51
|
+
* (see harness/compile/context-loader.ts)
|
|
42
52
|
* WREN_HARNESS_AGENT_SDK_BIN path to the agent-sdk CLI binary (dispatched)
|
|
43
53
|
* WREN_HARNESS_CODEX_LOCAL_BIN path to the warble-codex-local dispatcher CLI
|
|
44
54
|
* WREN_HARNESS_CODEX_BIN optional path to the Codex CLI used by that dispatcher
|
|
@@ -95,7 +105,8 @@
|
|
|
95
105
|
*/
|
|
96
106
|
import { serve } from "@hono/node-server";
|
|
97
107
|
import { createRequire } from "node:module";
|
|
98
|
-
import { existsSync } from "node:fs";
|
|
108
|
+
import { existsSync, realpathSync } from "node:fs";
|
|
109
|
+
import os from "node:os";
|
|
99
110
|
import path from "node:path";
|
|
100
111
|
import { fileURLToPath } from "node:url";
|
|
101
112
|
import { WebSocketServer } from "ws";
|
|
@@ -104,7 +115,10 @@ import { buildTierBindingFromFlags, parseChatTimeoutMs, parseSetupMaxTurns, reso
|
|
|
104
115
|
import { createApp } from "./app.js";
|
|
105
116
|
import { recoverBootstrapProjectBinding } from "./bootstrap-project-binding.js";
|
|
106
117
|
import { createHarnessProfileSources } from "./harness-profile-sources.js";
|
|
118
|
+
import { ProfileRegistry } from "./profile-registry.js";
|
|
119
|
+
import { getWarbleIdentity } from "../harness/compile/warble-identity.js";
|
|
107
120
|
import { compileProfile, compileRawProfile } from "../harness/compile/index.js";
|
|
121
|
+
import { resolveWarbleBinary } from "../harness/compile/resolve-binary.js";
|
|
108
122
|
import { toAuthChoiceFromRuntimeSettings } from "./auth-choice.js";
|
|
109
123
|
import { Store } from "./db.js";
|
|
110
124
|
import { resolveEnrichmentBinding, resolveProjectIdentity } from "./enrichment.js";
|
|
@@ -113,6 +127,9 @@ import { dispatchInteractiveArtifacts, getInteractiveTerminalReadiness, Interact
|
|
|
113
127
|
import { createProbedPtyFactory, ensureDarwinNodePtySpawnHelper } from "./node-pty-host.js";
|
|
114
128
|
import { NativeSessionService } from "./native-sessions.js";
|
|
115
129
|
import { NativeArtifactService } from "./native-artifacts.js";
|
|
130
|
+
import { RuntimeHost } from "./runtime-host/local.js";
|
|
131
|
+
import { resolveManagedWrenRuntime } from "./managed-wren-runtime.js";
|
|
132
|
+
import { assertNativeExecutableIdentity, assertNativeRuntimeSpec, attestNativeExecutable, buildNativeChildEnvironment, buildNativeRuntimeSpec, resolveNativeExecutable } from "./native-runtime-spec.js";
|
|
116
133
|
import { initializeNativeSessionStateBase, legacyInteractiveWorkspace, validateLegacyInteractiveWorkspace } from "./native-session-workspace.js";
|
|
117
134
|
import { codexModelsForRuntime, compileUnboundProfileTierNames, effectiveTierModel, materializeRuntimeRouteOptions, } from "./runtime-binding.js";
|
|
118
135
|
import { invalidateBundleAgentIdsCache } from "./turn.js";
|
|
@@ -170,6 +187,42 @@ async function main() {
|
|
|
170
187
|
// (server/turn.ts) and `server/spa.ts`.
|
|
171
188
|
const candidateStaticDir = path.join(packageRoot, "dist");
|
|
172
189
|
const staticDir = existsSync(path.join(candidateStaticDir, "index.html")) ? candidateStaticDir : undefined;
|
|
190
|
+
// Resolve the Warble binary once, here, and hand the resolved path to every
|
|
191
|
+
// consumer. Passing the bare name `"warble"` looked like a harmless default and
|
|
192
|
+
// was not: `resolveWarbleBinary` treats any value it is given as an explicit
|
|
193
|
+
// path and only searches when given nothing, so that default disabled the one
|
|
194
|
+
// lookup that finds the `@warble/cli` an install actually ships. It also made
|
|
195
|
+
// two resolvers disagree about the same string — the native preflight resolves
|
|
196
|
+
// a bare name through PATH and reported the producer healthy, while every
|
|
197
|
+
// compile failed.
|
|
198
|
+
const resolvedWarbleBin = await resolveWarbleBinary(flags.warbleBin).catch((error) => {
|
|
199
|
+
// Not fatal: the app still starts, and readiness reports the reason per
|
|
200
|
+
// vendor rather than the process dying at boot over a feature the user may
|
|
201
|
+
// not reach.
|
|
202
|
+
process.stderr.write(`warning: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
203
|
+
return undefined;
|
|
204
|
+
});
|
|
205
|
+
const warbleBinOption = resolvedWarbleBin !== undefined ? { warbleBin: resolvedWarbleBin } : {};
|
|
206
|
+
const nativeHome = realpathSync(os.homedir());
|
|
207
|
+
const nodeExecutable = attestNativeExecutable("node", process.execPath);
|
|
208
|
+
const producerExecutable = resolvedWarbleBin ? attestNativeExecutable("producer", resolvedWarbleBin) : undefined;
|
|
209
|
+
const bootPath = process.env["PATH"];
|
|
210
|
+
const claudeExecutable = resolveNativeExecutable("vendor", "claude", bootPath);
|
|
211
|
+
const codexExecutable = resolveNativeExecutable("vendor", "codex", bootPath);
|
|
212
|
+
const vendorExecutables = Object.freeze({
|
|
213
|
+
...(claudeExecutable ? { claude: claudeExecutable } : {}),
|
|
214
|
+
...(codexExecutable ? { codex: codexExecutable } : {}),
|
|
215
|
+
});
|
|
216
|
+
const nativeWrenExecutable = resolveNativeExecutable("wren", "wren", bootPath);
|
|
217
|
+
const nativeToolDirectories = Object.freeze([...new Set([
|
|
218
|
+
path.dirname(nodeExecutable.executable),
|
|
219
|
+
// Native auth and CLI tools need fixed OS utilities, even with a sanitized PATH.
|
|
220
|
+
...(["darwin", "linux"].includes(process.platform) ? ["/usr/bin", "/bin", "/usr/sbin", "/sbin"].map((directory) => realpathSync(directory)) : []),
|
|
221
|
+
...(nativeWrenExecutable ? [path.dirname(nativeWrenExecutable.executable)] : []),
|
|
222
|
+
...Object.values(vendorExecutables).filter((value) => value !== undefined).map((value) => path.dirname(value.executable)),
|
|
223
|
+
...(producerExecutable ? [path.dirname(producerExecutable.executable)] : []),
|
|
224
|
+
])]);
|
|
225
|
+
const nativeHostEnvironment = buildNativeChildEnvironment({ toolDirectories: nativeToolDirectories, home: nativeHome });
|
|
173
226
|
const profileSource = flags.profile ?? resolveDefaultProfileSource();
|
|
174
227
|
// Frozen at boot: GET /api/harness accepts only a purpose and must never
|
|
175
228
|
// inherit a caller-supplied RouteOptions.profileSource.
|
|
@@ -186,6 +239,19 @@ async function main() {
|
|
|
186
239
|
// Initialization creates the fixed private namespaces once at startup.
|
|
187
240
|
const nativeMaterializationState = initializeNativeSessionStateBase(dbPath);
|
|
188
241
|
const store = new Store(dbPath);
|
|
242
|
+
// Conversation profiles a session may start inside: the shipped ones under this package's
|
|
243
|
+
// `profiles/` plus any the operator adds under the workspace. Registration compiles each one, so
|
|
244
|
+
// it runs in the background; the first `/api/profiles` request awaits the same promise.
|
|
245
|
+
const profileRegistry = new ProfileRegistry({
|
|
246
|
+
store,
|
|
247
|
+
builtinProfilesDir: path.dirname(harnessProfileSources.analysis),
|
|
248
|
+
userProfilesDir: path.join(workspaceRoot, "profiles"),
|
|
249
|
+
compileRaw: (source) => compileRawProfile({ profileSource: source, mode: "native", ...warbleBinOption }),
|
|
250
|
+
warbleIdentity: async () => (resolvedWarbleBin !== undefined ? getWarbleIdentity(resolvedWarbleBin) : "warble:unresolved"),
|
|
251
|
+
});
|
|
252
|
+
void profileRegistry.ensureReady().catch((error) => {
|
|
253
|
+
process.stderr.write(`warning: profile registry failed to initialize: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
254
|
+
});
|
|
189
255
|
const getCodexModels = () => codexModelsForRuntime(store.getRuntimeSettings());
|
|
190
256
|
const getRuntimeTierNames = () => compileUnboundProfileTierNames({
|
|
191
257
|
profileSource,
|
|
@@ -243,9 +309,11 @@ async function main() {
|
|
|
243
309
|
boundProject = identity.path;
|
|
244
310
|
const { revokedNativeSessionIds } = store.activateEnrichmentBindingAndRevokeBoundNativeSessions(identity);
|
|
245
311
|
nativeSessions.revokeBindingCapabilities(revokedNativeSessionIds);
|
|
312
|
+
deps?.structuredRuntime?.revoke();
|
|
246
313
|
invalidateBundleAgentIdsCache(deps);
|
|
247
314
|
}
|
|
248
315
|
function unbindProject() {
|
|
316
|
+
deps?.structuredRuntime?.revoke();
|
|
249
317
|
boundProject = undefined;
|
|
250
318
|
invalidateBundleAgentIdsCache(deps);
|
|
251
319
|
}
|
|
@@ -274,6 +342,10 @@ async function main() {
|
|
|
274
342
|
// a reset) later in the same boot. The actual logic lives in `./wren-home.js` so it's directly
|
|
275
343
|
// unit-testable without going through a mocked `TurnDeps`.
|
|
276
344
|
const originalWrenHome = process.env["WREN_HOME"];
|
|
345
|
+
const operatorWrenHome = originalWrenHome?.trim() || path.join(nativeHome, ".wren");
|
|
346
|
+
const nativeSourceWrenHome = () => store.getSetupMode() === "create"
|
|
347
|
+
? path.join(workspaceRoot, ".wren")
|
|
348
|
+
: operatorWrenHome;
|
|
277
349
|
const setWrenHomeForSetupMode = createSetWrenHomeForSetupMode(workspaceRoot, originalWrenHome);
|
|
278
350
|
// Mutable auth-choice binding — the auth-choice mirror of boundProject above. Starts at the
|
|
279
351
|
// boot-resolved `authChoice` and can be rebound later by PUT /api/config/runtime
|
|
@@ -436,7 +508,12 @@ async function main() {
|
|
|
436
508
|
try {
|
|
437
509
|
const require = createRequire(import.meta.url);
|
|
438
510
|
ensureDarwinNodePtySpawnHelper(require.resolve("node-pty"));
|
|
439
|
-
|
|
511
|
+
assertNativeExecutableIdentity(nodeExecutable);
|
|
512
|
+
return await createProbedPtyFactory(await import("node-pty"), {
|
|
513
|
+
cwd: nativeMaterializationState.root,
|
|
514
|
+
env: nativeHostEnvironment,
|
|
515
|
+
probeExecutable: nodeExecutable.executable,
|
|
516
|
+
});
|
|
440
517
|
}
|
|
441
518
|
catch {
|
|
442
519
|
throw new InteractiveLaunchError("interactive terminal host cannot spawn local processes on this machine");
|
|
@@ -453,14 +530,24 @@ async function main() {
|
|
|
453
530
|
async function prepareInteractiveTerminal(target, binding) {
|
|
454
531
|
if (enrichIrPath === undefined)
|
|
455
532
|
throw new InteractiveLaunchError("interactive enrichment artifacts are not configured");
|
|
533
|
+
if (producerExecutable === undefined)
|
|
534
|
+
throw new InteractiveLaunchError("interactive enrichment materialization failed");
|
|
456
535
|
const artifactRoot = legacyInteractiveWorkspace(nativeMaterializationState, binding.path, target);
|
|
536
|
+
const dispatchEnvironment = buildNativeChildEnvironment({
|
|
537
|
+
toolDirectories: nativeToolDirectories,
|
|
538
|
+
home: nativeHome,
|
|
539
|
+
projectPath: binding.path,
|
|
540
|
+
});
|
|
457
541
|
return prepareInteractiveHandoff({
|
|
458
542
|
target,
|
|
459
543
|
binding,
|
|
460
544
|
artifactRoot,
|
|
461
545
|
materializationState: nativeMaterializationState,
|
|
462
|
-
materialize: () => dispatchInteractiveArtifacts({
|
|
463
|
-
}, {
|
|
546
|
+
materialize: () => dispatchInteractiveArtifacts({ producer: producerExecutable, irPath: enrichIrPath, target, cwd: artifactRoot, env: dispatchEnvironment, boundProject: binding.path, materializationState: nativeMaterializationState }),
|
|
547
|
+
}, {
|
|
548
|
+
getCurrentBinding: currentInteractiveBinding,
|
|
549
|
+
resolveExecutable: (executable) => executable === "claude" ? vendorExecutables.claude : vendorExecutables.codex,
|
|
550
|
+
});
|
|
464
551
|
}
|
|
465
552
|
const nativeMcpUrl = `http://127.0.0.1:${port}/api/native-sessions/mcp`;
|
|
466
553
|
const nativeArtifacts = new NativeArtifactService({
|
|
@@ -470,6 +557,27 @@ async function main() {
|
|
|
470
557
|
mcpUrl: process.env["WREN_HARNESS_NATIVE_MCP_URL"] ?? nativeMcpUrl,
|
|
471
558
|
getBinding: currentInteractiveBinding,
|
|
472
559
|
});
|
|
560
|
+
const nativeTerminalHostAvailable = async () => {
|
|
561
|
+
try {
|
|
562
|
+
await loadPty();
|
|
563
|
+
return true;
|
|
564
|
+
}
|
|
565
|
+
catch {
|
|
566
|
+
return false;
|
|
567
|
+
}
|
|
568
|
+
};
|
|
569
|
+
// This is the sole production composition of the Phase-1 policy. Browser
|
|
570
|
+
// requests never reach this selection or its executable/policy inputs.
|
|
571
|
+
const codexConfiguration = readCodexBootConfiguration(process.env);
|
|
572
|
+
const codexComposition = createCodexRuntimeComposition({ configuration: codexConfiguration, packageRoot,
|
|
573
|
+
producerBinary: producerExecutable?.executable ?? "", profileSource: harnessProfileSources.analysis, store,
|
|
574
|
+
getBinding: currentInteractiveBinding, sourceWrenHome: nativeSourceWrenHome, artifacts: nativeArtifacts });
|
|
575
|
+
const nativeRuntimeHost = new RuntimeHost({
|
|
576
|
+
selected: codexConfiguration.selected,
|
|
577
|
+
deployment: process.env["NODE_ENV"] === "production" ? "production" : "development",
|
|
578
|
+
localAvailable: () => codexConfiguration.allowLocal ? nativeTerminalHostAvailable() : false,
|
|
579
|
+
vendorProbes: { "codex-app-server": codexComposition.probe },
|
|
580
|
+
});
|
|
473
581
|
const nativeSessions = new NativeSessionService({
|
|
474
582
|
store,
|
|
475
583
|
terminalManager,
|
|
@@ -486,30 +594,56 @@ async function main() {
|
|
|
486
594
|
// Setup has no binding by definition, so it compiles the profile's authored
|
|
487
595
|
// context with no user project composed in; its golden resolves nothing
|
|
488
596
|
// either, which is why Setup never showed this symptom.
|
|
489
|
-
resolveDispatchIr: async (purpose, binding) => {
|
|
490
|
-
|
|
597
|
+
resolveDispatchIr: async (purpose, binding, selectedProfileSource) => {
|
|
598
|
+
// A selected conversation profile compiles from the registry's copy; everything else from the
|
|
599
|
+
// boot-time source for its purpose.
|
|
600
|
+
const profileSource = selectedProfileSource ?? harnessProfileSources[purpose];
|
|
491
601
|
const compiled = purpose === "setup" || binding === undefined
|
|
492
|
-
? await compileRawProfile({ profileSource, mode: "native",
|
|
493
|
-
: await compileProfile({ profileSource, userProject: binding.path, mode: "native",
|
|
602
|
+
? await compileRawProfile({ profileSource, mode: "native", ...warbleBinOption })
|
|
603
|
+
: await compileProfile({ profileSource, userProject: binding.path, mode: "native", ...warbleBinOption });
|
|
494
604
|
return compiled.irPath;
|
|
495
605
|
},
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
try {
|
|
500
|
-
await loadPty();
|
|
501
|
-
return true;
|
|
502
|
-
}
|
|
503
|
-
catch {
|
|
504
|
-
return false;
|
|
505
|
-
}
|
|
606
|
+
resolveConversationProfile: async (id) => {
|
|
607
|
+
const { row, sourceDir } = await profileRegistry.resolveConversationProfileSource(id);
|
|
608
|
+
return { id: row.id, sourceDir, entry: row.entryKind === "agent" && row.entryVerb !== null ? { kind: "agent", verb: row.entryVerb } : { kind: "scope" } };
|
|
506
609
|
},
|
|
610
|
+
listConversationProfiles: async () => (await profileRegistry.list())
|
|
611
|
+
.filter((row) => row.role === "conversation")
|
|
612
|
+
.map((row) => ({
|
|
613
|
+
id: row.id,
|
|
614
|
+
selectable: row.admissionStatus === "admitted",
|
|
615
|
+
...(row.admissionReason !== null ? { reason: row.admissionReason } : {}),
|
|
616
|
+
...(row.entryKind !== null ? { entryKind: row.entryKind } : {}),
|
|
617
|
+
...(row.entryVerb !== null ? { entryVerb: row.entryVerb } : {}),
|
|
618
|
+
})),
|
|
619
|
+
// The service's option is a required string; when resolution failed the bare
|
|
620
|
+
// name is what the preflight will report as unresolvable, with its reason.
|
|
621
|
+
warbleBin: producerExecutable?.executable ?? "warble",
|
|
622
|
+
terminalHostAvailable: nativeTerminalHostAvailable,
|
|
623
|
+
runtimeHost: nativeRuntimeHost,
|
|
624
|
+
directCodex: codexComposition.native,
|
|
625
|
+
resolveManagedCodexWrenRuntime: () => resolveManagedWrenRuntime({ packageRoot }),
|
|
626
|
+
nativeHome,
|
|
627
|
+
sourceWrenHome: nativeSourceWrenHome,
|
|
628
|
+
...(baseRouteOptions.codexHome ? { codexHome: baseRouteOptions.codexHome } : {}),
|
|
629
|
+
vendorExecutables,
|
|
630
|
+
nodeExecutable,
|
|
631
|
+
childToolDirectories: nativeToolDirectories,
|
|
632
|
+
...(bootPath !== undefined ? { pathValue: bootPath } : {}),
|
|
507
633
|
artifactService: nativeArtifacts,
|
|
508
634
|
});
|
|
635
|
+
// Explicit opt-in remains behind the packaged exact-version certification gate.
|
|
636
|
+
const structuredRuntime = new StructuredRuntime({
|
|
637
|
+
selected: { codex: codexConfiguration.selected, claude: "local" }, allowLocal: codexConfiguration.allowLocal, deployment,
|
|
638
|
+
adapters: { codex: createCodexStructuredAdapter(codexComposition.structured) },
|
|
639
|
+
localRoute: route, localSetupFor: (choice) => setupRunnerFor?.(choice) ?? setupRunner,
|
|
640
|
+
});
|
|
509
641
|
deps = {
|
|
510
642
|
store,
|
|
643
|
+
structuredRuntime,
|
|
511
644
|
nativeSessions,
|
|
512
645
|
nativeArtifacts,
|
|
646
|
+
profileRegistry,
|
|
513
647
|
route,
|
|
514
648
|
baseRouteOptions,
|
|
515
649
|
describeBundle: (options) => {
|
|
@@ -521,8 +655,8 @@ async function main() {
|
|
|
521
655
|
: {}),
|
|
522
656
|
});
|
|
523
657
|
},
|
|
524
|
-
describeHarnessBundle: (purpose, options) => {
|
|
525
|
-
const profileSource = harnessProfileSources[purpose];
|
|
658
|
+
describeHarnessBundle: (purpose, options, selectedProfileSource) => {
|
|
659
|
+
const profileSource = selectedProfileSource ?? harnessProfileSources[purpose];
|
|
526
660
|
const common = {
|
|
527
661
|
...options,
|
|
528
662
|
profileSource,
|
|
@@ -551,7 +685,24 @@ async function main() {
|
|
|
551
685
|
const { spec } = await prepareInteractiveTerminal(target, binding);
|
|
552
686
|
const manager = await terminalManager();
|
|
553
687
|
validateLegacyInteractiveWorkspace(nativeMaterializationState, binding.path, spec.cwd);
|
|
554
|
-
|
|
688
|
+
if (producerExecutable === undefined || spec.hostExecutable === undefined)
|
|
689
|
+
throw new InteractiveLaunchError("interactive enrichment materialization failed");
|
|
690
|
+
const runtimeSpec = buildNativeRuntimeSpec({
|
|
691
|
+
backend: "local",
|
|
692
|
+
vendor: target === "claude-code:interactive" ? "claude" : "codex",
|
|
693
|
+
executables: [
|
|
694
|
+
nodeExecutable,
|
|
695
|
+
producerExecutable,
|
|
696
|
+
attestNativeExecutable("vendor", spec.hostExecutable),
|
|
697
|
+
],
|
|
698
|
+
toolDirectories: nativeToolDirectories,
|
|
699
|
+
workspace: spec.cwd,
|
|
700
|
+
home: nativeHome,
|
|
701
|
+
binding,
|
|
702
|
+
...(target === "codex:interactive" && baseRouteOptions.codexHome ? { codexHome: baseRouteOptions.codexHome } : {}),
|
|
703
|
+
});
|
|
704
|
+
assertNativeRuntimeSpec(runtimeSpec);
|
|
705
|
+
return manager.start(spec, undefined, runtimeSpec.childEnvironment);
|
|
555
706
|
},
|
|
556
707
|
prepareInteractiveTerminal: async ({ target, binding }) => {
|
|
557
708
|
return (await prepareInteractiveTerminal(target, binding)).handoff;
|
|
@@ -573,6 +724,7 @@ async function main() {
|
|
|
573
724
|
return false;
|
|
574
725
|
}
|
|
575
726
|
},
|
|
727
|
+
resolveExecutable: (executable) => executable === "claude" ? vendorExecutables.claude : vendorExecutables.codex,
|
|
576
728
|
});
|
|
577
729
|
},
|
|
578
730
|
workspaceRoot,
|
|
@@ -586,6 +738,25 @@ async function main() {
|
|
|
586
738
|
const server = serve({ fetch: app.fetch, websocket: { server: websocket }, hostname: "127.0.0.1", port }, (info) => {
|
|
587
739
|
process.stdout.write(`wren-harness BFF listening on http://127.0.0.1:${info.port} (db: ${dbPath})\n`);
|
|
588
740
|
});
|
|
741
|
+
const shutdown = () => {
|
|
742
|
+
const deadline = setTimeout(() => process.exit(1), 12_000);
|
|
743
|
+
void shutdownNativeResources({
|
|
744
|
+
shutdownSessions: async () => {
|
|
745
|
+
const results = await Promise.allSettled([nativeSessions.shutdown(), structuredRuntime.shutdown(), codexComposition.shutdown()]);
|
|
746
|
+
if (results.some((result) => result.status === "rejected"))
|
|
747
|
+
throw new Error("Session cleanup failed");
|
|
748
|
+
},
|
|
749
|
+
closeTerminals: () => interactiveTerminal?.closeAll(),
|
|
750
|
+
closeServer: () => { server.close(); },
|
|
751
|
+
}).then((code) => {
|
|
752
|
+
if (code)
|
|
753
|
+
process.stderr.write("error: native session cleanup did not complete\n");
|
|
754
|
+
clearTimeout(deadline);
|
|
755
|
+
process.exit(code);
|
|
756
|
+
});
|
|
757
|
+
};
|
|
758
|
+
process.once("SIGTERM", shutdown);
|
|
759
|
+
process.once("SIGINT", shutdown);
|
|
589
760
|
// Without this, a port that is already taken surfaces as an unhandled 'error' event and a raw
|
|
590
761
|
// Node stack trace -- the first thing a new user is likely to hit, and the least readable.
|
|
591
762
|
server.on("error", (error) => {
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { HostQueryObservation } from "../harness/route/codex-host-query.js";
|
|
2
|
+
import { type RenderEnvelope } from "../harness/render/envelope.js";
|
|
3
|
+
/**
|
|
4
|
+
* Grounds a codex:local answer against the queries this turn's host query service executed,
|
|
5
|
+
* with the component path's predicate (`citedGroundedQuery`: a tool-proven definition that
|
|
6
|
+
* reads at least one table, cited by `query_id`, else by whitespace-insensitive SQL).
|
|
7
|
+
*
|
|
8
|
+
* Verified only when the answer cites at least one query and every citation grounds. The
|
|
9
|
+
* envelope is then rebuilt from the observations alone: one `table` and one `definition` block
|
|
10
|
+
* (carrying the observed `query_id`) per cited query, plus the model's `summary` text as written.
|
|
11
|
+
* A cited result the host saw truncated still grounds the rows it shows; the host then appends
|
|
12
|
+
* a note to `summary`, the only place the render contract carries such text. Anything else stays the model's answer with
|
|
13
|
+
* `verified: false`: no citation, a citation to a query this turn never ran (including one from
|
|
14
|
+
* an earlier turn), a query that read no table, or chart / KPI blocks the host cannot rebuild.
|
|
15
|
+
*/
|
|
16
|
+
export declare function groundCodexLocalEnvelope(envelope: RenderEnvelope, finalText: string, observations: readonly HostQueryObservation[]): RenderEnvelope;
|
|
17
|
+
/** Host-written, never model-written: a cited table that is only the first rows of its result. */
|
|
18
|
+
export declare function truncationNote(shown: number): string;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { citedGroundedQuery } from "../harness/components/normalize.js";
|
|
2
|
+
import { extractJsonCandidateFromText } from "../harness/render/envelope.js";
|
|
3
|
+
import { namedAnswerQueryId, namedAnswerSql, sqlKey } from "../harness/render/answering-query.js";
|
|
4
|
+
/** Blocks whose values the host cannot rebuild from one observed table; their presence leaves an answer unverified. */
|
|
5
|
+
const UNREBUILDABLE_DATA_BLOCKS = new Set(["chart", "kpi_card"]);
|
|
6
|
+
function record(value) {
|
|
7
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : undefined;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* The queries a codex:local answer names. A `{blocks}` answer cites through its `definition`
|
|
11
|
+
* blocks; a flat or query-result answer through its own `definition` / top-level fields, read
|
|
12
|
+
* from the raw text because envelope normalization drops a cited `query_id`. A host id wins over
|
|
13
|
+
* SQL, like the component path.
|
|
14
|
+
*/
|
|
15
|
+
function citations(envelope, finalText) {
|
|
16
|
+
const raw = extractJsonCandidateFromText(finalText);
|
|
17
|
+
if (!Array.isArray(record(raw)?.blocks)) {
|
|
18
|
+
const queryId = namedAnswerQueryId(raw);
|
|
19
|
+
if (queryId !== undefined)
|
|
20
|
+
return [{ queryId }];
|
|
21
|
+
const sql = namedAnswerSql(raw);
|
|
22
|
+
return sql !== undefined ? [{ sql }] : [];
|
|
23
|
+
}
|
|
24
|
+
const found = [];
|
|
25
|
+
const seen = new Set();
|
|
26
|
+
for (const block of Array.isArray(envelope.blocks) ? envelope.blocks : []) {
|
|
27
|
+
const fields = record(block);
|
|
28
|
+
if (fields?.type !== "definition")
|
|
29
|
+
continue;
|
|
30
|
+
const queryId = typeof fields.query_id === "string" && fields.query_id.length > 0 ? fields.query_id : undefined;
|
|
31
|
+
const sql = typeof fields.sql === "string" && fields.sql.trim() ? fields.sql : undefined;
|
|
32
|
+
const citation = queryId !== undefined ? { queryId } : sql !== undefined ? { sql } : undefined;
|
|
33
|
+
if (!citation)
|
|
34
|
+
continue;
|
|
35
|
+
const key = queryId !== undefined ? `id:${queryId}` : `sql:${sqlKey(sql)}`;
|
|
36
|
+
if (seen.has(key))
|
|
37
|
+
continue;
|
|
38
|
+
seen.add(key);
|
|
39
|
+
found.push(citation);
|
|
40
|
+
}
|
|
41
|
+
return found;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Grounds a codex:local answer against the queries this turn's host query service executed,
|
|
45
|
+
* with the component path's predicate (`citedGroundedQuery`: a tool-proven definition that
|
|
46
|
+
* reads at least one table, cited by `query_id`, else by whitespace-insensitive SQL).
|
|
47
|
+
*
|
|
48
|
+
* Verified only when the answer cites at least one query and every citation grounds. The
|
|
49
|
+
* envelope is then rebuilt from the observations alone: one `table` and one `definition` block
|
|
50
|
+
* (carrying the observed `query_id`) per cited query, plus the model's `summary` text as written.
|
|
51
|
+
* A cited result the host saw truncated still grounds the rows it shows; the host then appends
|
|
52
|
+
* a note to `summary`, the only place the render contract carries such text. Anything else stays the model's answer with
|
|
53
|
+
* `verified: false`: no citation, a citation to a query this turn never ran (including one from
|
|
54
|
+
* an earlier turn), a query that read no table, or chart / KPI blocks the host cannot rebuild.
|
|
55
|
+
*/
|
|
56
|
+
export function groundCodexLocalEnvelope(envelope, finalText, observations) {
|
|
57
|
+
const unverified = { ...envelope, verified: false };
|
|
58
|
+
if (observations.length === 0)
|
|
59
|
+
return unverified;
|
|
60
|
+
const blocks = Array.isArray(envelope.blocks) ? envelope.blocks : [];
|
|
61
|
+
if (blocks.some((block) => UNREBUILDABLE_DATA_BLOCKS.has(String(record(block)?.type))))
|
|
62
|
+
return unverified;
|
|
63
|
+
const cited = citations(envelope, finalText);
|
|
64
|
+
if (cited.length === 0)
|
|
65
|
+
return unverified;
|
|
66
|
+
const rebuilt = [];
|
|
67
|
+
const notes = [];
|
|
68
|
+
for (const citation of cited) {
|
|
69
|
+
const query = citedGroundedQuery(observations, citation);
|
|
70
|
+
if (!query)
|
|
71
|
+
return unverified;
|
|
72
|
+
const observation = observations.find((call) => call.output?.query_id === query.queryId);
|
|
73
|
+
if (observation?.output?.truncated === true)
|
|
74
|
+
notes.push(truncationNote(query.table.rows.length));
|
|
75
|
+
// The definition keeps the observed query's host id, so grounding the rebuilt envelope
|
|
76
|
+
// again (the artifact path does) resolves to the very same observation.
|
|
77
|
+
rebuilt.push({ type: "table", columns: query.table.columns, rows: query.table.rows }, { type: "definition", ...query.definition, ...(query.queryId !== undefined ? { query_id: query.queryId } : {}) });
|
|
78
|
+
}
|
|
79
|
+
const summary = withNotes(typeof envelope.summary === "string" ? envelope.summary : undefined, notes);
|
|
80
|
+
return { blocks: rebuilt, ...(summary !== undefined ? { summary } : {}), verified: true };
|
|
81
|
+
}
|
|
82
|
+
/** Host-written, never model-written: a cited table that is only the first rows of its result. */
|
|
83
|
+
export function truncationNote(shown) {
|
|
84
|
+
return `Host note: this query returned more than ${shown} rows; the table shows only the first ${shown}.`;
|
|
85
|
+
}
|
|
86
|
+
/** Appends each host note once, so grounding an already-grounded envelope again changes nothing. */
|
|
87
|
+
function withNotes(summary, notes) {
|
|
88
|
+
const missing = [...new Set(notes)].filter((note) => !summary?.includes(note));
|
|
89
|
+
if (missing.length === 0)
|
|
90
|
+
return summary;
|
|
91
|
+
return [summary, ...missing].filter((part) => part !== undefined && part.length > 0).join("\n\n");
|
|
92
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { CodexAppServerBackend, CodexLaunchPermit } from "./runtime-host/codex-app-server.js";
|
|
2
|
+
import type { CodexSessionTools } from "./runtime-host/codex-direct-tools.js";
|
|
3
|
+
import type { EnrichmentBinding } from "./enrichment.js";
|
|
4
|
+
import type { NativeSessionRow } from "./db.js";
|
|
5
|
+
/** Server-only provisioner. A ready probe alone never authorizes materialization. */
|
|
6
|
+
export interface DirectCodexProvisioner {
|
|
7
|
+
/** Official remote CLI renderer; the host still owns the governed thread. */
|
|
8
|
+
readonly terminal?: boolean;
|
|
9
|
+
readonly terminalCertified?: (entry: string) => boolean;
|
|
10
|
+
readonly backend: Pick<CodexAppServerBackend, "prepareLaunch" | "open">;
|
|
11
|
+
/** Own and clean partial allocations on failure. Never receives browser paths or credentials. */
|
|
12
|
+
prepare(input: {
|
|
13
|
+
readonly session: NativeSessionRow;
|
|
14
|
+
readonly binding: EnrichmentBinding;
|
|
15
|
+
readonly permit: CodexLaunchPermit;
|
|
16
|
+
readonly signal: AbortSignal;
|
|
17
|
+
readonly assertActive: () => void;
|
|
18
|
+
}): Promise<{
|
|
19
|
+
readonly input: Omit<Parameters<CodexAppServerBackend["open"]>[1], "onEvent" | "tools"> & {
|
|
20
|
+
readonly tools: CodexSessionTools;
|
|
21
|
+
};
|
|
22
|
+
/** Runs after the conversation and scoped tools have closed. */
|
|
23
|
+
dispose(): Promise<void>;
|
|
24
|
+
}>;
|
|
25
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { Store } from "./db.js";
|
|
2
|
+
import type { EnrichmentBinding } from "./enrichment.js";
|
|
3
|
+
import type { NativeArtifactService } from "./native-artifacts.js";
|
|
4
|
+
import type { RuntimeBackendProbeResult } from "./runtime-host/types.js";
|
|
5
|
+
import type { DirectCodexProvisioner } from "./codex-native-session.js";
|
|
6
|
+
import type { CodexStructuredProvisioner } from "./codex-structured-ask.js";
|
|
7
|
+
export interface CodexBootConfiguration {
|
|
8
|
+
readonly selected: "local" | "codex-app-server";
|
|
9
|
+
readonly allowLocal: boolean;
|
|
10
|
+
readonly executable?: string;
|
|
11
|
+
readonly source?: string;
|
|
12
|
+
readonly loginHome?: string;
|
|
13
|
+
readonly accountEmail?: string;
|
|
14
|
+
}
|
|
15
|
+
/** Read once at composition. No browser values, PATH discovery or credential copying. */
|
|
16
|
+
export declare function readCodexBootConfiguration(env: NodeJS.ProcessEnv): CodexBootConfiguration;
|
|
17
|
+
interface Options {
|
|
18
|
+
readonly configuration: CodexBootConfiguration;
|
|
19
|
+
readonly packageRoot: string;
|
|
20
|
+
readonly producerBinary: string;
|
|
21
|
+
readonly profileSource: string;
|
|
22
|
+
readonly store: Store;
|
|
23
|
+
readonly getBinding: () => EnrichmentBinding | undefined;
|
|
24
|
+
readonly sourceWrenHome: () => string;
|
|
25
|
+
readonly artifacts: NativeArtifactService;
|
|
26
|
+
}
|
|
27
|
+
/** Real composition for both entry surfaces; certification is enforced by the packaged backend. */
|
|
28
|
+
export declare function createCodexRuntimeComposition(options: Options): {
|
|
29
|
+
probe: () => Promise<RuntimeBackendProbeResult<"codex-app-server">>;
|
|
30
|
+
native: DirectCodexProvisioner;
|
|
31
|
+
structured: CodexStructuredProvisioner;
|
|
32
|
+
shutdown: () => Promise<void>;
|
|
33
|
+
};
|
|
34
|
+
export {};
|