@moxxy/plugin-view 0.28.0 → 0.29.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
- package/src/discovery.test.ts +6 -1
- package/src/index.test.ts +37 -18
- package/src/index.ts +5 -2
- package/src/present-view.test.ts +8 -2
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,cAAc,EACnB,KAAK,MAAM,EACX,KAAK,OAAO,EAGZ,KAAK,eAAe,EACrB,MAAM,YAAY,CAAC;AAkCpB;;;;GAIG;AACH,MAAM,WAAW,WAAW;IAC1B,yEAAyE;IACzE,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,iEAAiE;IACjE,UAAU,IAAI,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,sBAAsB;IACrC;;;;OAIG;IACH,WAAW,EAAE,MAAM,eAAe,GAAG,IAAI,CAAC;IAC1C,2DAA2D;IAC3D,UAAU,CAAC,EAAE,MAAM,WAAW,GAAG,IAAI,CAAC;CACvC;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,sBAAsB,EAAE,KAAK,CAAC,EAAE,cAAc,GAAG,MAAM,CAsH5F;AAUD;;;;;;;GAOG;AACH,eAAO,MAAM,UAAU,EAAE,MAgBrB,CAAC;AAEL,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC;IACrB;;;;;;;OAOG;IACH,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,uEAAuE;IACvE,QAAQ,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,MAAM,CAAC,EAAE,aAAa,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACrE;AAGD,eAAe,UAAU,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { defineTool, definePlugin, z, } from '@moxxy/sdk';
|
|
1
|
+
import { defineTool, definePlugin, assertDefined, z, } from '@moxxy/sdk';
|
|
2
2
|
/**
|
|
3
3
|
* Hard ceiling on the validated AST size. The zod `spec` cap (20k chars) only
|
|
4
4
|
* bounds the *source* — rich components (e.g. `results`) expand at parse time
|
|
@@ -20,6 +20,7 @@ function countNodesBounded(root, limit) {
|
|
|
20
20
|
let count = 0;
|
|
21
21
|
while (stack.length > 0) {
|
|
22
22
|
const node = stack.pop();
|
|
23
|
+
assertDefined(node, 'stack is non-empty inside the while (stack.length > 0) loop');
|
|
23
24
|
count++;
|
|
24
25
|
if (count > limit)
|
|
25
26
|
return count;
|
|
@@ -60,7 +61,8 @@ export function buildViewPlugin(opts, hooks) {
|
|
|
60
61
|
// Short-circuit a cancelled turn before the heaviest work (parse + count
|
|
61
62
|
// + AST serialization on a near-20k spec). ctx is optional only so unit
|
|
62
63
|
// tests can call the handler bare; the registry always supplies it.
|
|
63
|
-
|
|
64
|
+
const signal = ctx?.signal;
|
|
65
|
+
if (signal?.aborted) {
|
|
64
66
|
return { ok: false, rendered: false, errors: [{ message: 'aborted' }] };
|
|
65
67
|
}
|
|
66
68
|
const renderer = opts.getRenderer();
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,EACV,YAAY,EACZ,CAAC,GAOF,MAAM,YAAY,CAAC;AAEpB;;;;;;;GAOG;AACH,MAAM,cAAc,GAAG,KAAK,CAAC;AAE7B;;;;;;GAMG;AACH,SAAS,iBAAiB,CAAC,IAAc,EAAE,KAAa;IACtD,MAAM,KAAK,GAAe,CAAC,IAAI,CAAC,CAAC;IACjC,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACxB,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@moxxy/plugin-view",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.29.0",
|
|
4
4
|
"description": "Agent-authored UIs for moxxy. A present_view tool parses the agent's JSX-like view-spec (via the active view renderer) into a validated AST that channels render as rich interactive UI.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"moxxy",
|
|
@@ -44,15 +44,15 @@
|
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
46
|
"zod": "^3.24.0",
|
|
47
|
-
"@moxxy/sdk": "0.
|
|
47
|
+
"@moxxy/sdk": "0.29.0"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
50
|
"@types/node": "^22.10.0",
|
|
51
51
|
"typescript": "^5.7.3",
|
|
52
52
|
"vitest": "^2.1.8",
|
|
53
|
+
"@moxxy/core": "0.29.0",
|
|
53
54
|
"@moxxy/tsconfig": "0.0.0",
|
|
54
|
-
"@moxxy/vitest-preset": "0.0.0"
|
|
55
|
-
"@moxxy/core": "0.28.0"
|
|
55
|
+
"@moxxy/vitest-preset": "0.0.0"
|
|
56
56
|
},
|
|
57
57
|
"scripts": {
|
|
58
58
|
"build": "tsc -p tsconfig.json",
|
package/src/discovery.test.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { describe, expect, it, vi } from 'vitest';
|
|
2
2
|
import type { ServiceRegistry } from '@moxxy/sdk';
|
|
3
|
+
import { assertDefined } from '@moxxy/sdk';
|
|
3
4
|
import { viewPlugin } from './index.js';
|
|
4
5
|
|
|
5
6
|
/**
|
|
@@ -22,7 +23,11 @@ describe('viewPlugin (discovery-loadable)', () => {
|
|
|
22
23
|
: undefined,
|
|
23
24
|
);
|
|
24
25
|
const services = { get, register: () => {}, require: () => undefined, has: () => true } as unknown as ServiceRegistry;
|
|
25
|
-
viewPlugin.hooks
|
|
26
|
+
const hooks = viewPlugin.hooks;
|
|
27
|
+
assertDefined(hooks, 'plugin declares hooks');
|
|
28
|
+
const onInit = hooks.onInit;
|
|
29
|
+
assertDefined(onInit, 'plugin declares onInit');
|
|
30
|
+
onInit({ services } as never);
|
|
26
31
|
expect(get).toHaveBeenCalledWith('viewRenderers');
|
|
27
32
|
expect(get).toHaveBeenCalledWith('viewSurface');
|
|
28
33
|
});
|
package/src/index.test.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
2
|
import type { ToolContext, ToolDef, ViewNode, ViewParseResult, ViewRendererDef } from '@moxxy/sdk';
|
|
3
|
+
import { assertDefined } from '@moxxy/sdk';
|
|
3
4
|
import { defaultViewRenderer } from '@moxxy/core';
|
|
4
5
|
import { buildViewPlugin, type PresentViewResult } from './index.js';
|
|
5
6
|
|
|
@@ -54,7 +55,9 @@ describe('present_view tool', () => {
|
|
|
54
55
|
it('returns errors for a bad spec without throwing', () => {
|
|
55
56
|
const r = call(`<view><script>x</script></view>`);
|
|
56
57
|
expect(r.ok).toBe(false);
|
|
57
|
-
|
|
58
|
+
const err = r.errors?.[0];
|
|
59
|
+
assertDefined(err, 'a failed parse reports at least one error');
|
|
60
|
+
expect(err.message).toMatch(/unknown tag/);
|
|
58
61
|
});
|
|
59
62
|
|
|
60
63
|
it('surfaces the url + viewId when a surface is attached', () => {
|
|
@@ -75,7 +78,9 @@ describe('present_view tool', () => {
|
|
|
75
78
|
const t = toolOf(buildViewPlugin({ getRenderer: () => null }));
|
|
76
79
|
const r = t.handler({ spec: `<view><text>hi</text></view>` }, ctx) as PresentViewResult;
|
|
77
80
|
expect(r.ok).toBe(false);
|
|
78
|
-
|
|
81
|
+
const err = r.errors?.[0];
|
|
82
|
+
assertDefined(err, 'a failed parse reports at least one error');
|
|
83
|
+
expect(err.message).toMatch(/no active view renderer/);
|
|
79
84
|
});
|
|
80
85
|
|
|
81
86
|
it('rejects an over-large AST (node-count ceiling) without serializing it', () => {
|
|
@@ -86,19 +91,22 @@ describe('present_view tool', () => {
|
|
|
86
91
|
expect(r.ok).toBe(false);
|
|
87
92
|
expect(r.rendered).toBe(false);
|
|
88
93
|
expect(r.ast).toBeUndefined();
|
|
89
|
-
|
|
94
|
+
const err = r.errors?.[0];
|
|
95
|
+
assertDefined(err, 'a failed parse reports at least one error');
|
|
96
|
+
expect(err.message).toMatch(/view too large/);
|
|
90
97
|
});
|
|
91
98
|
|
|
92
99
|
it('does not throw (no stack overflow) on a pathologically deep AST', () => {
|
|
93
100
|
// Deeper than any recursion limit; iterative counting + node cap must turn
|
|
94
101
|
// this into a structured error, never a RangeError out of the handler.
|
|
95
102
|
const t = toolOf(buildViewPlugin({ getRenderer: () => fixedRenderer(deepChain(200_000)) }));
|
|
96
|
-
let r: PresentViewResult;
|
|
103
|
+
let r: PresentViewResult | undefined;
|
|
97
104
|
expect(() => {
|
|
98
105
|
r = t.handler({ spec: '<view/>' }, ctx) as PresentViewResult;
|
|
99
106
|
}).not.toThrow();
|
|
100
|
-
|
|
101
|
-
expect(r
|
|
107
|
+
assertDefined(r, 'handler assigned r inside the non-throwing closure');
|
|
108
|
+
expect(r.ok).toBe(false);
|
|
109
|
+
expect(r.ast).toBeUndefined();
|
|
102
110
|
});
|
|
103
111
|
|
|
104
112
|
it('accepts an AST exactly at the ceiling', () => {
|
|
@@ -170,13 +178,16 @@ describe('present_view tool', () => {
|
|
|
170
178
|
validate: () => [],
|
|
171
179
|
};
|
|
172
180
|
const t = toolOf(buildViewPlugin({ getRenderer: () => throwing }));
|
|
173
|
-
let r: PresentViewResult;
|
|
181
|
+
let r: PresentViewResult | undefined;
|
|
174
182
|
expect(() => {
|
|
175
183
|
r = t.handler({ spec: '<view/>' }, ctx) as PresentViewResult;
|
|
176
184
|
}).not.toThrow();
|
|
177
|
-
|
|
178
|
-
expect(r
|
|
179
|
-
expect(r
|
|
185
|
+
assertDefined(r, 'handler assigned r inside the non-throwing closure');
|
|
186
|
+
expect(r.ok).toBe(false);
|
|
187
|
+
expect(r.rendered).toBe(false);
|
|
188
|
+
const err = r.errors?.[0];
|
|
189
|
+
assertDefined(err, 'a failed parse reports at least one error');
|
|
190
|
+
expect(err.message).toMatch(/renderer exploded/);
|
|
180
191
|
});
|
|
181
192
|
|
|
182
193
|
it('rejects a malformed AST (missing root) with an honest message', () => {
|
|
@@ -189,13 +200,16 @@ describe('present_view tool', () => {
|
|
|
189
200
|
validate: () => [],
|
|
190
201
|
};
|
|
191
202
|
const t = toolOf(buildViewPlugin({ getRenderer: () => malformed }));
|
|
192
|
-
let r: PresentViewResult;
|
|
203
|
+
let r: PresentViewResult | undefined;
|
|
193
204
|
expect(() => {
|
|
194
205
|
r = t.handler({ spec: '<view/>' }, ctx) as PresentViewResult;
|
|
195
206
|
}).not.toThrow();
|
|
196
|
-
|
|
197
|
-
expect(r
|
|
198
|
-
expect(r
|
|
207
|
+
assertDefined(r, 'handler assigned r inside the non-throwing closure');
|
|
208
|
+
expect(r.ok).toBe(false);
|
|
209
|
+
expect(r.ast).toBeUndefined();
|
|
210
|
+
const err = r.errors?.[0];
|
|
211
|
+
assertDefined(err, 'a failed parse reports at least one error');
|
|
212
|
+
expect(err.message).toMatch(/malformed AST/);
|
|
199
213
|
});
|
|
200
214
|
|
|
201
215
|
it('degrades when a custom renderer reports a failure with non-iterable errors', () => {
|
|
@@ -208,12 +222,15 @@ describe('present_view tool', () => {
|
|
|
208
222
|
validate: () => [],
|
|
209
223
|
};
|
|
210
224
|
const t = toolOf(buildViewPlugin({ getRenderer: () => bad }));
|
|
211
|
-
let r: PresentViewResult;
|
|
225
|
+
let r: PresentViewResult | undefined;
|
|
212
226
|
expect(() => {
|
|
213
227
|
r = t.handler({ spec: '<view/>' }, ctx) as PresentViewResult;
|
|
214
228
|
}).not.toThrow();
|
|
215
|
-
|
|
216
|
-
expect(r
|
|
229
|
+
assertDefined(r, 'handler assigned r inside the non-throwing closure');
|
|
230
|
+
expect(r.ok).toBe(false);
|
|
231
|
+
const err = r.errors?.[0];
|
|
232
|
+
assertDefined(err, 'a failed parse reports at least one error');
|
|
233
|
+
expect(err.message).toMatch(/parse failure/);
|
|
217
234
|
});
|
|
218
235
|
|
|
219
236
|
it('short-circuits when the turn is already aborted', () => {
|
|
@@ -224,6 +241,8 @@ describe('present_view tool', () => {
|
|
|
224
241
|
const r = t.handler({ spec: '<view><text>hi</text></view>' }, abortedCtx) as PresentViewResult;
|
|
225
242
|
expect(r.ok).toBe(false);
|
|
226
243
|
expect(r.rendered).toBe(false);
|
|
227
|
-
|
|
244
|
+
const err = r.errors?.[0];
|
|
245
|
+
assertDefined(err, 'an aborted call reports at least one error');
|
|
246
|
+
expect(err.message).toMatch(/aborted/);
|
|
228
247
|
});
|
|
229
248
|
});
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
defineTool,
|
|
3
3
|
definePlugin,
|
|
4
|
+
assertDefined,
|
|
4
5
|
z,
|
|
5
6
|
type LifecycleHooks,
|
|
6
7
|
type Plugin,
|
|
@@ -31,7 +32,8 @@ function countNodesBounded(root: ViewNode, limit: number): number {
|
|
|
31
32
|
const stack: ViewNode[] = [root];
|
|
32
33
|
let count = 0;
|
|
33
34
|
while (stack.length > 0) {
|
|
34
|
-
const node = stack.pop()
|
|
35
|
+
const node = stack.pop();
|
|
36
|
+
assertDefined(node, 'stack is non-empty inside the while (stack.length > 0) loop');
|
|
35
37
|
count++;
|
|
36
38
|
if (count > limit) return count;
|
|
37
39
|
if (node.kind === 'element') {
|
|
@@ -95,7 +97,8 @@ export function buildViewPlugin(opts: BuildViewPluginOptions, hooks?: LifecycleH
|
|
|
95
97
|
// Short-circuit a cancelled turn before the heaviest work (parse + count
|
|
96
98
|
// + AST serialization on a near-20k spec). ctx is optional only so unit
|
|
97
99
|
// tests can call the handler bare; the registry always supplies it.
|
|
98
|
-
|
|
100
|
+
const signal = ctx?.signal;
|
|
101
|
+
if (signal?.aborted) {
|
|
99
102
|
return { ok: false, rendered: false, errors: [{ message: 'aborted' }] };
|
|
100
103
|
}
|
|
101
104
|
const renderer = opts.getRenderer();
|
package/src/present-view.test.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
2
|
import type { ToolContext, ToolDef } from '@moxxy/sdk';
|
|
3
|
+
import { assertDefined } from '@moxxy/sdk';
|
|
3
4
|
import { defaultViewRenderer } from '@moxxy/core';
|
|
4
5
|
import { buildViewPlugin, type PresentViewResult, type ViewSurface } from './index.js';
|
|
5
6
|
|
|
@@ -50,10 +51,15 @@ describe('present_view — output', () => {
|
|
|
50
51
|
|
|
51
52
|
it('reports no active renderer', () => {
|
|
52
53
|
const plugin = buildViewPlugin({ getRenderer: () => null });
|
|
53
|
-
const
|
|
54
|
+
const tools = plugin.tools;
|
|
55
|
+
assertDefined(tools, 'plugin declares tools');
|
|
56
|
+
const t = tools.find((x) => x.name === 'present_view');
|
|
57
|
+
assertDefined(t, 'plugin registers present_view');
|
|
54
58
|
const r = run(t, '<view/>');
|
|
55
59
|
expect(r.ok).toBe(false);
|
|
56
|
-
|
|
60
|
+
const err = r.errors?.[0];
|
|
61
|
+
assertDefined(err, 'a failed parse reports at least one error');
|
|
62
|
+
expect(err.message).toMatch(/no active view renderer/);
|
|
57
63
|
});
|
|
58
64
|
|
|
59
65
|
it('without a surface: rendered=false, no url/viewId', () => {
|