@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
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { type NativeSessionRow, type NativeStructuredAnswerRow, type Store } from "./db.js";
|
|
2
2
|
import type { EnrichmentBinding } from "./enrichment.js";
|
|
3
|
+
import type { ComponentAccess } from "../harness/components/broker.js";
|
|
3
4
|
export declare const NATIVE_MCP_CREDENTIAL_ENV_VAR = "WARBLE_MCP_CONNECTION_CREDENTIAL";
|
|
4
5
|
export declare const NATIVE_MCP_TOOL_NAME = "save_dashboard";
|
|
5
6
|
export declare const NATIVE_MCP_PERSIST_ANSWER_TOOL_NAME = "persist_answer";
|
|
7
|
+
export declare const NATIVE_MCP_QUERY_TOOL_NAME = "query";
|
|
6
8
|
export declare const NATIVE_MCP_SERVER_LABEL = "GenBI MCP";
|
|
7
9
|
export declare const NATIVE_MCP_ARTIFACTS_LABEL = "GenBI Artifacts";
|
|
8
10
|
/**
|
|
@@ -20,7 +22,6 @@ export declare const NATIVE_SAVE_DASHBOARD_SEMANTIC_CONSTRAINTS: {
|
|
|
20
22
|
readonly positionalRows: "Positional scalar rows in [x, ...series] order. Each row must contain exactly one x cell plus one cell per series entry.";
|
|
21
23
|
};
|
|
22
24
|
};
|
|
23
|
-
/** The exact public MCP shape advertised to native clients and accepted by the host. */
|
|
24
25
|
export declare const NATIVE_SAVE_DASHBOARD_INPUT_SCHEMA: {
|
|
25
26
|
readonly type: "object";
|
|
26
27
|
readonly additionalProperties: false;
|
|
@@ -90,7 +91,7 @@ export declare const NATIVE_SAVE_DASHBOARD_INPUT_SCHEMA: {
|
|
|
90
91
|
};
|
|
91
92
|
readonly answer_selection: {
|
|
92
93
|
readonly const: "latest";
|
|
93
|
-
readonly description: "Resolve the newest
|
|
94
|
+
readonly description: "Resolve the newest answer retained by this native session when its opaque answer_ref is no longer available in the conversation.";
|
|
94
95
|
};
|
|
95
96
|
readonly envelope: {
|
|
96
97
|
readonly type: "object";
|
|
@@ -98,7 +99,8 @@ export declare const NATIVE_SAVE_DASHBOARD_INPUT_SCHEMA: {
|
|
|
98
99
|
readonly required: readonly ["blocks", "verified"];
|
|
99
100
|
readonly properties: {
|
|
100
101
|
readonly verified: {
|
|
101
|
-
readonly
|
|
102
|
+
readonly type: "boolean";
|
|
103
|
+
readonly description: "Ignored: the host decides verified itself. It records verified only for an answer it grounded: a grounded component run, or an answer whose every definition cites a query this session ran through the query tool. Anything submitted here is not a claim the host keeps.";
|
|
102
104
|
};
|
|
103
105
|
readonly summary: {
|
|
104
106
|
readonly anyOf: readonly [{
|
|
@@ -286,6 +288,15 @@ export declare const NATIVE_SAVE_DASHBOARD_INPUT_SCHEMA: {
|
|
|
286
288
|
};
|
|
287
289
|
};
|
|
288
290
|
};
|
|
291
|
+
readonly query_id: {
|
|
292
|
+
readonly type: "string";
|
|
293
|
+
readonly not: {
|
|
294
|
+
readonly pattern: "<\\s*\\/?(?:[sS][cC][rR][iI][pP][tT]|[hH][tT][mM][lL])\\b|[jJ][aA][vV][aA][sS][cC][rR][iI][pP][tT]\\s*:";
|
|
295
|
+
};
|
|
296
|
+
readonly minLength: 1;
|
|
297
|
+
readonly maxLength: 64;
|
|
298
|
+
readonly description: "Optional: the query_id the query tool returned for the query this definition describes.";
|
|
299
|
+
};
|
|
289
300
|
};
|
|
290
301
|
}, {
|
|
291
302
|
readonly type: "object";
|
|
@@ -349,7 +360,8 @@ export declare const NATIVE_PERSIST_ANSWER_INPUT_SCHEMA: {
|
|
|
349
360
|
readonly required: readonly ["blocks", "verified"];
|
|
350
361
|
readonly properties: {
|
|
351
362
|
readonly verified: {
|
|
352
|
-
readonly
|
|
363
|
+
readonly type: "boolean";
|
|
364
|
+
readonly description: "Ignored: the host decides verified itself. It records verified only for an answer it grounded: a grounded component run, or an answer whose every definition cites a query this session ran through the query tool. Anything submitted here is not a claim the host keeps.";
|
|
353
365
|
};
|
|
354
366
|
readonly blocks: {
|
|
355
367
|
readonly type: "array";
|
|
@@ -433,6 +445,15 @@ export declare const NATIVE_PERSIST_ANSWER_INPUT_SCHEMA: {
|
|
|
433
445
|
};
|
|
434
446
|
};
|
|
435
447
|
};
|
|
448
|
+
readonly query_id: {
|
|
449
|
+
readonly type: "string";
|
|
450
|
+
readonly not: {
|
|
451
|
+
readonly pattern: "<\\s*\\/?(?:[sS][cC][rR][iI][pP][tT]|[hH][tT][mM][lL])\\b|[jJ][aA][vV][aA][sS][cC][rR][iI][pP][tT]\\s*:";
|
|
452
|
+
};
|
|
453
|
+
readonly minLength: 1;
|
|
454
|
+
readonly maxLength: 64;
|
|
455
|
+
readonly description: "Optional: the query_id the query tool returned for the query this definition describes.";
|
|
456
|
+
};
|
|
436
457
|
};
|
|
437
458
|
}];
|
|
438
459
|
};
|
|
@@ -580,7 +601,7 @@ export declare const NATIVE_SAVE_DASHBOARD_CONTRACT: {
|
|
|
580
601
|
};
|
|
581
602
|
readonly answer_selection: {
|
|
582
603
|
readonly const: "latest";
|
|
583
|
-
readonly description: "Resolve the newest
|
|
604
|
+
readonly description: "Resolve the newest answer retained by this native session when its opaque answer_ref is no longer available in the conversation.";
|
|
584
605
|
};
|
|
585
606
|
readonly envelope: {
|
|
586
607
|
readonly type: "object";
|
|
@@ -588,7 +609,8 @@ export declare const NATIVE_SAVE_DASHBOARD_CONTRACT: {
|
|
|
588
609
|
readonly required: readonly ["blocks", "verified"];
|
|
589
610
|
readonly properties: {
|
|
590
611
|
readonly verified: {
|
|
591
|
-
readonly
|
|
612
|
+
readonly type: "boolean";
|
|
613
|
+
readonly description: "Ignored: the host decides verified itself. It records verified only for an answer it grounded: a grounded component run, or an answer whose every definition cites a query this session ran through the query tool. Anything submitted here is not a claim the host keeps.";
|
|
592
614
|
};
|
|
593
615
|
readonly summary: {
|
|
594
616
|
readonly anyOf: readonly [{
|
|
@@ -776,6 +798,15 @@ export declare const NATIVE_SAVE_DASHBOARD_CONTRACT: {
|
|
|
776
798
|
};
|
|
777
799
|
};
|
|
778
800
|
};
|
|
801
|
+
readonly query_id: {
|
|
802
|
+
readonly type: "string";
|
|
803
|
+
readonly not: {
|
|
804
|
+
readonly pattern: "<\\s*\\/?(?:[sS][cC][rR][iI][pP][tT]|[hH][tT][mM][lL])\\b|[jJ][aA][vV][aA][sS][cC][rR][iI][pP][tT]\\s*:";
|
|
805
|
+
};
|
|
806
|
+
readonly minLength: 1;
|
|
807
|
+
readonly maxLength: 64;
|
|
808
|
+
readonly description: "Optional: the query_id the query tool returned for the query this definition describes.";
|
|
809
|
+
};
|
|
779
810
|
};
|
|
780
811
|
}, {
|
|
781
812
|
readonly type: "object";
|
|
@@ -848,7 +879,8 @@ export declare const NATIVE_PERSIST_ANSWER_CONTRACT: {
|
|
|
848
879
|
readonly required: readonly ["blocks", "verified"];
|
|
849
880
|
readonly properties: {
|
|
850
881
|
readonly verified: {
|
|
851
|
-
readonly
|
|
882
|
+
readonly type: "boolean";
|
|
883
|
+
readonly description: "Ignored: the host decides verified itself. It records verified only for an answer it grounded: a grounded component run, or an answer whose every definition cites a query this session ran through the query tool. Anything submitted here is not a claim the host keeps.";
|
|
852
884
|
};
|
|
853
885
|
readonly blocks: {
|
|
854
886
|
readonly type: "array";
|
|
@@ -932,6 +964,15 @@ export declare const NATIVE_PERSIST_ANSWER_CONTRACT: {
|
|
|
932
964
|
};
|
|
933
965
|
};
|
|
934
966
|
};
|
|
967
|
+
readonly query_id: {
|
|
968
|
+
readonly type: "string";
|
|
969
|
+
readonly not: {
|
|
970
|
+
readonly pattern: "<\\s*\\/?(?:[sS][cC][rR][iI][pP][tT]|[hH][tT][mM][lL])\\b|[jJ][aA][vV][aA][sS][cC][rR][iI][pP][tT]\\s*:";
|
|
971
|
+
};
|
|
972
|
+
readonly minLength: 1;
|
|
973
|
+
readonly maxLength: 64;
|
|
974
|
+
readonly description: "Optional: the query_id the query tool returned for the query this definition describes.";
|
|
975
|
+
};
|
|
935
976
|
};
|
|
936
977
|
}];
|
|
937
978
|
};
|
|
@@ -953,6 +994,29 @@ export declare const NATIVE_PERSIST_ANSWER_CONTRACT: {
|
|
|
953
994
|
};
|
|
954
995
|
readonly semanticConstraint: "The envelope contains exactly one typed table block and at most one typed definition block, with no presentation-only fields.";
|
|
955
996
|
};
|
|
997
|
+
/** The session `query` tool: one read-only SQL query run by the host through governed access. */
|
|
998
|
+
export declare const NATIVE_QUERY_INPUT_SCHEMA: {
|
|
999
|
+
readonly type: "object";
|
|
1000
|
+
readonly additionalProperties: false;
|
|
1001
|
+
readonly required: readonly ["sql"];
|
|
1002
|
+
readonly properties: {
|
|
1003
|
+
readonly sql: {
|
|
1004
|
+
readonly type: "string";
|
|
1005
|
+
readonly minLength: 1;
|
|
1006
|
+
readonly description: "One read-only SELECT over the bound semantic models.";
|
|
1007
|
+
};
|
|
1008
|
+
readonly limit: {
|
|
1009
|
+
readonly type: "integer";
|
|
1010
|
+
readonly minimum: 1;
|
|
1011
|
+
readonly description: "Maximum rows to return (default 1000).";
|
|
1012
|
+
};
|
|
1013
|
+
};
|
|
1014
|
+
};
|
|
1015
|
+
export interface NativeQueryResult {
|
|
1016
|
+
readonly isError: boolean;
|
|
1017
|
+
readonly text: string;
|
|
1018
|
+
readonly result?: unknown;
|
|
1019
|
+
}
|
|
956
1020
|
export interface NativeArtifactServiceOptions {
|
|
957
1021
|
readonly store: Store;
|
|
958
1022
|
readonly artifactsRoot: string;
|
|
@@ -961,6 +1025,11 @@ export interface NativeArtifactServiceOptions {
|
|
|
961
1025
|
/** Optional override, accepted only when it is the expected local endpoint. */
|
|
962
1026
|
readonly mcpUrl: string;
|
|
963
1027
|
readonly getBinding: () => EnrichmentBinding | undefined;
|
|
1028
|
+
/**
|
|
1029
|
+
* Opens governed Wren access for one native analysis session's `query` tool, against the
|
|
1030
|
+
* session's bound project. Without it, the tool is not offered.
|
|
1031
|
+
*/
|
|
1032
|
+
readonly openQueryAccess?: (binding: EnrichmentBinding, signal: AbortSignal) => Promise<ComponentAccess>;
|
|
964
1033
|
/** Test-only seam; production writes one contained JSON file atomically. */
|
|
965
1034
|
readonly writeAtomic?: (target: string, contents: string) => void;
|
|
966
1035
|
/** Test-only seam for a persistence outage; production uses the typed Store method. */
|
|
@@ -970,6 +1039,7 @@ export interface NativeArtifactServiceOptions {
|
|
|
970
1039
|
readonly idempotencyKey: string;
|
|
971
1040
|
readonly envelopeJson: string;
|
|
972
1041
|
readonly digest: string;
|
|
1042
|
+
readonly grounded: boolean;
|
|
973
1043
|
}) => {
|
|
974
1044
|
readonly row: NativeStructuredAnswerRow;
|
|
975
1045
|
readonly created: boolean;
|
|
@@ -978,11 +1048,32 @@ export interface NativeArtifactServiceOptions {
|
|
|
978
1048
|
export declare class NativeArtifactService {
|
|
979
1049
|
private readonly options;
|
|
980
1050
|
private readonly credentials;
|
|
1051
|
+
/** One host query recorder per native session: its observations are never shared across sessions. */
|
|
1052
|
+
private readonly queryRecorders;
|
|
1053
|
+
private readonly queryClosing;
|
|
981
1054
|
constructor(options: NativeArtifactServiceOptions);
|
|
982
1055
|
/** Validate the exact local BFF endpoint before advertising or issuing it. */
|
|
983
1056
|
readiness(): NativeMcpReadiness;
|
|
984
1057
|
issue(session: NativeSessionRow, binding: EnrichmentBinding | undefined): NativeMcpDescriptor;
|
|
985
1058
|
revoke(credential: string | undefined): void;
|
|
1059
|
+
/** Whether native analysis sessions are offered the host `query` tool. */
|
|
1060
|
+
queryAvailable(): boolean;
|
|
1061
|
+
/** Resolves once every session query recorder closed so far has released its governed access. */
|
|
1062
|
+
queriesClosed(): Promise<void>;
|
|
1063
|
+
private closeQueries;
|
|
1064
|
+
/**
|
|
1065
|
+
* The session `query` tool: runs one read-only query through governed access against the
|
|
1066
|
+
* session's bound project, records it as this session's observation and returns its rows with
|
|
1067
|
+
* a session-tagged host `query_id` that a later persist_answer or save_dashboard may cite.
|
|
1068
|
+
*/
|
|
1069
|
+
query(credential: string | undefined, input: unknown): Promise<NativeQueryResult>;
|
|
1070
|
+
/**
|
|
1071
|
+
* The host's grounding decision for a model-written envelope: the same envelope rebuilt from
|
|
1072
|
+
* this session's query observations when every query it cites grounds on them (see
|
|
1073
|
+
* `groundOnHostEvidence`), else `undefined`. Only this decision reaches a grounded entry from
|
|
1074
|
+
* the MCP surface.
|
|
1075
|
+
*/
|
|
1076
|
+
private groundedEnvelope;
|
|
986
1077
|
/** Releases opaque credentials when the owning BFF service shuts down. */
|
|
987
1078
|
dispose(): void;
|
|
988
1079
|
hasCredential(credential: string | undefined): boolean;
|
|
@@ -1000,5 +1091,19 @@ export declare class NativeArtifactService {
|
|
|
1000
1091
|
* a later follow-up can only reference this stored result, never recompute.
|
|
1001
1092
|
*/
|
|
1002
1093
|
persistAnswer(credential: string | undefined, input: unknown): PersistedStructuredAnswer;
|
|
1094
|
+
/**
|
|
1095
|
+
* Host-only entry for a grounded component root (never exposed over MCP).
|
|
1096
|
+
* The caller must already have checked the component's verified provenance.
|
|
1097
|
+
*/
|
|
1098
|
+
persistGroundedAnswer(credential: string | undefined, input: unknown): PersistedStructuredAnswer;
|
|
1099
|
+
private persistStructured;
|
|
1003
1100
|
save(credential: string | undefined, input: unknown): SavedNativeArtifact;
|
|
1101
|
+
/**
|
|
1102
|
+
* Host-only entry for a grounded component render root (never exposed over
|
|
1103
|
+
* MCP). The caller must already have checked verified provenance.
|
|
1104
|
+
*/
|
|
1105
|
+
saveGrounded(credential: string | undefined, input: unknown): SavedNativeArtifact;
|
|
1106
|
+
/** The credential, session and binding checks every save runs before it reads its payload. */
|
|
1107
|
+
private authorizeSave;
|
|
1108
|
+
private saveArtifact;
|
|
1004
1109
|
}
|
|
@@ -7,6 +7,8 @@ import { createHash, randomUUID } from "node:crypto";
|
|
|
7
7
|
import { lstatSync, mkdirSync, realpathSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
8
8
|
import path from "node:path";
|
|
9
9
|
import { newId } from "./db.js";
|
|
10
|
+
import { createHostQueryRecorder } from "../harness/route/host-query.js";
|
|
11
|
+
import { groundOnHostEvidence } from "./host-evidence-grounding.js";
|
|
10
12
|
const MAX_DASHBOARD_BYTES = 256 * 1024;
|
|
11
13
|
const MAX_NAME_LENGTH = 120;
|
|
12
14
|
const MAX_IDEMPOTENCY_KEY_LENGTH = 128;
|
|
@@ -14,6 +16,7 @@ const ANSWER_REFERENCE_PATTERN = /^answer-[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{
|
|
|
14
16
|
export const NATIVE_MCP_CREDENTIAL_ENV_VAR = "WARBLE_MCP_CONNECTION_CREDENTIAL";
|
|
15
17
|
export const NATIVE_MCP_TOOL_NAME = "save_dashboard";
|
|
16
18
|
export const NATIVE_MCP_PERSIST_ANSWER_TOOL_NAME = "persist_answer";
|
|
19
|
+
export const NATIVE_MCP_QUERY_TOOL_NAME = "query";
|
|
17
20
|
export const NATIVE_MCP_SERVER_LABEL = "GenBI MCP";
|
|
18
21
|
export const NATIVE_MCP_ARTIFACTS_LABEL = "GenBI Artifacts";
|
|
19
22
|
// Keep this JSON Schema compatible with the ECMA-262 regular-expression
|
|
@@ -53,6 +56,8 @@ const POSITIONAL_ROWS_SCHEMA = {
|
|
|
53
56
|
items: { type: "array", items: SCALAR_SCHEMA },
|
|
54
57
|
};
|
|
55
58
|
/** The exact public MCP shape advertised to native clients and accepted by the host. */
|
|
59
|
+
const VERIFIED_FIELD_SCHEMA = { type: "boolean", description: "Ignored: the host decides verified itself. It records verified only for an answer it grounded: a grounded component run, or an answer whose every definition cites a query this session ran through the query tool. Anything submitted here is not a claim the host keeps." };
|
|
60
|
+
const QUERY_ID_SCHEMA = { ...SAFE_TEXT_SCHEMA, minLength: 1, maxLength: 64, description: "Optional: the query_id the query tool returned for the query this definition describes." };
|
|
56
61
|
export const NATIVE_SAVE_DASHBOARD_INPUT_SCHEMA = {
|
|
57
62
|
type: "object",
|
|
58
63
|
additionalProperties: false,
|
|
@@ -67,18 +72,18 @@ export const NATIVE_SAVE_DASHBOARD_INPUT_SCHEMA = {
|
|
|
67
72
|
name: { ...SAFE_TEXT_SCHEMA, minLength: 1, maxLength: MAX_NAME_LENGTH, pattern: "\\S" },
|
|
68
73
|
idempotency_key: { ...SAFE_TEXT_SCHEMA, minLength: 8, maxLength: MAX_IDEMPOTENCY_KEY_LENGTH, pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]+$" },
|
|
69
74
|
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
|
|
75
|
+
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
76
|
envelope: {
|
|
72
77
|
type: "object", additionalProperties: false, required: ["blocks", "verified"],
|
|
73
78
|
properties: {
|
|
74
|
-
verified:
|
|
79
|
+
verified: VERIFIED_FIELD_SCHEMA, summary: NULLABLE_SAFE_TEXT_SCHEMA, estimate: { type: ["boolean", "null"] },
|
|
75
80
|
blocks: {
|
|
76
81
|
type: "array", minItems: 1,
|
|
77
82
|
items: { oneOf: [
|
|
78
83
|
{ type: "object", additionalProperties: false, required: ["type", "label", "value"], properties: { type: { const: "kpi_card" }, label: SAFE_TEXT_SCHEMA, value: SAFE_TEXT_OR_NUMBER_SCHEMA, unit: NULLABLE_SAFE_TEXT_SCHEMA, delta: { type: ["number", "null"] } } },
|
|
79
84
|
{ type: "object", additionalProperties: false, required: ["type", "columns", "rows"], properties: { type: { const: "table" }, columns: { type: "array", minItems: 1, uniqueItems: true, items: { ...SAFE_TEXT_SCHEMA, minLength: 1 } }, rows: { ...POSITIONAL_ROWS_SCHEMA, description: NATIVE_SAVE_DASHBOARD_SEMANTIC_CONSTRAINTS.table.positionalRows } } },
|
|
80
85
|
{ type: "object", additionalProperties: false, required: ["type", "chart_type", "x", "series", "rows"], properties: { type: { const: "chart" }, chart_type: { enum: ["bar", "line", "pie", "area", "scatter"] }, x: { ...SAFE_TEXT_SCHEMA, minLength: 1, description: NATIVE_SAVE_DASHBOARD_SEMANTIC_CONSTRAINTS.chart.x }, series: { type: "array", minItems: 1, uniqueItems: true, description: NATIVE_SAVE_DASHBOARD_SEMANTIC_CONSTRAINTS.chart.series, items: { ...SAFE_TEXT_SCHEMA, minLength: 1 } }, rows: { ...POSITIONAL_ROWS_SCHEMA, description: NATIVE_SAVE_DASHBOARD_SEMANTIC_CONSTRAINTS.chart.positionalRows } } },
|
|
81
|
-
{ type: "object", additionalProperties: false, required: ["type", "sql", "source_tables", "filters"], properties: { type: { const: "definition" }, sql: { ...SAFE_TEXT_SCHEMA, minLength: 1 }, source_tables: { type: "array", minItems: 1, items: { ...SAFE_TEXT_SCHEMA, minLength: 1 } }, filters: { type: "array", items: SAFE_TEXT_SCHEMA } } },
|
|
86
|
+
{ type: "object", additionalProperties: false, required: ["type", "sql", "source_tables", "filters"], properties: { type: { const: "definition" }, sql: { ...SAFE_TEXT_SCHEMA, minLength: 1 }, source_tables: { type: "array", minItems: 1, items: { ...SAFE_TEXT_SCHEMA, minLength: 1 } }, filters: { type: "array", items: SAFE_TEXT_SCHEMA }, query_id: QUERY_ID_SCHEMA } },
|
|
82
87
|
{ type: "object", additionalProperties: false, required: ["type", "text"], properties: { type: { const: "narrative" }, text: { ...SAFE_TEXT_SCHEMA, minLength: 1 }, title: NULLABLE_SAFE_TEXT_SCHEMA } },
|
|
83
88
|
] },
|
|
84
89
|
},
|
|
@@ -97,7 +102,7 @@ const NATIVE_STRUCTURED_ANSWER_ENVELOPE_SCHEMA = {
|
|
|
97
102
|
additionalProperties: false,
|
|
98
103
|
required: ["blocks", "verified"],
|
|
99
104
|
properties: {
|
|
100
|
-
verified:
|
|
105
|
+
verified: VERIFIED_FIELD_SCHEMA,
|
|
101
106
|
blocks: {
|
|
102
107
|
type: "array",
|
|
103
108
|
minItems: 1,
|
|
@@ -162,6 +167,30 @@ export const NATIVE_PERSIST_ANSWER_CONTRACT = {
|
|
|
162
167
|
inputSchema: NATIVE_PERSIST_ANSWER_INPUT_SCHEMA,
|
|
163
168
|
semanticConstraint: "The envelope contains exactly one typed table block and at most one typed definition block, with no presentation-only fields.",
|
|
164
169
|
};
|
|
170
|
+
/** The session `query` tool: one read-only SQL query run by the host through governed access. */
|
|
171
|
+
export const NATIVE_QUERY_INPUT_SCHEMA = {
|
|
172
|
+
type: "object",
|
|
173
|
+
additionalProperties: false,
|
|
174
|
+
required: ["sql"],
|
|
175
|
+
properties: {
|
|
176
|
+
sql: { type: "string", minLength: 1, description: "One read-only SELECT over the bound semantic models." },
|
|
177
|
+
limit: { type: "integer", minimum: 1, description: "Maximum rows to return (default 1000)." },
|
|
178
|
+
},
|
|
179
|
+
};
|
|
180
|
+
/** The sentence the model sees per host error class; nothing from the governed transport is forwarded. */
|
|
181
|
+
const NATIVE_QUERY_ERROR_TEXT = {
|
|
182
|
+
invalid_request: "The request did not match the query contract: pass a non-empty sql string and an optional positive integer limit.",
|
|
183
|
+
model_not_found: "The query references a table that is not a model in the bound semantic context; query only the models it defines.",
|
|
184
|
+
policy_rejected: "The read-only analytical policy rejected the query: use one SELECT that reads at least one bound model.",
|
|
185
|
+
invalid_sql: "The SQL could not be parsed or planned against the semantic context.",
|
|
186
|
+
execution_failed: "The data source rejected the query at execution time, for example an unknown column or a type mismatch.",
|
|
187
|
+
timeout: "The query exceeded the statement time limit.",
|
|
188
|
+
datasource_unavailable: "The bound data source could not be reached.",
|
|
189
|
+
result_too_large: "The result exceeded the governed byte limit; narrow the query.",
|
|
190
|
+
budget_exhausted: "This session's query budget is exhausted; answer from the results you already have.",
|
|
191
|
+
internal: "The governed query failed for an unclassified reason.",
|
|
192
|
+
transport: "The host query service is unavailable.",
|
|
193
|
+
};
|
|
165
194
|
function rejectsExecutableText(value) {
|
|
166
195
|
if (typeof value === "string")
|
|
167
196
|
return /<\s*\/?(?:script|html)\b|javascript\s*:/i.test(value);
|
|
@@ -201,7 +230,7 @@ function validBlock(block) {
|
|
|
201
230
|
// Existing object rows remain exactly bound to x + series.
|
|
202
231
|
: isRecord(row) && scalar(row[x]) && series.every((item) => scalar(row[item])) && Object.keys(row).every((key) => key === x || series.includes(key)));
|
|
203
232
|
case "definition":
|
|
204
|
-
return onlyKeys(block, ["type", "sql", "source_tables", "filters"]) && typeof block.sql === "string" && block.sql.length > 0 && Array.isArray(block.source_tables) && block.source_tables.length > 0 && block.source_tables.every((source) => typeof source === "string" && source.length > 0) && Array.isArray(block.filters) && block.filters.every((filter) => typeof filter === "string");
|
|
233
|
+
return (onlyKeys(block, ["type", "sql", "source_tables", "filters"]) || (onlyKeys(block, ["type", "sql", "source_tables", "filters", "query_id"]) && typeof block.query_id === "string" && block.query_id.length > 0 && block.query_id.length <= 64)) && typeof block.sql === "string" && block.sql.length > 0 && Array.isArray(block.source_tables) && block.source_tables.length > 0 && block.source_tables.every((source) => typeof source === "string" && source.length > 0) && Array.isArray(block.filters) && block.filters.every((filter) => typeof filter === "string");
|
|
205
234
|
case "narrative":
|
|
206
235
|
return (onlyKeys(block, ["type", "text"]) || onlyKeys(block, ["type", "text", "title"])) && typeof block.text === "string" && block.text.length > 0 && (block.title === undefined || block.title === null || typeof block.title === "string");
|
|
207
236
|
default:
|
|
@@ -211,8 +240,8 @@ function validBlock(block) {
|
|
|
211
240
|
function validateDashboardEnvelope(envelope) {
|
|
212
241
|
if (!isRecord(envelope) || !Object.hasOwn(envelope, "blocks") || !Object.hasOwn(envelope, "verified") || !Object.keys(envelope).every((key) => ["blocks", "summary", "verified", "estimate"].includes(key)))
|
|
213
242
|
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
|
|
243
|
+
if (typeof envelope.verified !== "boolean")
|
|
244
|
+
throw new NativeArtifactError("invalid dashboard render envelope: verified must be a boolean");
|
|
216
245
|
if (!Array.isArray(envelope.blocks) || envelope.blocks.length === 0)
|
|
217
246
|
throw new NativeArtifactError("invalid dashboard render envelope: blocks must be a non-empty array");
|
|
218
247
|
const invalidBlock = envelope.blocks.findIndex((block) => !validBlock(block));
|
|
@@ -258,6 +287,24 @@ function validateInput(value) {
|
|
|
258
287
|
throw new NativeArtifactError("invalid Save to Artifacts payload: answer_selection must be latest");
|
|
259
288
|
return { version: "1", name: value.name, answer_selection: "latest", idempotency_key: value.idempotency_key };
|
|
260
289
|
}
|
|
290
|
+
function isValidSaveInput(value) {
|
|
291
|
+
try {
|
|
292
|
+
validateInput(value);
|
|
293
|
+
return true;
|
|
294
|
+
}
|
|
295
|
+
catch {
|
|
296
|
+
return false;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
function isValidPersistedStructuredAnswer(value) {
|
|
300
|
+
try {
|
|
301
|
+
validatePersistedStructuredAnswer(value);
|
|
302
|
+
return true;
|
|
303
|
+
}
|
|
304
|
+
catch {
|
|
305
|
+
return false;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
261
308
|
function validatePersistedStructuredAnswer(value) {
|
|
262
309
|
if (!isRecord(value) || !onlyKeys(value, ["version", "idempotency_key", "envelope"]))
|
|
263
310
|
throw new NativeArtifactError("invalid structured answer payload: expected exactly version, idempotency_key, and envelope");
|
|
@@ -275,6 +322,14 @@ function validatePersistedStructuredAnswer(value) {
|
|
|
275
322
|
}
|
|
276
323
|
return { version: "1", idempotency_key: value.idempotency_key, envelope: value.envelope };
|
|
277
324
|
}
|
|
325
|
+
/**
|
|
326
|
+
* The stored `verified` flag is host provenance, never the caller's claim: the
|
|
327
|
+
* persisted envelope carries exactly the flag the host recorded, so its own
|
|
328
|
+
* badge cannot disagree with the artifact row.
|
|
329
|
+
*/
|
|
330
|
+
function withHostVerified(envelope, verified) {
|
|
331
|
+
return { ...envelope, verified };
|
|
332
|
+
}
|
|
278
333
|
function structuredAnswerDigest(envelopeJson) {
|
|
279
334
|
return `sha256:${createHash("sha256").update(envelopeJson).digest("hex")}`;
|
|
280
335
|
}
|
|
@@ -296,6 +351,9 @@ function validateStoredStructuredAnswer(source, nativeSessionId) {
|
|
|
296
351
|
export class NativeArtifactService {
|
|
297
352
|
options;
|
|
298
353
|
credentials = new Map();
|
|
354
|
+
/** One host query recorder per native session: its observations are never shared across sessions. */
|
|
355
|
+
queryRecorders = new Map();
|
|
356
|
+
queryClosing = new Set();
|
|
299
357
|
constructor(options) {
|
|
300
358
|
this.options = options;
|
|
301
359
|
}
|
|
@@ -327,7 +385,78 @@ export class NativeArtifactService {
|
|
|
327
385
|
revoke(credential) {
|
|
328
386
|
if (!credential)
|
|
329
387
|
return;
|
|
388
|
+
const context = this.credentials.get(credential);
|
|
330
389
|
this.credentials.delete(credential);
|
|
390
|
+
// A session can hold more than one credential (a Codex session's terminal and its component
|
|
391
|
+
// runtime each get one). Its query recorder lives until the session ends: the last credential
|
|
392
|
+
// for the session is revoked, which every session end and `dispose()` do.
|
|
393
|
+
if (context && ![...this.credentials.values()].some((other) => other.session.id === context.session.id))
|
|
394
|
+
this.closeQueries(context.session.id);
|
|
395
|
+
}
|
|
396
|
+
/** Whether native analysis sessions are offered the host `query` tool. */
|
|
397
|
+
queryAvailable() { return this.options.openQueryAccess !== undefined; }
|
|
398
|
+
/** Resolves once every session query recorder closed so far has released its governed access. */
|
|
399
|
+
async queriesClosed() { await Promise.allSettled([...this.queryClosing]); }
|
|
400
|
+
closeQueries(sessionId) {
|
|
401
|
+
const recorder = this.queryRecorders.get(sessionId);
|
|
402
|
+
if (!recorder)
|
|
403
|
+
return;
|
|
404
|
+
this.queryRecorders.delete(sessionId);
|
|
405
|
+
const closing = recorder.close();
|
|
406
|
+
this.queryClosing.add(closing);
|
|
407
|
+
void closing.finally(() => this.queryClosing.delete(closing));
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* The session `query` tool: runs one read-only query through governed access against the
|
|
411
|
+
* session's bound project, records it as this session's observation and returns its rows with
|
|
412
|
+
* a session-tagged host `query_id` that a later persist_answer or save_dashboard may cite.
|
|
413
|
+
*/
|
|
414
|
+
async query(credential, input) {
|
|
415
|
+
const session = this.authorize(credential);
|
|
416
|
+
const binding = this.credentials.get(credential)?.binding;
|
|
417
|
+
const openQueryAccess = this.options.openQueryAccess;
|
|
418
|
+
if (session.purpose !== "analysis" || !binding || !openQueryAccess)
|
|
419
|
+
throw new NativeArtifactError("the query tool is unavailable for this native session", 409);
|
|
420
|
+
const args = isRecord(input) ? input : {};
|
|
421
|
+
if (!isRecord(input) || !Object.keys(input).every((key) => key === "sql" || key === "limit"))
|
|
422
|
+
return { isError: true, text: NATIVE_QUERY_ERROR_TEXT.invalid_request };
|
|
423
|
+
let recorder = this.queryRecorders.get(session.id);
|
|
424
|
+
if (!recorder) {
|
|
425
|
+
recorder = createHostQueryRecorder({ openAccess: (signal) => openQueryAccess(binding, signal) });
|
|
426
|
+
this.queryRecorders.set(session.id, recorder);
|
|
427
|
+
}
|
|
428
|
+
const outcome = await recorder.execute(args.sql, args.limit);
|
|
429
|
+
if ("error" in outcome)
|
|
430
|
+
return { isError: true, text: `Governed Wren query failed [${outcome.error}]: ${NATIVE_QUERY_ERROR_TEXT[outcome.error] ?? NATIVE_QUERY_ERROR_TEXT.internal}` };
|
|
431
|
+
return { isError: false, text: JSON.stringify(outcome.result), result: outcome.result };
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
434
|
+
* The host's grounding decision for a model-written envelope: the same envelope rebuilt from
|
|
435
|
+
* this session's query observations when every query it cites grounds on them (see
|
|
436
|
+
* `groundOnHostEvidence`), else `undefined`. Only this decision reaches a grounded entry from
|
|
437
|
+
* the MCP surface.
|
|
438
|
+
*/
|
|
439
|
+
groundedEnvelope(credential, envelope) {
|
|
440
|
+
const context = credential === undefined ? undefined : this.credentials.get(credential);
|
|
441
|
+
const recorder = context ? this.queryRecorders.get(context.session.id) : undefined;
|
|
442
|
+
if (!recorder || !isRecord(envelope))
|
|
443
|
+
return undefined;
|
|
444
|
+
// The host rebuilds only table and definition blocks. Any other block (narrative, chart, KPI)
|
|
445
|
+
// or an `estimate` flag would be dropped by the rebuild, so such an envelope is never grounded:
|
|
446
|
+
// it is kept intact and stored unverified rather than losing content silently.
|
|
447
|
+
if (envelope.estimate !== undefined && envelope.estimate !== null)
|
|
448
|
+
return undefined;
|
|
449
|
+
if (!Array.isArray(envelope.blocks) || envelope.blocks.some((block) => !isRecord(block) || (block.type !== "table" && block.type !== "definition")))
|
|
450
|
+
return undefined;
|
|
451
|
+
const grounded = groundOnHostEvidence(envelope, envelope, recorder.observations());
|
|
452
|
+
if (grounded.verified !== true)
|
|
453
|
+
return undefined;
|
|
454
|
+
// The rebuild emits one table and one definition per cited query. If it has fewer of either
|
|
455
|
+
// than the envelope did (an extra uncited table, say), content would be lost: keep it intact.
|
|
456
|
+
const count = (value, type) => Array.isArray(value.blocks) ? value.blocks.filter((block) => isRecord(block) && block.type === type).length : 0;
|
|
457
|
+
if (count(grounded, "table") !== count(envelope, "table") || count(grounded, "definition") !== count(envelope, "definition"))
|
|
458
|
+
return undefined;
|
|
459
|
+
return grounded;
|
|
331
460
|
}
|
|
332
461
|
/** Releases opaque credentials when the owning BFF service shuts down. */
|
|
333
462
|
dispose() { for (const credential of [...this.credentials.keys()])
|
|
@@ -377,11 +506,35 @@ export class NativeArtifactService {
|
|
|
377
506
|
* a later follow-up can only reference this stored result, never recompute.
|
|
378
507
|
*/
|
|
379
508
|
persistAnswer(credential, input) {
|
|
509
|
+
// Authorize before validating, as `persistStructured` does: a bad credential is a 401 and a
|
|
510
|
+
// non-analysis session a 409 whatever the payload.
|
|
380
511
|
const session = this.authorize(credential);
|
|
381
512
|
if (session.purpose !== "analysis")
|
|
382
513
|
throw new NativeArtifactError("structured answer persistence is unavailable for this native session", 409);
|
|
383
514
|
const payload = validatePersistedStructuredAnswer(input);
|
|
384
|
-
const
|
|
515
|
+
const grounded = this.groundedEnvelope(credential, payload.envelope);
|
|
516
|
+
if (grounded) {
|
|
517
|
+
const candidate = { ...payload, envelope: grounded };
|
|
518
|
+
// A rebuilt envelope the persist contract cannot hold (a truncation note needs a summary,
|
|
519
|
+
// which this contract has no field for) is persisted unverified, as written.
|
|
520
|
+
if (isValidPersistedStructuredAnswer(candidate))
|
|
521
|
+
return this.persistStructured(credential, candidate, true);
|
|
522
|
+
}
|
|
523
|
+
return this.persistStructured(credential, input, false);
|
|
524
|
+
}
|
|
525
|
+
/**
|
|
526
|
+
* Host-only entry for a grounded component root (never exposed over MCP).
|
|
527
|
+
* The caller must already have checked the component's verified provenance.
|
|
528
|
+
*/
|
|
529
|
+
persistGroundedAnswer(credential, input) {
|
|
530
|
+
return this.persistStructured(credential, input, true);
|
|
531
|
+
}
|
|
532
|
+
persistStructured(credential, input, grounded) {
|
|
533
|
+
const session = this.authorize(credential);
|
|
534
|
+
if (session.purpose !== "analysis")
|
|
535
|
+
throw new NativeArtifactError("structured answer persistence is unavailable for this native session", 409);
|
|
536
|
+
const payload = validatePersistedStructuredAnswer(input);
|
|
537
|
+
const envelopeJson = JSON.stringify(withHostVerified(payload.envelope, grounded));
|
|
385
538
|
const digest = structuredAnswerDigest(envelopeJson);
|
|
386
539
|
const existing = this.options.store.getNativeStructuredAnswerByIdempotency(session.id, payload.idempotency_key);
|
|
387
540
|
if (existing) {
|
|
@@ -392,7 +545,7 @@ export class NativeArtifactService {
|
|
|
392
545
|
}
|
|
393
546
|
try {
|
|
394
547
|
const persisted = (this.options.persistStructuredAnswer ?? ((params) => this.options.store.createNativeStructuredAnswer(params)))({
|
|
395
|
-
id: newId("answer"), nativeSessionId: session.id, idempotencyKey: payload.idempotency_key, envelopeJson, digest,
|
|
548
|
+
id: newId("answer"), nativeSessionId: session.id, idempotencyKey: payload.idempotency_key, envelopeJson, digest, grounded,
|
|
396
549
|
});
|
|
397
550
|
if (persisted.row.digest !== digest || persisted.row.envelopeJson !== envelopeJson) {
|
|
398
551
|
throw new NativeArtifactError("structured answer idempotency key is already bound to a different answer", 409);
|
|
@@ -409,6 +562,27 @@ export class NativeArtifactService {
|
|
|
409
562
|
}
|
|
410
563
|
}
|
|
411
564
|
save(credential, input) {
|
|
565
|
+
// Authorize before validating, as `saveArtifact` does.
|
|
566
|
+
this.authorizeSave(credential);
|
|
567
|
+
const payload = validateInput(input);
|
|
568
|
+
// Only an envelope save is grounded here; a save by reference inherits the retained answer's provenance.
|
|
569
|
+
const grounded = "envelope" in payload ? this.groundedEnvelope(credential, payload.envelope) : undefined;
|
|
570
|
+
if (grounded) {
|
|
571
|
+
const candidate = { ...payload, envelope: grounded };
|
|
572
|
+
if (isValidSaveInput(candidate))
|
|
573
|
+
return this.saveArtifact(credential, candidate, true);
|
|
574
|
+
}
|
|
575
|
+
return this.saveArtifact(credential, input, false);
|
|
576
|
+
}
|
|
577
|
+
/**
|
|
578
|
+
* Host-only entry for a grounded component render root (never exposed over
|
|
579
|
+
* MCP). The caller must already have checked verified provenance.
|
|
580
|
+
*/
|
|
581
|
+
saveGrounded(credential, input) {
|
|
582
|
+
return this.saveArtifact(credential, input, true);
|
|
583
|
+
}
|
|
584
|
+
/** The credential, session and binding checks every save runs before it reads its payload. */
|
|
585
|
+
authorizeSave(credential) {
|
|
412
586
|
const context = credential === undefined ? undefined : this.credentials.get(credential);
|
|
413
587
|
if (!context)
|
|
414
588
|
throw new NativeArtifactError("GenBI MCP bearer credential is invalid. Restart this native session to refresh its GenBI MCP connection.", 401);
|
|
@@ -418,8 +592,11 @@ export class NativeArtifactService {
|
|
|
418
592
|
this.revoke(credential);
|
|
419
593
|
throw new NativeArtifactError("GenBI MCP session binding is stale. Start a new native session.", 409);
|
|
420
594
|
}
|
|
595
|
+
return { session, binding };
|
|
596
|
+
}
|
|
597
|
+
saveArtifact(credential, input, grounded) {
|
|
598
|
+
const { session, binding } = this.authorizeSave(credential);
|
|
421
599
|
const payload = validateInput(input);
|
|
422
|
-
const referenceSave = "answer_ref" in payload || "answer_selection" in payload;
|
|
423
600
|
const source = "answer_ref" in payload
|
|
424
601
|
? this.options.store.getNativeStructuredAnswer(payload.answer_ref)
|
|
425
602
|
: "answer_selection" in payload
|
|
@@ -431,13 +608,16 @@ export class NativeArtifactService {
|
|
|
431
608
|
throw new NativeArtifactError("no persisted answer is available for this native session", 404);
|
|
432
609
|
if (source)
|
|
433
610
|
validateStoredStructuredAnswer(source, session.id);
|
|
434
|
-
|
|
435
|
-
|
|
611
|
+
// A reference save inherits the retained answer's provenance; an envelope
|
|
612
|
+
// save is verified only through the host-only grounded entry.
|
|
613
|
+
const verified = source ? source.grounded : grounded;
|
|
614
|
+
const contents = JSON.stringify(withHostVerified(source ? JSON.parse(source.envelopeJson) : "envelope" in payload ? payload.envelope : undefined, verified));
|
|
615
|
+
const digest = structuredAnswerDigest(contents);
|
|
436
616
|
const sameSaveRequest = (artifact) => artifact !== undefined &&
|
|
437
617
|
artifact.name === payload.name.trim() &&
|
|
438
618
|
artifact.projectIdentity === binding.identity && artifact.bindingGeneration === binding.generation && artifact.projectRevision === binding.revision &&
|
|
439
619
|
artifact.nativeVendor === session.vendor && artifact.nativeAgent === session.agent &&
|
|
440
|
-
artifact.contentDigest === digest && artifact.sourceAnswerId === (source?.id ?? null);
|
|
620
|
+
artifact.contentDigest === digest && artifact.sourceAnswerId === (source?.id ?? null) && artifact.verified === verified;
|
|
441
621
|
const existing = this.options.store.getNativeArtifactByIdempotency(session.id, payload.idempotency_key);
|
|
442
622
|
if (existing) {
|
|
443
623
|
if (!sameSaveRequest(existing))
|
|
@@ -450,7 +630,7 @@ export class NativeArtifactService {
|
|
|
450
630
|
try {
|
|
451
631
|
const safeTarget = assertSafeContainedTarget(this.options.artifactsRoot, target);
|
|
452
632
|
(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 } : {}) });
|
|
633
|
+
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
634
|
if (!inserted.created && !sameSaveRequest(inserted.row)) {
|
|
455
635
|
rmSync(target, { force: true });
|
|
456
636
|
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>;
|