@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
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
import { normalizeComponentResult } from "@warble/claude-agent-sdk";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { answerShapeSchema } from "../providers/zone.js";
|
|
4
|
+
/**
|
|
5
|
+
* The two-stage report: host-side slot materialisation and synthesis.
|
|
6
|
+
*
|
|
7
|
+
* A report component (the Hub `plan_report`) runs two model steps. The first
|
|
8
|
+
* lays out blocks whose data cells are `{slot_id}` placeholders and asks the
|
|
9
|
+
* callee for every answer in one batched call; the second writes the summary
|
|
10
|
+
* and per-block notes. Neither step is allowed to type a value into the
|
|
11
|
+
* report: this module is where the values come from.
|
|
12
|
+
*
|
|
13
|
+
* - {@link buildSlotTable} turns the disclosed child results (what the caller
|
|
14
|
+
* was allowed to see, after egress) and the provenance the egress step kept
|
|
15
|
+
* aside into one table keyed by slot id.
|
|
16
|
+
* - {@link materialiseReportPlan} fills the layout from that table for the
|
|
17
|
+
* narrator to read: filled cells carry their values, refused or unanswered
|
|
18
|
+
* cells carry `status: unavailable` and a reason category. Provenance
|
|
19
|
+
* (SQL, source tables) is withheld here; the narrator runs on a public tier.
|
|
20
|
+
* - {@link synthesiseReport} builds the final render envelope from the layout,
|
|
21
|
+
* the slot table and only the narrator's text (summary, notes, titles). A
|
|
22
|
+
* value the narrator retyped is never read. Provenance is attached here, by
|
|
23
|
+
* the host, from the child run.
|
|
24
|
+
*
|
|
25
|
+
* Every value in the rendered report therefore traces to a disclosed answer,
|
|
26
|
+
* and every disclosed answer traces to an observed query result in the child.
|
|
27
|
+
*/
|
|
28
|
+
export const BLOCK_SHAPES = { kpi_card: "scalar", chart: "series", table: "table", narrative: "narrative" };
|
|
29
|
+
const dataBlockTypeSchema = z.enum(["kpi_card", "chart", "table", "narrative"]);
|
|
30
|
+
const chartTypeSchema = z.enum(["bar", "line", "pie", "area", "scatter"]);
|
|
31
|
+
const layoutSlotSchema = z.object({
|
|
32
|
+
slot_id: z.string().min(1).max(128),
|
|
33
|
+
block_type: dataBlockTypeSchema,
|
|
34
|
+
expected_shape: answerShapeSchema,
|
|
35
|
+
question: z.string().min(1),
|
|
36
|
+
unit: z.string().optional(),
|
|
37
|
+
max_rows: z.number().int().positive().optional(),
|
|
38
|
+
});
|
|
39
|
+
/** Unknown keys are stripped: a number the planner copied into a placeholder is dropped, never read. */
|
|
40
|
+
const layoutBlockSchema = z.object({
|
|
41
|
+
type: dataBlockTypeSchema,
|
|
42
|
+
label: z.string().optional(),
|
|
43
|
+
title: z.string().optional(),
|
|
44
|
+
chart_type: chartTypeSchema.optional(),
|
|
45
|
+
slot_id: z.string().min(1).max(128),
|
|
46
|
+
});
|
|
47
|
+
export const reportPlanSchema = z.object({
|
|
48
|
+
title: z.string().optional(),
|
|
49
|
+
preamble: z.object({ period: z.string().optional(), currency: z.string().optional(), filters: z.array(z.string()).default([]) }).default({ filters: [] }),
|
|
50
|
+
slots: z.array(layoutSlotSchema).max(64),
|
|
51
|
+
blocks: z.array(layoutBlockSchema).min(1).max(64),
|
|
52
|
+
summary_brief: z.string().optional(),
|
|
53
|
+
});
|
|
54
|
+
const cell = z.union([z.string(), z.number(), z.boolean(), z.null()]);
|
|
55
|
+
const disclosedAnswerSchema = z.object({
|
|
56
|
+
slot_id: z.string().min(1),
|
|
57
|
+
status: z.enum(["ok", "partial", "refused"]),
|
|
58
|
+
shape: answerShapeSchema.optional(),
|
|
59
|
+
columns: z.array(z.string()).optional(),
|
|
60
|
+
rows: z.array(z.unknown()).optional(),
|
|
61
|
+
value: z.unknown().optional(),
|
|
62
|
+
text: z.string().optional(),
|
|
63
|
+
unit: z.string().optional(),
|
|
64
|
+
summary: z.string().optional(),
|
|
65
|
+
reason_category: z.string().optional(),
|
|
66
|
+
}).strict();
|
|
67
|
+
const disclosedBatchSchema = z.object({ answers: z.array(disclosedAnswerSchema) }).passthrough();
|
|
68
|
+
/**
|
|
69
|
+
* One row per slot from the disclosed child results (first answer per slot id
|
|
70
|
+
* wins; a follow-up call adds new ids). Provenance is joined by slot id.
|
|
71
|
+
*/
|
|
72
|
+
export function buildSlotTable(children, provenance = []) {
|
|
73
|
+
const table = new Map();
|
|
74
|
+
const kept = new Map();
|
|
75
|
+
for (const item of provenance)
|
|
76
|
+
if (!kept.has(item.slot_id))
|
|
77
|
+
kept.set(item.slot_id, item);
|
|
78
|
+
for (const child of children) {
|
|
79
|
+
if (child.status !== "ok" || child.output.kind !== "value")
|
|
80
|
+
continue;
|
|
81
|
+
const batch = disclosedBatchSchema.safeParse(child.output.value);
|
|
82
|
+
if (!batch.success)
|
|
83
|
+
continue;
|
|
84
|
+
for (const answer of batch.data.answers) {
|
|
85
|
+
if (table.has(answer.slot_id))
|
|
86
|
+
continue;
|
|
87
|
+
const aside = kept.get(answer.slot_id);
|
|
88
|
+
table.set(answer.slot_id, { answer: answer, ...(aside ? { provenance: aside } : {}) });
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return table;
|
|
92
|
+
}
|
|
93
|
+
/** Positional rows for the render contract's `row[]`; object rows are projected through `columns`. */
|
|
94
|
+
function positionalRows(columns, rows) {
|
|
95
|
+
const out = [];
|
|
96
|
+
for (const row of rows) {
|
|
97
|
+
const values = Array.isArray(row) ? row : row && typeof row === "object" ? columns.map((column) => row[column]) : undefined;
|
|
98
|
+
if (values === undefined || values.length !== columns.length)
|
|
99
|
+
return undefined;
|
|
100
|
+
const parsed = z.array(cell).safeParse(values.map((value) => value === undefined ? null : value));
|
|
101
|
+
if (!parsed.success)
|
|
102
|
+
return undefined;
|
|
103
|
+
out.push(parsed.data);
|
|
104
|
+
}
|
|
105
|
+
return out;
|
|
106
|
+
}
|
|
107
|
+
function labelOf(block) { return block.label ?? block.title ?? block.slot_id; }
|
|
108
|
+
function unavailable(block, reason) {
|
|
109
|
+
return { type: block.type, label: labelOf(block), slot_id: block.slot_id, status: "unavailable", reason_category: reason };
|
|
110
|
+
}
|
|
111
|
+
/** Fills one layout block from its slot entry, or marks it unavailable with the reason. */
|
|
112
|
+
function fillBlock(block, slot, entry) {
|
|
113
|
+
if (!slot)
|
|
114
|
+
return unavailable(block, "not_declared");
|
|
115
|
+
if (BLOCK_SHAPES[block.type] !== slot.expected_shape)
|
|
116
|
+
return unavailable(block, "shape_mismatch");
|
|
117
|
+
if (!entry)
|
|
118
|
+
return unavailable(block, "not_requested");
|
|
119
|
+
const answer = entry.answer;
|
|
120
|
+
if (answer.status === "refused")
|
|
121
|
+
return unavailable(block, answer.reason_category ?? "refused");
|
|
122
|
+
const unit = slot.unit ?? answer.unit;
|
|
123
|
+
const columns = answer.columns ?? [];
|
|
124
|
+
const rows = answer.rows ?? [];
|
|
125
|
+
switch (block.type) {
|
|
126
|
+
case "kpi_card": {
|
|
127
|
+
const raw = answer.value !== undefined ? answer.value : positionalRows(columns, rows)?.[0]?.[0];
|
|
128
|
+
if (typeof raw !== "number" && typeof raw !== "string")
|
|
129
|
+
return unavailable(block, "invalid_answer");
|
|
130
|
+
if (typeof raw === "number" && !Number.isFinite(raw))
|
|
131
|
+
return unavailable(block, "invalid_answer");
|
|
132
|
+
return { type: "kpi_card", label: labelOf(block), slot_id: block.slot_id, value: raw, ...(unit !== undefined ? { unit } : {}) };
|
|
133
|
+
}
|
|
134
|
+
case "chart": {
|
|
135
|
+
const positional = columns.length >= 2 ? positionalRows(columns, rows) : undefined;
|
|
136
|
+
if (!positional)
|
|
137
|
+
return unavailable(block, "invalid_answer");
|
|
138
|
+
return { type: "chart", title: labelOf(block), chart_type: block.chart_type ?? "bar", slot_id: block.slot_id, x: columns[0], series: columns.slice(1), rows: positional };
|
|
139
|
+
}
|
|
140
|
+
case "table": {
|
|
141
|
+
const positional = columns.length >= 1 ? positionalRows(columns, rows) : undefined;
|
|
142
|
+
if (!positional)
|
|
143
|
+
return unavailable(block, "invalid_answer");
|
|
144
|
+
return { type: "table", title: labelOf(block), slot_id: block.slot_id, columns: [...columns], rows: positional };
|
|
145
|
+
}
|
|
146
|
+
case "narrative": {
|
|
147
|
+
const text = answer.text ?? answer.summary;
|
|
148
|
+
if (typeof text !== "string" || text.length === 0)
|
|
149
|
+
return unavailable(block, "invalid_answer");
|
|
150
|
+
return { type: "narrative", title: labelOf(block), slot_id: block.slot_id, text };
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* The layout with every data cell resolved from the slot table. This is what
|
|
156
|
+
* the narrator consumes as `report_plan`: values it must copy, never compute.
|
|
157
|
+
* No `definition` block is included; provenance stays host-side until
|
|
158
|
+
* {@link synthesiseReport}.
|
|
159
|
+
*/
|
|
160
|
+
export function materialiseReportPlan(plan, table) {
|
|
161
|
+
const slots = new Map(plan.slots.map((slot) => [slot.slot_id, slot]));
|
|
162
|
+
return {
|
|
163
|
+
...(plan.title !== undefined ? { title: plan.title } : {}),
|
|
164
|
+
preamble: plan.preamble,
|
|
165
|
+
blocks: plan.blocks.map((block) => fillBlock(block, slots.get(block.slot_id), table.get(block.slot_id))),
|
|
166
|
+
...(plan.summary_brief !== undefined ? { summary_brief: plan.summary_brief } : {}),
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
/** Parses a step product (string or object) as a report plan; undefined when it is not one. */
|
|
170
|
+
export function parseReportPlan(product) {
|
|
171
|
+
let value = product;
|
|
172
|
+
if (typeof product === "string") {
|
|
173
|
+
try {
|
|
174
|
+
value = JSON.parse(extractJsonObject(product));
|
|
175
|
+
}
|
|
176
|
+
catch {
|
|
177
|
+
return undefined;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
const parsed = reportPlanSchema.safeParse(value);
|
|
181
|
+
return parsed.success ? parsed.data : undefined;
|
|
182
|
+
}
|
|
183
|
+
function extractJsonObject(text) {
|
|
184
|
+
const fenced = text.match(/```(?:json)?\s*([\s\S]*?)```/i);
|
|
185
|
+
const candidate = (fenced?.[1] ?? text).trim();
|
|
186
|
+
const start = candidate.indexOf("{");
|
|
187
|
+
const end = candidate.lastIndexOf("}");
|
|
188
|
+
if (start < 0 || end <= start)
|
|
189
|
+
throw new Error("no JSON object");
|
|
190
|
+
return candidate.slice(start, end + 1);
|
|
191
|
+
}
|
|
192
|
+
/** The only fields the narrator contributes: prose. Bounded so a runaway narrator cannot bloat the artifact. */
|
|
193
|
+
const NOTE_LIMIT = 4_000;
|
|
194
|
+
const TITLE_LIMIT = 200;
|
|
195
|
+
const SUMMARY_LIMIT = 20_000;
|
|
196
|
+
const narratorBlockSchema = z.object({
|
|
197
|
+
slot_id: z.string().min(1).optional(),
|
|
198
|
+
note: z.string().max(NOTE_LIMIT).optional(),
|
|
199
|
+
label: z.string().min(1).max(TITLE_LIMIT).optional(),
|
|
200
|
+
title: z.string().min(1).max(TITLE_LIMIT).optional(),
|
|
201
|
+
}).passthrough();
|
|
202
|
+
const narratorOutputSchema = z.object({
|
|
203
|
+
blocks: z.array(narratorBlockSchema),
|
|
204
|
+
summary: z.string().max(SUMMARY_LIMIT).optional(),
|
|
205
|
+
}).passthrough();
|
|
206
|
+
/** Reads the narrator's text out of its output; the values it may have written are not looked at. */
|
|
207
|
+
export function readNarratorText(product) {
|
|
208
|
+
let value = product;
|
|
209
|
+
if (typeof product === "string") {
|
|
210
|
+
try {
|
|
211
|
+
value = JSON.parse(extractJsonObject(product));
|
|
212
|
+
}
|
|
213
|
+
catch {
|
|
214
|
+
return undefined;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
const parsed = narratorOutputSchema.safeParse(value);
|
|
218
|
+
if (!parsed.success)
|
|
219
|
+
return undefined;
|
|
220
|
+
const notes = new Map();
|
|
221
|
+
for (const block of parsed.data.blocks) {
|
|
222
|
+
if (block.slot_id === undefined || notes.has(block.slot_id))
|
|
223
|
+
continue;
|
|
224
|
+
const label = block.label ?? block.title;
|
|
225
|
+
notes.set(block.slot_id, { ...(block.note !== undefined ? { note: block.note } : {}), ...(label !== undefined ? { label } : {}) });
|
|
226
|
+
}
|
|
227
|
+
return { ...(parsed.data.summary !== undefined ? { summary: parsed.data.summary } : {}), notes };
|
|
228
|
+
}
|
|
229
|
+
const definitionSchema = z.object({ sql: z.string().min(1), source_tables: z.array(z.string()).default([]), filters: z.array(z.string()).default([]) }).passthrough();
|
|
230
|
+
/**
|
|
231
|
+
* The rendered report. Values come from the slot table through the same
|
|
232
|
+
* materialisation the narrator read; the narrator contributes summary, notes
|
|
233
|
+
* and adjusted labels only; the host appends one `definition` block per
|
|
234
|
+
* filled slot whose provenance carries SQL. The narrator cannot add, drop or
|
|
235
|
+
* reorder blocks, and a number it emitted is never read.
|
|
236
|
+
*/
|
|
237
|
+
export function synthesiseReport(plan, table, narrator) {
|
|
238
|
+
const materialised = materialiseReportPlan(plan, table);
|
|
239
|
+
const blocks = [];
|
|
240
|
+
const definitions = [];
|
|
241
|
+
let filled = 0;
|
|
242
|
+
let verified = true;
|
|
243
|
+
for (const block of materialised.blocks) {
|
|
244
|
+
const text = narrator?.notes.get(block.slot_id);
|
|
245
|
+
if ("status" in block) {
|
|
246
|
+
blocks.push({ type: "unavailable", label: text?.label ?? block.label, block_type: block.type, reason_category: block.reason_category,
|
|
247
|
+
slot_id: block.slot_id, ...(text?.note !== undefined ? { note: text.note } : {}) });
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
filled += 1;
|
|
251
|
+
const entry = table.get(block.slot_id);
|
|
252
|
+
if (entry?.provenance?.verified !== true)
|
|
253
|
+
verified = false;
|
|
254
|
+
const named = block.type === "kpi_card"
|
|
255
|
+
? { ...block, label: text?.label ?? block.label }
|
|
256
|
+
: { ...block, title: text?.label ?? block.title };
|
|
257
|
+
blocks.push({ ...named, ...(text?.note !== undefined && block.type !== "narrative" ? { note: text.note } : {}) });
|
|
258
|
+
const source = entry?.provenance?.definition ?? (entry?.provenance?.sql !== undefined
|
|
259
|
+
? { sql: entry.provenance.sql, source_tables: entry.provenance.source_tables ?? [] } : undefined);
|
|
260
|
+
const definition = definitionSchema.safeParse(source);
|
|
261
|
+
if (definition.success) {
|
|
262
|
+
blocks.push({ type: "definition", sql: definition.data.sql, source_tables: [...definition.data.source_tables], filters: [...definition.data.filters], slot_id: block.slot_id });
|
|
263
|
+
definitions.push(block.slot_id);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
return { blocks, ...(narrator?.summary !== undefined ? { summary: narrator.summary } : {}), verified: filled > 0 && verified, definitions };
|
|
267
|
+
}
|
|
268
|
+
/** A component whose render contract declares an `unavailable` cell is a report: the host synthesises its envelope. */
|
|
269
|
+
export function isReportComponent(component) {
|
|
270
|
+
const render = component.declaration.effect?.render_blocks;
|
|
271
|
+
return Array.isArray(render) && render.some((block) => block?.type === "unavailable")
|
|
272
|
+
&& component.steps.some((step) => step.calls.length > 0);
|
|
273
|
+
}
|
|
274
|
+
/** The step that lays the report out (owns the call edge) and the one that narrates it (consumes the layout). */
|
|
275
|
+
export function reportSteps(component) {
|
|
276
|
+
const layout = component.steps.find((step) => step.calls.length > 0);
|
|
277
|
+
if (!layout)
|
|
278
|
+
return undefined;
|
|
279
|
+
const narrate = component.steps.find((step) => !step.repairOf && step !== layout && step.consumes.includes(layout.produces));
|
|
280
|
+
return { layout, ...(narrate ? { narrate } : {}) };
|
|
281
|
+
}
|
|
282
|
+
function refused(message) {
|
|
283
|
+
return { status: "refused", code: "callee_refused", message };
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Root normalization for a report component: the host-owned synthesis. The
|
|
287
|
+
* produced envelope is validated against the component's declared render
|
|
288
|
+
* contract exactly as a model-authored one would be.
|
|
289
|
+
*/
|
|
290
|
+
export function normalizeReportEvidence(component, evidence) {
|
|
291
|
+
const steps = reportSteps(component);
|
|
292
|
+
if (!steps)
|
|
293
|
+
return refused("The report component declares no layout step.");
|
|
294
|
+
const plan = parseReportPlan(evidence.steps[steps.layout.produces]);
|
|
295
|
+
if (!plan)
|
|
296
|
+
return refused("The planner did not produce a report layout.");
|
|
297
|
+
const narrator = steps.narrate ? readNarratorText(evidence.steps[steps.narrate.produces]) : undefined;
|
|
298
|
+
if (steps.narrate && !narrator)
|
|
299
|
+
return refused("The narrator did not produce a report.");
|
|
300
|
+
const table = buildSlotTable(evidence.children, evidence.egress ?? []);
|
|
301
|
+
const report = synthesiseReport(plan, table, narrator);
|
|
302
|
+
const contract = z.array(z.object({ type: z.string(), fields: z.record(z.string(), z.string()) }).strict())
|
|
303
|
+
.parse(component.declaration.effect.render_blocks ?? []);
|
|
304
|
+
const envelope = { blocks: report.blocks, ...(report.summary !== undefined ? { summary: report.summary } : {}), verified: report.verified };
|
|
305
|
+
const normalized = normalizeComponentResult(JSON.stringify(envelope), contract).value;
|
|
306
|
+
if (normalized.status !== "ok")
|
|
307
|
+
return normalized;
|
|
308
|
+
return { ...normalized, provenance: { verified: report.verified } };
|
|
309
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { type ComponentInvocationResult } from "@warble/claude-agent-sdk";
|
|
2
|
+
import { type EgressDecision, type EgressProvenance } from "./egress.js";
|
|
3
|
+
import { type StepUsage } from "./usage.js";
|
|
4
|
+
export type { StepUsage } from "./usage.js";
|
|
5
|
+
export interface ComponentTool {
|
|
6
|
+
readonly name: string;
|
|
7
|
+
readonly source: string;
|
|
8
|
+
}
|
|
9
|
+
/** A grant that executes SQL: its successful results carry a host-assigned `query_id`. */
|
|
10
|
+
export declare function isQueryGrant(grant: ComponentTool): boolean;
|
|
11
|
+
export interface ComponentStep {
|
|
12
|
+
readonly name: string;
|
|
13
|
+
readonly tier: string;
|
|
14
|
+
readonly prompt: string;
|
|
15
|
+
readonly consumes: readonly string[];
|
|
16
|
+
readonly produces: string;
|
|
17
|
+
readonly tools: readonly ComponentTool[];
|
|
18
|
+
readonly calls: readonly {
|
|
19
|
+
readonly alias: string;
|
|
20
|
+
readonly component: string;
|
|
21
|
+
}[];
|
|
22
|
+
readonly repairOf?: string;
|
|
23
|
+
}
|
|
24
|
+
export interface ComponentPlan {
|
|
25
|
+
readonly id: string;
|
|
26
|
+
readonly brief?: string;
|
|
27
|
+
readonly steps: readonly ComponentStep[];
|
|
28
|
+
/** Preserved compiler declaration, for host context/guardrail/render validation. */
|
|
29
|
+
readonly declaration: Readonly<Record<string, unknown>>;
|
|
30
|
+
}
|
|
31
|
+
export interface ExecutionPlan {
|
|
32
|
+
readonly identity: string;
|
|
33
|
+
readonly contextBinding?: Readonly<Record<string, unknown>>;
|
|
34
|
+
readonly systemPrompt?: string;
|
|
35
|
+
readonly entries: readonly string[];
|
|
36
|
+
readonly components: Readonly<Record<string, ComponentPlan>>;
|
|
37
|
+
}
|
|
38
|
+
export declare const COMPONENT_LIMITS: Readonly<{
|
|
39
|
+
depth: 8;
|
|
40
|
+
calls: 32;
|
|
41
|
+
steps: 40;
|
|
42
|
+
childSteps: 12;
|
|
43
|
+
requestBytes: 65536;
|
|
44
|
+
resultBytes: 1048576;
|
|
45
|
+
timeoutMs: 120000;
|
|
46
|
+
}>;
|
|
47
|
+
export interface StepResponse {
|
|
48
|
+
readonly value: unknown;
|
|
49
|
+
readonly failed?: boolean;
|
|
50
|
+
readonly usage?: StepUsage;
|
|
51
|
+
}
|
|
52
|
+
export interface BoundTool {
|
|
53
|
+
readonly name: string;
|
|
54
|
+
readonly source: string;
|
|
55
|
+
readonly inputSchema?: Readonly<Record<string, unknown>>;
|
|
56
|
+
execute(input: unknown, signal: AbortSignal): Promise<unknown>;
|
|
57
|
+
}
|
|
58
|
+
export interface ToolEvidence {
|
|
59
|
+
readonly step: string;
|
|
60
|
+
readonly tool: string;
|
|
61
|
+
readonly input: unknown;
|
|
62
|
+
readonly output: unknown;
|
|
63
|
+
}
|
|
64
|
+
export interface ComponentEvidence {
|
|
65
|
+
readonly steps: Readonly<Record<string, unknown>>;
|
|
66
|
+
readonly tools: readonly ToolEvidence[];
|
|
67
|
+
/** Child results exactly as the caller saw them, i.e. after egress verification. */
|
|
68
|
+
readonly children: readonly ComponentInvocationResult[];
|
|
69
|
+
/** Provenance the egress step kept aside (definition, SQL); host-only, never model input. */
|
|
70
|
+
readonly egress?: readonly EgressProvenance[];
|
|
71
|
+
}
|
|
72
|
+
/** Where a child result is about to cross back into its caller. */
|
|
73
|
+
export interface ChildVerificationContext {
|
|
74
|
+
readonly caller: string;
|
|
75
|
+
readonly step: string;
|
|
76
|
+
readonly alias: string;
|
|
77
|
+
readonly callee: string;
|
|
78
|
+
/** The callee's invocation depth (the caller's depth plus one). */
|
|
79
|
+
readonly depth: number;
|
|
80
|
+
/** The normalized alias-call request (request text plus `input`), as the child received it. */
|
|
81
|
+
readonly request: {
|
|
82
|
+
readonly request: string;
|
|
83
|
+
readonly input: Readonly<Record<string, unknown>>;
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
export interface ChildVerification {
|
|
87
|
+
/** What the caller receives; may only be narrower than the child result. */
|
|
88
|
+
readonly disclosed: ComponentInvocationResult;
|
|
89
|
+
readonly decisions: readonly EgressDecision[];
|
|
90
|
+
readonly provenance: readonly EgressProvenance[];
|
|
91
|
+
}
|
|
92
|
+
/** Host-created after exact context, model, account and tool authority validation. */
|
|
93
|
+
export interface ComponentBinding {
|
|
94
|
+
readonly tools: readonly BoundTool[];
|
|
95
|
+
/** Tests identity/revocation, including after close() has released step resources. */
|
|
96
|
+
isCurrent(): boolean;
|
|
97
|
+
/** Pure result validation/normalization; must never persist child artifacts. */
|
|
98
|
+
normalize(evidence: ComponentEvidence, signal: AbortSignal): Promise<ComponentInvocationResult>;
|
|
99
|
+
close(): Promise<void>;
|
|
100
|
+
}
|
|
101
|
+
/** Transport gets a fresh request each time; no previous component/step transcript. */
|
|
102
|
+
export interface StepRun {
|
|
103
|
+
readonly terminal?: boolean;
|
|
104
|
+
readonly tier: string;
|
|
105
|
+
readonly request: string;
|
|
106
|
+
readonly input: Readonly<Record<string, unknown>>;
|
|
107
|
+
readonly brief?: string;
|
|
108
|
+
readonly prompt: string;
|
|
109
|
+
readonly consumes: Readonly<Record<string, unknown>>;
|
|
110
|
+
/** Child results of this invocation so far, exactly as the caller saw them (after egress); host material for consumed artifacts. */
|
|
111
|
+
readonly children: readonly ComponentInvocationResult[];
|
|
112
|
+
readonly tools: Readonly<Record<string, (input: unknown) => Promise<unknown>>>;
|
|
113
|
+
readonly toolSchemas: Readonly<Record<string, Readonly<Record<string, unknown>>>>;
|
|
114
|
+
readonly toolDescriptions: Readonly<Record<string, string>>;
|
|
115
|
+
readonly signal: AbortSignal;
|
|
116
|
+
/**
|
|
117
|
+
* True when the alias-call request declares per-slot answers (`input.slots`, the same
|
|
118
|
+
* declaration the egress seam verifies against). A tool error is then one slot's outcome,
|
|
119
|
+
* which the model reports as `{slot_id, status: "unanswerable"}` while answering the other
|
|
120
|
+
* slots, not a step failure. A request without slots keeps the single-answer rule: any tool
|
|
121
|
+
* error fails the step, its repair step runs, and an unrepaired failure ends the child.
|
|
122
|
+
*/
|
|
123
|
+
readonly perSlot?: boolean;
|
|
124
|
+
}
|
|
125
|
+
export interface RunnerHost {
|
|
126
|
+
prepare(component: ComponentPlan, signal: AbortSignal): Promise<ComponentBinding>;
|
|
127
|
+
runStep(run: StepRun, binding: ComponentBinding): Promise<StepResponse>;
|
|
128
|
+
/** Separate root-only sink, invoked once after successful closure execution. */
|
|
129
|
+
persistRoot?(result: Extract<ComponentInvocationResult, {
|
|
130
|
+
status: "ok";
|
|
131
|
+
}>, binding: ComponentBinding, signal: AbortSignal): Promise<void>;
|
|
132
|
+
/**
|
|
133
|
+
* The egress verification seam: every child result passes through here,
|
|
134
|
+
* after the child invocation and before the alias resolves for the caller.
|
|
135
|
+
* The caller never sees `result`, only `disclosed`. Optional so a single-zone
|
|
136
|
+
* composition keeps today's pass-through; a host with a disclosure policy
|
|
137
|
+
* installs one.
|
|
138
|
+
*/
|
|
139
|
+
verifyChild?(context: ChildVerificationContext, result: ComponentInvocationResult, signal: AbortSignal): Promise<ChildVerification>;
|
|
140
|
+
onEvent?(event: ComponentEvent): void;
|
|
141
|
+
}
|
|
142
|
+
export interface ComponentEvent {
|
|
143
|
+
readonly kind: "step.start" | "step.finish" | "call.start" | "call.finish" | "tool.start" | "tool.finish" | "egress" | "usage";
|
|
144
|
+
readonly invocation: string;
|
|
145
|
+
readonly parent: string | null;
|
|
146
|
+
readonly component: string;
|
|
147
|
+
readonly step?: string;
|
|
148
|
+
readonly tool?: string;
|
|
149
|
+
readonly callId?: string;
|
|
150
|
+
readonly depth: number;
|
|
151
|
+
readonly status?: "ok" | "error" | "cancelled";
|
|
152
|
+
/** `egress` events: slot id and outcome only, never the payload. */
|
|
153
|
+
readonly slot?: string;
|
|
154
|
+
readonly egress?: EgressDecision["status"];
|
|
155
|
+
readonly reason?: string;
|
|
156
|
+
/** `usage` events: one per completed step call, zeros when the transport reported none. */
|
|
157
|
+
readonly usage?: StepUsage;
|
|
158
|
+
}
|
|
159
|
+
/** Immutable, root-scoped execution. Constructed only from a validated producer adapter. */
|
|
160
|
+
export declare class ComponentRunner {
|
|
161
|
+
private readonly host;
|
|
162
|
+
private readonly plan;
|
|
163
|
+
private readonly controller;
|
|
164
|
+
private readonly bindings;
|
|
165
|
+
private readonly releases;
|
|
166
|
+
private used;
|
|
167
|
+
private cleanupFailure;
|
|
168
|
+
get cleanupFailed(): boolean;
|
|
169
|
+
private calls;
|
|
170
|
+
private steps;
|
|
171
|
+
private usage;
|
|
172
|
+
private deadline;
|
|
173
|
+
constructor(plan: ExecutionPlan, host: RunnerHost);
|
|
174
|
+
cancel(): void;
|
|
175
|
+
get observedUsage(): Readonly<StepUsage>;
|
|
176
|
+
run(entry: string, request: unknown, signal?: AbortSignal): Promise<ComponentInvocationResult>;
|
|
177
|
+
/** Runs the host's egress seam, or passes the result through when the host installed none. */
|
|
178
|
+
private verifyChild;
|
|
179
|
+
private release;
|
|
180
|
+
private check;
|
|
181
|
+
private wait;
|
|
182
|
+
/**
|
|
183
|
+
* A per-slot child behind an egress seam that throws `callee_failed` (an unrepaired step) reaches
|
|
184
|
+
* the seam as a `callee_failed` result instead of failing the caller, so the seam can answer every
|
|
185
|
+
* slot unavailable. A grandchild's seam-narrowing violation thrown as `callee_failed` inside the
|
|
186
|
+
* child lands here too and renders all-unavailable (fails closed). Cancellation, budget, invalid
|
|
187
|
+
* results and any other error keep propagating, as does every failure of a child without slots
|
|
188
|
+
* or without a seam. A refused child is returned as is; the seam discloses it with its own category.
|
|
189
|
+
*/
|
|
190
|
+
private invokeChild;
|
|
191
|
+
private invoke;
|
|
192
|
+
private validate;
|
|
193
|
+
}
|