@wrenai/genbi 0.0.4 → 0.0.6
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 +3 -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 +43 -45
- 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/describe.js +45 -6
- package/dist-server/harness/route/dispatched.d.ts +20 -0
- package/dist-server/harness/route/dispatched.js +122 -24
- package/dist-server/harness/route/host-query.d.ts +93 -0
- package/dist-server/harness/route/host-query.js +257 -0
- 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 +8 -2
- package/dist-server/harness/route/types.d.ts +47 -1
- package/dist-server/harness/route/wren-host-proxy.d.ts +33 -0
- package/dist-server/harness/route/wren-host-proxy.js +228 -0
- package/dist-server/harness/session/run.js +30 -10
- package/dist-server/harness/setup/runner.d.ts +17 -3
- package/dist-server/harness/setup/runner.js +272 -89
- package/dist-server/harness/setup/yaml-scalar.d.ts +23 -0
- package/dist-server/harness/setup/yaml-scalar.js +62 -0
- package/dist-server/harness/tools/setup-native.d.ts +7 -0
- package/dist-server/harness/tools/setup-native.js +7 -4
- package/dist-server/server/adopt.js +1 -17
- package/dist-server/server/app.d.ts +1 -0
- package/dist-server/server/app.js +175 -17
- 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 +185 -25
- 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 +23 -4
- package/dist-server/server/fold.js +42 -5
- package/dist-server/server/harness.d.ts +2 -2
- package/dist-server/server/harness.js +33 -7
- package/dist-server/server/host-evidence-grounding.d.ts +21 -0
- package/dist-server/server/host-evidence-grounding.js +93 -0
- 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 +112 -7
- package/dist-server/server/native-artifacts.js +194 -14
- 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 +839 -244
- 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 +86 -26
- 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
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { mkdtemp, readFile, rm } from "node:fs/promises";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { isDeepStrictEqual } from "node:util";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import { generatePreparedContext } from "../harness/compile/context-loader.js";
|
|
7
|
+
import { hashDirectory } from "../harness/compile/fingerprint.js";
|
|
8
|
+
import { captureWrenAccessIdentity } from "../harness/components/wren-access.js";
|
|
9
|
+
import { prepareCodexDirectTools } from "./runtime-host/codex-direct-tools.js";
|
|
10
|
+
import { prepareNativeComponentHost } from "./native-component-preparation.js";
|
|
11
|
+
import { createNativeComponentRuntimeBindings } from "./native-component-runtime.js";
|
|
12
|
+
/** Capture file-only context before host admission; never opens a vendor or Wren process. */
|
|
13
|
+
export async function prepareCapturedNativeComponentHost(irDocument, scope, options) {
|
|
14
|
+
const capturedScope = structuredClone(scope);
|
|
15
|
+
const bindings = await captureBindings(irDocument, options);
|
|
16
|
+
return prepareNativeComponentHost(irDocument, capturedScope, bindings);
|
|
17
|
+
}
|
|
18
|
+
/** Direct-session variant shares the same captured project/credential provenance. */
|
|
19
|
+
export async function prepareCapturedCodexDirectTools(irDocument, scope, options) {
|
|
20
|
+
if (options.vendor.vendor !== "codex" || options.identity.vendor !== "codex")
|
|
21
|
+
throw new Error("Direct session requires Codex");
|
|
22
|
+
const capturedScope = structuredClone(scope);
|
|
23
|
+
const accountEmail = options.vendor.accountEmail;
|
|
24
|
+
const bindings = await captureBindings(irDocument, options);
|
|
25
|
+
return prepareCodexDirectTools({ irDocument, scope: capturedScope, bindings, producerBinary: options.producerBinary,
|
|
26
|
+
accountEmail, expiresAt: options.expiresAt, signal: options.signal });
|
|
27
|
+
}
|
|
28
|
+
async function captureBindings(irDocument, options) {
|
|
29
|
+
const identity = structuredClone(options.identity);
|
|
30
|
+
const project = path.resolve(options.project);
|
|
31
|
+
const signal = options.signal;
|
|
32
|
+
const currentIdentity = options.currentIdentity.bind(options);
|
|
33
|
+
const assertBinding = options.assertCurrent.bind(options);
|
|
34
|
+
const assertVendor = options.vendor.assertCurrent.bind(options.vendor);
|
|
35
|
+
const check = () => {
|
|
36
|
+
signal.throwIfAborted();
|
|
37
|
+
assertBinding();
|
|
38
|
+
assertVendor();
|
|
39
|
+
if (!isDeepStrictEqual(currentIdentity(), identity))
|
|
40
|
+
throw new Error("Native component context binding expired");
|
|
41
|
+
};
|
|
42
|
+
check();
|
|
43
|
+
const ir = z.object({ warble_ir_version: z.literal("0.9"), components: z.array(z.object({
|
|
44
|
+
id: z.string(), context_binding: z.record(z.string(), z.unknown()).optional(),
|
|
45
|
+
}).passthrough()) }).passthrough().parse(JSON.parse(irDocument));
|
|
46
|
+
if (new Set(ir.components.map((component) => component.id)).size !== ir.components.length)
|
|
47
|
+
throw new Error("Duplicate component identity");
|
|
48
|
+
const fingerprint = await hashDirectory(project);
|
|
49
|
+
const accessIdentity = await captureWrenAccessIdentity(project, options.wrenEnvironment);
|
|
50
|
+
check();
|
|
51
|
+
const scratch = await mkdtemp(path.join(os.tmpdir(), "genbi-native-context-"));
|
|
52
|
+
try {
|
|
53
|
+
const snapshotPath = path.join(scratch, "context.json");
|
|
54
|
+
await generatePreparedContext(options.contextLoaderBinary, project, snapshotPath, signal);
|
|
55
|
+
const snapshot = z.object({ context_version: z.literal(2), parseable: z.literal(true) }).passthrough()
|
|
56
|
+
.parse(JSON.parse(await readFile(snapshotPath, "utf8")));
|
|
57
|
+
await accessIdentity.assertCurrent();
|
|
58
|
+
if (fingerprint !== await hashDirectory(project))
|
|
59
|
+
throw new Error("Native component project changed during context capture");
|
|
60
|
+
check();
|
|
61
|
+
const contexts = Object.fromEntries(ir.components.flatMap((component) => {
|
|
62
|
+
const binding = component.context_binding;
|
|
63
|
+
// Other, unreachable components cannot contribute a project or context grant.
|
|
64
|
+
// Preparation below requires a captured context for every reachable component.
|
|
65
|
+
if (!binding || typeof binding.project !== "string" || path.resolve(binding.project) !== project)
|
|
66
|
+
return [];
|
|
67
|
+
return [[component.id, { binding, snapshot }]];
|
|
68
|
+
}));
|
|
69
|
+
const bindings = createNativeComponentRuntimeBindings({ ...options, identity, contexts, currentIdentity, assertCurrent: check,
|
|
70
|
+
wren: { executable: options.wrenBinary, project, fingerprint, identity: accessIdentity } });
|
|
71
|
+
return bindings;
|
|
72
|
+
}
|
|
73
|
+
finally {
|
|
74
|
+
await rm(scratch, { recursive: true, force: true });
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type ComponentBrokerOptions } from "../harness/components/broker.js";
|
|
2
|
+
import type { NativeComponentIdentity, NativeComponentPreparation } from "./native-components.js";
|
|
3
|
+
/** The certified provisioner supplies bindings; this module derives scope/closure authority. */
|
|
4
|
+
export interface NativeComponentBindings {
|
|
5
|
+
readonly identity: NativeComponentIdentity;
|
|
6
|
+
readonly contexts: ComponentBrokerOptions["contexts"];
|
|
7
|
+
readonly verifierBinary: string;
|
|
8
|
+
readonly prepare: ComponentBrokerOptions["prepare"];
|
|
9
|
+
readonly step: ComponentBrokerOptions["step"];
|
|
10
|
+
readonly normalize: ComponentBrokerOptions["normalize"];
|
|
11
|
+
readonly persistRoot?: ComponentBrokerOptions["persistRoot"];
|
|
12
|
+
readonly onEvent?: ComponentBrokerOptions["onEvent"];
|
|
13
|
+
currentIdentity(): NativeComponentIdentity | undefined;
|
|
14
|
+
/** Includes exact executable/account/generation and context snapshot provenance. */
|
|
15
|
+
assertCurrent(): void;
|
|
16
|
+
}
|
|
17
|
+
/** Select only composed eligible roots; direct non-composed vendor entries retain their path. */
|
|
18
|
+
export declare function prepareNativeComponentHost(irDocument: string, scope: Readonly<Record<string, unknown>>, bindings: NativeComponentBindings): NativeComponentPreparation | undefined;
|
|
19
|
+
/** Direct Codex sessions pin one root even when it has no child components. */
|
|
20
|
+
export declare function prepareDirectCodexComponentHost(irDocument: string, scope: Readonly<Record<string, unknown>>, bindings: NativeComponentBindings): NativeComponentPreparation;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { isDeepStrictEqual } from "node:util";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { createComponentBroker } from "../harness/components/broker.js";
|
|
4
|
+
import { HOST_FEATURES, planDigest } from "../harness/components/plan.js";
|
|
5
|
+
const nodeSchema = z.object({ id: z.string().min(1), verb: z.string().min(1), entrypoint: z.boolean().optional(),
|
|
6
|
+
type: z.literal("analytical"), realization_kind: z.literal("skill"), trigger: z.object({ kind: z.literal("one_shot") }),
|
|
7
|
+
required_capabilities: z.array(z.string()), guardrails: z.array(z.record(z.string(), z.unknown())),
|
|
8
|
+
context_binding: z.record(z.string(), z.unknown()),
|
|
9
|
+
llm_calls: z.array(z.object({ tier: z.string().min(1), component_calls: z.array(z.object({ alias: z.string(), component: z.string() })).optional() }).passthrough()),
|
|
10
|
+
}).passthrough();
|
|
11
|
+
const irSchema = z.object({ warble_ir_version: z.literal("0.9"), components: z.array(z.record(z.string(), z.unknown())) }).passthrough();
|
|
12
|
+
const supportedGuards = new Set(["read_only_execution", "row_limit", "statement_timeout", "deterministic_gate", "artifact_write", "additivity_guard", "drill_depth_limit"]);
|
|
13
|
+
const execution = ["ordered_steps", "isolated_step_tools", "artifact_provenance", "per_step_tiers", "bounded_repair", "render_contract"];
|
|
14
|
+
function freeze(value) {
|
|
15
|
+
if (value && typeof value === "object" && !Object.isFrozen(value)) {
|
|
16
|
+
for (const child of Object.values(value))
|
|
17
|
+
freeze(child);
|
|
18
|
+
Object.freeze(value);
|
|
19
|
+
}
|
|
20
|
+
return value;
|
|
21
|
+
}
|
|
22
|
+
/** Select only composed eligible roots; direct non-composed vendor entries retain their path. */
|
|
23
|
+
export function prepareNativeComponentHost(irDocument, scope, bindings) {
|
|
24
|
+
return prepareHost(irDocument, scope, bindings, false);
|
|
25
|
+
}
|
|
26
|
+
/** Direct Codex sessions pin one root even when it has no child components. */
|
|
27
|
+
export function prepareDirectCodexComponentHost(irDocument, scope, bindings) {
|
|
28
|
+
if (bindings.identity.vendor !== "codex")
|
|
29
|
+
throw new Error("Direct session requires Codex");
|
|
30
|
+
const prepared = prepareHost(irDocument, scope, bindings, true);
|
|
31
|
+
if (!prepared || Object.keys(prepared.hostRoots).length !== 1)
|
|
32
|
+
throw new Error("Direct session requires one entry");
|
|
33
|
+
return prepared;
|
|
34
|
+
}
|
|
35
|
+
function prepareHost(irDocument, scope, bindings, direct) {
|
|
36
|
+
bindings.assertCurrent();
|
|
37
|
+
const identity = freeze(structuredClone(bindings.identity));
|
|
38
|
+
const contexts = freeze(structuredClone(bindings.contexts));
|
|
39
|
+
const assertCurrent = bindings.assertCurrent.bind(bindings);
|
|
40
|
+
const currentIdentity = bindings.currentIdentity.bind(bindings);
|
|
41
|
+
const prepare = bindings.prepare, step = bindings.step, normalize = bindings.normalize;
|
|
42
|
+
const persistRoot = bindings.persistRoot, onEvent = bindings.onEvent, verifierBinary = bindings.verifierBinary;
|
|
43
|
+
const check = () => {
|
|
44
|
+
assertCurrent();
|
|
45
|
+
if (!isDeepStrictEqual(currentIdentity(), identity))
|
|
46
|
+
throw new Error("Native component binding expired");
|
|
47
|
+
};
|
|
48
|
+
check();
|
|
49
|
+
const ir = irSchema.parse(JSON.parse(irDocument));
|
|
50
|
+
const nodes = new Map(ir.components.map((node) => [z.string().min(1).parse(node.id), node]));
|
|
51
|
+
if (nodes.size !== ir.components.length)
|
|
52
|
+
throw new Error("Duplicate component identity");
|
|
53
|
+
const entry = z.union([
|
|
54
|
+
z.object({ kind: z.literal("scope"), prompt: z.string() }).strict(),
|
|
55
|
+
z.object({ kind: z.literal("agent").default("agent"), verb: z.string(), prompt: z.string() }).strict(),
|
|
56
|
+
]).parse(scope.entry);
|
|
57
|
+
if (identity.vendor === "codex" && entry.kind !== "agent")
|
|
58
|
+
throw new Error("Codex requires a single pinned entry");
|
|
59
|
+
if (!isDeepStrictEqual(scope.binding, identity.binding))
|
|
60
|
+
throw new Error("Native component scope binding mismatch");
|
|
61
|
+
const eligible = [...nodes.values()].filter((node) => node.entrypoint !== false && (entry.kind === "scope" || node.verb === entry.verb));
|
|
62
|
+
if (entry.kind === "agent" && eligible.length !== 1)
|
|
63
|
+
throw new Error("Unknown or ambiguous pinned entry");
|
|
64
|
+
const roots = eligible.filter((node) => direct || z.array(z.object({ component_calls: z.array(z.unknown()).optional() }).passthrough()).parse(node.llm_calls)
|
|
65
|
+
.some((step) => (step.component_calls?.length ?? 0) > 0));
|
|
66
|
+
if (!roots.length)
|
|
67
|
+
return undefined;
|
|
68
|
+
const needed = new Set();
|
|
69
|
+
const hostRoots = Object.fromEntries(roots.map((root) => {
|
|
70
|
+
const visited = new Set();
|
|
71
|
+
const visiting = new Set();
|
|
72
|
+
const visit = (id, depth) => {
|
|
73
|
+
if (depth > 8 || visiting.has(id))
|
|
74
|
+
throw new Error("Invalid component invocation closure");
|
|
75
|
+
if (visited.has(id))
|
|
76
|
+
return;
|
|
77
|
+
const node = nodeSchema.parse(nodes.get(id));
|
|
78
|
+
visiting.add(id);
|
|
79
|
+
for (const guard of node.guardrails)
|
|
80
|
+
if (!supportedGuards.has(String(guard.name)))
|
|
81
|
+
throw new Error("Unsupported native component guard");
|
|
82
|
+
for (const call of node.llm_calls)
|
|
83
|
+
for (const edge of call.component_calls ?? [])
|
|
84
|
+
visit(edge.component, depth + 1);
|
|
85
|
+
visiting.delete(id);
|
|
86
|
+
visited.add(id);
|
|
87
|
+
needed.add(id);
|
|
88
|
+
};
|
|
89
|
+
visit(String(root.id), 0);
|
|
90
|
+
const components = Object.fromEntries([...visited].sort().map((id) => {
|
|
91
|
+
const node = nodeSchema.parse(nodes.get(id));
|
|
92
|
+
const context = contexts[id];
|
|
93
|
+
if (!context || !isDeepStrictEqual(context.binding, node.context_binding))
|
|
94
|
+
throw new Error("Missing or mismatched component context");
|
|
95
|
+
const capabilities = Object.fromEntries(node.required_capabilities.filter((capability) => !capability.startsWith("llm:")).map((capability) => {
|
|
96
|
+
if (capability === "sql_execution:read_only")
|
|
97
|
+
return [capability, { tool: "query_read_only" }];
|
|
98
|
+
if (capability === "semantic_introspection")
|
|
99
|
+
return [capability, { tool: "inspect_context" }];
|
|
100
|
+
if (["component_invocation", "artifact_write", "render_contract"].includes(capability))
|
|
101
|
+
return [capability, {}];
|
|
102
|
+
throw new Error("Unsupported native component capability");
|
|
103
|
+
}));
|
|
104
|
+
return [id, { version: "2", tiers: [...new Set(node.llm_calls.map((call) => call.tier))], guardrails: node.guardrails, capabilities, execution }];
|
|
105
|
+
}));
|
|
106
|
+
return [String(root.id), { version: "2", protocol: "warble-component-host/1", execution: HOST_FEATURES, components }];
|
|
107
|
+
}));
|
|
108
|
+
freeze(hostRoots);
|
|
109
|
+
const selectedContexts = Object.fromEntries([...needed].sort().map((id) => [id, contexts[id]]));
|
|
110
|
+
const snapshots = Object.fromEntries(Object.entries(selectedContexts).map(([id, value]) => [id, value.snapshot]));
|
|
111
|
+
return freeze({
|
|
112
|
+
identity, hostRoots, contexts: freeze(snapshots), assertCurrent: check,
|
|
113
|
+
async host(root, plan, signal) {
|
|
114
|
+
check();
|
|
115
|
+
signal.throwIfAborted();
|
|
116
|
+
const descriptor = hostRoots[root];
|
|
117
|
+
if (!descriptor || plan.entries.length !== 1 || plan.entries[0] !== root || !isDeepStrictEqual(Object.keys(plan.components).sort(), Object.keys(descriptor.components).sort()))
|
|
118
|
+
throw new Error("Native component root mismatch");
|
|
119
|
+
const rootContexts = Object.fromEntries(Object.keys(plan.components).map((id) => [id, selectedContexts[id]]));
|
|
120
|
+
const brokerIdentity = { session: identity.session_id, vendor: identity.vendor, account: identity.auth_identity,
|
|
121
|
+
generation: identity.runtime_generation, project: identity.binding.project_identity, bindingRevision: identity.binding.revision,
|
|
122
|
+
contextDigest: planDigest(rootContexts), planDigest: plan.identity };
|
|
123
|
+
return createComponentBroker({ plan, contexts: rootContexts, verifierBinary,
|
|
124
|
+
identity: brokerIdentity, currentIdentity: () => { check(); return signal.aborted ? undefined : brokerIdentity; },
|
|
125
|
+
prepare, step, normalize, ...(persistRoot ? { persistRoot } : {}), ...(onEvent ? { onEvent } : {}),
|
|
126
|
+
});
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { type WrenAccessIdentity } from "../harness/components/wren-access.js";
|
|
2
|
+
import type { NativeComponentBindings } from "./native-component-preparation.js";
|
|
3
|
+
import { type ClaudeComponentRuntime } from "./runtime-host/claude-component-step.js";
|
|
4
|
+
import { type CodexComponentPolicy } from "./runtime-host/codex-component-step.js";
|
|
5
|
+
import type { RpcTransport } from "./runtime-host/codex-rpc.js";
|
|
6
|
+
/** Injected only after vendor certification. This factory does not confer certification. */
|
|
7
|
+
export type NativeComponentVendor = {
|
|
8
|
+
readonly authIdentity: string;
|
|
9
|
+
readonly accountEmail: string;
|
|
10
|
+
readonly generation: string;
|
|
11
|
+
readonly models: Readonly<Record<string, string>>;
|
|
12
|
+
assertCurrent(): void;
|
|
13
|
+
assertLive?(): void;
|
|
14
|
+
} & ({
|
|
15
|
+
readonly vendor: "claude";
|
|
16
|
+
open(model: string, signal: AbortSignal): Promise<ClaudeComponentRuntime>;
|
|
17
|
+
} | {
|
|
18
|
+
readonly vendor: "codex";
|
|
19
|
+
open(model: string, signal: AbortSignal): Promise<{
|
|
20
|
+
readonly transport: RpcTransport;
|
|
21
|
+
readonly policy: CodexComponentPolicy;
|
|
22
|
+
}>;
|
|
23
|
+
});
|
|
24
|
+
export interface NativeComponentRuntimeOptions extends Pick<NativeComponentBindings, "identity" | "contexts" | "verifierBinary" | "currentIdentity" | "assertCurrent" | "persistRoot" | "onEvent"> {
|
|
25
|
+
readonly vendor: NativeComponentVendor;
|
|
26
|
+
/** Optional cheap scope guard; integrity remains mandatory at protected operations. */
|
|
27
|
+
assertLive?(): void;
|
|
28
|
+
readonly wren: {
|
|
29
|
+
readonly executable: string;
|
|
30
|
+
readonly project: string;
|
|
31
|
+
readonly fingerprint: string;
|
|
32
|
+
readonly identity: WrenAccessIdentity;
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/** Bind governed steps to one approved account and captured tier map; no outer-session transport. */
|
|
36
|
+
export declare function createNativeComponentRuntimeBindings(options: NativeComponentRuntimeOptions): NativeComponentBindings;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { isDeepStrictEqual } from "node:util";
|
|
2
|
+
import { hashDirectory } from "../harness/compile/fingerprint.js";
|
|
3
|
+
import { normalizeComponentEvidence } from "../harness/components/normalize.js";
|
|
4
|
+
import { openWrenComponentAccess } from "../harness/components/wren-access.js";
|
|
5
|
+
import { runClaudeComponentStep } from "./runtime-host/claude-component-step.js";
|
|
6
|
+
import { runCodexComponentStep } from "./runtime-host/codex-component-step.js";
|
|
7
|
+
/** Bind governed steps to one approved account and captured tier map; no outer-session transport. */
|
|
8
|
+
export function createNativeComponentRuntimeBindings(options) {
|
|
9
|
+
const identity = structuredClone(options.identity);
|
|
10
|
+
const contexts = structuredClone(options.contexts);
|
|
11
|
+
const provider = options.vendor;
|
|
12
|
+
const vendor = provider.vendor;
|
|
13
|
+
const account = { authIdentity: provider.authIdentity, accountEmail: provider.accountEmail, generation: provider.generation };
|
|
14
|
+
const models = structuredClone(provider.models);
|
|
15
|
+
const assertBinding = options.assertCurrent.bind(options);
|
|
16
|
+
const currentIdentity = options.currentIdentity.bind(options);
|
|
17
|
+
const assertVendor = provider.assertCurrent.bind(provider);
|
|
18
|
+
const open = provider.open.bind(provider);
|
|
19
|
+
const wren = { ...options.wren };
|
|
20
|
+
const persistRoot = options.persistRoot;
|
|
21
|
+
const used = new WeakSet();
|
|
22
|
+
const assertBindingLive = options.assertLive?.bind(options) ?? assertBinding;
|
|
23
|
+
const assertVendorLive = provider.assertLive?.bind(provider) ?? assertVendor;
|
|
24
|
+
const checkIdentity = () => {
|
|
25
|
+
if (identity.vendor !== vendor || identity.auth_identity !== account.authIdentity || identity.runtime_generation !== account.generation
|
|
26
|
+
|| !account.accountEmail || !isDeepStrictEqual(currentIdentity(), identity)
|
|
27
|
+
|| provider.vendor !== vendor || provider.authIdentity !== account.authIdentity || provider.accountEmail !== account.accountEmail
|
|
28
|
+
|| provider.generation !== account.generation || !isDeepStrictEqual(provider.models, models))
|
|
29
|
+
throw new Error("Native component runtime binding expired");
|
|
30
|
+
};
|
|
31
|
+
const check = () => { assertBinding(); assertVendor(); checkIdentity(); };
|
|
32
|
+
const checkLive = () => { assertBindingLive(); assertVendorLive(); checkIdentity(); };
|
|
33
|
+
check();
|
|
34
|
+
const checkAccess = async (signal) => {
|
|
35
|
+
signal.throwIfAborted();
|
|
36
|
+
check();
|
|
37
|
+
await wren.identity.assertCurrent();
|
|
38
|
+
if (await hashDirectory(wren.project) !== wren.fingerprint)
|
|
39
|
+
throw new Error("Native component project changed");
|
|
40
|
+
signal.throwIfAborted();
|
|
41
|
+
check();
|
|
42
|
+
};
|
|
43
|
+
return {
|
|
44
|
+
identity, contexts, verifierBinary: options.verifierBinary, currentIdentity, assertCurrent: check,
|
|
45
|
+
...(persistRoot ? { persistRoot: async (...args) => { await checkAccess(args[2]); await persistRoot(...args); } } : {}), ...(options.onEvent ? { onEvent: options.onEvent } : {}),
|
|
46
|
+
async prepare(component, _identity, signal) {
|
|
47
|
+
await checkAccess(signal);
|
|
48
|
+
if (component.steps.some((step) => !models[step.tier]))
|
|
49
|
+
throw new Error("Missing native component tier binding");
|
|
50
|
+
if (component.steps.every((step) => step.tools.length === 0))
|
|
51
|
+
return {
|
|
52
|
+
async query() { throw new Error("No component query grant"); }, async inspect() { throw new Error("No component context grant"); }, async close() { },
|
|
53
|
+
};
|
|
54
|
+
return openWrenComponentAccess({ ...wren, signal });
|
|
55
|
+
},
|
|
56
|
+
async step(run, component) {
|
|
57
|
+
await checkAccess(run.signal);
|
|
58
|
+
const model = models[run.tier];
|
|
59
|
+
if (!model || !component.steps.some((step) => step.tier === run.tier))
|
|
60
|
+
throw new Error("Missing native component step binding");
|
|
61
|
+
const resource = await open(model, run.signal);
|
|
62
|
+
const transport = "transport" in resource ? resource.transport : resource;
|
|
63
|
+
let closing;
|
|
64
|
+
const close = () => closing ??= transport.close();
|
|
65
|
+
try {
|
|
66
|
+
await checkAccess(run.signal);
|
|
67
|
+
if (used.has(transport))
|
|
68
|
+
throw new Error("Native component step requires a fresh runtime");
|
|
69
|
+
used.add(transport);
|
|
70
|
+
if (vendor === "codex" && "transport" in resource) {
|
|
71
|
+
if (resource.policy.accountEmail !== account.accountEmail || resource.policy.model !== model)
|
|
72
|
+
throw new Error("Native component account or model mismatch");
|
|
73
|
+
const originalCheck = resource.policy.assertCurrent.bind(resource.policy);
|
|
74
|
+
const originalLive = resource.policy.assertLive?.bind(resource.policy) ?? originalCheck;
|
|
75
|
+
return await runCodexComponentStep({ listen: resource.transport.listen.bind(resource.transport), write: resource.transport.write.bind(resource.transport), close }, { ...resource.policy, assertCurrent() { check(); originalCheck(); },
|
|
76
|
+
assertLive() { checkLive(); originalLive(); } }, run);
|
|
77
|
+
}
|
|
78
|
+
if (vendor === "claude" && !("transport" in resource)) {
|
|
79
|
+
if (resource.account.email !== account.accountEmail || resource.model !== model)
|
|
80
|
+
throw new Error("Native component account or model mismatch");
|
|
81
|
+
const originalCheck = resource.assertCurrent.bind(resource);
|
|
82
|
+
return await runClaudeComponentStep({ ...resource, query: resource.query.bind(resource), close, assertCurrent() { check(); originalCheck(); } }, run);
|
|
83
|
+
}
|
|
84
|
+
throw new Error("Native component vendor mismatch");
|
|
85
|
+
}
|
|
86
|
+
finally {
|
|
87
|
+
await close();
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
async normalize(component, evidence, signal, context) { await checkAccess(signal); return normalizeComponentEvidence(component, evidence, context); },
|
|
91
|
+
};
|
|
92
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { type ExecutionPlan, type RunnerHost } from "../harness/components/runner.js";
|
|
3
|
+
import type { ComponentInvocationResult } from "@warble/claude-agent-sdk";
|
|
4
|
+
export declare const NATIVE_COMPONENT_REQUEST_SCHEMA: {
|
|
5
|
+
readonly type: "object";
|
|
6
|
+
readonly additionalProperties: false;
|
|
7
|
+
readonly properties: {
|
|
8
|
+
readonly request: {
|
|
9
|
+
readonly type: "string";
|
|
10
|
+
readonly minLength: 1;
|
|
11
|
+
readonly maxLength: 65536;
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
readonly required: readonly ["request"];
|
|
15
|
+
};
|
|
16
|
+
declare const launchHost: z.ZodObject<{
|
|
17
|
+
protocol: z.ZodLiteral<"warble-component-host/1">;
|
|
18
|
+
execution: z.ZodLiteral<"host_owned_steps">;
|
|
19
|
+
session_id: z.ZodString;
|
|
20
|
+
vendor: z.ZodEnum<{
|
|
21
|
+
claude: "claude";
|
|
22
|
+
codex: "codex";
|
|
23
|
+
}>;
|
|
24
|
+
auth_identity: z.ZodString;
|
|
25
|
+
runtime_generation: z.ZodString;
|
|
26
|
+
binding: z.ZodObject<{
|
|
27
|
+
project_identity: z.ZodString;
|
|
28
|
+
generation: z.ZodString;
|
|
29
|
+
revision: z.ZodString;
|
|
30
|
+
}, z.core.$strict>;
|
|
31
|
+
host_plan_sha256: z.ZodString;
|
|
32
|
+
plan_path: z.ZodLiteral<".warble/component-plans.json">;
|
|
33
|
+
root_tools: z.ZodRecord<z.ZodString, z.ZodString>;
|
|
34
|
+
}, z.core.$strict>;
|
|
35
|
+
export type NativeComponentIdentity = Pick<z.infer<typeof launchHost>, "session_id" | "vendor" | "auth_identity" | "runtime_generation" | "binding">;
|
|
36
|
+
export interface NativeComponentReceipt {
|
|
37
|
+
readonly identity: NativeComponentIdentity;
|
|
38
|
+
readonly irDocument: string;
|
|
39
|
+
readonly scopeDocument: string;
|
|
40
|
+
readonly hostRoots: Readonly<Record<string, unknown>>;
|
|
41
|
+
readonly contexts: Readonly<Record<string, unknown>>;
|
|
42
|
+
}
|
|
43
|
+
export interface NativeComponentPlans {
|
|
44
|
+
readonly identity: NativeComponentIdentity;
|
|
45
|
+
readonly digest: string;
|
|
46
|
+
readonly roots: Readonly<Record<string, ExecutionPlan>>;
|
|
47
|
+
readonly tools: Readonly<Record<string, string>>;
|
|
48
|
+
}
|
|
49
|
+
/** Supplied by a certified server-side provisioner, never browser/runtime settings JSON. */
|
|
50
|
+
export interface NativeComponentPreparation {
|
|
51
|
+
readonly identity: NativeComponentIdentity;
|
|
52
|
+
readonly hostRoots: Readonly<Record<string, unknown>>;
|
|
53
|
+
readonly contexts: Readonly<Record<string, unknown>>;
|
|
54
|
+
/** Revalidates the approved account and exact vendor execution generation. */
|
|
55
|
+
assertCurrent(): void;
|
|
56
|
+
host(root: string, plan: ExecutionPlan, signal: AbortSignal): Promise<RunnerHost>;
|
|
57
|
+
}
|
|
58
|
+
export declare function nativeComponentHostContract(prepared: NativeComponentPreparation): Readonly<Record<string, unknown>>;
|
|
59
|
+
/** Correlate emitted launch/plan data with host-owned inputs, never model arguments. */
|
|
60
|
+
export declare function readNativeComponentPlans(document: string, launch: unknown, receipt: NativeComponentReceipt): NativeComponentPlans;
|
|
61
|
+
/** Per-session fixed-root admission. A tool name selects a root, never an active step. */
|
|
62
|
+
export declare class NativeComponentAdmission {
|
|
63
|
+
private readonly current;
|
|
64
|
+
private readonly host;
|
|
65
|
+
private readonly controller;
|
|
66
|
+
private readonly requests;
|
|
67
|
+
private active;
|
|
68
|
+
private activeController;
|
|
69
|
+
private cleanupFailure;
|
|
70
|
+
private closing?;
|
|
71
|
+
private readonly plans;
|
|
72
|
+
constructor(plans: NativeComponentPlans, current: () => NativeComponentIdentity | undefined, host: (root: string, plan: ExecutionPlan, signal: AbortSignal) => Promise<RunnerHost>);
|
|
73
|
+
private check;
|
|
74
|
+
list(): readonly {
|
|
75
|
+
name: string;
|
|
76
|
+
description: string;
|
|
77
|
+
inputSchema: typeof NATIVE_COMPONENT_REQUEST_SCHEMA;
|
|
78
|
+
}[];
|
|
79
|
+
call(name: string, input: unknown, requestId: string | number, signal?: AbortSignal): Promise<ComponentInvocationResult>;
|
|
80
|
+
/** Browser detach cancels work while allowing the retained terminal to reconnect. */
|
|
81
|
+
cancelActive(): void;
|
|
82
|
+
close(): Promise<void>;
|
|
83
|
+
}
|
|
84
|
+
export {};
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { isDeepStrictEqual } from "node:util";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { canonicalPlanJson, planDigest, readExecutionPlan } from "../harness/components/plan.js";
|
|
5
|
+
import { ComponentRunner } from "../harness/components/runner.js";
|
|
6
|
+
const digest = z.string().regex(/^sha256:[a-f0-9]{64}$/);
|
|
7
|
+
const binding = z.object({ project_identity: z.string().min(1), generation: z.string().min(1), revision: z.string().min(1) }).strict();
|
|
8
|
+
const identity = {
|
|
9
|
+
protocol: z.literal("warble-component-host/1"), execution: z.literal("host_owned_steps"),
|
|
10
|
+
session_id: z.string().min(1), vendor: z.enum(["claude", "codex"]), auth_identity: z.string().min(1),
|
|
11
|
+
runtime_generation: z.string().min(1), binding,
|
|
12
|
+
};
|
|
13
|
+
export const NATIVE_COMPONENT_REQUEST_SCHEMA = { type: "object", additionalProperties: false,
|
|
14
|
+
properties: { request: { type: "string", minLength: 1, maxLength: 65_536 } }, required: ["request"],
|
|
15
|
+
};
|
|
16
|
+
const rootTools = z.record(z.string(), z.string().regex(/^warble_run_[a-f0-9]{32}$/));
|
|
17
|
+
const launchHost = z.object({ ...identity, host_plan_sha256: digest,
|
|
18
|
+
plan_path: z.literal(".warble/component-plans.json"), root_tools: rootTools,
|
|
19
|
+
}).strict();
|
|
20
|
+
const nativePlan = z.object({ ...identity, version: z.literal("1"), execution_status: z.literal("not_executed"),
|
|
21
|
+
host_plan_sha256: digest, input_ir_sha256: digest, scope_sha256: digest,
|
|
22
|
+
plans: z.record(z.string(), z.record(z.string(), z.unknown())), prepared_contexts: z.record(z.string(), digest),
|
|
23
|
+
root_tools: rootTools, request_schema: z.unknown(),
|
|
24
|
+
}).strict();
|
|
25
|
+
export function nativeComponentHostContract(prepared) {
|
|
26
|
+
prepared.assertCurrent();
|
|
27
|
+
return { version: "1", protocol: "warble-component-host/1", ...structuredClone(prepared.identity),
|
|
28
|
+
roots: structuredClone(prepared.hostRoots),
|
|
29
|
+
prepared_contexts: Object.fromEntries(Object.entries(prepared.contexts).map(([id, context]) => [id, planDigest(context)])),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
function equal(left, right) {
|
|
33
|
+
if (!isDeepStrictEqual(left, right))
|
|
34
|
+
throw new Error("Native component receipt mismatch");
|
|
35
|
+
}
|
|
36
|
+
function bytesDigest(value) { return `sha256:${createHash("sha256").update(value).digest("hex")}`; }
|
|
37
|
+
/** Correlate emitted launch/plan data with host-owned inputs, never model arguments. */
|
|
38
|
+
export function readNativeComponentPlans(document, launch, receipt) {
|
|
39
|
+
if (Buffer.byteLength(document) > 4 * 1024 * 1024)
|
|
40
|
+
throw new Error("Native component plan exceeds byte limit");
|
|
41
|
+
const raw = JSON.parse(document);
|
|
42
|
+
const parsed = nativePlan.parse(raw);
|
|
43
|
+
const descriptor = launchHost.parse(launch);
|
|
44
|
+
const { host_plan_sha256: claimed, ...payload } = raw;
|
|
45
|
+
equal(claimed, planDigest(payload));
|
|
46
|
+
equal(descriptor.host_plan_sha256, claimed);
|
|
47
|
+
for (const key of ["session_id", "vendor", "auth_identity", "runtime_generation", "binding"]) {
|
|
48
|
+
equal(parsed[key], receipt.identity[key]);
|
|
49
|
+
equal(descriptor[key], receipt.identity[key]);
|
|
50
|
+
}
|
|
51
|
+
equal(parsed.input_ir_sha256, bytesDigest(receipt.irDocument));
|
|
52
|
+
const scope = JSON.parse(receipt.scopeDocument);
|
|
53
|
+
// The v3 scope contract defaults an omitted entry kind to the pinned agent form.
|
|
54
|
+
if (scope.entry?.kind === undefined && typeof scope.entry?.verb === "string")
|
|
55
|
+
scope.entry.kind = "agent";
|
|
56
|
+
equal(parsed.scope_sha256, planDigest(scope));
|
|
57
|
+
equal(parsed.root_tools, descriptor.root_tools);
|
|
58
|
+
equal(parsed.request_schema, NATIVE_COMPONENT_REQUEST_SCHEMA);
|
|
59
|
+
equal(Object.keys(parsed.plans).sort(), Object.keys(receipt.hostRoots).sort());
|
|
60
|
+
equal(Object.keys(parsed.root_tools).sort(), Object.keys(parsed.plans).sort());
|
|
61
|
+
if (new Set(Object.values(parsed.root_tools)).size !== Object.keys(parsed.root_tools).length)
|
|
62
|
+
throw new Error("Duplicate native admission tool");
|
|
63
|
+
equal(parsed.prepared_contexts, Object.fromEntries(Object.entries(receipt.contexts).map(([id, context]) => [id, planDigest(context)])));
|
|
64
|
+
const ir = z.object({ context_binding: z.record(z.string(), z.unknown()), components: z.array(z.object({ id: z.string() }).passthrough()) }).passthrough().parse(JSON.parse(receipt.irDocument));
|
|
65
|
+
const declarations = Object.fromEntries(ir.components.map((component) => [component.id, component]));
|
|
66
|
+
const roots = Object.fromEntries(Object.entries(parsed.plans).map(([root, plan]) => {
|
|
67
|
+
equal(plan.entry, root);
|
|
68
|
+
const executable = readExecutionPlan(JSON.stringify(plan), { digest: digest.parse(plan.plan_sha256), inputIrDigest: parsed.input_ir_sha256,
|
|
69
|
+
contextBinding: ir.context_binding, declarations, directHostDocument: canonicalPlanJson(receipt.hostRoots[root]),
|
|
70
|
+
});
|
|
71
|
+
for (const id of Object.keys(executable.components))
|
|
72
|
+
if (!Object.hasOwn(receipt.contexts, id))
|
|
73
|
+
throw new Error("Missing native component context");
|
|
74
|
+
return [root, executable];
|
|
75
|
+
}));
|
|
76
|
+
return { identity: structuredClone(receipt.identity), digest: parsed.host_plan_sha256, roots, tools: parsed.root_tools };
|
|
77
|
+
}
|
|
78
|
+
/** Per-session fixed-root admission. A tool name selects a root, never an active step. */
|
|
79
|
+
export class NativeComponentAdmission {
|
|
80
|
+
current;
|
|
81
|
+
host;
|
|
82
|
+
controller = new AbortController();
|
|
83
|
+
requests = new Set();
|
|
84
|
+
active;
|
|
85
|
+
activeController;
|
|
86
|
+
cleanupFailure = false;
|
|
87
|
+
closing;
|
|
88
|
+
plans;
|
|
89
|
+
constructor(plans, current, host) {
|
|
90
|
+
this.current = current;
|
|
91
|
+
this.host = host;
|
|
92
|
+
this.plans = structuredClone(plans);
|
|
93
|
+
}
|
|
94
|
+
check() {
|
|
95
|
+
this.controller.signal.throwIfAborted();
|
|
96
|
+
if (this.cleanupFailure)
|
|
97
|
+
throw new Error("Native component cleanup failed");
|
|
98
|
+
if (!isDeepStrictEqual(this.current(), this.plans.identity)) {
|
|
99
|
+
this.controller.abort();
|
|
100
|
+
throw new Error("Native component session expired");
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
list() {
|
|
104
|
+
this.check();
|
|
105
|
+
return Object.entries(this.plans.tools).map(([root, name]) => ({ name,
|
|
106
|
+
description: String(this.plans.roots[root].components[root].declaration.description ?? `Run ${root}`), inputSchema: NATIVE_COMPONENT_REQUEST_SCHEMA, }));
|
|
107
|
+
}
|
|
108
|
+
async call(name, input, requestId, signal) {
|
|
109
|
+
this.check();
|
|
110
|
+
const root = Object.entries(this.plans.tools).find(([, tool]) => tool === name)?.[0];
|
|
111
|
+
const parsed = z.object({ request: z.string().trim().min(1).max(65_536) }).strict().parse(input);
|
|
112
|
+
if (!root || Buffer.byteLength(JSON.stringify(parsed)) > 65_536)
|
|
113
|
+
throw new Error("Invalid native component request");
|
|
114
|
+
const key = JSON.stringify(requestId);
|
|
115
|
+
if (this.active || this.requests.has(key) || this.requests.size >= 1024)
|
|
116
|
+
throw new Error("Native component request is busy or replayed");
|
|
117
|
+
this.requests.add(key);
|
|
118
|
+
const activeController = new AbortController();
|
|
119
|
+
this.activeController = activeController;
|
|
120
|
+
const combined = AbortSignal.any([this.controller.signal, activeController.signal, ...(signal ? [signal] : [])]);
|
|
121
|
+
const plan = this.plans.roots[root];
|
|
122
|
+
const run = (async () => {
|
|
123
|
+
const host = await this.host(root, plan, combined);
|
|
124
|
+
this.check();
|
|
125
|
+
combined.throwIfAborted();
|
|
126
|
+
const runner = new ComponentRunner(plan, host);
|
|
127
|
+
const result = await runner.run(root, parsed, combined);
|
|
128
|
+
this.cleanupFailure ||= runner.cleanupFailed;
|
|
129
|
+
this.check();
|
|
130
|
+
combined.throwIfAborted();
|
|
131
|
+
return result;
|
|
132
|
+
})();
|
|
133
|
+
this.active = run;
|
|
134
|
+
try {
|
|
135
|
+
return await run;
|
|
136
|
+
}
|
|
137
|
+
finally {
|
|
138
|
+
this.active = undefined;
|
|
139
|
+
this.activeController = undefined;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
/** Browser detach cancels work while allowing the retained terminal to reconnect. */
|
|
143
|
+
cancelActive() { this.activeController?.abort(); }
|
|
144
|
+
close() {
|
|
145
|
+
this.controller.abort();
|
|
146
|
+
return this.closing ??= (async () => {
|
|
147
|
+
let timer;
|
|
148
|
+
try {
|
|
149
|
+
await Promise.race([this.active?.catch(() => { }), new Promise((_, reject) => {
|
|
150
|
+
timer = setTimeout(() => reject(new Error("Native component cleanup timed out")), 5_000);
|
|
151
|
+
})]);
|
|
152
|
+
if (this.cleanupFailure)
|
|
153
|
+
throw new Error("Native component cleanup failed");
|
|
154
|
+
}
|
|
155
|
+
finally {
|
|
156
|
+
clearTimeout(timer);
|
|
157
|
+
}
|
|
158
|
+
})();
|
|
159
|
+
}
|
|
160
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** Bounded compiler process group; cancellation also reaps descendants before returning. */
|
|
2
|
+
export declare function runNativeDispatchProcess(input: {
|
|
3
|
+
executable: string;
|
|
4
|
+
args: readonly string[];
|
|
5
|
+
cwd: string;
|
|
6
|
+
env: NodeJS.ProcessEnv;
|
|
7
|
+
signal?: AbortSignal;
|
|
8
|
+
}): Promise<void>;
|