@north-light/crouter-api 0.3.205 → 0.3.206
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.
|
@@ -1,4 +1,14 @@
|
|
|
1
1
|
import type { InboxTierDTO, IsoTime, NodeIdDTO } from './common.js';
|
|
2
|
+
/** One runtime card supplied by a caller: the runtime renders and escapes it
|
|
3
|
+
* exactly once, so a caller cannot emit a malformed card or smuggle markup
|
|
4
|
+
* into one. `kind` must be namespaced (contain `:`) — bare kinds are
|
|
5
|
+
* crouter's own closed vocabulary. */
|
|
6
|
+
export interface RuntimeCardRequest {
|
|
7
|
+
kind: string;
|
|
8
|
+
facts?: Record<string, string | number>;
|
|
9
|
+
/** Data, not markup. */
|
|
10
|
+
body?: string;
|
|
11
|
+
}
|
|
2
12
|
/** `POST /v1/nodes/{id}/messages` body. */
|
|
3
13
|
export interface SendMessageRequest {
|
|
4
14
|
body: string;
|
|
@@ -16,6 +26,14 @@ export interface SendMessageRequest {
|
|
|
16
26
|
* a `<situational-context>` block, never visible chat (`--situational-context`).
|
|
17
27
|
* Immediate only; valid alone (no body). */
|
|
18
28
|
situational_context?: string;
|
|
29
|
+
/** A runtime card that REPLACES the target's situational sidecar, delivered
|
|
30
|
+
* ahead of the body in the same turn and re-stated by the session-start
|
|
31
|
+
* bearings after a context refresh. Mutually exclusive with
|
|
32
|
+
* `situational_context`; valid alone (no body) on the durable path. */
|
|
33
|
+
situational_card?: RuntimeCardRequest;
|
|
34
|
+
/** One-shot runtime cards for this turn only — never persisted. Delivered in
|
|
35
|
+
* array order, after `situational_card` and ahead of the body. */
|
|
36
|
+
context_cards?: RuntimeCardRequest[];
|
|
19
37
|
/** Raw JSON-schema string granting a one-off `submit` tool before delivery
|
|
20
38
|
* (`--output-schema`). Immediate only. */
|
|
21
39
|
output_schema?: string;
|
|
@@ -24,7 +42,10 @@ export interface SendMessageRequest {
|
|
|
24
42
|
* instead of the durable inbox; a dormant or mid-revive target falls back to
|
|
25
43
|
* the durable inbox + revive (watcher delivers post-boot). Plain immediate
|
|
26
44
|
* body only — rejected with fresh/reopen/situational_context/
|
|
27
|
-
* output_schema or tier 'deferred'.
|
|
45
|
+
* output_schema or tier 'deferred'. Runtime cards ARE accepted: a
|
|
46
|
+
* card-bearing send is an ordinary human send that happens to carry context,
|
|
47
|
+
* and the live deliver frame places the cards ahead of the body in one turn.
|
|
48
|
+
* Absent → durable inbox (unchanged). */
|
|
28
49
|
delivery?: 'interactive';
|
|
29
50
|
}
|
|
30
51
|
/** Result of an immediate message send. */
|
|
@@ -71,10 +71,21 @@ export interface CardKindDefinition {
|
|
|
71
71
|
}
|
|
72
72
|
/** The closed crouter vocabulary, plus custom-role aliases for the same cards. */
|
|
73
73
|
export declare const KIND_TABLE: Readonly<Record<string, CardKindDefinition>>;
|
|
74
|
+
/** Contribute presentation for namespaced kinds this process will render.
|
|
75
|
+
* Idempotent and last-write-wins, so a re-imported module cannot fail a boot.
|
|
76
|
+
* A process that never registers is not degraded: an unregistered kind still
|
|
77
|
+
* parses and only falls back to the generic label and summary. */
|
|
78
|
+
export declare function registerCardKinds(kinds: Readonly<Record<string, CardKindDefinition>>): void;
|
|
74
79
|
/** Plain text from a generated message's string or text-block content. */
|
|
75
80
|
export declare function generatedContextText(message: GeneratedContextMessageLike): string;
|
|
76
81
|
/** Wrap a user-role runtime message in its whole-message card envelope. */
|
|
77
82
|
export declare function formatCard(kind: string, facts: Record<string, string | number | boolean | undefined>, body: string): string;
|
|
83
|
+
/** Wrap a card whose body is DATA, not markup — the only escape on the API
|
|
84
|
+
* path, so a caller can neither emit a malformed card nor smuggle markup into
|
|
85
|
+
* one. `formatCard` keeps its raw body for the trusted crouter producers that
|
|
86
|
+
* deliberately nest markup (bearings blocks, the inbox `<from>`/`<entry>`
|
|
87
|
+
* grammar `parseInboxBody` depends on). */
|
|
88
|
+
export declare function formatDataCard(kind: string, facts: Record<string, string | number | boolean | undefined>, body: string): string;
|
|
78
89
|
/** Parse a runtime envelope, with pre-envelope customType compatibility. */
|
|
79
90
|
export declare function parseCard(message: GeneratedContextMessageLike): GeneratedCard | null;
|
|
80
91
|
/** Format a complete inbox card from report bodies resolved by the caller. */
|
|
@@ -114,6 +114,21 @@ const GENERIC_CARD = {
|
|
|
114
114
|
summary: () => 'runtime context',
|
|
115
115
|
expandable: true,
|
|
116
116
|
};
|
|
117
|
+
/** Kinds contributed by a product or plugin, in whatever process renders them. */
|
|
118
|
+
const KIND_REGISTRY = {};
|
|
119
|
+
/** Contribute presentation for namespaced kinds this process will render.
|
|
120
|
+
* Idempotent and last-write-wins, so a re-imported module cannot fail a boot.
|
|
121
|
+
* A process that never registers is not degraded: an unregistered kind still
|
|
122
|
+
* parses and only falls back to the generic label and summary. */
|
|
123
|
+
export function registerCardKinds(kinds) {
|
|
124
|
+
for (const [kind, definition] of Object.entries(kinds)) {
|
|
125
|
+
// Bare kinds are crouter core's closed vocabulary. A product claiming one
|
|
126
|
+
// is a programming error at boot, not a runtime condition to tolerate.
|
|
127
|
+
if (!kind.includes(':'))
|
|
128
|
+
throw new Error(`Runtime card kind must be namespaced (contain ':'): ${kind}`);
|
|
129
|
+
KIND_REGISTRY[kind] = definition;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
117
132
|
const XML_ATTRIBUTE_NAME = /^[A-Za-z_:][A-Za-z0-9_:.-]*$/;
|
|
118
133
|
const ATTRIBUTE_RE = /\s+([A-Za-z_:][A-Za-z0-9_:.-]*)\s*=\s*(?:"([^"]*)"|'([^']*)')/gy;
|
|
119
134
|
function escapeXmlAttribute(value) {
|
|
@@ -187,6 +202,14 @@ export function formatCard(kind, facts, body) {
|
|
|
187
202
|
throw new Error('Runtime card facts cannot replace kind');
|
|
188
203
|
return `<runtime${formatAttributes({ kind, ...facts })}>${body}</runtime>`;
|
|
189
204
|
}
|
|
205
|
+
/** Wrap a card whose body is DATA, not markup — the only escape on the API
|
|
206
|
+
* path, so a caller can neither emit a malformed card nor smuggle markup into
|
|
207
|
+
* one. `formatCard` keeps its raw body for the trusted crouter producers that
|
|
208
|
+
* deliberately nest markup (bearings blocks, the inbox `<from>`/`<entry>`
|
|
209
|
+
* grammar `parseInboxBody` depends on). */
|
|
210
|
+
export function formatDataCard(kind, facts, body) {
|
|
211
|
+
return formatCard(kind, facts, escapeXmlText(body));
|
|
212
|
+
}
|
|
190
213
|
function parseInboxBody(body) {
|
|
191
214
|
const senders = [];
|
|
192
215
|
const fromRe = /<from\b([^>]*)>([\s\S]*?)<\/from>/g;
|
|
@@ -225,7 +248,7 @@ function parseInboxBody(body) {
|
|
|
225
248
|
return senders;
|
|
226
249
|
}
|
|
227
250
|
function cardFor(kind, facts, body, senders) {
|
|
228
|
-
const definition = KIND_TABLE[kind] ?? GENERIC_CARD;
|
|
251
|
+
const definition = KIND_TABLE[kind] ?? KIND_REGISTRY[kind] ?? GENERIC_CARD;
|
|
229
252
|
return {
|
|
230
253
|
kind,
|
|
231
254
|
facts,
|
|
@@ -244,9 +267,13 @@ function parseEnvelope(text) {
|
|
|
244
267
|
const attributes = parseAttributes(opening[1] ?? '');
|
|
245
268
|
if (attributes?.kind === undefined)
|
|
246
269
|
return null;
|
|
247
|
-
const
|
|
270
|
+
const raw = text.slice(opening[0].length, -'</runtime>'.length);
|
|
248
271
|
const { kind, ...factValues } = attributes;
|
|
249
272
|
const facts = Object.freeze(factValues);
|
|
273
|
+
// A namespaced kind can only have been produced through the API path, whose
|
|
274
|
+
// renderer is `formatDataCard`, so its body is escaped by construction and
|
|
275
|
+
// decodes here. A bare kind's body is raw markup written by a core producer.
|
|
276
|
+
const body = kind.includes(':') ? unescapeXmlAttribute(raw) : raw;
|
|
250
277
|
return cardFor(kind, facts, body, kind === 'inbox' ? parseInboxBody(body) : []);
|
|
251
278
|
}
|
|
252
279
|
/** Parse a runtime envelope, with pre-envelope customType compatibility. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@north-light/crouter-api",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.206",
|
|
4
4
|
"description": "Typed crtrd /v1 API contract — DTOs, route builders, the error contract, and the CrtrClient. Zero runtime dependencies.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/api/index.js",
|
|
@@ -11,6 +11,12 @@
|
|
|
11
11
|
"import": "./dist/api/index.js",
|
|
12
12
|
"require": "./dist/api/index.js",
|
|
13
13
|
"default": "./dist/api/index.js"
|
|
14
|
+
},
|
|
15
|
+
"./cards": {
|
|
16
|
+
"types": "./dist/shared/generated-context.d.ts",
|
|
17
|
+
"import": "./dist/shared/generated-context.js",
|
|
18
|
+
"require": "./dist/shared/generated-context.js",
|
|
19
|
+
"default": "./dist/shared/generated-context.js"
|
|
14
20
|
}
|
|
15
21
|
},
|
|
16
22
|
"files": [
|