@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
|
@@ -9,6 +9,7 @@ import { mkdtemp, readFile, rm } from "node:fs/promises";
|
|
|
9
9
|
import { tmpdir } from "node:os";
|
|
10
10
|
import path from "node:path";
|
|
11
11
|
import { resolveHubDir, resolveWarbleBinary, runWarble } from "../harness/index.js";
|
|
12
|
+
import { GATEWAY_API_KEY_ENV } from "./env-detect.js";
|
|
12
13
|
export class RuntimeBindingError extends Error {
|
|
13
14
|
}
|
|
14
15
|
/**
|
|
@@ -43,9 +44,13 @@ function bindingByTier(settings) {
|
|
|
43
44
|
}
|
|
44
45
|
return bindings;
|
|
45
46
|
}
|
|
46
|
-
/**
|
|
47
|
+
/**
|
|
48
|
+
* The old single Model field is the default for every tier without a model
|
|
49
|
+
* override; in gateway mode the gateway's own default model comes first.
|
|
50
|
+
*/
|
|
47
51
|
export function effectiveTierModel(binding, settings) {
|
|
48
|
-
|
|
52
|
+
const gatewayDefault = settings.authMode === "gateway" ? nonEmpty(settings.gatewayModel) : undefined;
|
|
53
|
+
return nonEmpty(binding.model) ?? gatewayDefault ?? nonEmpty(settings.apiKeyModel);
|
|
49
54
|
}
|
|
50
55
|
/**
|
|
51
56
|
* Returns a browser-safe correction for an already persisted Runtime. This is
|
|
@@ -96,8 +101,24 @@ function effectiveAdapter(binding, settings) {
|
|
|
96
101
|
return binding.adapter;
|
|
97
102
|
if (settings.authMode === "local")
|
|
98
103
|
return "local";
|
|
104
|
+
if (settings.authMode === "gateway")
|
|
105
|
+
return "pi-ai";
|
|
99
106
|
return settings.apiKeyAdapter ?? "anthropic";
|
|
100
107
|
}
|
|
108
|
+
function requireGatewayProvider(tier, settings) {
|
|
109
|
+
const provider = nonEmpty(settings.gatewayProvider);
|
|
110
|
+
if (!provider)
|
|
111
|
+
throw new RuntimeBindingError(`A gateway provider is required for the pi-ai adapter on tier "${tier}".`);
|
|
112
|
+
return provider;
|
|
113
|
+
}
|
|
114
|
+
/** The pi-ai base URL is optional: without one the provider's own catalogued endpoint is used. */
|
|
115
|
+
function optionalGatewayBaseUrl(tier, binding, settings) {
|
|
116
|
+
const baseURL = nonEmpty(binding.baseURL) ?? nonEmpty(settings.gatewayBaseURL);
|
|
117
|
+
if (baseURL !== undefined && !absoluteHttpUrl(baseURL)) {
|
|
118
|
+
throw new RuntimeBindingError(`Tier "${tier}": gateway Base URL must be an absolute http(s) URL — got "${baseURL}".`);
|
|
119
|
+
}
|
|
120
|
+
return baseURL;
|
|
121
|
+
}
|
|
101
122
|
/** Credential env vars used by the materialized in-process rows (local rows need none). */
|
|
102
123
|
export function requiredInProcessCredentialEnvVars(settings) {
|
|
103
124
|
const required = new Set();
|
|
@@ -107,6 +128,8 @@ export function requiredInProcessCredentialEnvVars(settings) {
|
|
|
107
128
|
required.add("ANTHROPIC_API_KEY");
|
|
108
129
|
if (adapter === "openai-compatible")
|
|
109
130
|
required.add("OPENAI_API_KEY");
|
|
131
|
+
if (adapter === "pi-ai")
|
|
132
|
+
required.add(GATEWAY_API_KEY_ENV);
|
|
110
133
|
}
|
|
111
134
|
return [...required];
|
|
112
135
|
}
|
|
@@ -131,6 +154,14 @@ export function validateRuntimeTierBindings(settings, bundleTiers) {
|
|
|
131
154
|
const adapter = effectiveAdapter(binding, settings);
|
|
132
155
|
if (adapter === "openai-compatible" || adapter === "local")
|
|
133
156
|
requireBaseUrl(tier, binding, settings);
|
|
157
|
+
if (adapter === "pi-ai") {
|
|
158
|
+
// The subscription dispatchers have no pi-ai backend; gateway rows run in-process only.
|
|
159
|
+
if (settings.authMode === "subscription") {
|
|
160
|
+
throw new RuntimeBindingError(`Tier "${tier}": the pi-ai adapter runs in-process only and cannot be used with a subscription dispatcher.`);
|
|
161
|
+
}
|
|
162
|
+
requireGatewayProvider(tier, settings);
|
|
163
|
+
optionalGatewayBaseUrl(tier, binding, settings);
|
|
164
|
+
}
|
|
134
165
|
}
|
|
135
166
|
if (settings.authMode === "subscription" && !nonEmpty(settings.subscriptionDriverModel)) {
|
|
136
167
|
throw new RuntimeBindingError("A subscription dispatcher driver model is required.");
|
|
@@ -149,12 +180,29 @@ function inProcessSpec(tier, binding, settings) {
|
|
|
149
180
|
return { adapter: "openai-compatible", config: { model, baseURL: requireBaseUrl(tier, binding, settings), apiKey: process.env["OPENAI_API_KEY"] ?? "" } };
|
|
150
181
|
case "local":
|
|
151
182
|
return { adapter: "openai-compatible", config: { model, baseURL: requireBaseUrl(tier, binding, settings) } };
|
|
183
|
+
case "pi-ai": {
|
|
184
|
+
// Like openai-compatible: the env-backed key lives only in this
|
|
185
|
+
// transient spec, never in the persisted settings.
|
|
186
|
+
const baseUrl = optionalGatewayBaseUrl(tier, binding, settings);
|
|
187
|
+
const apiKey = nonEmpty(process.env[GATEWAY_API_KEY_ENV]);
|
|
188
|
+
return {
|
|
189
|
+
adapter: "pi-ai",
|
|
190
|
+
config: {
|
|
191
|
+
provider: requireGatewayProvider(tier, settings),
|
|
192
|
+
model,
|
|
193
|
+
...(baseUrl !== undefined ? { baseUrl } : {}),
|
|
194
|
+
...(apiKey !== undefined ? { apiKey } : {}),
|
|
195
|
+
},
|
|
196
|
+
};
|
|
197
|
+
}
|
|
152
198
|
}
|
|
153
199
|
}
|
|
154
200
|
function dispatchedTierYaml(tier, binding, settings) {
|
|
155
201
|
const model = requireModel(tier, binding, settings);
|
|
156
202
|
const adapter = effectiveAdapter(binding, settings);
|
|
157
203
|
const key = JSON.stringify(tier);
|
|
204
|
+
if (adapter === "pi-ai")
|
|
205
|
+
throw new RuntimeBindingError(`Tier "${tier}": the pi-ai adapter has no subscription dispatcher equivalent.`);
|
|
158
206
|
if (adapter === "anthropic")
|
|
159
207
|
return [` ${key}: ${JSON.stringify(model)}`];
|
|
160
208
|
return [
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type Options, type Query, type SDKUserMessage } from "@anthropic-ai/claude-agent-sdk";
|
|
2
|
+
import type { StepRun, StepResponse } from "../../harness/components/runner.js";
|
|
3
|
+
/** Supplied only by the certified same-account process provisioner. No default spawn. */
|
|
4
|
+
export interface ClaudeComponentRuntime {
|
|
5
|
+
readonly cwd: string;
|
|
6
|
+
readonly executable: string;
|
|
7
|
+
readonly model: string;
|
|
8
|
+
readonly account: {
|
|
9
|
+
readonly email: string;
|
|
10
|
+
readonly tokenSource: string;
|
|
11
|
+
readonly subscriptionType: string;
|
|
12
|
+
};
|
|
13
|
+
readonly environment: Readonly<Record<string, string>>;
|
|
14
|
+
readonly spawn: NonNullable<Options["spawnClaudeCodeProcess"]>;
|
|
15
|
+
readonly query: (input: {
|
|
16
|
+
prompt: AsyncIterable<SDKUserMessage>;
|
|
17
|
+
options: Options;
|
|
18
|
+
}) => Query;
|
|
19
|
+
/** Revalidates approved login, isolated home, SDK/CLI/SRT and runtime generation. */
|
|
20
|
+
assertCurrent(): void;
|
|
21
|
+
/** Resolves only after all owned vendor processes and descendants are gone. */
|
|
22
|
+
close(): Promise<void>;
|
|
23
|
+
}
|
|
24
|
+
/** One fresh SDK query and SDK MCP server per step; never reuse the outer PTY. */
|
|
25
|
+
export declare function runClaudeComponentStep(runtime: ClaudeComponentRuntime, run: StepRun): Promise<StepResponse>;
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { createSdkMcpServer, tool } from "@anthropic-ai/claude-agent-sdk";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
class ClaudeComponentError extends Error {
|
|
4
|
+
reason;
|
|
5
|
+
constructor(reason) {
|
|
6
|
+
super(`Claude component ${reason}`);
|
|
7
|
+
this.reason = reason;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
function deny() { throw new ClaudeComponentError("protocol"); }
|
|
11
|
+
const record = z.record(z.string(), z.unknown());
|
|
12
|
+
const messageIdentity = z.object({ session_id: z.string().min(1), uuid: z.string().min(1) });
|
|
13
|
+
const allowedEnvironment = new Set(["PATH", "HOME", "CLAUDE_CONFIG_DIR", "TMPDIR", "LANG", "LC_ALL"]);
|
|
14
|
+
/** One fresh SDK query and SDK MCP server per step; never reuse the outer PTY. */
|
|
15
|
+
export async function runClaudeComponentStep(runtime, run) {
|
|
16
|
+
const { cwd, executable, model } = runtime;
|
|
17
|
+
const account = structuredClone(runtime.account);
|
|
18
|
+
const environment = Object.freeze({ ...runtime.environment });
|
|
19
|
+
const assertCurrent = runtime.assertCurrent.bind(runtime);
|
|
20
|
+
const query = runtime.query.bind(runtime);
|
|
21
|
+
const spawn = runtime.spawn;
|
|
22
|
+
const close = runtime.close.bind(runtime);
|
|
23
|
+
const controller = new AbortController();
|
|
24
|
+
const signal = AbortSignal.any([run.signal, controller.signal]);
|
|
25
|
+
let active = true;
|
|
26
|
+
let initialized = false;
|
|
27
|
+
let sessionId;
|
|
28
|
+
let pending = 0;
|
|
29
|
+
let calls = 0;
|
|
30
|
+
let failed = false;
|
|
31
|
+
let toolQueue = Promise.resolve();
|
|
32
|
+
let releaseInput;
|
|
33
|
+
const inputReady = new Promise((resolve) => { releaseInput = resolve; });
|
|
34
|
+
const check = () => { if (!active || failed)
|
|
35
|
+
deny(); if (signal.aborted)
|
|
36
|
+
throw new ClaudeComponentError("cancelled"); assertCurrent(); };
|
|
37
|
+
let session;
|
|
38
|
+
const timeout = setTimeout(() => controller.abort(), 120_000);
|
|
39
|
+
const abort = new Promise((_, reject) => {
|
|
40
|
+
signal.addEventListener("abort", () => reject(new ClaudeComponentError("cancelled")), { once: true });
|
|
41
|
+
});
|
|
42
|
+
void abort.catch(() => { });
|
|
43
|
+
const wait = (value) => Promise.race([value, abort]);
|
|
44
|
+
const tools = Object.keys(run.tools).map((name) => ({ name, wire: `mcp__component__${name}` }));
|
|
45
|
+
try {
|
|
46
|
+
check();
|
|
47
|
+
if (!cwd.startsWith("/") || !executable.startsWith("/") || !model || !account.email || !account.tokenSource || !account.subscriptionType
|
|
48
|
+
|| !environment.HOME || !environment.CLAUDE_CONFIG_DIR || Object.keys(environment).some((name) => !allowedEnvironment.has(name)))
|
|
49
|
+
deny();
|
|
50
|
+
const sdkTools = tools.map(({ name }) => {
|
|
51
|
+
const schema = z.fromJSONSchema(run.toolSchemas[name] ?? deny());
|
|
52
|
+
if (!(schema instanceof z.ZodObject))
|
|
53
|
+
deny();
|
|
54
|
+
return tool(name, run.toolDescriptions[name] ?? name, schema.shape, async (input) => {
|
|
55
|
+
check();
|
|
56
|
+
if (!initialized || ++calls > 32)
|
|
57
|
+
deny();
|
|
58
|
+
pending++;
|
|
59
|
+
const result = toolQueue.then(async () => {
|
|
60
|
+
check();
|
|
61
|
+
const parsed = schema.parse(input);
|
|
62
|
+
if (Buffer.byteLength(JSON.stringify(parsed)) > 65_536)
|
|
63
|
+
deny();
|
|
64
|
+
const output = await wait(run.tools[name](parsed));
|
|
65
|
+
check();
|
|
66
|
+
const text = JSON.stringify(output);
|
|
67
|
+
if (text === undefined || Buffer.byteLength(text) > 1_048_000)
|
|
68
|
+
deny();
|
|
69
|
+
return { content: [{ type: "text", text }] };
|
|
70
|
+
});
|
|
71
|
+
toolQueue = result.catch(() => { failed = true; controller.abort(); });
|
|
72
|
+
try {
|
|
73
|
+
return await result;
|
|
74
|
+
}
|
|
75
|
+
finally {
|
|
76
|
+
pending--;
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
async function* prompt() {
|
|
81
|
+
try {
|
|
82
|
+
await wait(inputReady);
|
|
83
|
+
check();
|
|
84
|
+
yield { type: "user", session_id: "", parent_tool_use_id: null,
|
|
85
|
+
message: { role: "user", content: JSON.stringify({ request: run.request, input: run.input, consumes: run.consumes }) } };
|
|
86
|
+
// Keep the SDK input channel open while governed tool exchanges run.
|
|
87
|
+
await wait(new Promise(() => { }));
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
if (!signal.aborted) {
|
|
91
|
+
failed = true;
|
|
92
|
+
controller.abort();
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
session = query({ prompt: prompt(), options: {
|
|
97
|
+
abortController: controller, cwd, model, pathToClaudeCodeExecutable: executable,
|
|
98
|
+
spawnClaudeCodeProcess: spawn, env: { ...environment },
|
|
99
|
+
tools: [], allowedTools: tools.map(({ wire }) => wire),
|
|
100
|
+
mcpServers: { component: createSdkMcpServer({ name: "component", tools: sdkTools }) },
|
|
101
|
+
canUseTool: async () => { failed = true; controller.abort(); return { behavior: "deny", message: "Undeclared tool is unavailable" }; },
|
|
102
|
+
permissionMode: "default", settingSources: [], plugins: [], agents: {}, additionalDirectories: [],
|
|
103
|
+
persistSession: false, enableFileCheckpointing: false, maxTurns: 12,
|
|
104
|
+
systemPrompt: [run.brief, run.prompt].filter(Boolean).join("\n\n"),
|
|
105
|
+
extraArgs: { "strict-mcp-config": null, "disable-slash-commands": null },
|
|
106
|
+
} });
|
|
107
|
+
const observed = z.object({ email: z.literal(account.email), tokenSource: z.literal(account.tokenSource), subscriptionType: z.literal(account.subscriptionType), apiKeySource: z.literal("none") }).parse(await wait(session.accountInfo()));
|
|
108
|
+
if (!observed.email)
|
|
109
|
+
deny();
|
|
110
|
+
check();
|
|
111
|
+
releaseInput();
|
|
112
|
+
const seen = new Set();
|
|
113
|
+
while (true) {
|
|
114
|
+
const next = await wait(session.next());
|
|
115
|
+
check();
|
|
116
|
+
if (next.done)
|
|
117
|
+
deny();
|
|
118
|
+
const message = record.parse(next.value);
|
|
119
|
+
if (Buffer.byteLength(JSON.stringify(message)) > 1_048_576)
|
|
120
|
+
deny();
|
|
121
|
+
const identity = messageIdentity.parse(message);
|
|
122
|
+
if (seen.has(identity.uuid) || seen.size >= 512)
|
|
123
|
+
deny();
|
|
124
|
+
seen.add(identity.uuid);
|
|
125
|
+
if (message.type === "system" && message.subtype === "init") {
|
|
126
|
+
if (initialized)
|
|
127
|
+
deny();
|
|
128
|
+
const init = z.object({ apiKeySource: z.literal("none"), claude_code_version: z.literal("2.1.259"), cwd: z.literal(cwd), model: z.literal(model),
|
|
129
|
+
tools: z.array(z.string()), mcp_servers: z.array(z.object({ name: z.literal("component"), status: z.literal("connected") })),
|
|
130
|
+
skills: z.array(z.never()), plugins: z.array(z.never()), agents: z.array(z.never()).optional(),
|
|
131
|
+
slash_commands: z.array(z.never()), permissionMode: z.literal("default"),
|
|
132
|
+
}).parse(message);
|
|
133
|
+
if (init.tools.length !== tools.length || new Set(init.tools).size !== tools.length || init.tools.some((name) => !tools.some(({ wire }) => wire === name)))
|
|
134
|
+
deny();
|
|
135
|
+
if (init.mcp_servers.length !== 1)
|
|
136
|
+
deny();
|
|
137
|
+
initialized = true;
|
|
138
|
+
sessionId = identity.session_id;
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
if (!initialized || identity.session_id !== sessionId)
|
|
142
|
+
deny();
|
|
143
|
+
if (message.type === "result") {
|
|
144
|
+
const result = z.object({ subtype: z.literal("success"), is_error: z.literal(false), result: z.string().max(1_048_576),
|
|
145
|
+
permission_denials: z.array(z.never()), modelUsage: z.record(z.string(), z.object({ webSearchRequests: z.literal(0), inputTokens: z.number().int().nonnegative(), outputTokens: z.number().int().nonnegative(), cacheReadInputTokens: z.number().int().nonnegative(), cacheCreationInputTokens: z.number().int().nonnegative() })), usage: z.object({ input_tokens: z.number().int().nonnegative(), output_tokens: z.number().int().nonnegative() }),
|
|
146
|
+
}).parse(message);
|
|
147
|
+
if (pending || failed || Object.keys(result.modelUsage).length !== 1 || !Object.hasOwn(result.modelUsage, model))
|
|
148
|
+
deny();
|
|
149
|
+
await wait(session.accountInfo()).then((value) => z.object({ email: z.literal(account.email), tokenSource: z.literal(account.tokenSource), subscriptionType: z.literal(account.subscriptionType), apiKeySource: z.literal("none") }).parse(value));
|
|
150
|
+
check();
|
|
151
|
+
const totals = result.modelUsage[model];
|
|
152
|
+
return { value: result.result, usage: { inputTokens: totals.inputTokens + totals.cacheReadInputTokens + totals.cacheCreationInputTokens, outputTokens: totals.outputTokens } };
|
|
153
|
+
}
|
|
154
|
+
if (message.type !== "assistant" && message.type !== "user")
|
|
155
|
+
deny();
|
|
156
|
+
if (message.parent_tool_use_id !== null)
|
|
157
|
+
deny();
|
|
158
|
+
const content = z.object({ content: z.union([z.string(), z.array(record)]) }).parse(message.message).content;
|
|
159
|
+
if (Array.isArray(content))
|
|
160
|
+
for (const block of content) {
|
|
161
|
+
if (block.type === "tool_use") {
|
|
162
|
+
if (!tools.some(({ wire }) => wire === block.name))
|
|
163
|
+
deny();
|
|
164
|
+
}
|
|
165
|
+
else if (!["text", "thinking", "redacted_thinking", "tool_result"].includes(String(block.type)))
|
|
166
|
+
deny();
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
catch (error) {
|
|
171
|
+
throw new ClaudeComponentError(error instanceof ClaudeComponentError ? error.reason : "protocol");
|
|
172
|
+
}
|
|
173
|
+
finally {
|
|
174
|
+
active = false;
|
|
175
|
+
clearTimeout(timeout);
|
|
176
|
+
controller.abort();
|
|
177
|
+
releaseInput();
|
|
178
|
+
// The provisioner owns process-tree cleanup; iterator cleanup alone is insufficient.
|
|
179
|
+
try {
|
|
180
|
+
await close();
|
|
181
|
+
}
|
|
182
|
+
catch {
|
|
183
|
+
throw new ClaudeComponentError("cleanup");
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { type ManagedWrenRuntimeRecord } from "../managed-wren-runtime.js";
|
|
2
|
+
import { type NativeRuntimeSpec } from "../native-runtime-spec.js";
|
|
3
|
+
import type { CodexWrenHome } from "../native-wren-home.js";
|
|
4
|
+
import { type CodexIdentityDiagnostic } from "./codex-compatibility.js";
|
|
5
|
+
import { CodexSession } from "./codex-session.js";
|
|
6
|
+
import type { CodexComponentPolicy } from "./codex-component-step.js";
|
|
7
|
+
import type { RpcTransport } from "./codex-rpc.js";
|
|
8
|
+
import type { CodexEvent } from "./codex-events.js";
|
|
9
|
+
import type { RuntimeBackendProbeResult, RuntimeBackendReasonCode } from "./types.js";
|
|
10
|
+
import type { CodexSessionTools } from "./codex-direct-tools.js";
|
|
11
|
+
type Reason = RuntimeBackendReasonCode<"codex-app-server">;
|
|
12
|
+
export declare class CodexBackendError extends Error {
|
|
13
|
+
readonly code: Reason;
|
|
14
|
+
/** Host-only drift detail for `codex_dependencies_drifted`; never part of the readiness text. */
|
|
15
|
+
readonly drift?: readonly string[];
|
|
16
|
+
constructor(code: Reason, drift?: readonly string[]);
|
|
17
|
+
}
|
|
18
|
+
export interface CodexBackendOptions {
|
|
19
|
+
/** Captured at BFF composition, not discovered from command/session input. */
|
|
20
|
+
readonly executable: string;
|
|
21
|
+
readonly source: string;
|
|
22
|
+
readonly packageRoot?: string;
|
|
23
|
+
readonly runtimeRoot?: string;
|
|
24
|
+
}
|
|
25
|
+
export interface CodexLaunchPermit {
|
|
26
|
+
readonly runtime: ManagedWrenRuntimeRecord;
|
|
27
|
+
/** Recheck immediately before each host-side materialization/persistence step. */
|
|
28
|
+
assertActive(): void;
|
|
29
|
+
/** Abandon before materialization or open; never deletes any runtime bytes. */
|
|
30
|
+
release(): void;
|
|
31
|
+
}
|
|
32
|
+
/** Disabled unless the package contains an approved runtime AND certified row. */
|
|
33
|
+
export declare class CodexAppServerBackend {
|
|
34
|
+
private permits;
|
|
35
|
+
private leases;
|
|
36
|
+
private sessions;
|
|
37
|
+
private steps;
|
|
38
|
+
private stopped;
|
|
39
|
+
private shutdownPromise?;
|
|
40
|
+
private readonly options;
|
|
41
|
+
constructor(options: CodexBackendOptions);
|
|
42
|
+
private resolve;
|
|
43
|
+
private inspect;
|
|
44
|
+
probe(): Promise<RuntimeBackendProbeResult<"codex-app-server"> & {
|
|
45
|
+
readonly diagnostic: CodexIdentityDiagnostic;
|
|
46
|
+
}>;
|
|
47
|
+
/** Call BEFORE durable rows or workspace materialization in Phase 5. */
|
|
48
|
+
prepareLaunch(): CodexLaunchPermit;
|
|
49
|
+
/** Include these generations in the provisioner's cleanup retain set. */
|
|
50
|
+
retainedManifestDigests(): readonly string[];
|
|
51
|
+
private assertRecord;
|
|
52
|
+
open(permit: CodexLaunchPermit, input: {
|
|
53
|
+
readonly model?: string;
|
|
54
|
+
readonly tools?: CodexSessionTools;
|
|
55
|
+
readonly spec: NativeRuntimeSpec;
|
|
56
|
+
readonly wrenHome: CodexWrenHome;
|
|
57
|
+
/** Existing host-owned binding/generation guard, never a browser callback. */
|
|
58
|
+
readonly assertScopeActive: () => void;
|
|
59
|
+
readonly onEvent: (event: CodexEvent) => void;
|
|
60
|
+
}): Promise<CodexSession>;
|
|
61
|
+
/** A fresh, governed transport per component step; same certification gate as the outer session. */
|
|
62
|
+
openStep(permit: CodexLaunchPermit, input: {
|
|
63
|
+
readonly spec: NativeRuntimeSpec;
|
|
64
|
+
readonly wrenHome: CodexWrenHome;
|
|
65
|
+
readonly model: string;
|
|
66
|
+
readonly accountEmail: string;
|
|
67
|
+
readonly signal: AbortSignal;
|
|
68
|
+
readonly assertScopeActive: () => void;
|
|
69
|
+
}): {
|
|
70
|
+
transport: RpcTransport;
|
|
71
|
+
policy: CodexComponentPolicy;
|
|
72
|
+
};
|
|
73
|
+
shutdown(): Promise<void>;
|
|
74
|
+
}
|
|
75
|
+
export {};
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { arch, platform } from "node:os";
|
|
3
|
+
import { ManagedWrenRuntimeError, resolveManagedWrenRuntime } from "../managed-wren-runtime.js";
|
|
4
|
+
import { attestNativeExecutable, assertNativeExecutableIdentity } from "../native-runtime-spec.js";
|
|
5
|
+
import { CODEX_CERTIFIED_ROWS, codexCertificationSchema, evaluateCodexIdentity } from "./codex-compatibility.js";
|
|
6
|
+
import { buildCodexSessionPolicy } from "./codex-policy.js";
|
|
7
|
+
import { spawnCodexTransport } from "./codex-process.js";
|
|
8
|
+
import { CodexSession } from "./codex-session.js";
|
|
9
|
+
import { CodexRpcError } from "./codex-rpc.js";
|
|
10
|
+
import { runtimeNotReady } from "./policy.js";
|
|
11
|
+
export class CodexBackendError extends Error {
|
|
12
|
+
code;
|
|
13
|
+
/** Host-only drift detail for `codex_dependencies_drifted`; never part of the readiness text. */
|
|
14
|
+
drift;
|
|
15
|
+
constructor(code, drift) {
|
|
16
|
+
super(runtimeNotReady("codex-app-server", "unavailable", code).message);
|
|
17
|
+
this.code = code;
|
|
18
|
+
if (drift)
|
|
19
|
+
this.drift = drift;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/** Disabled unless the package contains an approved runtime AND certified row. */
|
|
23
|
+
export class CodexAppServerBackend {
|
|
24
|
+
permits = new WeakMap();
|
|
25
|
+
leases = new Set();
|
|
26
|
+
sessions = new Set();
|
|
27
|
+
steps = new Set();
|
|
28
|
+
stopped = false;
|
|
29
|
+
shutdownPromise;
|
|
30
|
+
options;
|
|
31
|
+
constructor(options) { this.options = Object.freeze({ ...options }); }
|
|
32
|
+
resolve() {
|
|
33
|
+
try {
|
|
34
|
+
return resolveManagedWrenRuntime(this.options);
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
throw new CodexBackendError(error instanceof ManagedWrenRuntimeError ? error.code : "codex_wren_runtime_unprovisioned");
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
inspect() {
|
|
41
|
+
if (this.stopped)
|
|
42
|
+
throw new CodexBackendError("codex_app_server_unreachable");
|
|
43
|
+
if (platform() !== "darwin" || arch() !== "arm64")
|
|
44
|
+
throw new CodexBackendError("runtime_platform_unsupported");
|
|
45
|
+
const runtime = this.resolve(); // no fetch, install, mkdir, workspace or session side effects
|
|
46
|
+
let vendor;
|
|
47
|
+
try {
|
|
48
|
+
vendor = attestNativeExecutable("vendor", this.options.executable);
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
throw new CodexBackendError("codex_cli_missing");
|
|
52
|
+
}
|
|
53
|
+
// Hash before executing --version. An unknown executable never gets a probe
|
|
54
|
+
// process merely because the caller labels it Codex.
|
|
55
|
+
const row = CODEX_CERTIFIED_ROWS.map((value) => codexCertificationSchema.safeParse(value))
|
|
56
|
+
.find((value) => value.success && value.data.executableSha256 === vendor.digest.slice(7) && value.data.source === this.options.source);
|
|
57
|
+
if (!row?.success)
|
|
58
|
+
throw new CodexBackendError("codex_identity_uncertified");
|
|
59
|
+
let versionOutput;
|
|
60
|
+
try {
|
|
61
|
+
versionOutput = execFileSync(vendor.executable, ["--version"], { encoding: "utf8", timeout: 5_000, maxBuffer: 1024,
|
|
62
|
+
env: { PATH: "/usr/bin:/bin" }, stdio: ["ignore", "pipe", "ignore"] });
|
|
63
|
+
assertNativeExecutableIdentity(vendor);
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
throw new CodexBackendError("codex_app_server_unreachable");
|
|
67
|
+
}
|
|
68
|
+
// The exact executable digest binds the generated protocol schema and
|
|
69
|
+
// behavior evidence in its reviewed row; never generate schema files during
|
|
70
|
+
// readiness or equate an initialize acknowledgement with certification.
|
|
71
|
+
const result = evaluateCodexIdentity({ platform: "darwin-arm64", versionOutput,
|
|
72
|
+
executableSha256: vendor.digest.slice(7), source: this.options.source,
|
|
73
|
+
protocolSha256: row.data.protocolSha256, contracts: row.data.contracts }, CODEX_CERTIFIED_ROWS);
|
|
74
|
+
if (result.readiness.state !== "ready")
|
|
75
|
+
throw new CodexBackendError(result.readiness.code, result.diagnostic.drift);
|
|
76
|
+
return { runtime, vendor, result };
|
|
77
|
+
}
|
|
78
|
+
async probe() {
|
|
79
|
+
try {
|
|
80
|
+
return this.inspect().result;
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
const code = error instanceof CodexBackendError ? error.code : "runtime_probe_failed";
|
|
84
|
+
const drift = error instanceof CodexBackendError ? error.drift : undefined;
|
|
85
|
+
return { readiness: runtimeNotReady("codex-app-server", code.startsWith("codex_wren_") ? "unprovisioned" : "incompatible", code),
|
|
86
|
+
diagnostic: { phase: code.startsWith("codex_wren_") ? "provisioning" : "identity", ...(drift ? { drift } : {}) } };
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/** Call BEFORE durable rows or workspace materialization in Phase 5. */
|
|
90
|
+
prepareLaunch() {
|
|
91
|
+
const { runtime, vendor } = this.inspect();
|
|
92
|
+
const state = { runtime, vendor, consumed: false, released: false };
|
|
93
|
+
const permit = Object.freeze({ runtime, assertActive: () => {
|
|
94
|
+
if (state.released || state.consumed || this.stopped)
|
|
95
|
+
throw new CodexBackendError("runtime_policy_unavailable");
|
|
96
|
+
this.assertRecord(state);
|
|
97
|
+
}, release: () => {
|
|
98
|
+
if (state.consumed)
|
|
99
|
+
return;
|
|
100
|
+
state.released = true;
|
|
101
|
+
this.leases.delete(state);
|
|
102
|
+
} });
|
|
103
|
+
this.permits.set(permit, state);
|
|
104
|
+
this.leases.add(state);
|
|
105
|
+
return permit;
|
|
106
|
+
}
|
|
107
|
+
/** Include these generations in the provisioner's cleanup retain set. */
|
|
108
|
+
retainedManifestDigests() { return Object.freeze([...new Set([...this.leases].map((state) => state.runtime.manifest_digest))]); }
|
|
109
|
+
assertRecord(state) {
|
|
110
|
+
const current = this.resolve();
|
|
111
|
+
if (JSON.stringify(current) !== JSON.stringify(state.runtime))
|
|
112
|
+
throw new CodexBackendError("codex_wren_closure_mismatch");
|
|
113
|
+
try {
|
|
114
|
+
assertNativeExecutableIdentity(state.vendor);
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
throw new CodexBackendError("codex_identity_uncertified");
|
|
118
|
+
}
|
|
119
|
+
return current;
|
|
120
|
+
}
|
|
121
|
+
async open(permit, input) {
|
|
122
|
+
const state = this.permits.get(permit);
|
|
123
|
+
if (!state || state.consumed || state.released || this.stopped)
|
|
124
|
+
throw new CodexBackendError("runtime_policy_unavailable");
|
|
125
|
+
state.consumed = true;
|
|
126
|
+
const revalidate = () => {
|
|
127
|
+
const current = this.assertRecord(state);
|
|
128
|
+
if (input.spec.executables.vendor?.digest !== state.vendor.digest || input.spec.executables.vendor?.executable !== state.vendor.executable)
|
|
129
|
+
throw new CodexBackendError("codex_identity_uncertified");
|
|
130
|
+
try {
|
|
131
|
+
input.assertScopeActive();
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
throw new CodexBackendError("runtime_policy_unavailable");
|
|
135
|
+
}
|
|
136
|
+
try {
|
|
137
|
+
return buildCodexSessionPolicy(input.spec, current, input.wrenHome, input.tools !== undefined, input.model);
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
throw new CodexBackendError("codex_wren_child_env_invalid");
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
let spawned = false;
|
|
144
|
+
try {
|
|
145
|
+
const policy = revalidate();
|
|
146
|
+
const transport = spawnCodexTransport({ executable: state.vendor.executable, args: policy.args, cwd: policy.cwd, env: { ...policy.environment } });
|
|
147
|
+
spawned = true;
|
|
148
|
+
const session = await CodexSession.connect(transport, policy, () => {
|
|
149
|
+
if (this.stopped || JSON.stringify(revalidate()) !== JSON.stringify(policy))
|
|
150
|
+
throw new CodexBackendError("runtime_policy_unavailable");
|
|
151
|
+
}, input.onEvent, (opening) => {
|
|
152
|
+
// Own the connection before initialization: shutdown must also cancel
|
|
153
|
+
// a peer that never acknowledges the handshake.
|
|
154
|
+
this.sessions.add(opening);
|
|
155
|
+
const originalClose = opening.close.bind(opening);
|
|
156
|
+
opening.close = async () => { await originalClose(); this.sessions.delete(opening); this.leases.delete(state); };
|
|
157
|
+
}, input.tools);
|
|
158
|
+
if (this.stopped) {
|
|
159
|
+
await session.close();
|
|
160
|
+
this.leases.delete(state);
|
|
161
|
+
throw new CodexBackendError("codex_app_server_unreachable");
|
|
162
|
+
}
|
|
163
|
+
return session;
|
|
164
|
+
}
|
|
165
|
+
catch (error) {
|
|
166
|
+
// Pre-spawn failures have no process to retain. A post-spawn cleanup
|
|
167
|
+
// failure conservatively pins the generation for operator reconciliation.
|
|
168
|
+
if (!spawned || !(error instanceof CodexRpcError && error.reason === "cleanup"))
|
|
169
|
+
this.leases.delete(state);
|
|
170
|
+
if (error instanceof CodexBackendError)
|
|
171
|
+
throw error;
|
|
172
|
+
if (error instanceof CodexRpcError && error.reason === "cleanup")
|
|
173
|
+
throw new CodexBackendError("codex_app_server_cleanup_failed");
|
|
174
|
+
if (error instanceof CodexRpcError && error.reason === "permission")
|
|
175
|
+
throw new CodexBackendError("codex_permission_profile_unavailable");
|
|
176
|
+
throw new CodexBackendError("codex_app_server_protocol_incompatible");
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
/** A fresh, governed transport per component step; same certification gate as the outer session. */
|
|
180
|
+
openStep(permit, input) {
|
|
181
|
+
const state = this.permits.get(permit);
|
|
182
|
+
if (!state || state.consumed || state.released || this.stopped)
|
|
183
|
+
throw new CodexBackendError("runtime_policy_unavailable");
|
|
184
|
+
const checkLive = () => {
|
|
185
|
+
input.signal.throwIfAborted();
|
|
186
|
+
input.assertScopeActive();
|
|
187
|
+
if (this.stopped)
|
|
188
|
+
throw new CodexBackendError("runtime_policy_unavailable");
|
|
189
|
+
};
|
|
190
|
+
const check = () => {
|
|
191
|
+
checkLive();
|
|
192
|
+
const runtime = this.assertRecord(state);
|
|
193
|
+
if (input.spec.executables.vendor?.digest !== state.vendor.digest || input.spec.executables.vendor?.executable !== state.vendor.executable)
|
|
194
|
+
throw new CodexBackendError("codex_identity_uncertified");
|
|
195
|
+
return buildCodexSessionPolicy(input.spec, runtime, input.wrenHome, true, input.model);
|
|
196
|
+
};
|
|
197
|
+
const launch = check();
|
|
198
|
+
state.consumed = true;
|
|
199
|
+
let raw;
|
|
200
|
+
try {
|
|
201
|
+
raw = spawnCodexTransport({ executable: state.vendor.executable, args: launch.args, cwd: launch.cwd, env: { ...launch.environment } });
|
|
202
|
+
}
|
|
203
|
+
catch (error) {
|
|
204
|
+
this.leases.delete(state);
|
|
205
|
+
throw error;
|
|
206
|
+
}
|
|
207
|
+
let closing;
|
|
208
|
+
const transport = { listen: raw.listen.bind(raw), write: raw.write.bind(raw), close: () => closing ??= (async () => {
|
|
209
|
+
await raw.close();
|
|
210
|
+
this.steps.delete(transport);
|
|
211
|
+
this.leases.delete(state);
|
|
212
|
+
input.signal.removeEventListener("abort", abort);
|
|
213
|
+
})() };
|
|
214
|
+
const abort = () => { void transport.close().catch(() => { }); };
|
|
215
|
+
this.steps.add(transport);
|
|
216
|
+
input.signal.addEventListener("abort", abort, { once: true });
|
|
217
|
+
if (input.signal.aborted)
|
|
218
|
+
abort();
|
|
219
|
+
return { transport, policy: { cwd: launch.cwd, codexHome: launch.codexHome, permissionProfile: launch.profile,
|
|
220
|
+
model: input.model, accountEmail: input.accountEmail, configuration: launch.configuration,
|
|
221
|
+
assertLive: checkLive,
|
|
222
|
+
assertCurrent() { if (JSON.stringify(check()) !== JSON.stringify(launch))
|
|
223
|
+
throw new CodexBackendError("runtime_policy_unavailable"); } } };
|
|
224
|
+
}
|
|
225
|
+
shutdown() {
|
|
226
|
+
this.stopped = true;
|
|
227
|
+
this.shutdownPromise ??= (async () => {
|
|
228
|
+
const results = await Promise.allSettled([...this.sessions, ...this.steps].map((session) => session.close()));
|
|
229
|
+
for (const state of this.leases)
|
|
230
|
+
if (!state.consumed) {
|
|
231
|
+
state.released = true;
|
|
232
|
+
this.leases.delete(state);
|
|
233
|
+
}
|
|
234
|
+
if (results.some((result) => result.status === "rejected"))
|
|
235
|
+
throw new CodexBackendError("codex_app_server_cleanup_failed");
|
|
236
|
+
})();
|
|
237
|
+
return this.shutdownPromise;
|
|
238
|
+
}
|
|
239
|
+
}
|