@wrenai/genbi 0.0.4 → 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 +64 -2
- 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 +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 +177 -25
- 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 +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 +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
|
@@ -20,7 +20,6 @@ export declare const NATIVE_SAVE_DASHBOARD_SEMANTIC_CONSTRAINTS: {
|
|
|
20
20
|
readonly positionalRows: "Positional scalar rows in [x, ...series] order. Each row must contain exactly one x cell plus one cell per series entry.";
|
|
21
21
|
};
|
|
22
22
|
};
|
|
23
|
-
/** The exact public MCP shape advertised to native clients and accepted by the host. */
|
|
24
23
|
export declare const NATIVE_SAVE_DASHBOARD_INPUT_SCHEMA: {
|
|
25
24
|
readonly type: "object";
|
|
26
25
|
readonly additionalProperties: false;
|
|
@@ -90,7 +89,7 @@ export declare const NATIVE_SAVE_DASHBOARD_INPUT_SCHEMA: {
|
|
|
90
89
|
};
|
|
91
90
|
readonly answer_selection: {
|
|
92
91
|
readonly const: "latest";
|
|
93
|
-
readonly description: "Resolve the newest
|
|
92
|
+
readonly description: "Resolve the newest answer retained by this native session when its opaque answer_ref is no longer available in the conversation.";
|
|
94
93
|
};
|
|
95
94
|
readonly envelope: {
|
|
96
95
|
readonly type: "object";
|
|
@@ -98,7 +97,8 @@ export declare const NATIVE_SAVE_DASHBOARD_INPUT_SCHEMA: {
|
|
|
98
97
|
readonly required: readonly ["blocks", "verified"];
|
|
99
98
|
readonly properties: {
|
|
100
99
|
readonly verified: {
|
|
101
|
-
readonly
|
|
100
|
+
readonly type: "boolean";
|
|
101
|
+
readonly description: "Ignored: the host records verified only for an answer it produced from a grounded component run; anything submitted here is stored as unverified.";
|
|
102
102
|
};
|
|
103
103
|
readonly summary: {
|
|
104
104
|
readonly anyOf: readonly [{
|
|
@@ -349,7 +349,8 @@ export declare const NATIVE_PERSIST_ANSWER_INPUT_SCHEMA: {
|
|
|
349
349
|
readonly required: readonly ["blocks", "verified"];
|
|
350
350
|
readonly properties: {
|
|
351
351
|
readonly verified: {
|
|
352
|
-
readonly
|
|
352
|
+
readonly type: "boolean";
|
|
353
|
+
readonly description: "Ignored: the host records verified only for an answer it produced from a grounded component run; anything submitted here is stored as unverified.";
|
|
353
354
|
};
|
|
354
355
|
readonly blocks: {
|
|
355
356
|
readonly type: "array";
|
|
@@ -580,7 +581,7 @@ export declare const NATIVE_SAVE_DASHBOARD_CONTRACT: {
|
|
|
580
581
|
};
|
|
581
582
|
readonly answer_selection: {
|
|
582
583
|
readonly const: "latest";
|
|
583
|
-
readonly description: "Resolve the newest
|
|
584
|
+
readonly description: "Resolve the newest answer retained by this native session when its opaque answer_ref is no longer available in the conversation.";
|
|
584
585
|
};
|
|
585
586
|
readonly envelope: {
|
|
586
587
|
readonly type: "object";
|
|
@@ -588,7 +589,8 @@ export declare const NATIVE_SAVE_DASHBOARD_CONTRACT: {
|
|
|
588
589
|
readonly required: readonly ["blocks", "verified"];
|
|
589
590
|
readonly properties: {
|
|
590
591
|
readonly verified: {
|
|
591
|
-
readonly
|
|
592
|
+
readonly type: "boolean";
|
|
593
|
+
readonly description: "Ignored: the host records verified only for an answer it produced from a grounded component run; anything submitted here is stored as unverified.";
|
|
592
594
|
};
|
|
593
595
|
readonly summary: {
|
|
594
596
|
readonly anyOf: readonly [{
|
|
@@ -848,7 +850,8 @@ export declare const NATIVE_PERSIST_ANSWER_CONTRACT: {
|
|
|
848
850
|
readonly required: readonly ["blocks", "verified"];
|
|
849
851
|
readonly properties: {
|
|
850
852
|
readonly verified: {
|
|
851
|
-
readonly
|
|
853
|
+
readonly type: "boolean";
|
|
854
|
+
readonly description: "Ignored: the host records verified only for an answer it produced from a grounded component run; anything submitted here is stored as unverified.";
|
|
852
855
|
};
|
|
853
856
|
readonly blocks: {
|
|
854
857
|
readonly type: "array";
|
|
@@ -970,6 +973,7 @@ export interface NativeArtifactServiceOptions {
|
|
|
970
973
|
readonly idempotencyKey: string;
|
|
971
974
|
readonly envelopeJson: string;
|
|
972
975
|
readonly digest: string;
|
|
976
|
+
readonly grounded: boolean;
|
|
973
977
|
}) => {
|
|
974
978
|
readonly row: NativeStructuredAnswerRow;
|
|
975
979
|
readonly created: boolean;
|
|
@@ -1000,5 +1004,17 @@ export declare class NativeArtifactService {
|
|
|
1000
1004
|
* a later follow-up can only reference this stored result, never recompute.
|
|
1001
1005
|
*/
|
|
1002
1006
|
persistAnswer(credential: string | undefined, input: unknown): PersistedStructuredAnswer;
|
|
1007
|
+
/**
|
|
1008
|
+
* Host-only entry for a grounded component root (never exposed over MCP).
|
|
1009
|
+
* The caller must already have checked the component's verified provenance.
|
|
1010
|
+
*/
|
|
1011
|
+
persistGroundedAnswer(credential: string | undefined, input: unknown): PersistedStructuredAnswer;
|
|
1012
|
+
private persistStructured;
|
|
1003
1013
|
save(credential: string | undefined, input: unknown): SavedNativeArtifact;
|
|
1014
|
+
/**
|
|
1015
|
+
* Host-only entry for a grounded component render root (never exposed over
|
|
1016
|
+
* MCP). The caller must already have checked verified provenance.
|
|
1017
|
+
*/
|
|
1018
|
+
saveGrounded(credential: string | undefined, input: unknown): SavedNativeArtifact;
|
|
1019
|
+
private saveArtifact;
|
|
1004
1020
|
}
|
|
@@ -53,6 +53,7 @@ const POSITIONAL_ROWS_SCHEMA = {
|
|
|
53
53
|
items: { type: "array", items: SCALAR_SCHEMA },
|
|
54
54
|
};
|
|
55
55
|
/** The exact public MCP shape advertised to native clients and accepted by the host. */
|
|
56
|
+
const VERIFIED_FIELD_SCHEMA = { type: "boolean", description: "Ignored: the host records verified only for an answer it produced from a grounded component run; anything submitted here is stored as unverified." };
|
|
56
57
|
export const NATIVE_SAVE_DASHBOARD_INPUT_SCHEMA = {
|
|
57
58
|
type: "object",
|
|
58
59
|
additionalProperties: false,
|
|
@@ -67,11 +68,11 @@ export const NATIVE_SAVE_DASHBOARD_INPUT_SCHEMA = {
|
|
|
67
68
|
name: { ...SAFE_TEXT_SCHEMA, minLength: 1, maxLength: MAX_NAME_LENGTH, pattern: "\\S" },
|
|
68
69
|
idempotency_key: { ...SAFE_TEXT_SCHEMA, minLength: 8, maxLength: MAX_IDEMPOTENCY_KEY_LENGTH, pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]+$" },
|
|
69
70
|
answer_ref: { ...SAFE_TEXT_SCHEMA, pattern: "^answer-[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$", description: "Opaque reference returned by persist_answer for this native session." },
|
|
70
|
-
answer_selection: { const: "latest", description: "Resolve the newest
|
|
71
|
+
answer_selection: { const: "latest", description: "Resolve the newest answer retained by this native session when its opaque answer_ref is no longer available in the conversation." },
|
|
71
72
|
envelope: {
|
|
72
73
|
type: "object", additionalProperties: false, required: ["blocks", "verified"],
|
|
73
74
|
properties: {
|
|
74
|
-
verified:
|
|
75
|
+
verified: VERIFIED_FIELD_SCHEMA, summary: NULLABLE_SAFE_TEXT_SCHEMA, estimate: { type: ["boolean", "null"] },
|
|
75
76
|
blocks: {
|
|
76
77
|
type: "array", minItems: 1,
|
|
77
78
|
items: { oneOf: [
|
|
@@ -97,7 +98,7 @@ const NATIVE_STRUCTURED_ANSWER_ENVELOPE_SCHEMA = {
|
|
|
97
98
|
additionalProperties: false,
|
|
98
99
|
required: ["blocks", "verified"],
|
|
99
100
|
properties: {
|
|
100
|
-
verified:
|
|
101
|
+
verified: VERIFIED_FIELD_SCHEMA,
|
|
101
102
|
blocks: {
|
|
102
103
|
type: "array",
|
|
103
104
|
minItems: 1,
|
|
@@ -211,8 +212,8 @@ function validBlock(block) {
|
|
|
211
212
|
function validateDashboardEnvelope(envelope) {
|
|
212
213
|
if (!isRecord(envelope) || !Object.hasOwn(envelope, "blocks") || !Object.hasOwn(envelope, "verified") || !Object.keys(envelope).every((key) => ["blocks", "summary", "verified", "estimate"].includes(key)))
|
|
213
214
|
throw new NativeArtifactError("invalid dashboard render envelope: expected blocks and verified with optional summary or estimate");
|
|
214
|
-
if (envelope.verified !==
|
|
215
|
-
throw new NativeArtifactError("invalid dashboard render envelope: verified must be
|
|
215
|
+
if (typeof envelope.verified !== "boolean")
|
|
216
|
+
throw new NativeArtifactError("invalid dashboard render envelope: verified must be a boolean");
|
|
216
217
|
if (!Array.isArray(envelope.blocks) || envelope.blocks.length === 0)
|
|
217
218
|
throw new NativeArtifactError("invalid dashboard render envelope: blocks must be a non-empty array");
|
|
218
219
|
const invalidBlock = envelope.blocks.findIndex((block) => !validBlock(block));
|
|
@@ -275,6 +276,14 @@ function validatePersistedStructuredAnswer(value) {
|
|
|
275
276
|
}
|
|
276
277
|
return { version: "1", idempotency_key: value.idempotency_key, envelope: value.envelope };
|
|
277
278
|
}
|
|
279
|
+
/**
|
|
280
|
+
* The stored `verified` flag is host provenance, never the caller's claim: the
|
|
281
|
+
* persisted envelope carries exactly the flag the host recorded, so its own
|
|
282
|
+
* badge cannot disagree with the artifact row.
|
|
283
|
+
*/
|
|
284
|
+
function withHostVerified(envelope, verified) {
|
|
285
|
+
return { ...envelope, verified };
|
|
286
|
+
}
|
|
278
287
|
function structuredAnswerDigest(envelopeJson) {
|
|
279
288
|
return `sha256:${createHash("sha256").update(envelopeJson).digest("hex")}`;
|
|
280
289
|
}
|
|
@@ -377,11 +386,21 @@ export class NativeArtifactService {
|
|
|
377
386
|
* a later follow-up can only reference this stored result, never recompute.
|
|
378
387
|
*/
|
|
379
388
|
persistAnswer(credential, input) {
|
|
389
|
+
return this.persistStructured(credential, input, false);
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Host-only entry for a grounded component root (never exposed over MCP).
|
|
393
|
+
* The caller must already have checked the component's verified provenance.
|
|
394
|
+
*/
|
|
395
|
+
persistGroundedAnswer(credential, input) {
|
|
396
|
+
return this.persistStructured(credential, input, true);
|
|
397
|
+
}
|
|
398
|
+
persistStructured(credential, input, grounded) {
|
|
380
399
|
const session = this.authorize(credential);
|
|
381
400
|
if (session.purpose !== "analysis")
|
|
382
401
|
throw new NativeArtifactError("structured answer persistence is unavailable for this native session", 409);
|
|
383
402
|
const payload = validatePersistedStructuredAnswer(input);
|
|
384
|
-
const envelopeJson = JSON.stringify(payload.envelope);
|
|
403
|
+
const envelopeJson = JSON.stringify(withHostVerified(payload.envelope, grounded));
|
|
385
404
|
const digest = structuredAnswerDigest(envelopeJson);
|
|
386
405
|
const existing = this.options.store.getNativeStructuredAnswerByIdempotency(session.id, payload.idempotency_key);
|
|
387
406
|
if (existing) {
|
|
@@ -392,7 +411,7 @@ export class NativeArtifactService {
|
|
|
392
411
|
}
|
|
393
412
|
try {
|
|
394
413
|
const persisted = (this.options.persistStructuredAnswer ?? ((params) => this.options.store.createNativeStructuredAnswer(params)))({
|
|
395
|
-
id: newId("answer"), nativeSessionId: session.id, idempotencyKey: payload.idempotency_key, envelopeJson, digest,
|
|
414
|
+
id: newId("answer"), nativeSessionId: session.id, idempotencyKey: payload.idempotency_key, envelopeJson, digest, grounded,
|
|
396
415
|
});
|
|
397
416
|
if (persisted.row.digest !== digest || persisted.row.envelopeJson !== envelopeJson) {
|
|
398
417
|
throw new NativeArtifactError("structured answer idempotency key is already bound to a different answer", 409);
|
|
@@ -409,6 +428,16 @@ export class NativeArtifactService {
|
|
|
409
428
|
}
|
|
410
429
|
}
|
|
411
430
|
save(credential, input) {
|
|
431
|
+
return this.saveArtifact(credential, input, false);
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
434
|
+
* Host-only entry for a grounded component render root (never exposed over
|
|
435
|
+
* MCP). The caller must already have checked verified provenance.
|
|
436
|
+
*/
|
|
437
|
+
saveGrounded(credential, input) {
|
|
438
|
+
return this.saveArtifact(credential, input, true);
|
|
439
|
+
}
|
|
440
|
+
saveArtifact(credential, input, grounded) {
|
|
412
441
|
const context = credential === undefined ? undefined : this.credentials.get(credential);
|
|
413
442
|
if (!context)
|
|
414
443
|
throw new NativeArtifactError("GenBI MCP bearer credential is invalid. Restart this native session to refresh its GenBI MCP connection.", 401);
|
|
@@ -419,7 +448,6 @@ export class NativeArtifactService {
|
|
|
419
448
|
throw new NativeArtifactError("GenBI MCP session binding is stale. Start a new native session.", 409);
|
|
420
449
|
}
|
|
421
450
|
const payload = validateInput(input);
|
|
422
|
-
const referenceSave = "answer_ref" in payload || "answer_selection" in payload;
|
|
423
451
|
const source = "answer_ref" in payload
|
|
424
452
|
? this.options.store.getNativeStructuredAnswer(payload.answer_ref)
|
|
425
453
|
: "answer_selection" in payload
|
|
@@ -431,13 +459,16 @@ export class NativeArtifactService {
|
|
|
431
459
|
throw new NativeArtifactError("no persisted answer is available for this native session", 404);
|
|
432
460
|
if (source)
|
|
433
461
|
validateStoredStructuredAnswer(source, session.id);
|
|
434
|
-
|
|
435
|
-
|
|
462
|
+
// A reference save inherits the retained answer's provenance; an envelope
|
|
463
|
+
// save is verified only through the host-only grounded entry.
|
|
464
|
+
const verified = source ? source.grounded : grounded;
|
|
465
|
+
const contents = JSON.stringify(withHostVerified(source ? JSON.parse(source.envelopeJson) : "envelope" in payload ? payload.envelope : undefined, verified));
|
|
466
|
+
const digest = structuredAnswerDigest(contents);
|
|
436
467
|
const sameSaveRequest = (artifact) => artifact !== undefined &&
|
|
437
468
|
artifact.name === payload.name.trim() &&
|
|
438
469
|
artifact.projectIdentity === binding.identity && artifact.bindingGeneration === binding.generation && artifact.projectRevision === binding.revision &&
|
|
439
470
|
artifact.nativeVendor === session.vendor && artifact.nativeAgent === session.agent &&
|
|
440
|
-
artifact.contentDigest === digest && artifact.sourceAnswerId === (source?.id ?? null);
|
|
471
|
+
artifact.contentDigest === digest && artifact.sourceAnswerId === (source?.id ?? null) && artifact.verified === verified;
|
|
441
472
|
const existing = this.options.store.getNativeArtifactByIdempotency(session.id, payload.idempotency_key);
|
|
442
473
|
if (existing) {
|
|
443
474
|
if (!sameSaveRequest(existing))
|
|
@@ -450,7 +481,7 @@ export class NativeArtifactService {
|
|
|
450
481
|
try {
|
|
451
482
|
const safeTarget = assertSafeContainedTarget(this.options.artifactsRoot, target);
|
|
452
483
|
(this.options.writeAtomic ?? writeContainedAtomically)(safeTarget, contents);
|
|
453
|
-
const inserted = this.options.store.createNativeArtifact({ id, sessionId: session.id, nativeSessionId: session.id, name: payload.name.trim(), location, projectIdentity: binding.identity, bindingGeneration: binding.generation, projectRevision: binding.revision, vendor: session.vendor, agent: session.agent, digest, idempotencyKey: payload.idempotency_key, ...(source ? { sourceAnswerId: source.id } : {}) });
|
|
484
|
+
const inserted = this.options.store.createNativeArtifact({ id, sessionId: session.id, nativeSessionId: session.id, name: payload.name.trim(), location, projectIdentity: binding.identity, bindingGeneration: binding.generation, projectRevision: binding.revision, vendor: session.vendor, agent: session.agent, digest, idempotencyKey: payload.idempotency_key, verified, ...(source ? { sourceAnswerId: source.id } : {}) });
|
|
454
485
|
if (!inserted.created && !sameSaveRequest(inserted.row)) {
|
|
455
486
|
rmSync(target, { force: true });
|
|
456
487
|
throw new NativeArtifactError("artifact idempotency key is already bound to a different save request", 409);
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type CodexSessionTools } from "./runtime-host/codex-direct-tools.js";
|
|
2
|
+
import { type NativeComponentRuntimeOptions } from "./native-component-runtime.js";
|
|
3
|
+
import type { NativeComponentPreparation } from "./native-components.js";
|
|
4
|
+
export interface NativeComponentContextOptions extends Omit<NativeComponentRuntimeOptions, "contexts" | "wren"> {
|
|
5
|
+
/** All executables and the provider are resolved by the certified server provisioner. */
|
|
6
|
+
readonly contextLoaderBinary: string;
|
|
7
|
+
/** Explicit host-only credential environment supplied by the provisioner. */
|
|
8
|
+
readonly wrenEnvironment?: Readonly<NodeJS.ProcessEnv>;
|
|
9
|
+
readonly wrenBinary: string;
|
|
10
|
+
readonly project: string;
|
|
11
|
+
readonly signal: AbortSignal;
|
|
12
|
+
}
|
|
13
|
+
/** Capture file-only context before host admission; never opens a vendor or Wren process. */
|
|
14
|
+
export declare function prepareCapturedNativeComponentHost(irDocument: string, scope: Readonly<Record<string, unknown>>, options: NativeComponentContextOptions): Promise<NativeComponentPreparation | undefined>;
|
|
15
|
+
/** Direct-session variant shares the same captured project/credential provenance. */
|
|
16
|
+
export declare function prepareCapturedCodexDirectTools(irDocument: string, scope: Readonly<Record<string, unknown>>, options: NativeComponentContextOptions & {
|
|
17
|
+
readonly producerBinary: string;
|
|
18
|
+
readonly expiresAt: number;
|
|
19
|
+
}): Promise<CodexSessionTools>;
|
|
@@ -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
|
+
}
|