@llui/mcp 0.0.25 → 0.0.27

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,39 +1,35 @@
1
1
  import { existsSync } from 'node:fs';
2
+ import { z } from 'zod';
2
3
  import { generateReplayTest } from './replay-test-generator.js';
3
4
  import { domDiff, diffState } from '../util/diff.js';
4
5
  /**
5
- * Register the 23 debug-API-backed tools. Every handler here routes through
6
- * `ctx.relay!.call(method, args)` — which transparently dispatches to either
7
- * an in-process `LluiDebugAPI` or the WebSocket bridge, depending on how the
8
- * transport was wired.
6
+ * Register the 33 debug-API-backed tools. Every handler routes through
7
+ * `ctx.relay!.call(method, args)` — which transparently dispatches to
8
+ * either an in-process `LluiDebugAPI` or the WebSocket bridge,
9
+ * depending on how the transport was wired.
10
+ *
11
+ * Zod schemas drive both runtime input validation (the registry's
12
+ * `dispatch()` calls `schema.safeParse()` before invoking the handler)
13
+ * and the JSON Schema published in `tools/list` (derived once at
14
+ * registration time). Handlers receive parsed/typed arguments.
9
15
  */
10
16
  export function registerDebugApiTools(registry) {
11
17
  registry.register({
12
18
  name: 'llui_get_state',
13
19
  description: 'Get the current state of the LLui component. Returns a JSON-serializable state object.',
14
- inputSchema: {
15
- type: 'object',
16
- properties: {
17
- component: {
18
- type: 'string',
19
- description: 'Component name (defaults to root)',
20
- },
21
- },
22
- },
20
+ schema: z.object({
21
+ component: z.string().optional().describe('Component name (defaults to root)'),
22
+ }),
23
23
  }, 'debug-api', async (_args, ctx) => ctx.relay.call('getState', []));
24
24
  registry.register({
25
25
  name: 'llui_send_message',
26
26
  description: 'Send a message to the component and return the new state and effects. Validates the message first. Calls flush() automatically.',
27
- inputSchema: {
28
- type: 'object',
29
- properties: {
30
- msg: {
31
- type: 'object',
32
- description: 'The message to send (must be a valid Msg variant)',
33
- },
34
- },
35
- required: ['msg'],
36
- },
27
+ schema: z.object({
28
+ msg: z
29
+ .object({})
30
+ .passthrough()
31
+ .describe('The message to send (must be a valid Msg variant)'),
32
+ }),
37
33
  }, 'debug-api', async (args, ctx) => {
38
34
  const errors = (await ctx.relay.call('validateMessage', [args.msg]));
39
35
  if (errors)
@@ -45,47 +41,27 @@ export function registerDebugApiTools(registry) {
45
41
  registry.register({
46
42
  name: 'llui_eval_update',
47
43
  description: 'Dry-run: call update(state, msg) without applying. Returns what the new state and effects would be without modifying the running app.',
48
- inputSchema: {
49
- type: 'object',
50
- properties: {
51
- msg: {
52
- type: 'object',
53
- description: 'The hypothetical message to evaluate',
54
- },
55
- },
56
- required: ['msg'],
57
- },
44
+ schema: z.object({
45
+ msg: z.object({}).passthrough().describe('The hypothetical message to evaluate'),
46
+ }),
58
47
  }, 'debug-api', async (args, ctx) => ctx.relay.call('evalUpdate', [args.msg]));
59
48
  registry.register({
60
49
  name: 'llui_validate_message',
61
50
  description: 'Validate a message against the component Msg type. Returns errors or null if valid.',
62
- inputSchema: {
63
- type: 'object',
64
- properties: {
65
- msg: {
66
- type: 'object',
67
- description: 'The message to validate',
68
- },
69
- },
70
- required: ['msg'],
71
- },
51
+ schema: z.object({
52
+ msg: z.object({}).passthrough().describe('The message to validate'),
53
+ }),
72
54
  }, 'debug-api', async (args, ctx) => ctx.relay.call('validateMessage', [args.msg]));
73
55
  registry.register({
74
56
  name: 'llui_get_message_history',
75
57
  description: 'Get the chronological message history with state transitions, effects, and dirty masks. Supports pagination via `since` (exclusive, return entries with index > since) and `limit` (return at most N most-recent entries). Use both together for tail-fetching.',
76
- inputSchema: {
77
- type: 'object',
78
- properties: {
79
- since: {
80
- type: 'number',
81
- description: 'Return entries with index strictly greater than this.',
82
- },
83
- limit: {
84
- type: 'number',
85
- description: 'Max entries to return (the N most recent).',
86
- },
87
- },
88
- },
58
+ schema: z.object({
59
+ since: z
60
+ .number()
61
+ .optional()
62
+ .describe('Return entries with index strictly greater than this.'),
63
+ limit: z.number().optional().describe('Max entries to return (the N most recent).'),
64
+ }),
89
65
  }, 'debug-api', async (args, ctx) => {
90
66
  const opts = {};
91
67
  if (typeof args.since === 'number')
@@ -97,59 +73,33 @@ export function registerDebugApiTools(registry) {
97
73
  registry.register({
98
74
  name: 'llui_export_trace',
99
75
  description: 'Export the current session as a replayable LluiTrace JSON.',
100
- inputSchema: {
101
- type: 'object',
102
- properties: {},
103
- },
76
+ schema: z.object({}),
104
77
  }, 'debug-api', async (_args, ctx) => ctx.relay.call('exportTrace', []));
105
78
  registry.register({
106
79
  name: 'llui_get_bindings',
107
80
  description: 'Get all active reactive bindings with their masks, last values, and DOM targets.',
108
- inputSchema: {
109
- type: 'object',
110
- properties: {
111
- filter: {
112
- type: 'string',
113
- description: 'Filter by DOM selector or mask value',
114
- },
115
- },
116
- },
81
+ schema: z.object({
82
+ filter: z.string().optional().describe('Filter by DOM selector or mask value'),
83
+ }),
117
84
  }, 'debug-api', async (_args, ctx) => ctx.relay.call('getBindings', []));
118
85
  registry.register({
119
86
  name: 'llui_why_did_update',
120
87
  description: 'Explain why a specific binding re-evaluated: which mask bits were dirty, what the accessor returned, what the previous value was.',
121
- inputSchema: {
122
- type: 'object',
123
- properties: {
124
- bindingIndex: {
125
- type: 'number',
126
- description: 'The binding index to inspect',
127
- },
128
- },
129
- required: ['bindingIndex'],
130
- },
88
+ schema: z.object({
89
+ bindingIndex: z.number().describe('The binding index to inspect'),
90
+ }),
131
91
  }, 'debug-api', async (args, ctx) => ctx.relay.call('whyDidUpdate', [args.bindingIndex]));
132
92
  registry.register({
133
93
  name: 'llui_search_state',
134
94
  description: 'Search current state using a dot-separated path query. E.g., "cart.items" returns the items array.',
135
- inputSchema: {
136
- type: 'object',
137
- properties: {
138
- query: {
139
- type: 'string',
140
- description: 'Dot-separated path to search. E.g., "user.name", "items"',
141
- },
142
- },
143
- required: ['query'],
144
- },
95
+ schema: z.object({
96
+ query: z.string().describe('Dot-separated path to search. E.g., "user.name", "items"'),
97
+ }),
145
98
  }, 'debug-api', async (args, ctx) => ctx.relay.call('searchState', [args.query]));
146
99
  registry.register({
147
100
  name: 'llui_clear_log',
148
101
  description: 'Clear the message and effects history.',
149
- inputSchema: {
150
- type: 'object',
151
- properties: {},
152
- },
102
+ schema: z.object({}),
153
103
  }, 'debug-api', async (_args, ctx) => {
154
104
  await ctx.relay.call('clearLog', []);
155
105
  return { cleared: true };
@@ -157,85 +107,53 @@ export function registerDebugApiTools(registry) {
157
107
  registry.register({
158
108
  name: 'llui_list_messages',
159
109
  description: 'List all message variants the component accepts, with their field types. Returns { discriminant, variants: { [name]: { [field]: typeDescriptor } } }. Use this to discover what messages can be sent without reading source code.',
160
- inputSchema: {
161
- type: 'object',
162
- properties: {},
163
- },
110
+ schema: z.object({}),
164
111
  }, 'debug-api', async (_args, ctx) => ctx.relay.call('getMessageSchema', []));
165
112
  registry.register({
166
113
  name: 'llui_decode_mask',
167
114
  description: "Decode a dirty-mask value from llui_get_message_history (the 'dirtyMask' field) into the list of top-level state fields that changed. Requires 'mask' param.",
168
- inputSchema: {
169
- type: 'object',
170
- properties: {
171
- mask: { type: 'number', description: 'The dirtyMask value to decode' },
172
- },
173
- required: ['mask'],
174
- },
115
+ schema: z.object({
116
+ mask: z.number().describe('The dirtyMask value to decode'),
117
+ }),
175
118
  }, 'debug-api', async (args, ctx) => ctx.relay.call('decodeMask', [args.mask]));
176
119
  registry.register({
177
120
  name: 'llui_mask_legend',
178
121
  description: 'Return the compiler-generated bit→field map for this component. Example: { todos: 1, filter: 2, nextId: 4 } means bit 0 represents `todos`, etc.',
179
- inputSchema: {
180
- type: 'object',
181
- properties: {},
182
- },
122
+ schema: z.object({}),
183
123
  }, 'debug-api', async (_args, ctx) => ctx.relay.call('getMaskLegend', []));
184
124
  registry.register({
185
125
  name: 'llui_component_info',
186
126
  description: 'Get component name and source location (file + line) of the component() declaration. Lets you find where to read or edit the component.',
187
- inputSchema: {
188
- type: 'object',
189
- properties: {},
190
- },
127
+ schema: z.object({}),
191
128
  }, 'debug-api', async (_args, ctx) => ctx.relay.call('getComponentInfo', []));
192
129
  registry.register({
193
130
  name: 'llui_describe_state',
194
131
  description: "Return the State type's shape (not its value). Fields map to type descriptors: 'string', 'number', 'boolean', {kind:'enum',values:[...]}, {kind:'array',of:...}, {kind:'object',fields:...}, {kind:'optional',of:...}. Use this to know what fields exist and their types even when currently undefined.",
195
- inputSchema: {
196
- type: 'object',
197
- properties: {},
198
- },
132
+ schema: z.object({}),
199
133
  }, 'debug-api', async (_args, ctx) => ctx.relay.call('getStateSchema', []));
200
134
  registry.register({
201
135
  name: 'llui_list_effects',
202
136
  description: 'List all effect variants the component emits, with their field types (same format as llui_list_messages). Returns null if no Effect type is declared.',
203
- inputSchema: {
204
- type: 'object',
205
- properties: {},
206
- },
137
+ schema: z.object({}),
207
138
  }, 'debug-api', async (_args, ctx) => ctx.relay.call('getEffectSchema', []));
208
139
  registry.register({
209
140
  name: 'llui_trace_element',
210
141
  description: "Find all bindings targeting a DOM element matched by a CSS selector. Returns { bindingIndex, kind, key, mask, lastValue, relation }[] so you can answer 'why is this element wrong?' — combine with llui_why_did_update(bindingIndex) for a full narrative.",
211
- inputSchema: {
212
- type: 'object',
213
- properties: {
214
- selector: {
215
- type: 'string',
216
- description: 'CSS selector (e.g. `.todo.active`, `#submit`)',
217
- },
218
- },
219
- required: ['selector'],
220
- },
142
+ schema: z.object({
143
+ selector: z.string().describe('CSS selector (e.g. `.todo.active`, `#submit`)'),
144
+ }),
221
145
  }, 'debug-api', async (args, ctx) => ctx.relay.call('getBindingsFor', [args.selector]));
222
146
  registry.register({
223
147
  name: 'llui_snapshot_state',
224
148
  description: 'Capture the current state (deep clone). Returns the snapshot — store it, then call llui_restore_state later to roll back. Useful for safely exploring transitions during a debugging session.',
225
- inputSchema: { type: 'object', properties: {} },
149
+ schema: z.object({}),
226
150
  }, 'debug-api', async (_args, ctx) => ctx.relay.call('snapshotState', []));
227
151
  registry.register({
228
152
  name: 'llui_restore_state',
229
153
  description: 'Overwrite the current state with a previously-captured snapshot. Triggers a full re-render (FULL_MASK). Bypasses update() — snap must already be a valid state value.',
230
- inputSchema: {
231
- type: 'object',
232
- properties: {
233
- snapshot: {
234
- description: 'The state object returned by llui_snapshot_state.',
235
- },
236
- },
237
- required: ['snapshot'],
238
- },
154
+ schema: z.object({
155
+ snapshot: z.unknown().describe('The state object returned by llui_snapshot_state.'),
156
+ }),
239
157
  }, 'debug-api', async (args, ctx) => {
240
158
  await ctx.relay.call('restoreState', [args.snapshot]);
241
159
  return { restored: true, state: await ctx.relay.call('getState', []) };
@@ -243,38 +161,28 @@ export function registerDebugApiTools(registry) {
243
161
  registry.register({
244
162
  name: 'llui_list_components',
245
163
  description: 'List all currently-mounted LLui components + which one is active (being targeted by subsequent tool calls). Multi-mount apps show one entry per mount.',
246
- inputSchema: { type: 'object', properties: {} },
164
+ schema: z.object({}),
247
165
  }, 'debug-api', async (_args, ctx) => ctx.relay.call('__listComponents', []));
248
166
  registry.register({
249
167
  name: 'llui_select_component',
250
168
  description: 'Switch the active component (the one all other tool calls target). Use a key from llui_list_components.',
251
- inputSchema: {
252
- type: 'object',
253
- properties: {
254
- key: {
255
- type: 'string',
256
- description: 'Component key as returned by llui_list_components',
257
- },
258
- },
259
- required: ['key'],
260
- },
169
+ schema: z.object({
170
+ key: z.string().describe('Component key as returned by llui_list_components'),
171
+ }),
261
172
  }, 'debug-api', async (args, ctx) => ctx.relay.call('__selectComponent', [args.key]));
262
173
  registry.register({
263
174
  name: 'llui_replay_trace',
264
175
  description: 'Generate a ready-to-run vitest file that replays the current message history via `replayTrace()` from @llui/test. The output is a complete test file with the trace inlined — paste it into packages/<pkg>/test/ to reproduce the exact sequence of messages the component saw in this session. Use this to capture a debugging session as a regression test.',
265
- inputSchema: {
266
- type: 'object',
267
- properties: {
268
- importPath: {
269
- type: 'string',
270
- description: "Where to import the component def from in the generated test (default: '../src/index'). Example: '../src/todo-app'.",
271
- },
272
- exportName: {
273
- type: 'string',
274
- description: "Named export that holds the component def (default: the component's name).",
275
- },
276
- },
277
- },
176
+ schema: z.object({
177
+ importPath: z
178
+ .string()
179
+ .optional()
180
+ .describe("Where to import the component def from in the generated test (default: '../src/index'). Example: '../src/todo-app'."),
181
+ exportName: z
182
+ .string()
183
+ .optional()
184
+ .describe("Named export that holds the component def (default: the component's name)."),
185
+ }),
278
186
  }, 'debug-api', async (args, ctx) => {
279
187
  const trace = (await ctx.relay.call('exportTrace', []));
280
188
  const importPath = args.importPath ?? '../src/index';
@@ -288,87 +196,66 @@ export function registerDebugApiTools(registry) {
288
196
  registry.register({
289
197
  name: 'llui_inspect_element',
290
198
  description: 'Get a rich report for a DOM element: tag, attributes, classes, data-*, text, bounding box, a computed-style subset (display/visibility/position/dimensions), and the bindings targeting this node. Pass a CSS selector. Returns null if no element matches.',
291
- inputSchema: {
292
- type: 'object',
293
- properties: { selector: { type: 'string', description: 'CSS selector' } },
294
- required: ['selector'],
295
- },
199
+ schema: z.object({ selector: z.string().describe('CSS selector') }),
296
200
  }, 'debug-api', async (args, ctx) => ctx.relay.call('inspectElement', [args.selector]));
297
201
  registry.register({
298
202
  name: 'llui_get_rendered_html',
299
203
  description: "Get the outerHTML of the mounted component or a specific element. Pass 'selector' for a specific node (defaults to the mount root). Pass 'maxLength' to truncate output.",
300
- inputSchema: {
301
- type: 'object',
302
- properties: {
303
- selector: { type: 'string' },
304
- maxLength: { type: 'number' },
305
- },
306
- },
204
+ schema: z.object({
205
+ selector: z.string().optional(),
206
+ maxLength: z.number().optional(),
207
+ }),
307
208
  }, 'debug-api', async (args, ctx) => ctx.relay.call('getRenderedHtml', [args.selector, args.maxLength]));
308
209
  registry.register({
309
210
  name: 'llui_dispatch_event',
310
211
  description: "Synthesize and dispatch a browser event at a DOM element. Returns the history indices of any Msgs the handler produced plus the resulting state. 'type' is the event name (e.g. 'click', 'input', 'keydown'). 'init' is an EventInit object (e.g. { key: 'Enter' } for keydown).",
311
- inputSchema: {
312
- type: 'object',
313
- properties: {
314
- selector: { type: 'string' },
315
- type: { type: 'string' },
316
- init: { type: 'object' },
317
- },
318
- required: ['selector', 'type'],
319
- },
212
+ schema: z.object({
213
+ selector: z.string(),
214
+ type: z.string(),
215
+ init: z.record(z.string(), z.unknown()).optional(),
216
+ }),
320
217
  }, 'debug-api', async (args, ctx) => ctx.relay.call('dispatchDomEvent', [args.selector, args.type, args.init]));
321
218
  registry.register({
322
219
  name: 'llui_dom_diff',
323
220
  description: 'Compare expected HTML against the currently rendered HTML (from selector, or the mount root). Returns { match, differences }. Pass ignoreWhitespace=true to normalize whitespace.',
324
- inputSchema: {
325
- type: 'object',
326
- properties: {
327
- expected: { type: 'string' },
328
- selector: { type: 'string' },
329
- ignoreWhitespace: { type: 'boolean' },
330
- },
331
- required: ['expected'],
332
- },
221
+ schema: z.object({
222
+ expected: z.string(),
223
+ selector: z.string().optional(),
224
+ ignoreWhitespace: z.boolean().optional(),
225
+ }),
333
226
  }, 'debug-api', async (args, ctx) => {
334
227
  const actual = (await ctx.relay.call('getRenderedHtml', [args.selector]));
335
- return domDiff(String(args.expected), actual, {
228
+ return domDiff(args.expected, actual, {
336
229
  ignoreWhitespace: Boolean(args.ignoreWhitespace),
337
230
  });
338
231
  });
339
232
  registry.register({
340
233
  name: 'llui_get_focus',
341
234
  description: 'Return info about the currently focused element: { selector (if it has an id), tagName, selectionStart, selectionEnd }. Useful for catching "focus lost on re-render" bugs.',
342
- inputSchema: { type: 'object', properties: {} },
235
+ schema: z.object({}),
343
236
  }, 'debug-api', async (_args, ctx) => ctx.relay.call('getFocus', []));
344
237
  registry.register({
345
238
  name: 'llui_lint',
346
239
  description: "Lint LLui source code against ESLint LLui rules. Returns violations grouped by rule. Pass 'path' (absolute file path on the dev machine).",
347
- inputSchema: {
348
- type: 'object',
349
- properties: {
350
- path: {
351
- type: 'string',
352
- description: 'Absolute file path to read and lint.',
353
- },
354
- },
355
- required: ['path'],
356
- },
240
+ schema: z.object({
241
+ path: z.string().describe('Absolute file path to read and lint.'),
242
+ }),
357
243
  }, 'debug-api', async (args, _ctx) => {
358
- const pathArg = args.path;
359
- if (!pathArg.endsWith('.ts') && !pathArg.endsWith('.tsx')) {
360
- throw new Error(`llui_lint: only .ts/.tsx files are supported, got: ${pathArg}`);
244
+ if (!args.path.endsWith('.ts') && !args.path.endsWith('.tsx')) {
245
+ throw new Error(`llui_lint: only .ts/.tsx files are supported, got: ${args.path}`);
361
246
  }
362
- if (!existsSync(pathArg)) {
363
- throw new Error(`llui_lint: file not found: ${pathArg}`);
247
+ if (!existsSync(args.path)) {
248
+ throw new Error(`llui_lint: file not found: ${args.path}`);
364
249
  }
365
250
  const { execSync } = await import('node:child_process');
366
251
  try {
367
- const output = execSync(`pnpm exec eslint --format json "${pathArg}"`, { encoding: 'utf8' });
252
+ const output = execSync(`pnpm exec eslint --format json "${args.path}"`, {
253
+ encoding: 'utf8',
254
+ });
368
255
  const results = JSON.parse(output);
369
256
  const violations = results[0]?.messages || [];
370
257
  return {
371
- file: pathArg,
258
+ file: args.path,
372
259
  score: Math.max(0, 20 - violations.length),
373
260
  violations,
374
261
  summary: `${violations.length} violation(s)`,
@@ -381,7 +268,7 @@ export function registerDebugApiTools(registry) {
381
268
  const results = JSON.parse(e.stdout);
382
269
  const violations = results[0]?.messages || [];
383
270
  return {
384
- file: pathArg,
271
+ file: args.path,
385
272
  score: Math.max(0, 20 - violations.length),
386
273
  violations,
387
274
  summary: `${violations.length} violation(s)`,
@@ -397,39 +284,30 @@ export function registerDebugApiTools(registry) {
397
284
  registry.register({
398
285
  name: 'llui_force_rerender',
399
286
  description: "Re-evaluate every binding's accessor against the current state, apply changed values to the DOM, and return the indices of bindings that changed. If a binding's DOM value corrects itself after this call but not after a real message, the mask for that binding is wrong.",
400
- inputSchema: { type: 'object', properties: {} },
287
+ schema: z.object({}),
401
288
  }, 'debug-api', async (_args, ctx) => ctx.relay.call('forceRerender', []));
402
289
  registry.register({
403
290
  name: 'llui_each_diff',
404
291
  description: "Per-each-site reconciliation diffs (added/removed/moved/reused keys) from the dev-time diff log. Pass 'sinceIndex' to filter to entries after a specific message history index.",
405
- inputSchema: {
406
- type: 'object',
407
- properties: { sinceIndex: { type: 'number' } },
408
- },
292
+ schema: z.object({ sinceIndex: z.number().optional() }),
409
293
  }, 'debug-api', async (args, ctx) => ctx.relay.call('getEachDiff', [args.sinceIndex]));
410
294
  registry.register({
411
295
  name: 'llui_scope_tree',
412
296
  description: "Walk the scope tree starting at the component root (or a specific scopeId). Returns a LifetimeNode tree with kind (root/show/each/branch/child/portal/foreign) and children. Pass 'depth' to limit traversal, 'scopeId' to start elsewhere.",
413
- inputSchema: {
414
- type: 'object',
415
- properties: {
416
- depth: { type: 'number' },
417
- scopeId: { type: 'string' },
418
- },
419
- },
297
+ schema: z.object({
298
+ depth: z.number().optional(),
299
+ scopeId: z.string().optional(),
300
+ }),
420
301
  }, 'debug-api', async (args, ctx) => ctx.relay.call('getScopeTree', [{ depth: args.depth, scopeId: args.scopeId }]));
421
302
  registry.register({
422
303
  name: 'llui_disposer_log',
423
304
  description: "Recent onDispose firings with scope id and cause. Pass 'limit' to cap results to the N most recent entries. Catches 'leak on branch swap' class bugs.",
424
- inputSchema: {
425
- type: 'object',
426
- properties: { limit: { type: 'number' } },
427
- },
305
+ schema: z.object({ limit: z.number().optional() }),
428
306
  }, 'debug-api', async (args, ctx) => ctx.relay.call('getDisposerLog', [args.limit]));
429
307
  registry.register({
430
308
  name: 'llui_list_dead_bindings',
431
309
  description: "Bindings that are inactive (scope disposed) OR never matched a dirty mask OR never changed value. Useful for finding wasted work and 'this never updates' bugs. Returns the subset of get_bindings with an annotation on why it's flagged.",
432
- inputSchema: { type: 'object', properties: {} },
310
+ schema: z.object({}),
433
311
  }, 'debug-api', async (_args, ctx) => {
434
312
  const bindings = (await ctx.relay.call('getBindings', []));
435
313
  return bindings
@@ -442,56 +320,42 @@ export function registerDebugApiTools(registry) {
442
320
  registry.register({
443
321
  name: 'llui_binding_graph',
444
322
  description: 'Edge list: state path → binding indices that depend on it. Inverts the compiler-emitted mask legend to show, for each top-level state field, which bindings will re-evaluate when it changes.',
445
- inputSchema: { type: 'object', properties: {} },
323
+ schema: z.object({}),
446
324
  }, 'debug-api', async (_args, ctx) => ctx.relay.call('getBindingGraph', []));
447
325
  registry.register({
448
326
  name: 'llui_mock_effect',
449
327
  description: "Register a mock for an effect matching 'match' ({ type?, payloadPath?, payloadEquals? }). The next matching effect resolves with 'response' instead of running. Mocks are one-shot; pass { persist: true } to keep across matches. Returns { mockId } for later reference.",
450
- inputSchema: {
451
- type: 'object',
452
- properties: {
453
- match: { type: 'object' },
454
- response: {},
455
- opts: { type: 'object' },
456
- },
457
- required: ['match', 'response'],
458
- },
328
+ schema: z.object({
329
+ match: z.record(z.string(), z.unknown()),
330
+ response: z.unknown(),
331
+ opts: z.record(z.string(), z.unknown()).optional(),
332
+ }),
459
333
  }, 'debug-api', async (args, ctx) => ctx.relay.call('mockEffect', [args.match, args.response, args.opts]));
460
334
  registry.register({
461
335
  name: 'llui_resolve_effect',
462
336
  description: "Manually resolve a pending effect with a given response. The effect's onSuccess callback (if any) runs as if it had actually resolved. Pass effectId from llui_pending_effects.",
463
- inputSchema: {
464
- type: 'object',
465
- properties: {
466
- effectId: { type: 'string' },
467
- response: {},
468
- },
469
- required: ['effectId', 'response'],
470
- },
337
+ schema: z.object({
338
+ effectId: z.string(),
339
+ response: z.unknown(),
340
+ }),
471
341
  }, 'debug-api', async (args, ctx) => ctx.relay.call('resolveEffect', [args.effectId, args.response]));
472
342
  registry.register({
473
343
  name: 'llui_pending_effects',
474
344
  description: "Current queued and in-flight effects. Each entry has { id, type, dispatchedAt, status, payload }. Use 'id' with llui_resolve_effect to manually resolve one.",
475
- inputSchema: { type: 'object', properties: {} },
345
+ schema: z.object({}),
476
346
  }, 'debug-api', async (_args, ctx) => ctx.relay.call('getPendingEffects', []));
477
347
  registry.register({
478
348
  name: 'llui_effect_timeline',
479
349
  description: "Phased log of effect events: dispatched -> in-flight -> resolved/cancelled/resolved-mocked. Each entry has { effectId, type, phase, timestamp, durationMs? }. Pass 'limit' to cap the tail.",
480
- inputSchema: {
481
- type: 'object',
482
- properties: { limit: { type: 'number' } },
483
- },
350
+ schema: z.object({ limit: z.number().optional() }),
484
351
  }, 'debug-api', async (args, ctx) => ctx.relay.call('getEffectTimeline', [args.limit]));
485
352
  registry.register({
486
353
  name: 'llui_step_back',
487
354
  description: "Rewind state by replaying from init() with the last N messages excluded. 'mode' is 'pure' (default; suppresses effects) or 'live' (re-fires effects from replay). Returns the new state and rewindDepth.",
488
- inputSchema: {
489
- type: 'object',
490
- properties: {
491
- n: { type: 'number' },
492
- mode: { type: 'string', enum: ['pure', 'live'] },
493
- },
494
- },
355
+ schema: z.object({
356
+ n: z.number().optional(),
357
+ mode: z.enum(['pure', 'live']).optional(),
358
+ }),
495
359
  }, 'debug-api', async (args, ctx) => {
496
360
  const n = typeof args.n === 'number' ? args.n : 1;
497
361
  const mode = args.mode === 'live' ? 'live' : 'pure';
@@ -500,38 +364,29 @@ export function registerDebugApiTools(registry) {
500
364
  registry.register({
501
365
  name: 'llui_coverage',
502
366
  description: "Per-Msg-variant coverage for the current session: { fired: { variant: { count, lastIndex } }, neverFired: [variants] }. Shows which message types have run and which haven't — useful for finding untested paths.",
503
- inputSchema: { type: 'object', properties: {} },
367
+ schema: z.object({}),
504
368
  }, 'debug-api', async (_args, ctx) => ctx.relay.call('getCoverage', []));
505
369
  registry.register({
506
370
  name: 'llui_diff_state',
507
371
  description: "Structured JSON diff between two state values. Pass 'a' and 'b' — plain objects. Returns { added, removed, changed }.",
508
- inputSchema: {
509
- type: 'object',
510
- properties: {
511
- a: {},
512
- b: {},
513
- },
514
- required: ['a', 'b'],
515
- },
372
+ schema: z.object({
373
+ a: z.unknown(),
374
+ b: z.unknown(),
375
+ }),
516
376
  }, 'debug-api', async (args) => diffState(args.a, args.b));
517
377
  registry.register({
518
378
  name: 'llui_assert',
519
379
  description: "Evaluate a predicate against current state. Pass 'path' (dot-separated), 'op' (eq/neq/exists/gt/lt/in), and 'value'. Returns { pass, actual, expected, op }.",
520
- inputSchema: {
521
- type: 'object',
522
- properties: {
523
- path: { type: 'string' },
524
- op: { type: 'string', enum: ['eq', 'neq', 'exists', 'gt', 'lt', 'in'] },
525
- value: {},
526
- },
527
- required: ['path', 'op'],
528
- },
380
+ schema: z.object({
381
+ path: z.string(),
382
+ op: z.enum(['eq', 'neq', 'exists', 'gt', 'lt', 'in']),
383
+ value: z.unknown().optional(),
384
+ }),
529
385
  }, 'debug-api', async (args, ctx) => {
530
386
  const actual = await ctx.relay.call('searchState', [args.path]);
531
- const op = args.op;
532
387
  const expected = args.value;
533
388
  let pass = false;
534
- switch (op) {
389
+ switch (args.op) {
535
390
  case 'eq':
536
391
  pass = Object.is(actual, expected);
537
392
  break;
@@ -551,30 +406,23 @@ export function registerDebugApiTools(registry) {
551
406
  pass = Array.isArray(expected) && expected.includes(actual);
552
407
  break;
553
408
  }
554
- return { pass, actual, expected, op };
409
+ return { pass, actual, expected, op: args.op };
555
410
  });
556
411
  registry.register({
557
412
  name: 'llui_search_history',
558
413
  description: "Filtered message history. Pass 'filter' with { type?, statePath?, effectType?, fromIndex?, toIndex? }. Entries match if all present fields match — type is the Msg discriminant, statePath is a dot path whose value differs pre->post, effectType is a type present in the effects array.",
559
- inputSchema: {
560
- type: 'object',
561
- properties: {
562
- filter: {
563
- type: 'object',
564
- properties: {
565
- type: { type: 'string' },
566
- statePath: { type: 'string' },
567
- effectType: { type: 'string' },
568
- fromIndex: { type: 'number' },
569
- toIndex: { type: 'number' },
570
- },
571
- },
572
- },
573
- required: ['filter'],
574
- },
414
+ schema: z.object({
415
+ filter: z.object({
416
+ type: z.string().optional(),
417
+ statePath: z.string().optional(),
418
+ effectType: z.string().optional(),
419
+ fromIndex: z.number().optional(),
420
+ toIndex: z.number().optional(),
421
+ }),
422
+ }),
575
423
  }, 'debug-api', async (args, ctx) => {
576
424
  const history = (await ctx.relay.call('getMessageHistory', [{}]));
577
- const f = (args.filter ?? {});
425
+ const f = args.filter;
578
426
  function pathValue(obj, path) {
579
427
  const parts = path.split('.');
580
428
  let v = obj;
@@ -612,11 +460,7 @@ export function registerDebugApiTools(registry) {
612
460
  registry.register({
613
461
  name: 'llui_eval',
614
462
  description: "Arbitrary JavaScript in the page context via the debug relay. Returns { result, sideEffects }. 'result' is the expression's return value or { error }. 'sideEffects' makes any state changes, new history entries, new pending effects, and dirty bindings visible. Phase 1 does not support async expressions; expose async results via globalThis instead.",
615
- inputSchema: {
616
- type: 'object',
617
- properties: { code: { type: 'string' } },
618
- required: ['code'],
619
- },
463
+ schema: z.object({ code: z.string() }),
620
464
  }, 'debug-api', async (args, ctx) => ctx.relay.call('evalInPage', [args.code]));
621
465
  }
622
466
  //# sourceMappingURL=debug-api.js.map