@ai-matrx/chat 0.3.5 → 0.3.9

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.
Files changed (38) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/dist/agents/components/chat/ChatNewClient.d.ts +6 -1
  3. package/dist/agents/components/chat/ChatNewClient.js +10 -3
  4. package/dist/agents/components/chat/begin-fresh-chat.d.ts +2 -0
  5. package/dist/agents/components/chat/begin-fresh-chat.js +18 -1
  6. package/dist/agents/components/inputs/smart-input/InputActionButtons.d.ts +4 -0
  7. package/dist/agents/components/inputs/smart-input/InputActionButtons.js +16 -9
  8. package/dist/agents/components/inputs/smart-input/RunControlsMenu.d.ts +2 -0
  9. package/dist/agents/components/inputs/smart-input/RunControlsMenu.js +2 -0
  10. package/dist/agents/components/inputs/smart-input/SmartAgentInputStacked.js +1 -1
  11. package/dist/agents/components/inputs/smart-input/composer/ComposerAgentPeek.js +1 -1
  12. package/dist/agents/components/inputs/smart-input/composer/ComposerAgentPill.js +4 -6
  13. package/dist/agents/components/inputs/smart-input/composer/ComposerOutput.js +9 -4
  14. package/dist/agents/components/inputs/smart-input/composer/ComposerPlusMenu.d.ts +3 -1
  15. package/dist/agents/components/inputs/smart-input/composer/ComposerPlusMenu.js +2 -2
  16. package/dist/agents/components/inputs/smart-input/composer/composer-types.d.ts +5 -0
  17. package/dist/agents/components/inputs/variable-input-variations/AgentVariableCards.js +1 -1
  18. package/dist/agents/components/shared/AgentOptionsMenu.js +2 -2
  19. package/dist/agents/components/shared/AgentPeekDuplicateButton.d.ts +6 -3
  20. package/dist/agents/components/shared/AgentPeekDuplicateButton.js +8 -7
  21. package/dist/agents/redux/agent-definition/slice.d.ts +16 -1
  22. package/dist/agents/redux/agent-definition/slice.js +20 -0
  23. package/dist/agents/redux/execution-system/message-crud/edit-message.thunk.js +3 -6
  24. package/dist/agents/redux/execution-system/message-crud/fork-and-resubmit-from-message.thunk.js +1 -1
  25. package/dist/agents/types/agent-definition.types.d.ts +29 -1
  26. package/dist/compute/targets.d.ts +2 -6
  27. package/dist/context/sources/scopes.d.ts +3 -3
  28. package/dist/host/ui-slots.js +1 -1
  29. package/dist/host/window-openers.d.ts +21 -5
  30. package/dist/surfaces/lib/ui-surfaces-agent-writes.d.ts +61 -0
  31. package/dist/surfaces/lib/ui-surfaces-agent-writes.js +305 -0
  32. package/dist/surfaces/lib/ui-surfaces-scope.d.ts +40 -0
  33. package/dist/surfaces/lib/ui-surfaces-scope.js +116 -0
  34. package/dist/tool-call-visualization/effects/toolStateEffects.d.ts +1 -1
  35. package/dist/tool-call-visualization/registry/registry.js +0 -2
  36. package/dist/voice-agent/relay/VoiceRelayBar.d.ts +7 -1
  37. package/dist/voice-agent/relay/VoiceRelayBar.js +90 -24
  38. package/package.json +5 -5
@@ -116,15 +116,13 @@ export interface ChatComputeTargetsSource {
116
116
  error: string | null;
117
117
  fetchInstances: (opts?: {
118
118
  limit?: number;
119
- [option: string]: unknown;
120
119
  }) => Promise<unknown>;
121
120
  createInstance: (request: {
122
121
  organization_id: string;
123
122
  template?: string;
124
- tier?: string;
123
+ tier: "ec2" | "hosted";
125
124
  ttl_seconds?: number;
126
125
  labels?: Record<string, string>;
127
- [field: string]: unknown;
128
126
  }) => Promise<{
129
127
  instance: SandboxInstance | null;
130
128
  error: string | null;
@@ -190,15 +188,13 @@ export declare const useSandboxInstances: () => {
190
188
  error: string | null;
191
189
  fetchInstances: (opts?: {
192
190
  limit?: number;
193
- [option: string]: unknown;
194
191
  }) => Promise<unknown>;
195
192
  createInstance: (request: {
196
193
  organization_id: string;
197
194
  template?: string;
198
- tier?: string;
195
+ tier: "ec2" | "hosted";
199
196
  ttl_seconds?: number;
200
197
  labels?: Record<string, string>;
201
- [field: string]: unknown;
202
198
  }) => Promise<{
203
199
  instance: SandboxInstance | null;
204
200
  error: string | null;
@@ -17,6 +17,7 @@
17
17
  * have no honest default and throw, naming themselves.
18
18
  */
19
19
  import type { ComponentType } from "react";
20
+ import type { AssociationSourceEdge, AssociationTargetEdge } from "@ai-matrx/associations";
20
21
  import type { ChatDatabase, Json } from "../../host/db-types.js";
21
22
  import { type AnyComponent, type AnyFn } from "../../host/registered-source.js";
22
23
  export type ContextItemRow = ChatDatabase["context"]["Tables"]["context_items"]["Row"];
@@ -61,7 +62,6 @@ export interface ScopeTreeOrganization {
61
62
  projects?: Array<{
62
63
  id: string;
63
64
  name: string;
64
- [field: string]: unknown;
65
65
  }>;
66
66
  }
67
67
  export interface ContextItemValue {
@@ -175,10 +175,10 @@ export interface AssociationEdgeRow {
175
175
  }
176
176
  export interface ChatAssociationsService {
177
177
  listForTargets(targetType: string, targetIds: string[], ...rest: any[]): Promise<ScopesRpcResult<{
178
- edges: AssociationEdgeRow[];
178
+ edges: AssociationTargetEdge[];
179
179
  }>>;
180
180
  listForSources(sourceType: string, sourceIds: string[], ...rest: any[]): Promise<ScopesRpcResult<{
181
- edges: AssociationEdgeRow[];
181
+ edges: AssociationSourceEdge[];
182
182
  }>>;
183
183
  add(input: any): Promise<ScopesRpcResult<any>>;
184
184
  remove(input: any): Promise<ScopesRpcResult<any>>;
@@ -410,7 +410,7 @@ const loadCodeEditHistoryThunk = slotFn("loadCodeEditHistoryThunk");
410
410
  const applySkillStreamEvent = slotFn("applySkillStreamEvent");
411
411
  const isSkillStreamEvent = slotFn("isSkillStreamEvent", () => false);
412
412
  const materializeMessageArtifacts = slotFn("materializeMessageArtifacts");
413
- const reconcileMessagesArtifacts = slotFn("reconcileMessagesArtifacts", () => void 0);
413
+ const reconcileMessagesArtifacts = slotFn("reconcileMessagesArtifacts", async () => void 0);
414
414
  const noteBrowserActivity = slotFn("noteBrowserActivity");
415
415
  const selectCloudBrowserRunLive = slotFn("selectCloudBrowserRunLive");
416
416
  const adoptCloudBrowserRunFromStream = slotFn("adoptCloudBrowserRunFromStream");
@@ -27,7 +27,7 @@ export interface OpenAgentIdWindowOptions {
27
27
  agentId?: string | null;
28
28
  }
29
29
  export type OpenAgentAdminFindUsagesWindowOptions = OpenAgentIdWindowOptions;
30
- export type OpenAgentCreateAppWindowOptions = OpenAgentIdWindowOptions;
30
+ export type OpenAgentCreateAppWindowOptions = Record<string, never>;
31
31
  export type OpenAgentDataStorageWindowOptions = OpenAgentIdWindowOptions;
32
32
  export type OpenAgentFindUsagesWindowOptions = OpenAgentIdWindowOptions;
33
33
  export type OpenAgentInterfaceVariationsWindowOptions = OpenAgentIdWindowOptions;
@@ -222,7 +222,7 @@ export interface EditorPrimaryAction {
222
222
  label: string;
223
223
  variant?: "default" | "secondary" | "outline" | "destructive" | "ghost" | "link";
224
224
  }
225
- export interface OpenFullScreenMarkdownEditorOptions {
225
+ interface OpenFullScreenMarkdownEditorOptionsBase {
226
226
  instanceId?: string;
227
227
  content?: string;
228
228
  mode?: unknown;
@@ -236,14 +236,29 @@ export interface OpenFullScreenMarkdownEditorOptions {
236
236
  showSaveButton?: boolean;
237
237
  showCopyButton?: boolean;
238
238
  primaryActions?: EditorPrimaryAction[];
239
- onSave?: (content: string) => void | Promise<void>;
240
- onAction?: (action: string, content: string) => void | Promise<void>;
239
+ }
240
+ type OpenFullScreenMarkdownEditorHandlers = {
241
+ onSave: (content: string) => Promise<void>;
242
+ onAction?: never;
241
243
  onEvent?: (event: {
242
244
  type: "save";
243
245
  content: string;
244
246
  action?: string;
245
247
  }) => void | Promise<void>;
246
- }
248
+ } | {
249
+ onSave?: never;
250
+ onAction: (action: string, content: string) => Promise<void>;
251
+ onEvent?: (event: {
252
+ type: "save";
253
+ content: string;
254
+ action?: string;
255
+ }) => void | Promise<void>;
256
+ } | {
257
+ onSave?: undefined;
258
+ onAction?: undefined;
259
+ onEvent?: undefined;
260
+ };
261
+ export type OpenFullScreenMarkdownEditorOptions = OpenFullScreenMarkdownEditorOptionsBase & OpenFullScreenMarkdownEditorHandlers;
247
262
  export interface ChatFullScreenEditorHandle {
248
263
  instanceId: string;
249
264
  callbackGroupId: string | null;
@@ -361,3 +376,4 @@ export declare function useOpenTopicalMapWindow(): (opts: OpenTopicalMapWindowOp
361
376
  export declare function useOpenScratchpadPanel(): (opts?: OpenScratchpadPanelOptions) => ChatWindowHandle;
362
377
  export declare function useOpenWorkingDocumentPanel(): (opts: OpenWorkingDocumentPanelOptions) => ChatWindowHandle;
363
378
  export declare function useOpenWorkingDocumentWindow(): (opts: OpenWorkingDocumentWindowOptions) => ChatInstanceWindowHandle;
379
+ export {};
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Pure parsers for the UI Surfaces registry's agent write targets
3
+ * (`create_surfaces` / `update_surfaces` / `delete_surfaces` on
4
+ * `matrx-admin/ui-surfaces`). Each reads the agent's WHOLE list, reports every
5
+ * problem at once, and returns plans the page saves through its own service
6
+ * functions. No I/O here — unit-tested in `__tests__/ui-surfaces-agent-writes.test.ts`.
7
+ */
8
+ export declare const DEFAULT_PARENT = "matrx-default/default";
9
+ export declare const DEFAULT_SORT_ORDER = 150;
10
+ /** What a parser needs to know about one existing surface. */
11
+ export interface ExistingSurface {
12
+ name: string;
13
+ has_manifest: boolean;
14
+ }
15
+ export interface SurfaceWriteContext {
16
+ existing: readonly ExistingSurface[];
17
+ clientNames: readonly string[];
18
+ }
19
+ export interface CreateSurfacePlan {
20
+ name: string;
21
+ client_name: string;
22
+ label: string | null;
23
+ description: string;
24
+ parent_surface_name: string | null;
25
+ sort_order: number;
26
+ is_active: boolean;
27
+ executor_name: string | null;
28
+ url_pattern: string | null;
29
+ }
30
+ export interface UpdateSurfacePatch {
31
+ description?: string | null;
32
+ parent_surface_name?: string | null;
33
+ sort_order?: number;
34
+ is_active?: boolean;
35
+ executor_name?: string | null;
36
+ url_pattern?: string | null;
37
+ }
38
+ export interface UpdateSurfacePlan {
39
+ name: string;
40
+ patch: UpdateSurfacePatch;
41
+ changed: string[];
42
+ }
43
+ export declare function parseCreateSurfacesValue(value: unknown, ctx: SurfaceWriteContext): CreateSurfacePlan[];
44
+ export declare function parseUpdateSurfacesValue(value: unknown, ctx: SurfaceWriteContext): UpdateSurfacePlan[];
45
+ export declare function parseDeleteSurfacesValue(value: unknown, ctx: SurfaceWriteContext): ExistingSurface[];
46
+ /** Fields the New surface dialog holds — the `new_surface_draft` target. */
47
+ export interface NewSurfaceDraftFields {
48
+ client?: string;
49
+ local?: string;
50
+ /** A surface name, or null for a root surface. */
51
+ parent_surface_name?: string | null;
52
+ /** A tier label: Pages | Specialized | Overlays | Editor variants | Debug. */
53
+ tier?: string;
54
+ description?: string;
55
+ }
56
+ export declare const NEW_SURFACE_TIERS: readonly ["Pages", "Specialized", "Overlays", "Editor variants", "Debug"];
57
+ /**
58
+ * Read the `new_surface_draft` value: an OBJECT with any of { name, parent_surface_name,
59
+ * tier, description }. Every problem is reported at once; nothing is saved.
60
+ */
61
+ export declare function parseNewSurfaceDraftValue(value: unknown, ctx: SurfaceWriteContext): NewSurfaceDraftFields;
@@ -0,0 +1,305 @@
1
+ import {
2
+ collectProblems,
3
+ ListLevelProblem,
4
+ ProblemList,
5
+ readCollectionList,
6
+ repeatsProblem
7
+ } from "../runtime/collection-write-targets.js";
8
+ const DEFAULT_PARENT = "matrx-default/default";
9
+ const DEFAULT_SORT_ORDER = 150;
10
+ const LOCAL_RE = /^[a-z0-9][a-z0-9-/]*$/;
11
+ const UPDATE_FIELDS = [
12
+ "description",
13
+ "parent_surface_name",
14
+ "sort_order",
15
+ "is_active",
16
+ "executor_name",
17
+ "url_pattern"
18
+ ];
19
+ const MANIFEST_OWNED = /* @__PURE__ */ new Set([
20
+ "description",
21
+ "parent_surface_name",
22
+ "is_active",
23
+ "url_pattern"
24
+ ]);
25
+ function asObject(raw) {
26
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw))
27
+ throw new Error("must be an object.");
28
+ return raw;
29
+ }
30
+ function optionalText(obj, key, problems, nullable) {
31
+ if (!(key in obj)) return void 0;
32
+ const v = obj[key];
33
+ if (v === null && nullable) return null;
34
+ if (typeof v !== "string") {
35
+ problems.add(`${key} must be a string${nullable ? " or null" : ""}.`);
36
+ return void 0;
37
+ }
38
+ return v.trim();
39
+ }
40
+ function readSortOrder(obj, problems) {
41
+ if (!("sort_order" in obj)) return void 0;
42
+ const v = obj.sort_order;
43
+ if (typeof v !== "number" || !Number.isInteger(v) || v < 100) {
44
+ problems.add(
45
+ "sort_order must be a whole number of at least 100 (0-99 is reserved)."
46
+ );
47
+ return void 0;
48
+ }
49
+ return v;
50
+ }
51
+ function readParent(obj, names, problems, self) {
52
+ const parent = optionalText(obj, "parent_surface_name", problems, true);
53
+ if (typeof parent === "string") {
54
+ if (!parent) return null;
55
+ if (parent === self) {
56
+ problems.add("a surface cannot be its own parent.");
57
+ return void 0;
58
+ }
59
+ if (!names.has(parent)) {
60
+ problems.add(`parent_surface_name "${parent}" is not a surface in the registry.`);
61
+ return void 0;
62
+ }
63
+ }
64
+ return parent;
65
+ }
66
+ function nameOfRaw(raw) {
67
+ if (typeof raw === "string") return raw;
68
+ if (raw && typeof raw === "object" && typeof raw.name === "string")
69
+ return raw.name;
70
+ return void 0;
71
+ }
72
+ function parseCreateSurfacesValue(value, ctx) {
73
+ const target = "create_surfaces";
74
+ const list = readCollectionList(target, "surfaces", value);
75
+ const names = new Set(ctx.existing.map((s) => s.name));
76
+ const clients = new Set(ctx.clientNames);
77
+ return collectProblems(
78
+ target,
79
+ list,
80
+ (raw) => {
81
+ const obj = asObject(raw);
82
+ const problems = new ProblemList(target);
83
+ const name = typeof obj.name === "string" ? obj.name.trim() : "";
84
+ let client = "";
85
+ if (!name) problems.add('name is required ("<client>/<local>").');
86
+ else {
87
+ const slash = name.indexOf("/");
88
+ client = slash > 0 ? name.slice(0, slash) : "";
89
+ const local = slash > 0 ? name.slice(slash + 1) : "";
90
+ if (!client || !local)
91
+ problems.add(`name "${name}" must be "<client>/<local>".`);
92
+ else {
93
+ if (!clients.has(client))
94
+ problems.add(
95
+ `client "${client}" is not a UI client (one of: ${[...clients].join(", ")}).`
96
+ );
97
+ if (!LOCAL_RE.test(local))
98
+ problems.add(
99
+ `the local part "${local}" may use only lowercase letters, digits, hyphens and slashes.`
100
+ );
101
+ }
102
+ }
103
+ const label = optionalText(obj, "label", problems, true);
104
+ const description = optionalText(obj, "description", problems, true);
105
+ const parent = readParent(obj, names, problems);
106
+ const sort = readSortOrder(obj, problems);
107
+ const executor = optionalText(obj, "executor_name", problems, true);
108
+ const url = optionalText(obj, "url_pattern", problems, true);
109
+ let isActive = true;
110
+ if ("is_active" in obj) {
111
+ if (typeof obj.is_active !== "boolean")
112
+ problems.add("is_active must be true or false.");
113
+ else isActive = obj.is_active;
114
+ }
115
+ problems.throwIfAny();
116
+ return {
117
+ name,
118
+ client_name: client,
119
+ label: label || null,
120
+ description: description ?? "",
121
+ parent_surface_name: parent === void 0 ? DEFAULT_PARENT : parent,
122
+ sort_order: sort ?? DEFAULT_SORT_ORDER,
123
+ is_active: isActive,
124
+ executor_name: executor || null,
125
+ url_pattern: url || null
126
+ };
127
+ },
128
+ {
129
+ nameOf: nameOfRaw,
130
+ listChecks: (items) => [
131
+ repeatsProblem(target, items.map((i) => i.name), "name"),
132
+ ...items.filter((i) => i.name && names.has(i.name)).map(
133
+ (i) => `${target}[${i.index}] "${i.name}" already exists in the registry \u2014 use update_surfaces to change it.`
134
+ )
135
+ ]
136
+ }
137
+ );
138
+ }
139
+ function parseUpdateSurfacesValue(value, ctx) {
140
+ const target = "update_surfaces";
141
+ const list = readCollectionList(target, "surfaces", value);
142
+ const byName = new Map(ctx.existing.map((s) => [s.name, s]));
143
+ const names = new Set(byName.keys());
144
+ return collectProblems(
145
+ target,
146
+ list,
147
+ (raw) => {
148
+ const obj = asObject(raw);
149
+ const name = typeof obj.name === "string" ? obj.name.trim() : "";
150
+ if (!name) throw new Error("name is required (from surfaces).");
151
+ const current = byName.get(name);
152
+ if (!current)
153
+ throw new ListLevelProblem(
154
+ `${target}: "${name}" is not a surface in the registry.`
155
+ );
156
+ const problems = new ProblemList(target);
157
+ const unknown = Object.keys(obj).filter(
158
+ (k) => k !== "name" && !UPDATE_FIELDS.includes(k)
159
+ );
160
+ if (unknown.length)
161
+ problems.add(
162
+ `cannot change ${unknown.join(", ")} here (changeable: ${UPDATE_FIELDS.join(", ")}).`
163
+ );
164
+ if (current.has_manifest) {
165
+ const owned = Object.keys(obj).filter((k) => MANIFEST_OWNED.has(k));
166
+ if (owned.length)
167
+ problems.add(
168
+ `"${name}" has a code manifest, which owns ${owned.join(", ")} \u2014 Sync manifests would overwrite the change. Change the manifest in code; here only sort_order and executor_name may change.`
169
+ );
170
+ }
171
+ const patch = {};
172
+ const description = optionalText(obj, "description", problems, true);
173
+ if (description !== void 0) patch.description = description || null;
174
+ const parent = readParent(obj, names, problems, name);
175
+ if (parent !== void 0) patch.parent_surface_name = parent;
176
+ const sort = readSortOrder(obj, problems);
177
+ if (sort !== void 0) patch.sort_order = sort;
178
+ if ("is_active" in obj) {
179
+ if (typeof obj.is_active !== "boolean")
180
+ problems.add("is_active must be true or false.");
181
+ else patch.is_active = obj.is_active;
182
+ }
183
+ const executor = optionalText(obj, "executor_name", problems, true);
184
+ if (executor !== void 0) patch.executor_name = executor || null;
185
+ const url = optionalText(obj, "url_pattern", problems, true);
186
+ if (url !== void 0) patch.url_pattern = url || null;
187
+ problems.throwIfAny();
188
+ const changed = Object.keys(patch);
189
+ if (changed.length === 0) throw new Error("sends no field to change.");
190
+ return { name, patch, changed };
191
+ },
192
+ {
193
+ nameOf: nameOfRaw,
194
+ listChecks: (items) => [
195
+ repeatsProblem(target, items.map((i) => i.name), "surface")
196
+ ]
197
+ }
198
+ );
199
+ }
200
+ function parseDeleteSurfacesValue(value, ctx) {
201
+ const target = "delete_surfaces";
202
+ const list = readCollectionList(target, "surfaces", value);
203
+ const byName = new Map(ctx.existing.map((s) => [s.name, s]));
204
+ return collectProblems(
205
+ target,
206
+ list,
207
+ (raw) => {
208
+ const name = nameOfRaw(raw)?.trim();
209
+ if (!name) throw new Error('must be a surface name or { "name": "\u2026" }.');
210
+ const current = byName.get(name);
211
+ if (!current)
212
+ throw new ListLevelProblem(
213
+ `${target}: "${name}" is not a surface in the registry.`
214
+ );
215
+ if (current.has_manifest)
216
+ throw new Error(
217
+ `"${name}" has a code manifest, so code owns it: remove the manifest in code, or deactivate it instead.`
218
+ );
219
+ return current;
220
+ },
221
+ {
222
+ nameOf: nameOfRaw,
223
+ listChecks: (items) => [
224
+ repeatsProblem(target, items.map((i) => i.name), "surface")
225
+ ]
226
+ }
227
+ );
228
+ }
229
+ const NEW_SURFACE_TIERS = [
230
+ "Pages",
231
+ "Specialized",
232
+ "Overlays",
233
+ "Editor variants",
234
+ "Debug"
235
+ ];
236
+ function parseNewSurfaceDraftValue(value, ctx) {
237
+ const target = "new_surface_draft";
238
+ const problems = new ProblemList(target);
239
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
240
+ problems.add(
241
+ "new_surface_draft expects a JSON OBJECT like { name, parent_surface_name, tier, description }."
242
+ );
243
+ problems.throwIfAny();
244
+ }
245
+ const obj = value;
246
+ const allowed = ["name", "parent_surface_name", "tier", "description"];
247
+ const unknown = Object.keys(obj).filter((k) => !allowed.includes(k));
248
+ if (unknown.length)
249
+ problems.add(
250
+ `the dialog has no ${unknown.join(", ")} field (fields: ${allowed.join(", ")}).`
251
+ );
252
+ const out = {};
253
+ if ("name" in obj) {
254
+ const name = typeof obj.name === "string" ? obj.name.trim() : "";
255
+ const slash = name.indexOf("/");
256
+ const client = slash > 0 ? name.slice(0, slash) : "";
257
+ const local = slash > 0 ? name.slice(slash + 1) : "";
258
+ if (!client || !local) problems.add(`name must be "<client>/<local>".`);
259
+ else {
260
+ if (!ctx.clientNames.includes(client))
261
+ problems.add(
262
+ `client "${client}" is not a UI client (one of: ${ctx.clientNames.join(", ")}).`
263
+ );
264
+ if (!LOCAL_RE.test(local))
265
+ problems.add(
266
+ `the local part "${local}" may use only lowercase letters, digits, hyphens and slashes.`
267
+ );
268
+ if (ctx.existing.some((s) => s.name === name))
269
+ problems.add(`"${name}" already exists in the registry.`);
270
+ out.client = client;
271
+ out.local = local;
272
+ }
273
+ }
274
+ if ("parent_surface_name" in obj) {
275
+ const parent = readParent(
276
+ obj,
277
+ new Set(ctx.existing.map((s) => s.name)),
278
+ problems
279
+ );
280
+ if (parent !== void 0) out.parent_surface_name = parent;
281
+ }
282
+ if ("tier" in obj) {
283
+ if (typeof obj.tier !== "string" || !NEW_SURFACE_TIERS.includes(obj.tier))
284
+ problems.add(`tier must be one of: ${NEW_SURFACE_TIERS.join(", ")}.`);
285
+ else out.tier = obj.tier;
286
+ }
287
+ if ("description" in obj) {
288
+ if (typeof obj.description !== "string")
289
+ problems.add("description must be a string.");
290
+ else out.description = obj.description;
291
+ }
292
+ problems.throwIfAny();
293
+ if (Object.keys(out).length === 0)
294
+ throw new Error("new_surface_draft sends no field to fill. Nothing was changed.");
295
+ return out;
296
+ }
297
+ export {
298
+ DEFAULT_PARENT,
299
+ DEFAULT_SORT_ORDER,
300
+ NEW_SURFACE_TIERS,
301
+ parseCreateSurfacesValue,
302
+ parseDeleteSurfacesValue,
303
+ parseNewSurfaceDraftValue,
304
+ parseUpdateSurfacesValue
305
+ };
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Pure scope builder for the UI Surfaces registry page
3
+ * (`matrx-admin/ui-surfaces`, `/administration/ui/surfaces`).
4
+ *
5
+ * Called by `SurfacesContainer` with state the page already rendered — it
6
+ * never fetches (the runtime polls it every 400 ms). Not loaded yet → the
7
+ * registry keys are omitted; a failed load reports only `load_state`.
8
+ */
9
+ import type { SurfaceScopePayload } from "../types.js";
10
+ import { type UiSurfaceScopeEntry, type UiSurfacesFilterScope, type UiSurfacesRegistryCounts } from "../manifests/admin-ui-surfaces.manifest.js";
11
+ import { type SurfaceWithStats } from "../services/surfaces.service.js";
12
+ import type { SurfacesFilterState } from "../host-surface-types.js";
13
+ import type { NewSurfaceDraftScope } from "../host-surface-types.js";
14
+ /** How many rows `surface_list` carries up front (~4,000 chars). */
15
+ export declare const SURFACE_LIST_MAX_ROWS = 40;
16
+ export type UiSurfacesDialog = "new_surface" | "new_client" | "candidates" | "sync_manifests" | "drift_report";
17
+ export interface UiSurfacesScopeInput {
18
+ loading: boolean;
19
+ error: string | null;
20
+ /** Every row loaded (empty until the first successful load). */
21
+ surfaces: readonly SurfaceWithStats[];
22
+ /** The rows after the page's own filters, in the page's order. */
23
+ visible: readonly SurfaceWithStats[];
24
+ clientNames: readonly string[];
25
+ manifestedNames: ReadonlySet<string>;
26
+ filters: SurfacesFilterState;
27
+ readinessCounts: UiSurfacesRegistryCounts["readiness"];
28
+ candidatesAvailable: number;
29
+ /** The drift report's total (countDriftIssues); null until it loads. */
30
+ driftIssues: number | null;
31
+ peekedName: string | null;
32
+ openDialog: UiSurfacesDialog | null;
33
+ /** The New surface dialog's live values; null while it is closed. */
34
+ newSurfaceDraft: NewSurfaceDraftScope | null;
35
+ }
36
+ export declare function filtersToScope(f: SurfacesFilterState): UiSurfacesFilterScope;
37
+ export declare function toSurfaceScopeEntry(row: SurfaceWithStats, manifestedNames: ReadonlySet<string>): UiSurfaceScopeEntry;
38
+ /** The condensed on-screen list as one XML bundle. */
39
+ export declare function buildSurfaceListBundle(visible: readonly SurfaceWithStats[], manifestedNames: ReadonlySet<string>, filters: UiSurfacesFilterScope): string;
40
+ export declare function buildUiSurfacesScope(input: UiSurfacesScopeInput): SurfaceScopePayload;
@@ -0,0 +1,116 @@
1
+ import { getManifest } from "../runtime/registry.js";
2
+ import {
3
+ createAdminUiSurfacesScope
4
+ } from "../manifests/admin-ui-surfaces.manifest.js";
5
+ import {
6
+ readinessBucketOf,
7
+ tierFor
8
+ } from "../services/surfaces.service.js";
9
+ import { surfaceCheckState } from "../utils/surface-check-ledger.js";
10
+ import { xmlElement, xmlList } from "../runtime/context-bundle.js";
11
+ const SURFACE_LIST_MAX_ROWS = 40;
12
+ function filtersToScope(f) {
13
+ return {
14
+ client: f.client === "__all__" ? "all" : f.client,
15
+ status: f.status,
16
+ check_age: f.checked,
17
+ manifest: f.manifest,
18
+ parent: f.parent === "__all__" ? "all" : f.parent === "__none__" ? "none" : f.parent,
19
+ readiness: f.readiness
20
+ };
21
+ }
22
+ function toSurfaceScopeEntry(row, manifestedNames) {
23
+ return {
24
+ name: row.name,
25
+ label: row.label ?? null,
26
+ client_name: row.client_name,
27
+ executor_name: row.executor_name ?? null,
28
+ parent_surface_name: row.parent_surface_name ?? null,
29
+ sort_order: row.sort_order,
30
+ tier: tierFor(row.sort_order).label,
31
+ is_active: row.is_active !== false,
32
+ readiness: readinessBucketOf(row),
33
+ readiness_note: row.readiness_note ?? null,
34
+ has_manifest: manifestedNames.has(row.name),
35
+ value_count: row.surfaceValueCount,
36
+ declared_value_count: getManifest(row.name)?.values.length ?? null,
37
+ agent_count: row.agentCount,
38
+ tool_count: row.toolCount,
39
+ check_state: surfaceCheckState(row),
40
+ last_checked_at: row.last_checked_at ?? null,
41
+ last_checked_by: row.last_checked_by ?? null,
42
+ url_pattern: row.url_pattern ?? null,
43
+ description: row.description ?? null
44
+ };
45
+ }
46
+ function buildSurfaceListBundle(visible, manifestedNames, filters) {
47
+ const active = Object.fromEntries(
48
+ Object.entries(filters).filter(([, v]) => v !== "all")
49
+ );
50
+ return xmlList(
51
+ "surfaces",
52
+ visible,
53
+ (row) => xmlElement("s", {
54
+ name: row.name,
55
+ label: row.label,
56
+ client: row.client_name,
57
+ parent: row.parent_surface_name,
58
+ tier: `${tierFor(row.sort_order).label} ${row.sort_order}`,
59
+ readiness: readinessBucketOf(row),
60
+ values: row.surfaceValueCount,
61
+ declared: getManifest(row.name)?.values.length,
62
+ agents: row.agentCount,
63
+ tools: row.toolCount,
64
+ checked: surfaceCheckState(row),
65
+ active: row.is_active !== false,
66
+ manifest: manifestedNames.has(row.name)
67
+ }),
68
+ { maxRows: SURFACE_LIST_MAX_ROWS, attrs: active }
69
+ ) || '<surfaces total="0"/>';
70
+ }
71
+ function buildUiSurfacesScope(input) {
72
+ const filters = filtersToScope(input.filters);
73
+ const extras = {
74
+ ...input.peekedName ? { peeked_surface: input.peekedName } : {},
75
+ ...input.openDialog ? { open_dialog: input.openDialog } : {},
76
+ ...input.newSurfaceDraft ? { new_surface_draft: input.newSurfaceDraft } : {}
77
+ };
78
+ if (input.error) {
79
+ return createAdminUiSurfacesScope({
80
+ load_state: `error: ${input.error}`,
81
+ filters,
82
+ ...extras
83
+ });
84
+ }
85
+ if (input.loading && input.surfaces.length === 0) {
86
+ return createAdminUiSurfacesScope({ load_state: "loading", filters, ...extras });
87
+ }
88
+ const surfaces = input.surfaces.map(
89
+ (s) => toSurfaceScopeEntry(s, input.manifestedNames)
90
+ );
91
+ return createAdminUiSurfacesScope({
92
+ load_state: "ready",
93
+ filters,
94
+ surface_list: buildSurfaceListBundle(input.visible, input.manifestedNames, filters),
95
+ surfaces,
96
+ registry_counts: {
97
+ total: surfaces.length,
98
+ active: surfaces.filter((s) => s.is_active).length,
99
+ manifests: input.manifestedNames.size,
100
+ unused: surfaces.filter((s) => s.agent_count === 0 && s.tool_count === 0).length,
101
+ visible: input.visible.length,
102
+ readiness: input.readinessCounts,
103
+ candidates_available: input.candidatesAvailable,
104
+ ...input.driftIssues !== null ? { drift_issues: input.driftIssues } : {}
105
+ },
106
+ client_names: [...input.clientNames],
107
+ ...extras
108
+ });
109
+ }
110
+ export {
111
+ SURFACE_LIST_MAX_ROWS,
112
+ buildSurfaceListBundle,
113
+ buildUiSurfacesScope,
114
+ filtersToScope,
115
+ toSurfaceScopeEntry
116
+ };
@@ -27,7 +27,7 @@
27
27
  * that dispatches the feature's refetch thunk(s). Known not-yet-covered
28
28
  * writers (no canonical Redux refetch today, or their state is hook-local):
29
29
  * picklist (session-cached `usePickListDetail`), dictionary, workbook,
30
- * dataset/usertable, document. The working document is already reconciled by
30
+ * dataset, document. The working document is already reconciled by
31
31
  * its own `context_changed` re-read; the scratchpad is agent-readonly.
32
32
  */
33
33
  import type { ChatRootState } from "../../store/root-state.js";
@@ -1066,7 +1066,6 @@ const APP_FEATURE_TOOL_NAMES = /* @__PURE__ */ new Set([
1066
1066
  "user_todos",
1067
1067
  "document",
1068
1068
  "dataset",
1069
- "usertable_create",
1070
1069
  "knowledge_search",
1071
1070
  "document_search",
1072
1071
  "knowledge_browse",
@@ -1202,7 +1201,6 @@ const TOOL_GLYPHS = {
1202
1201
  update_plan: { icon: ListTodo, accent: "primary" },
1203
1202
  document: { icon: FileText, accent: "slate" },
1204
1203
  dataset: { icon: Table2, accent: "cyan" },
1205
- usertable_create: { icon: Table2, accent: "cyan" },
1206
1204
  workbook: { icon: Sheet, accent: "green" },
1207
1205
  dictionary: { icon: BookText, accent: "amber" },
1208
1206
  note: { icon: NotebookPen, accent: "amber" },