@femtomc/mu-core 26.2.106 → 26.2.108

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/hud.js DELETED
@@ -1,488 +0,0 @@
1
- import { z } from "zod";
2
- export const HUD_CONTRACT_VERSION = 1;
3
- const NonEmptyTextSchema = z.string().trim().min(1);
4
- export const HudToneSchema = z.enum(["info", "success", "warning", "error", "muted", "accent", "dim"]);
5
- export const HudActionKindSchema = z.enum(["primary", "secondary", "danger"]);
6
- export const HudTextWeightSchema = z.enum(["normal", "strong"]);
7
- export const HudTextStyleSchema = z
8
- .object({
9
- weight: HudTextWeightSchema.optional(),
10
- italic: z.boolean().optional(),
11
- code: z.boolean().optional(),
12
- })
13
- .strict();
14
- export const HudChipSchema = z
15
- .object({
16
- key: NonEmptyTextSchema,
17
- label: NonEmptyTextSchema,
18
- tone: HudToneSchema.optional(),
19
- style: HudTextStyleSchema.optional(),
20
- })
21
- .strict();
22
- export const HudKvItemSchema = z
23
- .object({
24
- key: NonEmptyTextSchema,
25
- label: NonEmptyTextSchema,
26
- value: NonEmptyTextSchema,
27
- tone: HudToneSchema.optional(),
28
- value_style: HudTextStyleSchema.optional(),
29
- })
30
- .strict();
31
- export const HudChecklistItemSchema = z
32
- .object({
33
- id: NonEmptyTextSchema,
34
- label: NonEmptyTextSchema,
35
- done: z.boolean(),
36
- style: HudTextStyleSchema.optional(),
37
- })
38
- .strict();
39
- export const HudSectionSchema = z.discriminatedUnion("kind", [
40
- z
41
- .object({
42
- kind: z.literal("kv"),
43
- title: NonEmptyTextSchema.optional(),
44
- title_style: HudTextStyleSchema.optional(),
45
- items: z.array(HudKvItemSchema).default([]),
46
- })
47
- .strict(),
48
- z
49
- .object({
50
- kind: z.literal("checklist"),
51
- title: NonEmptyTextSchema.optional(),
52
- title_style: HudTextStyleSchema.optional(),
53
- items: z.array(HudChecklistItemSchema).default([]),
54
- })
55
- .strict(),
56
- z
57
- .object({
58
- kind: z.literal("activity"),
59
- title: NonEmptyTextSchema.optional(),
60
- title_style: HudTextStyleSchema.optional(),
61
- lines: z.array(NonEmptyTextSchema).default([]),
62
- })
63
- .strict(),
64
- z
65
- .object({
66
- kind: z.literal("text"),
67
- title: NonEmptyTextSchema.optional(),
68
- title_style: HudTextStyleSchema.optional(),
69
- text: NonEmptyTextSchema,
70
- tone: HudToneSchema.optional(),
71
- style: HudTextStyleSchema.optional(),
72
- })
73
- .strict(),
74
- ]);
75
- export const HudActionSchema = z
76
- .object({
77
- id: NonEmptyTextSchema,
78
- label: NonEmptyTextSchema,
79
- command_text: NonEmptyTextSchema,
80
- kind: HudActionKindSchema.optional(),
81
- style: HudTextStyleSchema.optional(),
82
- })
83
- .strict();
84
- export const HudDocSchema = z
85
- .object({
86
- v: z.literal(HUD_CONTRACT_VERSION).default(HUD_CONTRACT_VERSION),
87
- hud_id: NonEmptyTextSchema,
88
- title: NonEmptyTextSchema,
89
- title_style: HudTextStyleSchema.optional(),
90
- scope: NonEmptyTextSchema.nullable().default(null),
91
- chips: z.array(HudChipSchema).default([]),
92
- sections: z.array(HudSectionSchema).default([]),
93
- actions: z.array(HudActionSchema).default([]),
94
- snapshot_compact: NonEmptyTextSchema,
95
- snapshot_style: HudTextStyleSchema.optional(),
96
- snapshot_multiline: NonEmptyTextSchema.optional(),
97
- updated_at_ms: z.number().int().nonnegative(),
98
- metadata: z.record(z.string(), z.unknown()).default({}),
99
- })
100
- .strict();
101
- function isPlainObject(value) {
102
- return typeof value === "object" && value !== null && !Array.isArray(value);
103
- }
104
- function parseHudDocCandidate(value) {
105
- const parsed = HudDocSchema.safeParse(value);
106
- if (!parsed.success) {
107
- return null;
108
- }
109
- return parsed.data;
110
- }
111
- export const HUD_STYLE_PRESET_NAMES = ["planning", "subagents"];
112
- function isHudStylePresetName(value) {
113
- return HUD_STYLE_PRESET_NAMES.includes(value);
114
- }
115
- function hudStylePresetNameFromMetadata(metadata) {
116
- const raw = metadata.style_preset;
117
- if (typeof raw !== "string") {
118
- return null;
119
- }
120
- const normalized = raw.trim().toLowerCase();
121
- if (!normalized || !isHudStylePresetName(normalized)) {
122
- return null;
123
- }
124
- return normalized;
125
- }
126
- function mergeHudTextStyle(preferred, fallback) {
127
- if (!preferred && !fallback) {
128
- return undefined;
129
- }
130
- const merged = {
131
- ...(fallback ?? {}),
132
- ...(preferred ?? {}),
133
- };
134
- if (merged.weight === undefined && merged.italic === undefined && merged.code === undefined) {
135
- return undefined;
136
- }
137
- return merged;
138
- }
139
- function defaultChipStyle(chip) {
140
- if (chip.tone === "dim" || chip.tone === "muted") {
141
- return { weight: "normal" };
142
- }
143
- return { weight: "strong" };
144
- }
145
- function planningKvValueStyle(item) {
146
- switch (item.key) {
147
- case "root":
148
- case "next":
149
- case "next_action":
150
- return { code: true };
151
- default:
152
- return undefined;
153
- }
154
- }
155
- function applyPlanningPreset(doc) {
156
- return {
157
- ...doc,
158
- title_style: mergeHudTextStyle(doc.title_style, { weight: "strong" }),
159
- snapshot_style: mergeHudTextStyle(doc.snapshot_style, { italic: true }),
160
- chips: doc.chips.map((chip) => ({
161
- ...chip,
162
- style: mergeHudTextStyle(chip.style, defaultChipStyle(chip)),
163
- })),
164
- sections: doc.sections.map((section) => {
165
- switch (section.kind) {
166
- case "kv":
167
- return {
168
- ...section,
169
- title_style: mergeHudTextStyle(section.title_style, { weight: "strong" }),
170
- items: section.items.map((item) => ({
171
- ...item,
172
- value_style: mergeHudTextStyle(item.value_style, planningKvValueStyle(item)),
173
- })),
174
- };
175
- case "checklist":
176
- return {
177
- ...section,
178
- title_style: mergeHudTextStyle(section.title_style, { weight: "strong" }),
179
- };
180
- case "activity":
181
- return {
182
- ...section,
183
- title_style: mergeHudTextStyle(section.title_style, { weight: "strong" }),
184
- };
185
- case "text":
186
- return {
187
- ...section,
188
- title_style: mergeHudTextStyle(section.title_style, { weight: "strong" }),
189
- };
190
- }
191
- }),
192
- actions: doc.actions.map((action) => ({
193
- ...action,
194
- style: mergeHudTextStyle(action.style, { italic: true }),
195
- })),
196
- };
197
- }
198
- function applySubagentsPreset(doc) {
199
- return {
200
- ...doc,
201
- title_style: mergeHudTextStyle(doc.title_style, { weight: "strong" }),
202
- snapshot_style: mergeHudTextStyle(doc.snapshot_style, { italic: true }),
203
- chips: doc.chips.map((chip) => ({
204
- ...chip,
205
- style: mergeHudTextStyle(chip.style, defaultChipStyle(chip)),
206
- })),
207
- sections: doc.sections.map((section) => {
208
- switch (section.kind) {
209
- case "kv":
210
- return {
211
- ...section,
212
- title_style: mergeHudTextStyle(section.title_style, { weight: "strong" }),
213
- };
214
- case "checklist":
215
- return {
216
- ...section,
217
- title_style: mergeHudTextStyle(section.title_style, { weight: "strong" }),
218
- };
219
- case "activity":
220
- return {
221
- ...section,
222
- title_style: mergeHudTextStyle(section.title_style, { weight: "strong" }),
223
- };
224
- case "text":
225
- return {
226
- ...section,
227
- title_style: mergeHudTextStyle(section.title_style, { weight: "strong" }),
228
- };
229
- }
230
- }),
231
- actions: doc.actions.map((action) => ({
232
- ...action,
233
- style: mergeHudTextStyle(action.style, { weight: "strong" }),
234
- })),
235
- };
236
- }
237
- function hasChipKey(doc, key) {
238
- return doc.chips.some((chip) => chip.key === key);
239
- }
240
- function hasSectionKind(doc, kind) {
241
- return doc.sections.some((section) => section.kind === kind);
242
- }
243
- function hudStylePresetWarningsForDoc(doc, preset) {
244
- const warnings = [];
245
- switch (preset) {
246
- case "planning":
247
- if (doc.hud_id !== "planning") {
248
- warnings.push(`style_preset=planning expects hud_id=planning (got ${doc.hud_id})`);
249
- }
250
- if (!hasChipKey(doc, "phase")) {
251
- warnings.push("style_preset=planning recommends chip key 'phase'");
252
- }
253
- if (!hasSectionKind(doc, "kv")) {
254
- warnings.push("style_preset=planning recommends a kv section");
255
- }
256
- if (!hasSectionKind(doc, "checklist")) {
257
- warnings.push("style_preset=planning recommends a checklist section");
258
- }
259
- break;
260
- case "subagents":
261
- if (doc.hud_id !== "subagents") {
262
- warnings.push(`style_preset=subagents expects hud_id=subagents (got ${doc.hud_id})`);
263
- }
264
- if (!hasChipKey(doc, "health")) {
265
- warnings.push("style_preset=subagents recommends chip key 'health'");
266
- }
267
- if (!hasSectionKind(doc, "kv")) {
268
- warnings.push("style_preset=subagents recommends a kv section");
269
- }
270
- if (!hasSectionKind(doc, "activity")) {
271
- warnings.push("style_preset=subagents recommends an activity section");
272
- }
273
- break;
274
- }
275
- return warnings;
276
- }
277
- export function hudStylePresetWarnings(input) {
278
- const doc = parseHudDocCandidate(input);
279
- if (!doc) {
280
- return [];
281
- }
282
- const preset = hudStylePresetNameFromMetadata(doc.metadata);
283
- if (!preset) {
284
- return [];
285
- }
286
- return hudStylePresetWarningsForDoc(doc, preset);
287
- }
288
- export function resolveHudStylePresetName(input) {
289
- const doc = parseHudDocCandidate(input);
290
- if (!doc) {
291
- return null;
292
- }
293
- return hudStylePresetNameFromMetadata(doc.metadata);
294
- }
295
- export function applyHudStylePreset(input) {
296
- const doc = parseHudDocCandidate(input);
297
- if (!doc) {
298
- return null;
299
- }
300
- const preset = hudStylePresetNameFromMetadata(doc.metadata);
301
- if (!preset) {
302
- return doc;
303
- }
304
- switch (preset) {
305
- case "planning":
306
- return applyPlanningPreset(doc);
307
- case "subagents":
308
- return applySubagentsPreset(doc);
309
- }
310
- }
311
- function deterministicHudDocChoice(a, b) {
312
- if (a.updated_at_ms !== b.updated_at_ms) {
313
- return a.updated_at_ms > b.updated_at_ms ? a : b;
314
- }
315
- const left = stableSerializeJson(a);
316
- const right = stableSerializeJson(b);
317
- return left <= right ? a : b;
318
- }
319
- function hudDocCandidates(input) {
320
- if (Array.isArray(input)) {
321
- return input;
322
- }
323
- if (isPlainObject(input)) {
324
- return [input];
325
- }
326
- return [];
327
- }
328
- function normalizedHudDocLimit(limit) {
329
- if (typeof limit !== "number" || !Number.isFinite(limit)) {
330
- return 12;
331
- }
332
- const parsed = Math.trunc(limit);
333
- if (parsed < 1) {
334
- return 1;
335
- }
336
- if (parsed > 64) {
337
- return 64;
338
- }
339
- return parsed;
340
- }
341
- function canonicalizeJson(value) {
342
- if (Array.isArray(value)) {
343
- return value.map((entry) => canonicalizeJson(entry));
344
- }
345
- if (!isPlainObject(value)) {
346
- return value;
347
- }
348
- const out = {};
349
- for (const key of Object.keys(value).sort()) {
350
- const nextValue = value[key];
351
- if (nextValue === undefined) {
352
- continue;
353
- }
354
- out[key] = canonicalizeJson(nextValue);
355
- }
356
- return out;
357
- }
358
- export function stableSerializeJson(value, opts = {}) {
359
- const normalized = canonicalizeJson(value);
360
- const text = JSON.stringify(normalized, null, opts.pretty ? 2 : undefined);
361
- return text ?? "null";
362
- }
363
- function truncateText(value, maxChars) {
364
- if (maxChars <= 0) {
365
- return "";
366
- }
367
- if (value.length <= maxChars) {
368
- return value;
369
- }
370
- if (maxChars <= 1) {
371
- return "…";
372
- }
373
- return `${value.slice(0, maxChars - 1)}…`;
374
- }
375
- function appendOverflowLine(lines, hiddenCount) {
376
- if (hiddenCount > 0) {
377
- lines.push(`… (+${hiddenCount} more)`);
378
- }
379
- }
380
- export function serializeHudDocTextFallback(input, opts = {}) {
381
- const doc = parseHudDocCandidate(input);
382
- if (!doc) {
383
- return "";
384
- }
385
- const mode = opts.mode ?? "multiline";
386
- const maxChars = typeof opts.maxChars === "number" && Number.isFinite(opts.maxChars) ? Math.max(32, Math.trunc(opts.maxChars)) : 8_192;
387
- if (mode === "compact") {
388
- const compact = `${doc.title} · ${doc.snapshot_compact}`;
389
- return truncateText(compact, maxChars);
390
- }
391
- const maxSectionItems = typeof opts.maxSectionItems === "number" && Number.isFinite(opts.maxSectionItems)
392
- ? Math.max(1, Math.trunc(opts.maxSectionItems))
393
- : 8;
394
- const maxActions = typeof opts.maxActions === "number" && Number.isFinite(opts.maxActions) ? Math.max(1, Math.trunc(opts.maxActions)) : 4;
395
- const lines = [];
396
- lines.push(`${doc.title} [${doc.hud_id}]`);
397
- if (doc.scope) {
398
- lines.push(`scope: ${doc.scope}`);
399
- }
400
- if (doc.chips.length > 0) {
401
- lines.push(`chips: ${doc.chips.map((chip) => chip.label).join(" · ")}`);
402
- }
403
- for (const section of doc.sections) {
404
- const title = section.title ? ` (${section.title})` : "";
405
- switch (section.kind) {
406
- case "kv": {
407
- lines.push(`section: kv${title}`);
408
- const visible = section.items.slice(0, maxSectionItems);
409
- for (const item of visible) {
410
- lines.push(`- ${item.label}: ${item.value}`);
411
- }
412
- appendOverflowLine(lines, section.items.length - visible.length);
413
- break;
414
- }
415
- case "checklist": {
416
- lines.push(`section: checklist${title}`);
417
- const visible = section.items.slice(0, maxSectionItems);
418
- for (const item of visible) {
419
- lines.push(`- [${item.done ? "x" : " "}] ${item.label}`);
420
- }
421
- appendOverflowLine(lines, section.items.length - visible.length);
422
- break;
423
- }
424
- case "activity": {
425
- lines.push(`section: activity${title}`);
426
- const visible = section.lines.slice(0, maxSectionItems);
427
- for (const line of visible) {
428
- lines.push(`- ${line}`);
429
- }
430
- appendOverflowLine(lines, section.lines.length - visible.length);
431
- break;
432
- }
433
- case "text":
434
- lines.push(`section: text${title}`);
435
- lines.push(`- ${section.text}`);
436
- break;
437
- }
438
- }
439
- if (doc.actions.length > 0) {
440
- lines.push("actions:");
441
- const visible = doc.actions.slice(0, maxActions);
442
- for (const action of visible) {
443
- lines.push(`- ${action.label}: ${action.command_text}`);
444
- }
445
- appendOverflowLine(lines, doc.actions.length - visible.length);
446
- }
447
- return truncateText(lines.join("\n"), maxChars);
448
- }
449
- export function serializeHudDocsTextFallback(input, opts = {}) {
450
- const docs = normalizeHudDocs(input, { maxDocs: opts.maxDocs });
451
- if (docs.length === 0) {
452
- return "";
453
- }
454
- const mode = opts.mode ?? "multiline";
455
- const rendered = docs
456
- .map((doc) => serializeHudDocTextFallback(doc, {
457
- mode,
458
- maxChars: opts.maxChars,
459
- maxSectionItems: opts.maxSectionItems,
460
- maxActions: opts.maxActions,
461
- }))
462
- .filter((value) => value.length > 0);
463
- return rendered.join(mode === "compact" ? " | " : "\n\n");
464
- }
465
- export function parseHudDoc(input) {
466
- return parseHudDocCandidate(input);
467
- }
468
- export function normalizeHudDocs(input, opts = {}) {
469
- const maxDocs = normalizedHudDocLimit(opts.maxDocs);
470
- const byId = new Map();
471
- for (const candidate of hudDocCandidates(input)) {
472
- const parsed = parseHudDocCandidate(candidate);
473
- if (!parsed) {
474
- continue;
475
- }
476
- const current = byId.get(parsed.hud_id);
477
- if (!current) {
478
- byId.set(parsed.hud_id, parsed);
479
- continue;
480
- }
481
- byId.set(parsed.hud_id, deterministicHudDocChoice(current, parsed));
482
- }
483
- const docs = [...byId.values()].sort((left, right) => left.hud_id.localeCompare(right.hud_id));
484
- if (docs.length <= maxDocs) {
485
- return docs;
486
- }
487
- return docs.slice(0, maxDocs);
488
- }
@@ -1,36 +0,0 @@
1
- import { type HudDoc } from "./hud.js";
2
- export type HudProviderRuntimeApi<Msg> = {
3
- dispatch: (message: Msg) => void;
4
- };
5
- export type HudProviderReducerResult<State, Effect> = {
6
- state: State;
7
- effects?: Effect[];
8
- };
9
- export type HudProvider<State, Msg, Effect> = {
10
- id: string;
11
- initialState: () => State;
12
- reduce: (state: State, message: Msg) => HudProviderReducerResult<State, Effect>;
13
- runEffect?: (effect: Effect, api: HudProviderRuntimeApi<Msg>) => void | Promise<void>;
14
- view: (state: State) => HudDoc | HudDoc[] | null;
15
- };
16
- export type HudRuntimeSnapshot = {
17
- provider_id: string;
18
- hud_docs: HudDoc[];
19
- };
20
- export type HudRuntimeListener = (snapshot: HudRuntimeSnapshot) => void;
21
- export type HudRuntimeDispatchResult = {
22
- provider_id: string;
23
- messages_processed: number;
24
- effects_processed: number;
25
- hud_docs: HudDoc[];
26
- };
27
- export declare class HudRuntime {
28
- #private;
29
- register<State, Msg, Effect>(provider: HudProvider<State, Msg, Effect>): void;
30
- unregister(providerId: string): boolean;
31
- listProviders(): string[];
32
- subscribe(listener: HudRuntimeListener): () => void;
33
- snapshot(providerId: string): HudRuntimeSnapshot;
34
- dispatch<Msg>(providerId: string, message: Msg): Promise<HudRuntimeDispatchResult>;
35
- }
36
- //# sourceMappingURL=hud_runtime.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"hud_runtime.d.ts","sourceRoot":"","sources":["../src/hud_runtime.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,MAAM,EAAoB,MAAM,UAAU,CAAC;AAEzD,MAAM,MAAM,qBAAqB,CAAC,GAAG,IAAI;IACxC,QAAQ,EAAE,CAAC,OAAO,EAAE,GAAG,KAAK,IAAI,CAAC;CACjC,CAAC;AAEF,MAAM,MAAM,wBAAwB,CAAC,KAAK,EAAE,MAAM,IAAI;IACrD,KAAK,EAAE,KAAK,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,WAAW,CAAC,KAAK,EAAE,GAAG,EAAE,MAAM,IAAI;IAC7C,EAAE,EAAE,MAAM,CAAC;IACX,YAAY,EAAE,MAAM,KAAK,CAAC;IAC1B,MAAM,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,KAAK,wBAAwB,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IAChF,SAAS,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,qBAAqB,CAAC,GAAG,CAAC,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACtF,IAAI,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,MAAM,GAAG,MAAM,EAAE,GAAG,IAAI,CAAC;CACjD,CAAC;AASF,MAAM,MAAM,kBAAkB,GAAG;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG,CAAC,QAAQ,EAAE,kBAAkB,KAAK,IAAI,CAAC;AAExE,MAAM,MAAM,wBAAwB,GAAG;IACtC,WAAW,EAAE,MAAM,CAAC;IACpB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,QAAQ,EAAE,MAAM,EAAE,CAAC;CACnB,CAAC;AAEF,qBAAa,UAAU;;IAIf,QAAQ,CAAC,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,WAAW,CAAC,KAAK,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,IAAI;IAe7E,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO;IAQvC,aAAa,IAAI,MAAM,EAAE;IAIzB,SAAS,CAAC,QAAQ,EAAE,kBAAkB,GAAG,MAAM,IAAI;IAOnD,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,kBAAkB;IAS1C,QAAQ,CAAC,GAAG,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,GAAG,OAAO,CAAC,wBAAwB,CAAC;CAmE/F"}
@@ -1,105 +0,0 @@
1
- import { normalizeHudDocs } from "./hud.js";
2
- export class HudRuntime {
3
- #providers = new Map();
4
- #listeners = new Set();
5
- register(provider) {
6
- const providerId = provider.id.trim();
7
- if (providerId.length === 0) {
8
- throw new Error("provider id must be non-empty");
9
- }
10
- if (this.#providers.has(providerId)) {
11
- throw new Error(`provider already registered: ${providerId}`);
12
- }
13
- this.#providers.set(providerId, {
14
- provider: provider,
15
- state: provider.initialState(),
16
- });
17
- this.#emit(providerId);
18
- }
19
- unregister(providerId) {
20
- const normalized = providerId.trim();
21
- if (normalized.length === 0) {
22
- return false;
23
- }
24
- return this.#providers.delete(normalized);
25
- }
26
- listProviders() {
27
- return [...this.#providers.keys()].sort((a, b) => a.localeCompare(b));
28
- }
29
- subscribe(listener) {
30
- this.#listeners.add(listener);
31
- return () => {
32
- this.#listeners.delete(listener);
33
- };
34
- }
35
- snapshot(providerId) {
36
- const record = this.#provider(providerId);
37
- const hudDocs = this.#hudDocs(record.provider, record.state);
38
- return {
39
- provider_id: providerId,
40
- hud_docs: hudDocs,
41
- };
42
- }
43
- async dispatch(providerId, message) {
44
- const record = this.#provider(providerId);
45
- const provider = record.provider;
46
- const messageQueue = [message];
47
- let messagesProcessed = 0;
48
- let effectsProcessed = 0;
49
- while (messageQueue.length > 0) {
50
- const currentMessage = messageQueue.shift();
51
- messagesProcessed += 1;
52
- const reduced = provider.reduce(record.state, currentMessage);
53
- record.state = reduced.state;
54
- const effects = Array.isArray(reduced.effects) ? reduced.effects : [];
55
- for (const effect of effects) {
56
- effectsProcessed += 1;
57
- if (!provider.runEffect) {
58
- continue;
59
- }
60
- const api = {
61
- dispatch: (nextMessage) => {
62
- messageQueue.push(nextMessage);
63
- },
64
- };
65
- await provider.runEffect(effect, api);
66
- }
67
- }
68
- const snapshot = this.#emit(providerId);
69
- return {
70
- provider_id: providerId,
71
- messages_processed: messagesProcessed,
72
- effects_processed: effectsProcessed,
73
- hud_docs: snapshot.hud_docs,
74
- };
75
- }
76
- #provider(providerId) {
77
- const normalized = providerId.trim();
78
- if (normalized.length === 0) {
79
- throw new Error("provider id must be non-empty");
80
- }
81
- const record = this.#providers.get(normalized);
82
- if (!record) {
83
- throw new Error(`unknown provider: ${normalized}`);
84
- }
85
- return record;
86
- }
87
- #hudDocs(provider, state) {
88
- const viewed = provider.view(state);
89
- if (viewed == null) {
90
- return [];
91
- }
92
- return normalizeHudDocs(Array.isArray(viewed) ? viewed : [viewed]);
93
- }
94
- #emit(providerId) {
95
- const record = this.#provider(providerId);
96
- const snapshot = {
97
- provider_id: providerId,
98
- hud_docs: this.#hudDocs(record.provider, record.state),
99
- };
100
- for (const listener of this.#listeners) {
101
- listener(snapshot);
102
- }
103
- return snapshot;
104
- }
105
- }