@femtomc/mu-core 26.2.99 → 26.2.101
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 +12 -0
- package/dist/hud.d.ts +207 -0
- package/dist/hud.d.ts.map +1 -0
- package/dist/hud.js +269 -0
- package/dist/hud_runtime.d.ts +36 -0
- package/dist/hud_runtime.d.ts.map +1 -0
- package/dist/hud_runtime.js +105 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -34,6 +34,18 @@ await runContext({ runId: newRunId() }, async () => {
|
|
|
34
34
|
console.log(await jsonl.read());
|
|
35
35
|
```
|
|
36
36
|
|
|
37
|
+
## HUD contract helpers
|
|
38
|
+
|
|
39
|
+
`@femtomc/mu-core` exports a versioned HUD contract, deterministic JSON helper, and shared runtime loop:
|
|
40
|
+
|
|
41
|
+
- `HudDocSchema`, `HUD_CONTRACT_VERSION`
|
|
42
|
+
- `parseHudDoc(...)`, `normalizeHudDocs(...)`
|
|
43
|
+
- `serializeHudDocTextFallback(...)`, `serializeHudDocsTextFallback(...)`
|
|
44
|
+
- `stableSerializeJson(...)`
|
|
45
|
+
- `HudRuntime` + `HudProvider` for provider registration, reducer updates, ordered effect execution, and HUD snapshot emission
|
|
46
|
+
|
|
47
|
+
These are runtime-agnostic primitives intended for shared HUD capture/render pipelines.
|
|
48
|
+
|
|
37
49
|
## Tests / Typecheck
|
|
38
50
|
|
|
39
51
|
From the `mu/` repo root:
|
package/dist/hud.d.ts
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export declare const HUD_CONTRACT_VERSION = 1;
|
|
3
|
+
export declare const HudToneSchema: z.ZodEnum<{
|
|
4
|
+
error: "error";
|
|
5
|
+
info: "info";
|
|
6
|
+
success: "success";
|
|
7
|
+
warning: "warning";
|
|
8
|
+
muted: "muted";
|
|
9
|
+
accent: "accent";
|
|
10
|
+
dim: "dim";
|
|
11
|
+
}>;
|
|
12
|
+
export type HudTone = z.infer<typeof HudToneSchema>;
|
|
13
|
+
export declare const HudActionKindSchema: z.ZodEnum<{
|
|
14
|
+
primary: "primary";
|
|
15
|
+
secondary: "secondary";
|
|
16
|
+
danger: "danger";
|
|
17
|
+
}>;
|
|
18
|
+
export type HudActionKind = z.infer<typeof HudActionKindSchema>;
|
|
19
|
+
export declare const HudChipSchema: z.ZodObject<{
|
|
20
|
+
key: z.ZodString;
|
|
21
|
+
label: z.ZodString;
|
|
22
|
+
tone: z.ZodOptional<z.ZodEnum<{
|
|
23
|
+
error: "error";
|
|
24
|
+
info: "info";
|
|
25
|
+
success: "success";
|
|
26
|
+
warning: "warning";
|
|
27
|
+
muted: "muted";
|
|
28
|
+
accent: "accent";
|
|
29
|
+
dim: "dim";
|
|
30
|
+
}>>;
|
|
31
|
+
}, z.core.$strict>;
|
|
32
|
+
export type HudChip = z.infer<typeof HudChipSchema>;
|
|
33
|
+
export declare const HudKvItemSchema: z.ZodObject<{
|
|
34
|
+
key: z.ZodString;
|
|
35
|
+
label: z.ZodString;
|
|
36
|
+
value: z.ZodString;
|
|
37
|
+
tone: z.ZodOptional<z.ZodEnum<{
|
|
38
|
+
error: "error";
|
|
39
|
+
info: "info";
|
|
40
|
+
success: "success";
|
|
41
|
+
warning: "warning";
|
|
42
|
+
muted: "muted";
|
|
43
|
+
accent: "accent";
|
|
44
|
+
dim: "dim";
|
|
45
|
+
}>>;
|
|
46
|
+
}, z.core.$strict>;
|
|
47
|
+
export type HudKvItem = z.infer<typeof HudKvItemSchema>;
|
|
48
|
+
export declare const HudChecklistItemSchema: z.ZodObject<{
|
|
49
|
+
id: z.ZodString;
|
|
50
|
+
label: z.ZodString;
|
|
51
|
+
done: z.ZodBoolean;
|
|
52
|
+
}, z.core.$strict>;
|
|
53
|
+
export type HudChecklistItem = z.infer<typeof HudChecklistItemSchema>;
|
|
54
|
+
export declare const HudSectionSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
55
|
+
kind: z.ZodLiteral<"kv">;
|
|
56
|
+
title: z.ZodOptional<z.ZodString>;
|
|
57
|
+
items: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
58
|
+
key: z.ZodString;
|
|
59
|
+
label: z.ZodString;
|
|
60
|
+
value: z.ZodString;
|
|
61
|
+
tone: z.ZodOptional<z.ZodEnum<{
|
|
62
|
+
error: "error";
|
|
63
|
+
info: "info";
|
|
64
|
+
success: "success";
|
|
65
|
+
warning: "warning";
|
|
66
|
+
muted: "muted";
|
|
67
|
+
accent: "accent";
|
|
68
|
+
dim: "dim";
|
|
69
|
+
}>>;
|
|
70
|
+
}, z.core.$strict>>>;
|
|
71
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
72
|
+
kind: z.ZodLiteral<"checklist">;
|
|
73
|
+
title: z.ZodOptional<z.ZodString>;
|
|
74
|
+
items: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
75
|
+
id: z.ZodString;
|
|
76
|
+
label: z.ZodString;
|
|
77
|
+
done: z.ZodBoolean;
|
|
78
|
+
}, z.core.$strict>>>;
|
|
79
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
80
|
+
kind: z.ZodLiteral<"activity">;
|
|
81
|
+
title: z.ZodOptional<z.ZodString>;
|
|
82
|
+
lines: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
83
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
84
|
+
kind: z.ZodLiteral<"text">;
|
|
85
|
+
title: z.ZodOptional<z.ZodString>;
|
|
86
|
+
text: z.ZodString;
|
|
87
|
+
tone: z.ZodOptional<z.ZodEnum<{
|
|
88
|
+
error: "error";
|
|
89
|
+
info: "info";
|
|
90
|
+
success: "success";
|
|
91
|
+
warning: "warning";
|
|
92
|
+
muted: "muted";
|
|
93
|
+
accent: "accent";
|
|
94
|
+
dim: "dim";
|
|
95
|
+
}>>;
|
|
96
|
+
}, z.core.$strict>], "kind">;
|
|
97
|
+
export type HudSection = z.infer<typeof HudSectionSchema>;
|
|
98
|
+
export declare const HudActionSchema: z.ZodObject<{
|
|
99
|
+
id: z.ZodString;
|
|
100
|
+
label: z.ZodString;
|
|
101
|
+
command_text: z.ZodString;
|
|
102
|
+
kind: z.ZodOptional<z.ZodEnum<{
|
|
103
|
+
primary: "primary";
|
|
104
|
+
secondary: "secondary";
|
|
105
|
+
danger: "danger";
|
|
106
|
+
}>>;
|
|
107
|
+
}, z.core.$strict>;
|
|
108
|
+
export type HudAction = z.infer<typeof HudActionSchema>;
|
|
109
|
+
export declare const HudDocSchema: z.ZodObject<{
|
|
110
|
+
v: z.ZodDefault<z.ZodLiteral<1>>;
|
|
111
|
+
hud_id: z.ZodString;
|
|
112
|
+
title: z.ZodString;
|
|
113
|
+
scope: z.ZodDefault<z.ZodNullable<z.ZodString>>;
|
|
114
|
+
chips: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
115
|
+
key: z.ZodString;
|
|
116
|
+
label: z.ZodString;
|
|
117
|
+
tone: z.ZodOptional<z.ZodEnum<{
|
|
118
|
+
error: "error";
|
|
119
|
+
info: "info";
|
|
120
|
+
success: "success";
|
|
121
|
+
warning: "warning";
|
|
122
|
+
muted: "muted";
|
|
123
|
+
accent: "accent";
|
|
124
|
+
dim: "dim";
|
|
125
|
+
}>>;
|
|
126
|
+
}, z.core.$strict>>>;
|
|
127
|
+
sections: z.ZodDefault<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
128
|
+
kind: z.ZodLiteral<"kv">;
|
|
129
|
+
title: z.ZodOptional<z.ZodString>;
|
|
130
|
+
items: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
131
|
+
key: z.ZodString;
|
|
132
|
+
label: z.ZodString;
|
|
133
|
+
value: z.ZodString;
|
|
134
|
+
tone: z.ZodOptional<z.ZodEnum<{
|
|
135
|
+
error: "error";
|
|
136
|
+
info: "info";
|
|
137
|
+
success: "success";
|
|
138
|
+
warning: "warning";
|
|
139
|
+
muted: "muted";
|
|
140
|
+
accent: "accent";
|
|
141
|
+
dim: "dim";
|
|
142
|
+
}>>;
|
|
143
|
+
}, z.core.$strict>>>;
|
|
144
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
145
|
+
kind: z.ZodLiteral<"checklist">;
|
|
146
|
+
title: z.ZodOptional<z.ZodString>;
|
|
147
|
+
items: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
148
|
+
id: z.ZodString;
|
|
149
|
+
label: z.ZodString;
|
|
150
|
+
done: z.ZodBoolean;
|
|
151
|
+
}, z.core.$strict>>>;
|
|
152
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
153
|
+
kind: z.ZodLiteral<"activity">;
|
|
154
|
+
title: z.ZodOptional<z.ZodString>;
|
|
155
|
+
lines: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
156
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
157
|
+
kind: z.ZodLiteral<"text">;
|
|
158
|
+
title: z.ZodOptional<z.ZodString>;
|
|
159
|
+
text: z.ZodString;
|
|
160
|
+
tone: z.ZodOptional<z.ZodEnum<{
|
|
161
|
+
error: "error";
|
|
162
|
+
info: "info";
|
|
163
|
+
success: "success";
|
|
164
|
+
warning: "warning";
|
|
165
|
+
muted: "muted";
|
|
166
|
+
accent: "accent";
|
|
167
|
+
dim: "dim";
|
|
168
|
+
}>>;
|
|
169
|
+
}, z.core.$strict>], "kind">>>;
|
|
170
|
+
actions: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
171
|
+
id: z.ZodString;
|
|
172
|
+
label: z.ZodString;
|
|
173
|
+
command_text: z.ZodString;
|
|
174
|
+
kind: z.ZodOptional<z.ZodEnum<{
|
|
175
|
+
primary: "primary";
|
|
176
|
+
secondary: "secondary";
|
|
177
|
+
danger: "danger";
|
|
178
|
+
}>>;
|
|
179
|
+
}, z.core.$strict>>>;
|
|
180
|
+
snapshot_compact: z.ZodString;
|
|
181
|
+
snapshot_multiline: z.ZodOptional<z.ZodString>;
|
|
182
|
+
updated_at_ms: z.ZodNumber;
|
|
183
|
+
metadata: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
184
|
+
}, z.core.$strict>;
|
|
185
|
+
export type HudDoc = z.infer<typeof HudDocSchema>;
|
|
186
|
+
export declare function stableSerializeJson(value: unknown, opts?: {
|
|
187
|
+
pretty?: boolean;
|
|
188
|
+
}): string;
|
|
189
|
+
export type HudTextFallbackMode = "compact" | "multiline";
|
|
190
|
+
export declare function serializeHudDocTextFallback(input: unknown, opts?: {
|
|
191
|
+
mode?: HudTextFallbackMode;
|
|
192
|
+
maxChars?: number;
|
|
193
|
+
maxSectionItems?: number;
|
|
194
|
+
maxActions?: number;
|
|
195
|
+
}): string;
|
|
196
|
+
export declare function serializeHudDocsTextFallback(input: unknown, opts?: {
|
|
197
|
+
mode?: HudTextFallbackMode;
|
|
198
|
+
maxChars?: number;
|
|
199
|
+
maxDocs?: number;
|
|
200
|
+
maxSectionItems?: number;
|
|
201
|
+
maxActions?: number;
|
|
202
|
+
}): string;
|
|
203
|
+
export declare function parseHudDoc(input: unknown): HudDoc | null;
|
|
204
|
+
export declare function normalizeHudDocs(input: unknown, opts?: {
|
|
205
|
+
maxDocs?: number;
|
|
206
|
+
}): HudDoc[];
|
|
207
|
+
//# sourceMappingURL=hud.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hud.d.ts","sourceRoot":"","sources":["../src/hud.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,eAAO,MAAM,oBAAoB,IAAI,CAAC;AAItC,eAAO,MAAM,aAAa;;;;;;;;EAA4E,CAAC;AACvG,MAAM,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;AAEpD,eAAO,MAAM,mBAAmB;;;;EAA6C,CAAC;AAC9E,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEhE,eAAO,MAAM,aAAa;;;;;;;;;;;;kBAMhB,CAAC;AACX,MAAM,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;AAEpD,eAAO,MAAM,eAAe;;;;;;;;;;;;;kBAOlB,CAAC;AACX,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAExD,eAAO,MAAM,sBAAsB;;;;kBAMzB,CAAC;AACX,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAEtE,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;4BA8B3B,CAAC;AACH,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAE1D,eAAO,MAAM,eAAe;;;;;;;;;kBAOlB,CAAC;AACX,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAExD,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAcf,CAAC;AACX,MAAM,MAAM,MAAM,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC;AAiElD,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,GAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,MAAM,CAI3F;AAED,MAAM,MAAM,mBAAmB,GAAG,SAAS,GAAG,WAAW,CAAC;AAqB1D,wBAAgB,2BAA2B,CAC1C,KAAK,EAAE,OAAO,EACd,IAAI,GAAE;IACL,IAAI,CAAC,EAAE,mBAAmB,CAAC;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC;CACf,GACJ,MAAM,CAwER;AAED,wBAAgB,4BAA4B,CAC3C,KAAK,EAAE,OAAO,EACd,IAAI,GAAE;IACL,IAAI,CAAC,EAAE,mBAAmB,CAAC;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC;CACf,GACJ,MAAM,CAiBR;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAEzD;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,GAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,MAAM,EAAE,CAoB1F"}
|
package/dist/hud.js
ADDED
|
@@ -0,0 +1,269 @@
|
|
|
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 HudChipSchema = z
|
|
7
|
+
.object({
|
|
8
|
+
key: NonEmptyTextSchema,
|
|
9
|
+
label: NonEmptyTextSchema,
|
|
10
|
+
tone: HudToneSchema.optional(),
|
|
11
|
+
})
|
|
12
|
+
.strict();
|
|
13
|
+
export const HudKvItemSchema = z
|
|
14
|
+
.object({
|
|
15
|
+
key: NonEmptyTextSchema,
|
|
16
|
+
label: NonEmptyTextSchema,
|
|
17
|
+
value: NonEmptyTextSchema,
|
|
18
|
+
tone: HudToneSchema.optional(),
|
|
19
|
+
})
|
|
20
|
+
.strict();
|
|
21
|
+
export const HudChecklistItemSchema = z
|
|
22
|
+
.object({
|
|
23
|
+
id: NonEmptyTextSchema,
|
|
24
|
+
label: NonEmptyTextSchema,
|
|
25
|
+
done: z.boolean(),
|
|
26
|
+
})
|
|
27
|
+
.strict();
|
|
28
|
+
export const HudSectionSchema = z.discriminatedUnion("kind", [
|
|
29
|
+
z
|
|
30
|
+
.object({
|
|
31
|
+
kind: z.literal("kv"),
|
|
32
|
+
title: NonEmptyTextSchema.optional(),
|
|
33
|
+
items: z.array(HudKvItemSchema).default([]),
|
|
34
|
+
})
|
|
35
|
+
.strict(),
|
|
36
|
+
z
|
|
37
|
+
.object({
|
|
38
|
+
kind: z.literal("checklist"),
|
|
39
|
+
title: NonEmptyTextSchema.optional(),
|
|
40
|
+
items: z.array(HudChecklistItemSchema).default([]),
|
|
41
|
+
})
|
|
42
|
+
.strict(),
|
|
43
|
+
z
|
|
44
|
+
.object({
|
|
45
|
+
kind: z.literal("activity"),
|
|
46
|
+
title: NonEmptyTextSchema.optional(),
|
|
47
|
+
lines: z.array(NonEmptyTextSchema).default([]),
|
|
48
|
+
})
|
|
49
|
+
.strict(),
|
|
50
|
+
z
|
|
51
|
+
.object({
|
|
52
|
+
kind: z.literal("text"),
|
|
53
|
+
title: NonEmptyTextSchema.optional(),
|
|
54
|
+
text: NonEmptyTextSchema,
|
|
55
|
+
tone: HudToneSchema.optional(),
|
|
56
|
+
})
|
|
57
|
+
.strict(),
|
|
58
|
+
]);
|
|
59
|
+
export const HudActionSchema = z
|
|
60
|
+
.object({
|
|
61
|
+
id: NonEmptyTextSchema,
|
|
62
|
+
label: NonEmptyTextSchema,
|
|
63
|
+
command_text: NonEmptyTextSchema,
|
|
64
|
+
kind: HudActionKindSchema.optional(),
|
|
65
|
+
})
|
|
66
|
+
.strict();
|
|
67
|
+
export const HudDocSchema = z
|
|
68
|
+
.object({
|
|
69
|
+
v: z.literal(HUD_CONTRACT_VERSION).default(HUD_CONTRACT_VERSION),
|
|
70
|
+
hud_id: NonEmptyTextSchema,
|
|
71
|
+
title: NonEmptyTextSchema,
|
|
72
|
+
scope: NonEmptyTextSchema.nullable().default(null),
|
|
73
|
+
chips: z.array(HudChipSchema).default([]),
|
|
74
|
+
sections: z.array(HudSectionSchema).default([]),
|
|
75
|
+
actions: z.array(HudActionSchema).default([]),
|
|
76
|
+
snapshot_compact: NonEmptyTextSchema,
|
|
77
|
+
snapshot_multiline: NonEmptyTextSchema.optional(),
|
|
78
|
+
updated_at_ms: z.number().int().nonnegative(),
|
|
79
|
+
metadata: z.record(z.string(), z.unknown()).default({}),
|
|
80
|
+
})
|
|
81
|
+
.strict();
|
|
82
|
+
function isPlainObject(value) {
|
|
83
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
84
|
+
}
|
|
85
|
+
function parseHudDocCandidate(value) {
|
|
86
|
+
const parsed = HudDocSchema.safeParse(value);
|
|
87
|
+
if (!parsed.success) {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
return parsed.data;
|
|
91
|
+
}
|
|
92
|
+
function deterministicHudDocChoice(a, b) {
|
|
93
|
+
if (a.updated_at_ms !== b.updated_at_ms) {
|
|
94
|
+
return a.updated_at_ms > b.updated_at_ms ? a : b;
|
|
95
|
+
}
|
|
96
|
+
const left = stableSerializeJson(a);
|
|
97
|
+
const right = stableSerializeJson(b);
|
|
98
|
+
return left <= right ? a : b;
|
|
99
|
+
}
|
|
100
|
+
function hudDocCandidates(input) {
|
|
101
|
+
if (Array.isArray(input)) {
|
|
102
|
+
return input;
|
|
103
|
+
}
|
|
104
|
+
if (isPlainObject(input)) {
|
|
105
|
+
return [input];
|
|
106
|
+
}
|
|
107
|
+
return [];
|
|
108
|
+
}
|
|
109
|
+
function normalizedHudDocLimit(limit) {
|
|
110
|
+
if (typeof limit !== "number" || !Number.isFinite(limit)) {
|
|
111
|
+
return 12;
|
|
112
|
+
}
|
|
113
|
+
const parsed = Math.trunc(limit);
|
|
114
|
+
if (parsed < 1) {
|
|
115
|
+
return 1;
|
|
116
|
+
}
|
|
117
|
+
if (parsed > 64) {
|
|
118
|
+
return 64;
|
|
119
|
+
}
|
|
120
|
+
return parsed;
|
|
121
|
+
}
|
|
122
|
+
function canonicalizeJson(value) {
|
|
123
|
+
if (Array.isArray(value)) {
|
|
124
|
+
return value.map((entry) => canonicalizeJson(entry));
|
|
125
|
+
}
|
|
126
|
+
if (!isPlainObject(value)) {
|
|
127
|
+
return value;
|
|
128
|
+
}
|
|
129
|
+
const out = {};
|
|
130
|
+
for (const key of Object.keys(value).sort()) {
|
|
131
|
+
const nextValue = value[key];
|
|
132
|
+
if (nextValue === undefined) {
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
out[key] = canonicalizeJson(nextValue);
|
|
136
|
+
}
|
|
137
|
+
return out;
|
|
138
|
+
}
|
|
139
|
+
export function stableSerializeJson(value, opts = {}) {
|
|
140
|
+
const normalized = canonicalizeJson(value);
|
|
141
|
+
const text = JSON.stringify(normalized, null, opts.pretty ? 2 : undefined);
|
|
142
|
+
return text ?? "null";
|
|
143
|
+
}
|
|
144
|
+
function truncateText(value, maxChars) {
|
|
145
|
+
if (maxChars <= 0) {
|
|
146
|
+
return "";
|
|
147
|
+
}
|
|
148
|
+
if (value.length <= maxChars) {
|
|
149
|
+
return value;
|
|
150
|
+
}
|
|
151
|
+
if (maxChars <= 1) {
|
|
152
|
+
return "…";
|
|
153
|
+
}
|
|
154
|
+
return `${value.slice(0, maxChars - 1)}…`;
|
|
155
|
+
}
|
|
156
|
+
function appendOverflowLine(lines, hiddenCount) {
|
|
157
|
+
if (hiddenCount > 0) {
|
|
158
|
+
lines.push(`… (+${hiddenCount} more)`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
export function serializeHudDocTextFallback(input, opts = {}) {
|
|
162
|
+
const doc = parseHudDocCandidate(input);
|
|
163
|
+
if (!doc) {
|
|
164
|
+
return "";
|
|
165
|
+
}
|
|
166
|
+
const mode = opts.mode ?? "multiline";
|
|
167
|
+
const maxChars = typeof opts.maxChars === "number" && Number.isFinite(opts.maxChars) ? Math.max(32, Math.trunc(opts.maxChars)) : 8_192;
|
|
168
|
+
if (mode === "compact") {
|
|
169
|
+
const compact = `${doc.title} · ${doc.snapshot_compact}`;
|
|
170
|
+
return truncateText(compact, maxChars);
|
|
171
|
+
}
|
|
172
|
+
const maxSectionItems = typeof opts.maxSectionItems === "number" && Number.isFinite(opts.maxSectionItems)
|
|
173
|
+
? Math.max(1, Math.trunc(opts.maxSectionItems))
|
|
174
|
+
: 8;
|
|
175
|
+
const maxActions = typeof opts.maxActions === "number" && Number.isFinite(opts.maxActions) ? Math.max(1, Math.trunc(opts.maxActions)) : 4;
|
|
176
|
+
const lines = [];
|
|
177
|
+
lines.push(`${doc.title} [${doc.hud_id}]`);
|
|
178
|
+
if (doc.scope) {
|
|
179
|
+
lines.push(`scope: ${doc.scope}`);
|
|
180
|
+
}
|
|
181
|
+
if (doc.chips.length > 0) {
|
|
182
|
+
lines.push(`chips: ${doc.chips.map((chip) => chip.label).join(" · ")}`);
|
|
183
|
+
}
|
|
184
|
+
for (const section of doc.sections) {
|
|
185
|
+
const title = section.title ? ` (${section.title})` : "";
|
|
186
|
+
switch (section.kind) {
|
|
187
|
+
case "kv": {
|
|
188
|
+
lines.push(`section: kv${title}`);
|
|
189
|
+
const visible = section.items.slice(0, maxSectionItems);
|
|
190
|
+
for (const item of visible) {
|
|
191
|
+
lines.push(`- ${item.label}: ${item.value}`);
|
|
192
|
+
}
|
|
193
|
+
appendOverflowLine(lines, section.items.length - visible.length);
|
|
194
|
+
break;
|
|
195
|
+
}
|
|
196
|
+
case "checklist": {
|
|
197
|
+
lines.push(`section: checklist${title}`);
|
|
198
|
+
const visible = section.items.slice(0, maxSectionItems);
|
|
199
|
+
for (const item of visible) {
|
|
200
|
+
lines.push(`- [${item.done ? "x" : " "}] ${item.label}`);
|
|
201
|
+
}
|
|
202
|
+
appendOverflowLine(lines, section.items.length - visible.length);
|
|
203
|
+
break;
|
|
204
|
+
}
|
|
205
|
+
case "activity": {
|
|
206
|
+
lines.push(`section: activity${title}`);
|
|
207
|
+
const visible = section.lines.slice(0, maxSectionItems);
|
|
208
|
+
for (const line of visible) {
|
|
209
|
+
lines.push(`- ${line}`);
|
|
210
|
+
}
|
|
211
|
+
appendOverflowLine(lines, section.lines.length - visible.length);
|
|
212
|
+
break;
|
|
213
|
+
}
|
|
214
|
+
case "text":
|
|
215
|
+
lines.push(`section: text${title}`);
|
|
216
|
+
lines.push(`- ${section.text}`);
|
|
217
|
+
break;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
if (doc.actions.length > 0) {
|
|
221
|
+
lines.push("actions:");
|
|
222
|
+
const visible = doc.actions.slice(0, maxActions);
|
|
223
|
+
for (const action of visible) {
|
|
224
|
+
lines.push(`- ${action.label}: ${action.command_text}`);
|
|
225
|
+
}
|
|
226
|
+
appendOverflowLine(lines, doc.actions.length - visible.length);
|
|
227
|
+
}
|
|
228
|
+
return truncateText(lines.join("\n"), maxChars);
|
|
229
|
+
}
|
|
230
|
+
export function serializeHudDocsTextFallback(input, opts = {}) {
|
|
231
|
+
const docs = normalizeHudDocs(input, { maxDocs: opts.maxDocs });
|
|
232
|
+
if (docs.length === 0) {
|
|
233
|
+
return "";
|
|
234
|
+
}
|
|
235
|
+
const mode = opts.mode ?? "multiline";
|
|
236
|
+
const rendered = docs
|
|
237
|
+
.map((doc) => serializeHudDocTextFallback(doc, {
|
|
238
|
+
mode,
|
|
239
|
+
maxChars: opts.maxChars,
|
|
240
|
+
maxSectionItems: opts.maxSectionItems,
|
|
241
|
+
maxActions: opts.maxActions,
|
|
242
|
+
}))
|
|
243
|
+
.filter((value) => value.length > 0);
|
|
244
|
+
return rendered.join(mode === "compact" ? " | " : "\n\n");
|
|
245
|
+
}
|
|
246
|
+
export function parseHudDoc(input) {
|
|
247
|
+
return parseHudDocCandidate(input);
|
|
248
|
+
}
|
|
249
|
+
export function normalizeHudDocs(input, opts = {}) {
|
|
250
|
+
const maxDocs = normalizedHudDocLimit(opts.maxDocs);
|
|
251
|
+
const byId = new Map();
|
|
252
|
+
for (const candidate of hudDocCandidates(input)) {
|
|
253
|
+
const parsed = parseHudDocCandidate(candidate);
|
|
254
|
+
if (!parsed) {
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
const current = byId.get(parsed.hud_id);
|
|
258
|
+
if (!current) {
|
|
259
|
+
byId.set(parsed.hud_id, parsed);
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
byId.set(parsed.hud_id, deterministicHudDocChoice(current, parsed));
|
|
263
|
+
}
|
|
264
|
+
const docs = [...byId.values()].sort((left, right) => left.hud_id.localeCompare(right.hud_id));
|
|
265
|
+
if (docs.length <= maxDocs) {
|
|
266
|
+
return docs;
|
|
267
|
+
}
|
|
268
|
+
return docs.slice(0, maxDocs);
|
|
269
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
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"}
|
|
@@ -0,0 +1,105 @@
|
|
|
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
|
+
}
|
package/dist/index.d.ts
CHANGED
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,UAAU,CAAC;AACzB,cAAc,kBAAkB,CAAC;AACjC,cAAc,WAAW,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,UAAU,CAAC;AACzB,cAAc,kBAAkB,CAAC;AACjC,cAAc,UAAU,CAAC;AACzB,cAAc,kBAAkB,CAAC;AACjC,cAAc,WAAW,CAAC"}
|
package/dist/index.js
CHANGED