@xemahq/kernel-contracts 0.2.2 → 0.3.0
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/dist/agent-workspace/awp-spec.json +1 -1
- package/dist/distribution/lib/distribution-lock.d.ts +7 -0
- package/dist/distribution/lib/distribution-lock.d.ts.map +1 -1
- package/dist/distribution/lib/distribution-lock.js +7 -1
- package/dist/distribution/lib/distribution-lock.js.map +1 -1
- package/dist/distribution/lib/distribution.d.ts +6 -0
- package/dist/distribution/lib/distribution.d.ts.map +1 -1
- package/dist/distribution/lib/distribution.js +6 -1
- package/dist/distribution/lib/distribution.js.map +1 -1
- package/dist/distribution/lib/image-lock.d.ts +7 -0
- package/dist/distribution/lib/image-lock.d.ts.map +1 -1
- package/dist/distribution/lib/image-lock.js +7 -1
- package/dist/distribution/lib/image-lock.js.map +1 -1
- package/dist/widget/index.d.ts +5 -0
- package/dist/widget/index.d.ts.map +1 -0
- package/dist/widget/index.js +21 -0
- package/dist/widget/index.js.map +1 -0
- package/dist/widget/lib/chat-widget-envelope.d.ts +13 -0
- package/dist/widget/lib/chat-widget-envelope.d.ts.map +1 -0
- package/dist/widget/lib/chat-widget-envelope.js +16 -0
- package/dist/widget/lib/chat-widget-envelope.js.map +1 -0
- package/dist/widget/lib/chat-widget-kind.d.ts +16 -0
- package/dist/widget/lib/chat-widget-kind.d.ts.map +1 -0
- package/dist/widget/lib/chat-widget-kind.js +21 -0
- package/dist/widget/lib/chat-widget-kind.js.map +1 -0
- package/dist/widget/lib/chat-widget-payloads.d.ts +72 -0
- package/dist/widget/lib/chat-widget-payloads.d.ts.map +1 -0
- package/dist/widget/lib/chat-widget-payloads.js +69 -0
- package/dist/widget/lib/chat-widget-payloads.js.map +1 -0
- package/dist/widget/lib/widget-contribution.d.ts +24 -0
- package/dist/widget/lib/widget-contribution.d.ts.map +1 -0
- package/dist/widget/lib/widget-contribution.js +23 -0
- package/dist/widget/lib/widget-contribution.js.map +1 -0
- package/package.json +18 -18
- package/src/distribution/lib/distribution-lock.ts +32 -0
- package/src/distribution/lib/distribution.ts +26 -0
- package/src/distribution/lib/image-lock.ts +25 -0
- package/src/widget/index.ts +4 -0
- package/src/widget/lib/chat-widget-envelope.ts +41 -0
- package/src/widget/lib/chat-widget-kind.ts +51 -0
- package/src/widget/lib/chat-widget-payloads.ts +130 -0
- package/src/widget/lib/widget-contribution.ts +94 -0
- package/LICENSE +0 -201
|
@@ -26,6 +26,15 @@ export interface LockedBiome {
|
|
|
26
26
|
/** Source repo + path the biome was resolved from (provenance for the build). */
|
|
27
27
|
repo: string;
|
|
28
28
|
path: string;
|
|
29
|
+
/**
|
|
30
|
+
* Deployable service names this biome ships (from the manifest `ships.apis[].name`).
|
|
31
|
+
* Empty for biomes with no deployable API (pure skill/agent bundles) and for
|
|
32
|
+
* web biomes (which bundle into the host shell, not their own container). This
|
|
33
|
+
* is what the deployment-roster generator reads — it runs in a standalone
|
|
34
|
+
* checkout without the biome sources, so the biome→service mapping must travel
|
|
35
|
+
* inside the lockfile, never be recomputed downstream.
|
|
36
|
+
*/
|
|
37
|
+
services: readonly string[];
|
|
29
38
|
/** Set by the image-manifest resolver; `image@sha256:<digest>`. */
|
|
30
39
|
imageDigest?: string;
|
|
31
40
|
}
|
|
@@ -39,9 +48,29 @@ export const LockedBiomeSchema = z.object({
|
|
|
39
48
|
mandatory: z.boolean(),
|
|
40
49
|
repo: z.string().min(1),
|
|
41
50
|
path: z.string().min(1),
|
|
51
|
+
services: z.array(z.string().min(1)).readonly(),
|
|
42
52
|
imageDigest: z.string().min(1).optional(),
|
|
43
53
|
}) as z.ZodType<LockedBiome>;
|
|
44
54
|
|
|
55
|
+
/**
|
|
56
|
+
* A platform-substrate service that is part of a distribution but is NOT a biome
|
|
57
|
+
* — the deployment "init system" (kernel server, host shell, opencode worker
|
|
58
|
+
* plane, docs). These always ship with any Xema deployment that includes the
|
|
59
|
+
* base layer; they are declared once on the `core` distribution and inherited
|
|
60
|
+
* via `extends`. Carried into the lock so the roster generator sees the full
|
|
61
|
+
* service set (biome services ∪ platform services) without any hardcoded floor.
|
|
62
|
+
*/
|
|
63
|
+
export interface LockedPlatformService {
|
|
64
|
+
name: string;
|
|
65
|
+
/** Wave/boot tier — drives ordering in the generated bring-up plan. */
|
|
66
|
+
tier: BiomeTier | 'edge';
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export const LockedPlatformServiceSchema = z.object({
|
|
70
|
+
name: z.string().min(1),
|
|
71
|
+
tier: z.union([BiomeTierSchema, z.literal('edge')]),
|
|
72
|
+
}) as z.ZodType<LockedPlatformService>;
|
|
73
|
+
|
|
45
74
|
/**
|
|
46
75
|
* `DistributionLock` — the deterministic, resolved output of a `Distribution`.
|
|
47
76
|
* THE single artifact every consumer reads (deploy generator, test-suite
|
|
@@ -56,6 +85,8 @@ export interface DistributionLock {
|
|
|
56
85
|
schemaVersion: 1;
|
|
57
86
|
distributionId: string;
|
|
58
87
|
biomes: readonly LockedBiome[];
|
|
88
|
+
/** Non-biome substrate services that ship with this distribution (the deploy "init system"). */
|
|
89
|
+
platformServices: readonly LockedPlatformService[];
|
|
59
90
|
/** ISO-8601 timestamp the resolver stamped (tooling-supplied). */
|
|
60
91
|
resolvedAt?: string;
|
|
61
92
|
/** Hash of the resolver inputs (manifest + index) for drift detection. */
|
|
@@ -66,6 +97,7 @@ export const DistributionLockSchema = z.object({
|
|
|
66
97
|
schemaVersion: z.literal(1),
|
|
67
98
|
distributionId: z.string().min(1),
|
|
68
99
|
biomes: z.array(LockedBiomeSchema).readonly(),
|
|
100
|
+
platformServices: z.array(LockedPlatformServiceSchema).readonly(),
|
|
69
101
|
resolvedAt: z.string().min(1).optional(),
|
|
70
102
|
inputHash: z.string().min(1).optional(),
|
|
71
103
|
}) as z.ZodType<DistributionLock>;
|
|
@@ -26,6 +26,25 @@ export const DistributionTrustPolicySchema = z.object({
|
|
|
26
26
|
allow: z.array(BiomeOriginSchema).readonly(),
|
|
27
27
|
}) as z.ZodType<DistributionTrustPolicy>;
|
|
28
28
|
|
|
29
|
+
/**
|
|
30
|
+
* A platform-substrate service a distribution ships that is NOT a biome — the
|
|
31
|
+
* deployment "init system" (kernel server, host shell, opencode worker plane,
|
|
32
|
+
* docs). Declared once on the floor distribution (`core`) and inherited via
|
|
33
|
+
* `extends`. These never go through biome selectors (they are not biomes); they
|
|
34
|
+
* are carried verbatim into the lock so the deployment-roster generator sees the
|
|
35
|
+
* full service set without any hardcoded floor in the generator.
|
|
36
|
+
*/
|
|
37
|
+
export interface DistributionPlatformService {
|
|
38
|
+
name: string;
|
|
39
|
+
/** Wave/boot tier — drives ordering in the generated bring-up plan. */
|
|
40
|
+
tier: 'kernel' | 'system' | 'base' | 'platform' | 'edge';
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export const DistributionPlatformServiceSchema = z.object({
|
|
44
|
+
name: z.string().min(1),
|
|
45
|
+
tier: z.enum(['kernel', 'system', 'base', 'platform', 'edge']),
|
|
46
|
+
}) as z.ZodType<DistributionPlatformService>;
|
|
47
|
+
|
|
29
48
|
/**
|
|
30
49
|
* `Distribution` — the top-level shape of `xema-distribution.json`: the
|
|
31
50
|
* PACKAGING primitive that declares which biomes a given Xema build ships.
|
|
@@ -52,6 +71,12 @@ export interface Distribution {
|
|
|
52
71
|
/** Biome ids to remove after composition. Cannot remove a kernel-tier biome. */
|
|
53
72
|
exclude?: readonly string[];
|
|
54
73
|
trustPolicy?: DistributionTrustPolicy;
|
|
74
|
+
/**
|
|
75
|
+
* Non-biome substrate services this distribution ships (the deploy "init
|
|
76
|
+
* system"). Composed parent→child like `include` (concatenated, de-duped by
|
|
77
|
+
* name with the child winning on tier). Usually declared only on `core`.
|
|
78
|
+
*/
|
|
79
|
+
platformServices?: readonly DistributionPlatformService[];
|
|
55
80
|
}
|
|
56
81
|
|
|
57
82
|
export const DistributionSchema = z.object({
|
|
@@ -63,6 +88,7 @@ export const DistributionSchema = z.object({
|
|
|
63
88
|
include: z.array(DistributionSelectorSchema).readonly(),
|
|
64
89
|
exclude: z.array(z.string().min(1)).readonly().optional(),
|
|
65
90
|
trustPolicy: DistributionTrustPolicySchema.optional(),
|
|
91
|
+
platformServices: z.array(DistributionPlatformServiceSchema).readonly().optional(),
|
|
66
92
|
}) as z.ZodType<Distribution>;
|
|
67
93
|
|
|
68
94
|
/**
|
|
@@ -36,6 +36,28 @@ export const LockedImageSchema = z.object({
|
|
|
36
36
|
path: z.string().min(1),
|
|
37
37
|
}) as z.ZodType<LockedImage>;
|
|
38
38
|
|
|
39
|
+
/**
|
|
40
|
+
* A web biome that ships in a distribution. Web biomes do NOT produce their own
|
|
41
|
+
* container image — they are static frontend bundles compiled INTO the single
|
|
42
|
+
* host-shell image (`xema-host-web`). They are listed here explicitly (never
|
|
43
|
+
* silently dropped) so the host-shell build knows exactly which web biomes to
|
|
44
|
+
* bundle for this distribution: the lockfile is the join key for the frontend
|
|
45
|
+
* too, not just the backend.
|
|
46
|
+
*/
|
|
47
|
+
export interface LockedWebBiome {
|
|
48
|
+
biomeId: string;
|
|
49
|
+
/** Source repo segment the web biome was resolved from. */
|
|
50
|
+
repo: string;
|
|
51
|
+
/** Source directory path (repo-root-relative) the web biome was resolved from. */
|
|
52
|
+
path: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export const LockedWebBiomeSchema = z.object({
|
|
56
|
+
biomeId: z.string().min(1),
|
|
57
|
+
repo: z.string().min(1),
|
|
58
|
+
path: z.string().min(1),
|
|
59
|
+
}) as z.ZodType<LockedWebBiome>;
|
|
60
|
+
|
|
39
61
|
/**
|
|
40
62
|
* `ImageLock` — the deterministic, content-addressed image manifest for a
|
|
41
63
|
* distribution. Resolved from `distribution.lock.json` by the image-manifest
|
|
@@ -48,6 +70,8 @@ export interface ImageLock {
|
|
|
48
70
|
schemaVersion: 1;
|
|
49
71
|
distributionId: string;
|
|
50
72
|
images: readonly LockedImage[];
|
|
73
|
+
/** Web biomes bundled into the host-shell image (no own container). */
|
|
74
|
+
webBiomes: readonly LockedWebBiome[];
|
|
51
75
|
/** ISO-8601 timestamp the resolver stamped (tooling-supplied). */
|
|
52
76
|
resolvedAt?: string;
|
|
53
77
|
}
|
|
@@ -56,6 +80,7 @@ export const ImageLockSchema = z.object({
|
|
|
56
80
|
schemaVersion: z.literal(1),
|
|
57
81
|
distributionId: z.string().min(1),
|
|
58
82
|
images: z.array(LockedImageSchema).readonly(),
|
|
83
|
+
webBiomes: z.array(LockedWebBiomeSchema).readonly(),
|
|
59
84
|
resolvedAt: z.string().min(1).optional(),
|
|
60
85
|
}) as z.ZodType<ImageLock>;
|
|
61
86
|
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* `ChatWidgetEnvelope` — a single widget instance recorded on an assistant
|
|
5
|
+
* message's `metadata.widgets[]` and rendered by the host-web ChatWidget
|
|
6
|
+
* registry (HANDOFF §3.2).
|
|
7
|
+
*
|
|
8
|
+
* `kind` is a first-party {@link ChatWidgetKind} value OR a namespaced biome
|
|
9
|
+
* string (`<biomeId>:<kind>`). `version` is the monotonic schema version for
|
|
10
|
+
* THIS kind — the FE renders the registered renderer when `version <=
|
|
11
|
+
* supportedVersion`, else a safe fallback card. `payload` is the kind-specific
|
|
12
|
+
* payload (validated by the producer against the kind's schema; the FE
|
|
13
|
+
* re-validates before render so a malformed payload renders the fallback rather
|
|
14
|
+
* than being dropped).
|
|
15
|
+
*
|
|
16
|
+
* This is the kernel-canonical mirror of the FE `ChatWidgetEnvelope` interface
|
|
17
|
+
* — keep the shapes value-identical.
|
|
18
|
+
*/
|
|
19
|
+
export interface ChatWidgetEnvelope {
|
|
20
|
+
readonly kind: string;
|
|
21
|
+
readonly version: number;
|
|
22
|
+
readonly payload: unknown;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Zod validator for {@link ChatWidgetEnvelope}. */
|
|
26
|
+
export const ChatWidgetEnvelopeSchema: z.ZodType<ChatWidgetEnvelope> = z.object({
|
|
27
|
+
kind: z.string().min(1),
|
|
28
|
+
version: z.number().int().positive(),
|
|
29
|
+
payload: z.unknown(),
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Why a widget fell back to the safe card — populated on the FE resolved
|
|
34
|
+
* decision so the Studio debugger can see WHY. Mirror of the FE
|
|
35
|
+
* `ChatWidgetFallbackReason`. Closed set.
|
|
36
|
+
*/
|
|
37
|
+
export enum ChatWidgetFallbackReason {
|
|
38
|
+
UnknownKind = 'unknown-kind',
|
|
39
|
+
InvalidPayload = 'invalid-payload',
|
|
40
|
+
UnsupportedVersion = 'unsupported-version',
|
|
41
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* `ChatWidgetKind` — the closed first-party set of rich in-chat widget kinds an
|
|
5
|
+
* agent may render through the generic `render_widget` tool (see the Xema OS
|
|
6
|
+
* ChatWidget feature). Each kind is a GENERIC, domain-agnostic presentation
|
|
7
|
+
* primitive — chosen to cover the broadest possible range of "structured thing
|
|
8
|
+
* the agent wants to show the user" without baking in any vertical semantics.
|
|
9
|
+
*
|
|
10
|
+
* ── Value-identity invariant ────────────────────────────────────────────────
|
|
11
|
+
* This enum is the kernel-canonical mirror of the host-web FE model
|
|
12
|
+
* (`submodules/xema-host-web/src/views/project/sessions/thin/chat-widget-model.ts`,
|
|
13
|
+
* `ChatWidgetKind`). The two MUST stay value-identical: the FE consumes this
|
|
14
|
+
* contract via the frontend-bridge sync, and the registry resolves a payload to
|
|
15
|
+
* a renderer by matching `kind`. Adding a generic kind = add the enum member in
|
|
16
|
+
* BOTH places (the "2-file rule") plus a payload schema below.
|
|
17
|
+
*
|
|
18
|
+
* ── Closed first-party set vs. open biome set ───────────────────────────────
|
|
19
|
+
* A biome contributes its OWN widget kind as a namespaced string
|
|
20
|
+
* (`<biomeId>:<kind>`) through a `widget-kind` contribution — it never extends
|
|
21
|
+
* this enum. This mirrors the closed-enum + open-namespaced-string pattern used
|
|
22
|
+
* by the FE registry's `registerChatWidget`.
|
|
23
|
+
*/
|
|
24
|
+
export enum ChatWidgetKind {
|
|
25
|
+
/** A checklist / plan (todo items with status). The agent's "here's the plan". */
|
|
26
|
+
Plan = 'plan',
|
|
27
|
+
/** A single-file unified diff (path + hunks). "Here's the change I propose." */
|
|
28
|
+
Diff = 'diff',
|
|
29
|
+
/** A suspended capability call awaiting human approval, keyed by approval id. */
|
|
30
|
+
Approval = 'approval',
|
|
31
|
+
/** A generic columns + rows table. The universal "show me structured records". */
|
|
32
|
+
Table = 'table',
|
|
33
|
+
/** A compact bar/line chart over labelled series. "Visualise these numbers." */
|
|
34
|
+
Chart = 'chart',
|
|
35
|
+
/** A header-only file-change summary (path + add/del counts), no hunks. */
|
|
36
|
+
FileEdit = 'file-edit',
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Zod mirror of {@link ChatWidgetKind} for wire-boundary validation. */
|
|
40
|
+
export const ChatWidgetKindSchema = z.nativeEnum(ChatWidgetKind);
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* The chart shapes the first-party `chart` widget can draw. Closed set — kept
|
|
44
|
+
* value-identical with the FE's `ChatChartKind`.
|
|
45
|
+
*/
|
|
46
|
+
export enum ChatChartKind {
|
|
47
|
+
Bar = 'bar',
|
|
48
|
+
Line = 'line',
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export const ChatChartKindSchema = z.nativeEnum(ChatChartKind);
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
import { ChatChartKindSchema, ChatWidgetKind } from './chat-widget-kind';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Per-kind payload contracts for the first-party {@link ChatWidgetKind} set.
|
|
7
|
+
*
|
|
8
|
+
* These schemas are the WIRE contract the `render_widget` tool validates against
|
|
9
|
+
* before a widget envelope is recorded onto an assistant message's
|
|
10
|
+
* `metadata.widgets[]`. They are kept STRUCTURALLY value-identical with the FE
|
|
11
|
+
* narrowing validators in
|
|
12
|
+
* `submodules/xema-host-web/src/views/project/sessions/thin/chat-widget-builtins.tsx`
|
|
13
|
+
* — the FE re-validates client-side (so a future/unknown payload still routes to
|
|
14
|
+
* the safe fallback card), but the runtime fails fast on a bad payload so a
|
|
15
|
+
* malformed widget never reaches the transcript.
|
|
16
|
+
*
|
|
17
|
+
* Zero runtime deps beyond zod (kernel-leaf invariant).
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/** Mirror of the FE `TodoState` (session-kit). Closed set. */
|
|
21
|
+
export enum WidgetTodoState {
|
|
22
|
+
Todo = 'todo',
|
|
23
|
+
Active = 'active',
|
|
24
|
+
Done = 'done',
|
|
25
|
+
}
|
|
26
|
+
export const WidgetTodoStateSchema = z.nativeEnum(WidgetTodoState);
|
|
27
|
+
|
|
28
|
+
/** A single plan/checklist item. Mirrors session-kit `TodoItem`. */
|
|
29
|
+
export const PlanWidgetItemSchema = z.object({
|
|
30
|
+
label: z.string().min(1),
|
|
31
|
+
state: WidgetTodoStateSchema,
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
/** `plan` payload — a titled checklist of todo items. */
|
|
35
|
+
export const PlanWidgetPayloadSchema = z.object({
|
|
36
|
+
title: z.string().optional(),
|
|
37
|
+
items: z.array(PlanWidgetItemSchema),
|
|
38
|
+
});
|
|
39
|
+
export type PlanWidgetPayload = z.infer<typeof PlanWidgetPayloadSchema>;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* `diff` payload — a single-file unified diff. `hunks` are raw diff lines whose
|
|
43
|
+
* first character is the marker (`+`/`-`/space). Mirrors session-kit `DiffFile`.
|
|
44
|
+
*/
|
|
45
|
+
export const DiffWidgetPayloadSchema = z.object({
|
|
46
|
+
path: z.string().min(1),
|
|
47
|
+
add: z.number(),
|
|
48
|
+
del: z.number(),
|
|
49
|
+
hunks: z.array(z.string()).optional(),
|
|
50
|
+
});
|
|
51
|
+
export type DiffWidgetPayload = z.infer<typeof DiffWidgetPayloadSchema>;
|
|
52
|
+
|
|
53
|
+
/** `file-edit` payload — a header-only file change summary (no hunks). */
|
|
54
|
+
export const FileEditWidgetPayloadSchema = z.object({
|
|
55
|
+
path: z.string().min(1),
|
|
56
|
+
summary: z.string().optional(),
|
|
57
|
+
add: z.number(),
|
|
58
|
+
del: z.number(),
|
|
59
|
+
});
|
|
60
|
+
export type FileEditWidgetPayload = z.infer<typeof FileEditWidgetPayloadSchema>;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* `approval` payload — the suspended capability call awaiting approval, keyed by
|
|
64
|
+
* the session it belongs to. The FE mounts the same Pillar-3 inline approval
|
|
65
|
+
* card from this id.
|
|
66
|
+
*/
|
|
67
|
+
export const ApprovalWidgetPayloadSchema = z.object({
|
|
68
|
+
sessionId: z.string().min(1),
|
|
69
|
+
accent: z.string().optional(),
|
|
70
|
+
});
|
|
71
|
+
export type ApprovalWidgetPayload = z.infer<typeof ApprovalWidgetPayloadSchema>;
|
|
72
|
+
|
|
73
|
+
/** A `table` column descriptor. */
|
|
74
|
+
export const TableWidgetColumnSchema = z.object({
|
|
75
|
+
key: z.string().min(1),
|
|
76
|
+
label: z.string().min(1),
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
/** `table` payload — generic columns + rows. Cells are strings or numbers. */
|
|
80
|
+
export const TableWidgetPayloadSchema = z.object({
|
|
81
|
+
columns: z.array(TableWidgetColumnSchema),
|
|
82
|
+
rows: z.array(z.record(z.string(), z.union([z.string(), z.number()]))),
|
|
83
|
+
});
|
|
84
|
+
export type TableWidgetPayload = z.infer<typeof TableWidgetPayloadSchema>;
|
|
85
|
+
|
|
86
|
+
/** A single chart series (one bar/line group). */
|
|
87
|
+
export const ChartWidgetSeriesSchema = z.object({
|
|
88
|
+
label: z.string().min(1),
|
|
89
|
+
points: z.array(z.object({ x: z.string(), y: z.number() })),
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
/** `chart` payload — a compact bar/line chart over labelled series. */
|
|
93
|
+
export const ChartWidgetPayloadSchema = z.object({
|
|
94
|
+
kind: ChatChartKindSchema,
|
|
95
|
+
series: z.array(ChartWidgetSeriesSchema),
|
|
96
|
+
});
|
|
97
|
+
export type ChartWidgetPayload = z.infer<typeof ChartWidgetPayloadSchema>;
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* The closed registry of first-party kind → payload schema. The single source
|
|
101
|
+
* of truth the `render_widget` tool & runtime use to validate a first-party
|
|
102
|
+
* widget payload by `kind`. Adding a generic kind = one enum member + one schema
|
|
103
|
+
* here + the FE mirror (the "2-file rule").
|
|
104
|
+
*/
|
|
105
|
+
export const FIRST_PARTY_WIDGET_PAYLOAD_SCHEMAS: Readonly<
|
|
106
|
+
Record<ChatWidgetKind, z.ZodTypeAny>
|
|
107
|
+
> = {
|
|
108
|
+
[ChatWidgetKind.Plan]: PlanWidgetPayloadSchema,
|
|
109
|
+
[ChatWidgetKind.Diff]: DiffWidgetPayloadSchema,
|
|
110
|
+
[ChatWidgetKind.Approval]: ApprovalWidgetPayloadSchema,
|
|
111
|
+
[ChatWidgetKind.Table]: TableWidgetPayloadSchema,
|
|
112
|
+
[ChatWidgetKind.Chart]: ChartWidgetPayloadSchema,
|
|
113
|
+
[ChatWidgetKind.FileEdit]: FileEditWidgetPayloadSchema,
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* The current schema version for each first-party kind. Bumped when a kind's
|
|
118
|
+
* payload schema changes shape; the FE renders the registered renderer only
|
|
119
|
+
* when `envelope.version <= supportedVersion`, else the safe fallback.
|
|
120
|
+
*/
|
|
121
|
+
export const FIRST_PARTY_WIDGET_VERSIONS: Readonly<
|
|
122
|
+
Record<ChatWidgetKind, number>
|
|
123
|
+
> = {
|
|
124
|
+
[ChatWidgetKind.Plan]: 1,
|
|
125
|
+
[ChatWidgetKind.Diff]: 1,
|
|
126
|
+
[ChatWidgetKind.Approval]: 1,
|
|
127
|
+
[ChatWidgetKind.Table]: 1,
|
|
128
|
+
[ChatWidgetKind.Chart]: 1,
|
|
129
|
+
[ChatWidgetKind.FileEdit]: 1,
|
|
130
|
+
};
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Manifest shape carried by every `ContributionKind.WidgetKind` envelope.
|
|
5
|
+
*
|
|
6
|
+
* One contribution = one widget kind. The shape mirrors
|
|
7
|
+
* `CapabilityContributionManifest` (see `../capability/lib/capability-contribution.ts`)
|
|
8
|
+
* so the generic `BootstrapContributionsService<TKind, TManifest>` template and
|
|
9
|
+
* the codemod can ingest it the same way every other contribution kind is
|
|
10
|
+
* ingested. Provenance (`biomeId`, `biomeVersion`) is stamped by the bootstrap
|
|
11
|
+
* pipeline from the discovering biome's manifest — authors do NOT declare it
|
|
12
|
+
* inline.
|
|
13
|
+
*
|
|
14
|
+
* A biome contributes a widget kind so its frontend biome can register a
|
|
15
|
+
* renderer for it via `registerChatWidget('<biomeId>:<kind>', …)`. The
|
|
16
|
+
* `kind` here is the BARE, biome-local kind slug; the catalogue namespaces it
|
|
17
|
+
* to `<biomeId>:<kind>` at ingest so it can never shadow a first-party kind.
|
|
18
|
+
*
|
|
19
|
+
* Validation is fail-fast: a contribution that violates any constraint is
|
|
20
|
+
* rejected at boot. `payloadSchema` is an opaque JSON Schema passed through
|
|
21
|
+
* verbatim (the catalogue surfaces it to the FE and to authoring tools; it is
|
|
22
|
+
* NOT a kernel-validated shape — validating it as JSON Schema here would be
|
|
23
|
+
* either partial or pull in a JSON-Schema validator, violating the kernel-leaf
|
|
24
|
+
* invariant).
|
|
25
|
+
*/
|
|
26
|
+
export interface WidgetContributionManifest {
|
|
27
|
+
/**
|
|
28
|
+
* Biome-local kind slug (lowercase, hyphenated). Namespaced to
|
|
29
|
+
* `<biomeId>:<kind>` by the catalogue — a biome can never claim a bare
|
|
30
|
+
* first-party kind.
|
|
31
|
+
*/
|
|
32
|
+
readonly kind: string;
|
|
33
|
+
/** Monotonic schema version for this kind's payload. */
|
|
34
|
+
readonly version: number;
|
|
35
|
+
/** Human-readable display name surfaced in the catalogue (≤ 200 chars). */
|
|
36
|
+
readonly displayName: string;
|
|
37
|
+
/** One-line summary of WHAT the widget shows + WHEN to use it (≤ 1000 chars). */
|
|
38
|
+
readonly summary: string;
|
|
39
|
+
/** JSON Schema for the widget payload — passed through verbatim. */
|
|
40
|
+
readonly payloadSchema: Record<string, unknown>;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const WIDGET_KIND_SLUG_REGEX = /^[a-z][a-z0-9-]*$/;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Zod validator for {@link WidgetContributionManifest}. The bootstrap template
|
|
47
|
+
* and the catalogue's `POST /widget-kinds/register` route BOTH route every
|
|
48
|
+
* candidate manifest through this before persisting, so a malformed
|
|
49
|
+
* contribution fails at the boundary instead of at render time.
|
|
50
|
+
*/
|
|
51
|
+
export const WidgetContributionManifestSchema: z.ZodType<WidgetContributionManifest> =
|
|
52
|
+
z.object({
|
|
53
|
+
kind: z
|
|
54
|
+
.string()
|
|
55
|
+
.min(1)
|
|
56
|
+
.max(100)
|
|
57
|
+
.regex(
|
|
58
|
+
WIDGET_KIND_SLUG_REGEX,
|
|
59
|
+
'Widget kind slug must be lowercase letters, digits, and hyphens (start with a letter).',
|
|
60
|
+
),
|
|
61
|
+
version: z.number().int().positive(),
|
|
62
|
+
displayName: z.string().min(1).max(200),
|
|
63
|
+
summary: z.string().min(1).max(1000),
|
|
64
|
+
payloadSchema: z.record(z.string(), z.unknown()),
|
|
65
|
+
}) as z.ZodType<WidgetContributionManifest>;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* A resolved widget-kind catalogue entry — what `GET /widget-kinds` returns. It
|
|
69
|
+
* unifies the closed first-party set and the open biome-contributed set behind
|
|
70
|
+
* ONE shape the FE consumes to know which kinds exist (and to register
|
|
71
|
+
* biome-contributed kinds into the open FE registry).
|
|
72
|
+
*
|
|
73
|
+
* `source` discriminates provenance; `kind` is the FULLY-NAMESPACED slug the FE
|
|
74
|
+
* registry keys by (a first-party kind is its bare enum value; a biome kind is
|
|
75
|
+
* `<biomeId>:<kind>`).
|
|
76
|
+
*/
|
|
77
|
+
export interface WidgetKindCatalogueEntry {
|
|
78
|
+
readonly kind: string;
|
|
79
|
+
readonly version: number;
|
|
80
|
+
readonly displayName: string;
|
|
81
|
+
readonly summary: string;
|
|
82
|
+
readonly payloadSchema: Record<string, unknown>;
|
|
83
|
+
readonly source: WidgetKindSource;
|
|
84
|
+
/** Present only for biome-contributed kinds. */
|
|
85
|
+
readonly biomeId?: string;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Provenance of a catalogue entry. Closed set. */
|
|
89
|
+
export enum WidgetKindSource {
|
|
90
|
+
FirstParty = 'first-party',
|
|
91
|
+
Biome = 'biome',
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export const WidgetKindSourceSchema = z.nativeEnum(WidgetKindSource);
|