@nxisai/agent-harness 0.2.4 → 0.4.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/README.md +90 -2
- package/dist/a2ui/stepper.d.ts +1 -1
- package/dist/a2ui/stepper.d.ts.map +1 -1
- package/dist/a2ui/stepper.js +2 -2
- package/dist/a2ui/stepper.js.map +1 -1
- package/dist/engine.d.ts +1 -0
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +1 -0
- package/dist/engine.js.map +1 -1
- package/dist/harness/harness.d.ts +58 -0
- package/dist/harness/harness.d.ts.map +1 -1
- package/dist/harness/harness.js +179 -4
- package/dist/harness/harness.js.map +1 -1
- package/dist/harness/renderer.d.ts +30 -0
- package/dist/harness/renderer.d.ts.map +1 -1
- package/dist/harness/steps.d.ts +101 -0
- package/dist/harness/steps.d.ts.map +1 -0
- package/dist/harness/steps.js +102 -0
- package/dist/harness/steps.js.map +1 -0
- package/dist/harness/tools/action-tools.d.ts +213 -0
- package/dist/harness/tools/action-tools.d.ts.map +1 -0
- package/dist/harness/tools/action-tools.js +724 -0
- package/dist/harness/tools/action-tools.js.map +1 -0
- package/dist/harness/tools/bridge.d.ts.map +1 -1
- package/dist/harness/tools/bridge.js +28 -2
- package/dist/harness/tools/bridge.js.map +1 -1
- package/dist/harness/tools/open-page.d.ts +42 -0
- package/dist/harness/tools/open-page.d.ts.map +1 -0
- package/dist/harness/tools/open-page.js +118 -0
- package/dist/harness/tools/open-page.js.map +1 -0
- package/dist/harness/tools/pending-action.d.ts +99 -0
- package/dist/harness/tools/pending-action.d.ts.map +1 -0
- package/dist/harness/tools/pending-action.js +175 -0
- package/dist/harness/tools/pending-action.js.map +1 -0
- package/dist/harness/tools/resume.d.ts +92 -0
- package/dist/harness/tools/resume.d.ts.map +1 -0
- package/dist/harness/tools/resume.js +84 -0
- package/dist/harness/tools/resume.js.map +1 -0
- package/dist/harness/tools/schema.js +29 -2
- package/dist/harness/tools/schema.js.map +1 -1
- package/dist/harness/tools/site-actions.d.ts +81 -0
- package/dist/harness/tools/site-actions.d.ts.map +1 -0
- package/dist/harness/tools/site-actions.js +314 -0
- package/dist/harness/tools/site-actions.js.map +1 -0
- package/dist/harness/tools/site-pages.d.ts +95 -0
- package/dist/harness/tools/site-pages.d.ts.map +1 -0
- package/dist/harness/tools/site-pages.js +199 -0
- package/dist/harness/tools/site-pages.js.map +1 -0
- package/dist/harness/tools/source.d.ts +58 -3
- package/dist/harness/tools/source.d.ts.map +1 -1
- package/dist/harness/tools/source.js +88 -7
- package/dist/harness/tools/source.js.map +1 -1
- package/dist/index.d.ts +14 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -1
- package/dist/providers/proxy.d.ts +5 -0
- package/dist/providers/proxy.d.ts.map +1 -1
- package/dist/providers/proxy.js +16 -1
- package/dist/providers/proxy.js.map +1 -1
- package/dist/providers/types.d.ts +15 -0
- package/dist/providers/types.d.ts.map +1 -1
- package/dist/types.d.ts +7 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/ui/agent-fullscreen.d.ts.map +1 -1
- package/dist/ui/agent-fullscreen.js +835 -36
- package/dist/ui/agent-fullscreen.js.map +1 -1
- package/dist/ui/index.d.ts +2 -0
- package/dist/ui/index.d.ts.map +1 -1
- package/dist/ui/index.js +1 -0
- package/dist/ui/index.js.map +1 -1
- package/dist/ui/journey.d.ts +33 -0
- package/dist/ui/journey.d.ts.map +1 -0
- package/dist/ui/journey.js +27 -0
- package/dist/ui/journey.js.map +1 -0
- package/dist/ui/tailwind.generated.d.ts +1 -1
- package/dist/ui/tailwind.generated.d.ts.map +1 -1
- package/dist/ui/tailwind.generated.js +1 -1
- package/dist/ui/tailwind.generated.js.map +1 -1
- package/dist/webmcp-contract.d.ts +6 -2
- package/dist/webmcp-contract.d.ts.map +1 -1
- package/docs/remote-providers.md +7 -1
- package/package.json +1 -1
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Finishing, on arrival, a call that was confirmed on another page.
|
|
3
|
+
*
|
|
4
|
+
* The decisions here are the awkward ones — is there a call waiting, did the
|
|
5
|
+
* page's script ever register the form, does the live form want more than the
|
|
6
|
+
* record said — and they are worth testing without a browser. So the work is
|
|
7
|
+
* a function that returns what happened, and the caller draws it.
|
|
8
|
+
*
|
|
9
|
+
* @module
|
|
10
|
+
*/
|
|
11
|
+
import { type PendingAction, type PendingOptions } from './pending-action.js';
|
|
12
|
+
import type { ModelContext, ModelContextToolInfo, ModelContextToolResponse } from '../../types.js';
|
|
13
|
+
/** What became of the call that was waiting for this page. */
|
|
14
|
+
export type ResumeOutcome =
|
|
15
|
+
/** Nothing was waiting for this page. The ordinary case. */
|
|
16
|
+
{
|
|
17
|
+
status: 'none';
|
|
18
|
+
}
|
|
19
|
+
/** WebMCP never appeared, so the form's tool could not be reached. */
|
|
20
|
+
| {
|
|
21
|
+
status: 'no-registry';
|
|
22
|
+
pending: PendingAction;
|
|
23
|
+
}
|
|
24
|
+
/** The page never registered a tool for this action. */
|
|
25
|
+
| {
|
|
26
|
+
status: 'no-tool';
|
|
27
|
+
pending: PendingAction;
|
|
28
|
+
}
|
|
29
|
+
/** The live form requires values the record did not carry. */
|
|
30
|
+
| {
|
|
31
|
+
status: 'incomplete';
|
|
32
|
+
pending: PendingAction;
|
|
33
|
+
tool: ModelContextToolInfo;
|
|
34
|
+
missing: string[];
|
|
35
|
+
}
|
|
36
|
+
/** Submitted. */
|
|
37
|
+
| {
|
|
38
|
+
status: 'submitted';
|
|
39
|
+
pending: PendingAction;
|
|
40
|
+
tool: ModelContextToolInfo;
|
|
41
|
+
text: string;
|
|
42
|
+
}
|
|
43
|
+
/** The tool ran and reported a failure. */
|
|
44
|
+
| {
|
|
45
|
+
status: 'rejected';
|
|
46
|
+
pending: PendingAction;
|
|
47
|
+
tool: ModelContextToolInfo;
|
|
48
|
+
text: string;
|
|
49
|
+
}
|
|
50
|
+
/** The call threw. */
|
|
51
|
+
| {
|
|
52
|
+
status: 'failed';
|
|
53
|
+
pending: PendingAction;
|
|
54
|
+
tool: ModelContextToolInfo;
|
|
55
|
+
error: string;
|
|
56
|
+
};
|
|
57
|
+
/** Overrides, so this can be exercised without a page. */
|
|
58
|
+
export interface ResumeOptions extends PendingOptions {
|
|
59
|
+
/** The page now open. Defaults to the browser's. */
|
|
60
|
+
href?: string;
|
|
61
|
+
/** How long to wait for the registry and the tool. */
|
|
62
|
+
timeoutMs?: number;
|
|
63
|
+
/** Wait for a WebMCP registry. */
|
|
64
|
+
registry?(timeoutMs: number): Promise<ModelContext | null>;
|
|
65
|
+
/** Wait for the page's own tool for this action. */
|
|
66
|
+
pageTool?(mc: ModelContext, ref: {
|
|
67
|
+
id: string;
|
|
68
|
+
name: string;
|
|
69
|
+
}, timeoutMs: number): Promise<ModelContextToolInfo | null>;
|
|
70
|
+
/** Run the tool. */
|
|
71
|
+
exec?(tool: ModelContextToolInfo, values: Record<string, unknown>): Promise<ModelContextToolResponse>;
|
|
72
|
+
/**
|
|
73
|
+
* Called once a waiting call is found, before the waits begin.
|
|
74
|
+
*
|
|
75
|
+
* The page has just loaded and the registry may take seconds to appear, so
|
|
76
|
+
* this is where a UI opens the conversation the call came from rather than
|
|
77
|
+
* leaving the visitor looking at an unrelated thread.
|
|
78
|
+
*/
|
|
79
|
+
onStart?(pending: PendingAction): void;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Submit the call waiting for this page, if there is one.
|
|
83
|
+
*
|
|
84
|
+
* Runs while the page is still loading, so neither the registry nor the
|
|
85
|
+
* page's tools exist yet — the script that installs them is usually still
|
|
86
|
+
* being fetched. Both waits are needed, and in this order.
|
|
87
|
+
*
|
|
88
|
+
* @param opts - Page, timeouts, and the seams tests replace.
|
|
89
|
+
* @returns What happened, for the caller to render.
|
|
90
|
+
*/
|
|
91
|
+
export declare function resumePendingAction(opts?: ResumeOptions): Promise<ResumeOutcome>;
|
|
92
|
+
//# sourceMappingURL=resume.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resume.d.ts","sourceRoot":"","sources":["../../../src/harness/tools/resume.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAqB,KAAK,aAAa,EAAE,KAAK,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAMjG,OAAO,KAAK,EAAE,YAAY,EAAE,oBAAoB,EAAE,wBAAwB,EAAE,MAAM,gBAAgB,CAAC;AAKnG,8DAA8D;AAC9D,MAAM,MAAM,aAAa;AACvB,4DAA4D;AAC1D;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE;AACpB,sEAAsE;GACpE;IAAE,MAAM,EAAE,aAAa,CAAC;IAAC,OAAO,EAAE,aAAa,CAAA;CAAE;AACnD,wDAAwD;GACtD;IAAE,MAAM,EAAE,SAAS,CAAC;IAAC,OAAO,EAAE,aAAa,CAAA;CAAE;AAC/C,8DAA8D;GAC5D;IAAE,MAAM,EAAE,YAAY,CAAC;IAAC,OAAO,EAAE,aAAa,CAAC;IAAC,IAAI,EAAE,oBAAoB,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAA;CAAE;AACjG,iBAAiB;GACf;IAAE,MAAM,EAAE,WAAW,CAAC;IAAC,OAAO,EAAE,aAAa,CAAC;IAAC,IAAI,EAAE,oBAAoB,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE;AAC3F,2CAA2C;GACzC;IAAE,MAAM,EAAE,UAAU,CAAC;IAAC,OAAO,EAAE,aAAa,CAAC;IAAC,IAAI,EAAE,oBAAoB,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE;AAC1F,sBAAsB;GACpB;IAAE,MAAM,EAAE,QAAQ,CAAC;IAAC,OAAO,EAAE,aAAa,CAAC;IAAC,IAAI,EAAE,oBAAoB,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAE5F,0DAA0D;AAC1D,MAAM,WAAW,aAAc,SAAQ,cAAc;IACnD,oDAAoD;IACpD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,sDAAsD;IACtD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kCAAkC;IAClC,QAAQ,CAAC,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAAC;IAC3D,oDAAoD;IACpD,QAAQ,CAAC,CACP,EAAE,EAAE,YAAY,EAChB,GAAG,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EACjC,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC,CAAC;IACxC,oBAAoB;IACpB,IAAI,CAAC,CACH,IAAI,EAAE,oBAAoB,EAC1B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC9B,OAAO,CAAC,wBAAwB,CAAC,CAAC;IACrC;;;;;;OAMG;IACH,OAAO,CAAC,CAAC,OAAO,EAAE,aAAa,GAAG,IAAI,CAAC;CACxC;AAED;;;;;;;;;GASG;AACH,wBAAsB,mBAAmB,CAAC,IAAI,GAAE,aAAkB,GAAG,OAAO,CAAC,aAAa,CAAC,CAwD1F"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Finishing, on arrival, a call that was confirmed on another page.
|
|
3
|
+
*
|
|
4
|
+
* The decisions here are the awkward ones — is there a call waiting, did the
|
|
5
|
+
* page's script ever register the form, does the live form want more than the
|
|
6
|
+
* record said — and they are worth testing without a browser. So the work is
|
|
7
|
+
* a function that returns what happened, and the caller draws it.
|
|
8
|
+
*
|
|
9
|
+
* @module
|
|
10
|
+
*/
|
|
11
|
+
import { takePendingAction } from './pending-action.js';
|
|
12
|
+
import { awaitPageTool } from './action-tools.js';
|
|
13
|
+
import { extractResult, missingRequired } from './schema.js';
|
|
14
|
+
import { recordStep, restoreJourney } from '../steps.js';
|
|
15
|
+
import { waitForModelContext } from './source.js';
|
|
16
|
+
import { WebMcpTools } from './webmcp.js';
|
|
17
|
+
/** How long to wait for the registry, and then for the form's own tool. */
|
|
18
|
+
const WAIT_MS = 8000;
|
|
19
|
+
/**
|
|
20
|
+
* Submit the call waiting for this page, if there is one.
|
|
21
|
+
*
|
|
22
|
+
* Runs while the page is still loading, so neither the registry nor the
|
|
23
|
+
* page's tools exist yet — the script that installs them is usually still
|
|
24
|
+
* being fetched. Both waits are needed, and in this order.
|
|
25
|
+
*
|
|
26
|
+
* @param opts - Page, timeouts, and the seams tests replace.
|
|
27
|
+
* @returns What happened, for the caller to render.
|
|
28
|
+
*/
|
|
29
|
+
export async function resumePendingAction(opts = {}) {
|
|
30
|
+
const pending = takePendingAction(opts.href, opts);
|
|
31
|
+
if (!pending)
|
|
32
|
+
return { status: 'none' };
|
|
33
|
+
// The journey continues from where the other page left it.
|
|
34
|
+
restoreJourney(pending.steps);
|
|
35
|
+
recordStep({ id: 'navigate', label: 'Opened the page', status: 'done' });
|
|
36
|
+
recordStep({ id: 'arrive', label: 'Found the form', status: 'active' });
|
|
37
|
+
opts.onStart?.(pending);
|
|
38
|
+
const timeoutMs = opts.timeoutMs ?? WAIT_MS;
|
|
39
|
+
const waitForRegistry = opts.registry ?? ((ms) => waitForModelContext(ms));
|
|
40
|
+
const mc = await waitForRegistry(timeoutMs);
|
|
41
|
+
if (!mc) {
|
|
42
|
+
recordStep({ id: 'arrive', label: 'This page has no agent tools', status: 'failed' });
|
|
43
|
+
return { status: 'no-registry', pending };
|
|
44
|
+
}
|
|
45
|
+
const findTool = opts.pageTool ?? ((c, ref, ms) => awaitPageTool(c, ref, ms));
|
|
46
|
+
const tool = await findTool(mc, { id: pending.actionId, name: pending.pageToolName }, timeoutMs);
|
|
47
|
+
if (!tool) {
|
|
48
|
+
recordStep({ id: 'arrive', label: 'Could not find the form here', status: 'failed' });
|
|
49
|
+
return { status: 'no-tool', pending };
|
|
50
|
+
}
|
|
51
|
+
recordStep({ id: 'arrive', label: 'Found the form', status: 'done' });
|
|
52
|
+
// The record says only what the crawler recorded; the form in front of us
|
|
53
|
+
// says what it actually insists on. Now that its page is open, ask rather
|
|
54
|
+
// than submit a half-filled form on someone's behalf.
|
|
55
|
+
const missing = missingRequired(tool, pending.values);
|
|
56
|
+
if (missing.length) {
|
|
57
|
+
recordStep({
|
|
58
|
+
id: 'collect',
|
|
59
|
+
label: 'This form needs more',
|
|
60
|
+
status: 'active',
|
|
61
|
+
detail: missing.join(', '),
|
|
62
|
+
});
|
|
63
|
+
return { status: 'incomplete', pending, tool, missing };
|
|
64
|
+
}
|
|
65
|
+
recordStep({ id: 'fill', label: 'Filling the form', status: 'active' });
|
|
66
|
+
const run = opts.exec ?? ((t, values) => new WebMcpTools().execTool(t, t.name, values));
|
|
67
|
+
try {
|
|
68
|
+
const res = await run(tool, pending.values);
|
|
69
|
+
const text = extractResult(res);
|
|
70
|
+
if (res?.isError) {
|
|
71
|
+
recordStep({ id: 'submit', label: 'It did not go through', status: 'failed', detail: text });
|
|
72
|
+
return { status: 'rejected', pending, tool, text };
|
|
73
|
+
}
|
|
74
|
+
recordStep({ id: 'fill', label: 'Filled the form', status: 'done' });
|
|
75
|
+
recordStep({ id: 'submit', label: 'Submitted', status: 'done' });
|
|
76
|
+
return { status: 'submitted', pending, tool, text };
|
|
77
|
+
}
|
|
78
|
+
catch (err) {
|
|
79
|
+
const error = String(err?.message || err);
|
|
80
|
+
recordStep({ id: 'submit', label: 'It did not go through', status: 'failed', detail: error });
|
|
81
|
+
return { status: 'failed', pending, tool, error };
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=resume.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resume.js","sourceRoot":"","sources":["../../../src/harness/tools/resume.ts"],"names":[],"mappings":"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"}
|
|
@@ -107,10 +107,10 @@ export function extractResult(res) {
|
|
|
107
107
|
if (res == null)
|
|
108
108
|
return '';
|
|
109
109
|
if (typeof res === 'string')
|
|
110
|
-
return res;
|
|
110
|
+
return unwrapResponse(res);
|
|
111
111
|
if (Array.isArray(res.content)) {
|
|
112
112
|
return res.content
|
|
113
|
-
.map((c) => (c && c.text) || '')
|
|
113
|
+
.map((c) => unwrapResponse((c && c.text) || ''))
|
|
114
114
|
.filter(Boolean)
|
|
115
115
|
.join('\n');
|
|
116
116
|
}
|
|
@@ -122,6 +122,33 @@ export function extractResult(res) {
|
|
|
122
122
|
}
|
|
123
123
|
}
|
|
124
124
|
// ── internals ───────────────────────────────────────────────────────────────
|
|
125
|
+
/**
|
|
126
|
+
* A response the registry stringified rather than passed through.
|
|
127
|
+
*
|
|
128
|
+
* A tool may answer with a response object, and a registry is free to hand
|
|
129
|
+
* that back as JSON in a single text block — which puts `{"content":[{"type":
|
|
130
|
+
* "text"…` in front of the user where a sentence belongs. Only a shape that is
|
|
131
|
+
* plainly a tool response is unwrapped; a tool whose own result happens to be
|
|
132
|
+
* JSON keeps it.
|
|
133
|
+
*/
|
|
134
|
+
function unwrapResponse(text) {
|
|
135
|
+
const trimmed = text.trim();
|
|
136
|
+
if (!trimmed.startsWith('{') || !trimmed.includes('"content"'))
|
|
137
|
+
return text;
|
|
138
|
+
try {
|
|
139
|
+
const parsed = JSON.parse(trimmed);
|
|
140
|
+
if (!Array.isArray(parsed.content))
|
|
141
|
+
return text;
|
|
142
|
+
const inner = parsed.content
|
|
143
|
+
.map((c) => (c && c.text) || '')
|
|
144
|
+
.filter(Boolean)
|
|
145
|
+
.join('\n');
|
|
146
|
+
return inner || text;
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
return text;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
125
152
|
/**
|
|
126
153
|
* Give every schema node a `type`, which the chat template requires.
|
|
127
154
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../../src/harness/tools/schema.ts"],"names":[],"mappings":"AAeA;;;;;;;;;;GAUG;AACH,MAAM,UAAU,UAAU,CAAC,IAA0B;IACnD,IAAI,GAAG,GACL,IAAI,IAAI,CAAC,IAAI,CAAC,WAAW,IAAI,IAAI,CAAC,YAAY,IAAI,IAAI,CAAC,UAAU,IAAI,IAAI,CAAC,MAAM,CAAC,CAAC;IACpF,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;QAC5B,IAAI,CAAC;YACH,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACxB,CAAC;QAAC,MAAM,CAAC;YACP,GAAG,GAAG,SAAS,CAAC;QAClB,CAAC;IACH,CAAC;IACD,MAAM,MAAM,GAAe,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;IACrE,2EAA2E;IAC3E,0DAA0D;IAC1D,MAAM,KAAK,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IACtE,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;QACjB,MAAM,MAAM,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC;QACnC,IAAI,MAAM,CAAC,MAAM;YAAE,MAAM,CAAC,QAAQ,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,IAAI,EAAE,CAAC,EAAE,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;IAC7F,CAAC;IACD,6EAA6E;IAC7E,8EAA8E;IAC9E,qDAAqD;IACrD,OAAO,gBAAgB,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;AACxC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,eAAe,CAAC,IAA0B,EAAE,IAAyC;IACnG,MAAM,QAAQ,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC,QAAQ,IAAI,EAAE,CAAC;IACjD,OAAO,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE;QAC3B,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QACrC,OAAO,CAAC,KAAK,SAAS,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACrF,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,aAAa,CAC3B,IAAyC,EACzC,MAAkB;IAElB,MAAM,KAAK,GAAG,MAAM,IAAI,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAChF,IAAI,CAAC,KAAK,CAAC,MAAM,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,IAAI,EAAE,CAAC;IAC9C,MAAM,IAAI,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC;IAC9E,MAAM,IAAI,GAAG,CAAC,CAAS,EAAE,EAAE,CACzB,MAAM,CAAC,CAAC,CAAC;SACN,WAAW,EAAE;SACb,KAAK,CAAC,YAAY,CAAC;SACnB,MAAM,CAAC,OAAO,CAAC,CAAC;IACrB,MAAM,GAAG,GAA4B,EAAE,CAAC;IACxC,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;QAChD,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;YACxB,GAAG,CAAC,GAAG,CAAC,GAAG,SAAS,CAAC,KAAK,EAAE,MAAM,CAAC,UAAW,CAAC,GAAG,CAAC,CAAC,CAAC;YACrD,SAAS;QACX,CAAC;QACD,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC;QACrB,IAAI,IAAI,GAAuB,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC;QACjE,IAAI,CAAC,IAAI;YAAE,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC,CAAC;QACtF,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;YACjC,IAAI,SAAS,GAAG,CAAC,CAAC;YAClB,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;gBACtB,MAAM,KAAK,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;gBACzD,IAAI,KAAK,GAAG,SAAS,EAAE,CAAC;oBACtB,SAAS,GAAG,KAAK,CAAC;oBAClB,IAAI,GAAG,CAAC,CAAC;gBACX,CAAC;YACH,CAAC;YACD,IAAI,SAAS,KAAK,CAAC;gBAAE,IAAI,GAAG,SAAS,CAAC;QACxC,CAAC;QACD,MAAM,SAAS,GAAG,IAAI,IAAI,GAAG,CAAC;QAC9B,GAAG,CAAC,SAAS,CAAC,GAAG,SAAS,CAAC,KAAK,EAAE,MAAM,CAAC,UAAW,CAAC,SAAS,CAAC,CAAC,CAAC;IACnE,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,aAAa,CAAC,GAAyD;IACrF,IAAI,GAAG,IAAI,IAAI;QAAE,OAAO,EAAE,CAAC;IAC3B,IAAI,OAAO,GAAG,KAAK,QAAQ;QAAE,OAAO,GAAG,CAAC;
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../../src/harness/tools/schema.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fetches every Schema.org Action on the site through typed retrieval.
|
|
3
|
+
*
|
|
4
|
+
* The page's own script registers tools only for the actions in the current
|
|
5
|
+
* page's JSON-LD. The site's MCP server holds them all, so an agent opened on
|
|
6
|
+
* any page can learn what the rest of the site offers. This module fetches and
|
|
7
|
+
* normalises that list; turning it into tools is a separate step.
|
|
8
|
+
*
|
|
9
|
+
* Records arrive with expanded IRI keys (`https://schema.org/name`) where a
|
|
10
|
+
* page carries compact ones (`name`), so both spellings are read, as the
|
|
11
|
+
* page's script does.
|
|
12
|
+
*
|
|
13
|
+
* @module
|
|
14
|
+
*/
|
|
15
|
+
import type { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
16
|
+
import type { McpServerInfo } from '../../types.js';
|
|
17
|
+
import type { NxisAction } from '../../webmcp-contract.js';
|
|
18
|
+
/**
|
|
19
|
+
* Read one typed-retrieval record as an action.
|
|
20
|
+
*
|
|
21
|
+
* @param raw - A record as returned by `nxis_query`.
|
|
22
|
+
* @returns The action with bare types and compact keys, or null when the record
|
|
23
|
+
* is not an Action or lacks an `@id`, a name, or a target with an HTTP method.
|
|
24
|
+
*/
|
|
25
|
+
export declare function parseAction(raw: unknown): NxisAction | null;
|
|
26
|
+
/**
|
|
27
|
+
* Whether two URLs name the same page, ignoring the query and fragment.
|
|
28
|
+
*
|
|
29
|
+
* A form is the same form whether it was reached with tracking parameters or
|
|
30
|
+
* without.
|
|
31
|
+
*/
|
|
32
|
+
export declare function samePage(a: string | undefined, b: string | undefined): boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Whether an action's form is on the page currently open.
|
|
35
|
+
*
|
|
36
|
+
* @param action - The action to place.
|
|
37
|
+
* @param href - The current page. Defaults to the browser's.
|
|
38
|
+
*/
|
|
39
|
+
export declare function sameActionPage(action: NxisAction, href?: string): boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Query every record whose `@type` contains `fragment`.
|
|
42
|
+
*
|
|
43
|
+
* `schema_type: "Action"` does not yet expand to subtypes on the server, so
|
|
44
|
+
* this matches on `@type` instead and the caller filters the results. When the
|
|
45
|
+
* server supports the type hierarchy, only this function needs to change.
|
|
46
|
+
*
|
|
47
|
+
* @param client - A connected client for the site's MCP server.
|
|
48
|
+
* @param fragment - Part of the type name: `Action`, `Page`.
|
|
49
|
+
* @returns Every matching record, across the server's result pages.
|
|
50
|
+
*/
|
|
51
|
+
export declare function queryTypedRecords(client: Client, fragment: string): Promise<unknown[]>;
|
|
52
|
+
/** Options for {@link loadSiteActions}. */
|
|
53
|
+
export interface SiteActionsOptions {
|
|
54
|
+
/**
|
|
55
|
+
* The origin actions must belong to. Defaults to the page's own.
|
|
56
|
+
*
|
|
57
|
+
* An action whose page is on any other origin is dropped.
|
|
58
|
+
*/
|
|
59
|
+
origin?: string;
|
|
60
|
+
/** Where the list is cached between page loads. Defaults to `sessionStorage`; null disables. */
|
|
61
|
+
storage?: Storage | null;
|
|
62
|
+
/** How long a cached list is reused, in milliseconds. Defaults to ten minutes. */
|
|
63
|
+
ttlMs?: number;
|
|
64
|
+
/** The clock, for tests. */
|
|
65
|
+
now?: () => number;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Every Action on the site that can be carried out from this origin.
|
|
69
|
+
*
|
|
70
|
+
* Reuses a list fetched in the same tab within the last ten minutes, so moving
|
|
71
|
+
* between pages costs one request per session rather than one per page.
|
|
72
|
+
*
|
|
73
|
+
* @param client - A connected client for the site's MCP server. The query is a
|
|
74
|
+
* public read and needs no key.
|
|
75
|
+
* @param info - The server's details; its endpoint keys the cache.
|
|
76
|
+
* @param opts - Origin, cache and clock overrides.
|
|
77
|
+
* @returns The actions, each once, in the order the server returned them.
|
|
78
|
+
* @throws When the query itself fails. The caller decides whether that matters.
|
|
79
|
+
*/
|
|
80
|
+
export declare function loadSiteActions(client: Client, info: Pick<McpServerInfo, 'endpoint'>, opts?: SiteActionsOptions): Promise<NxisAction[]>;
|
|
81
|
+
//# sourceMappingURL=site-actions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"site-actions.d.ts","sourceRoot":"","sources":["../../../src/harness/tools/site-actions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AACxE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AACpD,OAAO,KAAK,EACV,UAAU,EAKX,MAAM,0BAA0B,CAAC;AA8BlC;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,OAAO,GAAG,UAAU,GAAG,IAAI,CAyB3D;AA2DD;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,CAAC,EAAE,MAAM,GAAG,SAAS,EAAE,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAS9E;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAGzE;AA8CD;;;;;;;;;;GAUG;AACH,wBAAsB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,CA6B5F;AAoCD,2CAA2C;AAC3C,MAAM,WAAW,kBAAkB;IACjC;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gGAAgG;IAChG,OAAO,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IACzB,kFAAkF;IAClF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,4BAA4B;IAC5B,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,eAAe,CACnC,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,IAAI,CAAC,aAAa,EAAE,UAAU,CAAC,EACrC,IAAI,GAAE,kBAAuB,GAC5B,OAAO,CAAC,UAAU,EAAE,CAAC,CA8BvB"}
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
const SCHEMA_NS = 'https://schema.org/';
|
|
2
|
+
const NXIS_NS = 'https://nxis.ai/ns#';
|
|
3
|
+
/** The server caps a page of results at this many. */
|
|
4
|
+
const PAGE_LIMIT = 100;
|
|
5
|
+
/** A guard against a server that keeps returning page tokens. */
|
|
6
|
+
const MAX_PAGES = 10;
|
|
7
|
+
/** How long a fetched list is reused across page loads in the same tab. */
|
|
8
|
+
const CACHE_TTL_MS = 10 * 60 * 1000;
|
|
9
|
+
const CACHE_PREFIX = 'nxis:site-actions:v1:';
|
|
10
|
+
const VALUE_TYPES = ['string', 'number', 'integer', 'boolean', 'date', 'file'];
|
|
11
|
+
const isObj = (v) => !!v && typeof v === 'object' && !Array.isArray(v);
|
|
12
|
+
/** A schema.org property, by compact term or expanded IRI. */
|
|
13
|
+
const schemaValue = (o, prop) => o[prop] ?? o[SCHEMA_NS + prop];
|
|
14
|
+
/** An nxis extension property, by compact term or expanded IRI. */
|
|
15
|
+
const nxisValue = (o, prop) => o[`nxis:${prop}`] ?? o[NXIS_NS + prop];
|
|
16
|
+
const str = (v) => (typeof v === 'string' ? v : undefined);
|
|
17
|
+
const num = (v) => (typeof v === 'number' ? v : undefined);
|
|
18
|
+
/** `https://schema.org/InteractAction` → `InteractAction`. */
|
|
19
|
+
const bareType = (t) => t.replace(/^.*[/#]/, '');
|
|
20
|
+
/**
|
|
21
|
+
* Read one typed-retrieval record as an action.
|
|
22
|
+
*
|
|
23
|
+
* @param raw - A record as returned by `nxis_query`.
|
|
24
|
+
* @returns The action with bare types and compact keys, or null when the record
|
|
25
|
+
* is not an Action or lacks an `@id`, a name, or a target with an HTTP method.
|
|
26
|
+
*/
|
|
27
|
+
export function parseAction(raw) {
|
|
28
|
+
if (!isObj(raw))
|
|
29
|
+
return null;
|
|
30
|
+
const rawType = str(raw['@type']);
|
|
31
|
+
if (!rawType)
|
|
32
|
+
return null;
|
|
33
|
+
const type = bareType(rawType);
|
|
34
|
+
if (!type.endsWith('Action'))
|
|
35
|
+
return null;
|
|
36
|
+
const id = str(raw['@id']);
|
|
37
|
+
const name = str(schemaValue(raw, 'name'));
|
|
38
|
+
const target = parseEntryPoint(schemaValue(raw, 'target'));
|
|
39
|
+
if (!id || !name || !target)
|
|
40
|
+
return null;
|
|
41
|
+
// Inputs live under `nxis:input`: schema.org has no `input` on Action.
|
|
42
|
+
const rawInput = nxisValue(raw, 'input');
|
|
43
|
+
const input = (Array.isArray(rawInput) ? rawInput : [])
|
|
44
|
+
.map(parseInput)
|
|
45
|
+
.filter((i) => i !== null);
|
|
46
|
+
const action = { '@type': type, '@id': id, name, target };
|
|
47
|
+
const description = str(schemaValue(raw, 'description'));
|
|
48
|
+
if (description)
|
|
49
|
+
action.description = description;
|
|
50
|
+
if (input.length)
|
|
51
|
+
action.input = input;
|
|
52
|
+
const extractedAt = str(nxisValue(raw, 'extractedAt'));
|
|
53
|
+
if (extractedAt)
|
|
54
|
+
action['nxis:extractedAt'] = extractedAt;
|
|
55
|
+
return action;
|
|
56
|
+
}
|
|
57
|
+
function parseEntryPoint(raw) {
|
|
58
|
+
if (!isObj(raw))
|
|
59
|
+
return null;
|
|
60
|
+
const httpMethod = str(schemaValue(raw, 'httpMethod'))?.toUpperCase();
|
|
61
|
+
if (!httpMethod)
|
|
62
|
+
return null;
|
|
63
|
+
const target = { '@type': 'EntryPoint', httpMethod };
|
|
64
|
+
const urlTemplate = str(schemaValue(raw, 'urlTemplate'));
|
|
65
|
+
if (urlTemplate)
|
|
66
|
+
target.urlTemplate = urlTemplate;
|
|
67
|
+
const encodingType = str(schemaValue(raw, 'encodingType'));
|
|
68
|
+
if (encodingType)
|
|
69
|
+
target.encodingType = encodingType;
|
|
70
|
+
const kind = nxisValue(raw, 'targetKind');
|
|
71
|
+
if (kind === 'selfPage' || kind === 'clientSide')
|
|
72
|
+
target['nxis:targetKind'] = kind;
|
|
73
|
+
const pageUrl = str(nxisValue(raw, 'pageUrl'));
|
|
74
|
+
if (pageUrl)
|
|
75
|
+
target['nxis:pageUrl'] = pageUrl;
|
|
76
|
+
if (nxisValue(raw, 'jsHandled') === true)
|
|
77
|
+
target['nxis:jsHandled'] = true;
|
|
78
|
+
return target;
|
|
79
|
+
}
|
|
80
|
+
function parseInput(raw) {
|
|
81
|
+
if (!isObj(raw))
|
|
82
|
+
return null;
|
|
83
|
+
const valueName = str(schemaValue(raw, 'valueName'));
|
|
84
|
+
if (!valueName)
|
|
85
|
+
return null;
|
|
86
|
+
const declared = str(nxisValue(raw, 'valueType'));
|
|
87
|
+
const spec = {
|
|
88
|
+
'@type': 'PropertyValueSpecification',
|
|
89
|
+
valueName,
|
|
90
|
+
'nxis:valueType': declared && VALUE_TYPES.includes(declared) ? declared : 'string',
|
|
91
|
+
};
|
|
92
|
+
const description = str(schemaValue(raw, 'description'));
|
|
93
|
+
if (description)
|
|
94
|
+
spec.description = description;
|
|
95
|
+
if (schemaValue(raw, 'valueRequired') === true)
|
|
96
|
+
spec.valueRequired = true;
|
|
97
|
+
const defaultValue = str(schemaValue(raw, 'defaultValue'));
|
|
98
|
+
if (defaultValue !== undefined)
|
|
99
|
+
spec.defaultValue = defaultValue;
|
|
100
|
+
for (const key of ['valueMinLength', 'valueMaxLength', 'minValue', 'maxValue', 'stepValue']) {
|
|
101
|
+
const v = num(schemaValue(raw, key));
|
|
102
|
+
if (v !== undefined)
|
|
103
|
+
spec[key] = v;
|
|
104
|
+
}
|
|
105
|
+
const valuePattern = str(schemaValue(raw, 'valuePattern'));
|
|
106
|
+
if (valuePattern)
|
|
107
|
+
spec.valuePattern = valuePattern;
|
|
108
|
+
if (schemaValue(raw, 'multipleValues') === true)
|
|
109
|
+
spec.multipleValues = true;
|
|
110
|
+
if (schemaValue(raw, 'readonlyValue') === true)
|
|
111
|
+
spec.readonlyValue = true;
|
|
112
|
+
const rawOptions = nxisValue(raw, 'valueOption');
|
|
113
|
+
const options = [];
|
|
114
|
+
for (const o of Array.isArray(rawOptions) ? rawOptions : []) {
|
|
115
|
+
if (!isObj(o))
|
|
116
|
+
continue;
|
|
117
|
+
const value = str(schemaValue(o, 'value'));
|
|
118
|
+
if (value === undefined)
|
|
119
|
+
continue;
|
|
120
|
+
const label = str(schemaValue(o, 'name'));
|
|
121
|
+
options.push(label ? { '@type': 'nxis:ValueOption', value, name: label } : { '@type': 'nxis:ValueOption', value });
|
|
122
|
+
}
|
|
123
|
+
if (options.length)
|
|
124
|
+
spec['nxis:valueOption'] = options;
|
|
125
|
+
return spec;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Whether two URLs name the same page, ignoring the query and fragment.
|
|
129
|
+
*
|
|
130
|
+
* A form is the same form whether it was reached with tracking parameters or
|
|
131
|
+
* without.
|
|
132
|
+
*/
|
|
133
|
+
export function samePage(a, b) {
|
|
134
|
+
if (!a || !b)
|
|
135
|
+
return false;
|
|
136
|
+
try {
|
|
137
|
+
const x = new URL(a, b);
|
|
138
|
+
const y = new URL(b, a);
|
|
139
|
+
return x.origin === y.origin && x.pathname === y.pathname;
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Whether an action's form is on the page currently open.
|
|
147
|
+
*
|
|
148
|
+
* @param action - The action to place.
|
|
149
|
+
* @param href - The current page. Defaults to the browser's.
|
|
150
|
+
*/
|
|
151
|
+
export function sameActionPage(action, href) {
|
|
152
|
+
const here = href ?? (typeof location === 'undefined' ? undefined : location.href);
|
|
153
|
+
return samePage(action.target['nxis:pageUrl'], here);
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Whether an action can be carried out from this site.
|
|
157
|
+
*
|
|
158
|
+
* Actions run by filling the form on their own page, so the harness may later
|
|
159
|
+
* navigate to `nxis:pageUrl`. A record naming another origin, whether stale or
|
|
160
|
+
* tampered with, must never send the visitor there.
|
|
161
|
+
*/
|
|
162
|
+
function onOrigin(action, origin) {
|
|
163
|
+
const pageUrl = action.target['nxis:pageUrl'];
|
|
164
|
+
if (!pageUrl)
|
|
165
|
+
return false;
|
|
166
|
+
try {
|
|
167
|
+
return new URL(pageUrl, origin).origin === origin;
|
|
168
|
+
}
|
|
169
|
+
catch {
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* The records in a tool result, whether structured or JSON text.
|
|
175
|
+
*
|
|
176
|
+
* Typed loosely because the SDK's result type also admits the legacy
|
|
177
|
+
* `{ toolResult }` shape, which carries neither.
|
|
178
|
+
*/
|
|
179
|
+
function readResults(result) {
|
|
180
|
+
const text = (Array.isArray(result.content) ? result.content : [])
|
|
181
|
+
.map((c) => (isObj(c) && typeof c.text === 'string' ? c.text : ''))
|
|
182
|
+
.join('');
|
|
183
|
+
if (result.isError)
|
|
184
|
+
throw new Error(text || 'nxis_query failed');
|
|
185
|
+
let body = result.structuredContent;
|
|
186
|
+
if (!isObj(body)) {
|
|
187
|
+
try {
|
|
188
|
+
body = JSON.parse(text);
|
|
189
|
+
}
|
|
190
|
+
catch {
|
|
191
|
+
throw new Error('nxis_query returned a response that is not JSON');
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
const b = body;
|
|
195
|
+
return {
|
|
196
|
+
results: Array.isArray(b.results) ? b.results : [],
|
|
197
|
+
nextPageToken: str(b.next_page_token) || undefined,
|
|
198
|
+
total: num(b.total),
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Query every record whose `@type` contains `fragment`.
|
|
203
|
+
*
|
|
204
|
+
* `schema_type: "Action"` does not yet expand to subtypes on the server, so
|
|
205
|
+
* this matches on `@type` instead and the caller filters the results. When the
|
|
206
|
+
* server supports the type hierarchy, only this function needs to change.
|
|
207
|
+
*
|
|
208
|
+
* @param client - A connected client for the site's MCP server.
|
|
209
|
+
* @param fragment - Part of the type name: `Action`, `Page`.
|
|
210
|
+
* @returns Every matching record, across the server's result pages.
|
|
211
|
+
*/
|
|
212
|
+
export async function queryTypedRecords(client, fragment) {
|
|
213
|
+
const records = [];
|
|
214
|
+
let pageToken;
|
|
215
|
+
let total;
|
|
216
|
+
for (let page = 0; page < MAX_PAGES; page++) {
|
|
217
|
+
const result = await client.callTool({
|
|
218
|
+
name: 'nxis_query',
|
|
219
|
+
arguments: {
|
|
220
|
+
filter: {
|
|
221
|
+
schema_type: '',
|
|
222
|
+
field_filters: [{ path: '@type', operator: 'CONTAINS', value: fragment }],
|
|
223
|
+
},
|
|
224
|
+
limit: PAGE_LIMIT,
|
|
225
|
+
...(pageToken ? { page_token: pageToken } : {}),
|
|
226
|
+
},
|
|
227
|
+
});
|
|
228
|
+
const read = readResults(result);
|
|
229
|
+
records.push(...read.results);
|
|
230
|
+
total = read.total ?? total;
|
|
231
|
+
pageToken = read.nextPageToken;
|
|
232
|
+
if (!pageToken)
|
|
233
|
+
break;
|
|
234
|
+
}
|
|
235
|
+
if (total !== undefined && records.length < total) {
|
|
236
|
+
console.warn(`[nxis-mcp] site ${fragment} records truncated: fetched ${records.length} of ${total} — ` +
|
|
237
|
+
'the server returned no further pages');
|
|
238
|
+
}
|
|
239
|
+
return records;
|
|
240
|
+
}
|
|
241
|
+
/** The tab's session storage, or null where it is unavailable or blocked. */
|
|
242
|
+
function defaultStorage() {
|
|
243
|
+
try {
|
|
244
|
+
return typeof sessionStorage === 'undefined' ? null : sessionStorage;
|
|
245
|
+
}
|
|
246
|
+
catch {
|
|
247
|
+
return null;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
function readCache(storage, key, now, ttl) {
|
|
251
|
+
if (!storage)
|
|
252
|
+
return null;
|
|
253
|
+
try {
|
|
254
|
+
const entry = JSON.parse(storage.getItem(key) || 'null');
|
|
255
|
+
if (!entry || !Array.isArray(entry.actions) || typeof entry.fetchedAt !== 'number')
|
|
256
|
+
return null;
|
|
257
|
+
if (now - entry.fetchedAt > ttl || entry.fetchedAt > now)
|
|
258
|
+
return null;
|
|
259
|
+
return entry.actions;
|
|
260
|
+
}
|
|
261
|
+
catch {
|
|
262
|
+
return null;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
function writeCache(storage, key, entry) {
|
|
266
|
+
try {
|
|
267
|
+
storage?.setItem(key, JSON.stringify(entry));
|
|
268
|
+
}
|
|
269
|
+
catch {
|
|
270
|
+
// Full or blocked storage costs a refetch on the next page, nothing more.
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Every Action on the site that can be carried out from this origin.
|
|
275
|
+
*
|
|
276
|
+
* Reuses a list fetched in the same tab within the last ten minutes, so moving
|
|
277
|
+
* between pages costs one request per session rather than one per page.
|
|
278
|
+
*
|
|
279
|
+
* @param client - A connected client for the site's MCP server. The query is a
|
|
280
|
+
* public read and needs no key.
|
|
281
|
+
* @param info - The server's details; its endpoint keys the cache.
|
|
282
|
+
* @param opts - Origin, cache and clock overrides.
|
|
283
|
+
* @returns The actions, each once, in the order the server returned them.
|
|
284
|
+
* @throws When the query itself fails. The caller decides whether that matters.
|
|
285
|
+
*/
|
|
286
|
+
export async function loadSiteActions(client, info, opts = {}) {
|
|
287
|
+
const origin = opts.origin ?? location.origin;
|
|
288
|
+
const storage = opts.storage === undefined ? defaultStorage() : opts.storage;
|
|
289
|
+
const now = (opts.now ?? Date.now)();
|
|
290
|
+
const key = CACHE_PREFIX + info.endpoint;
|
|
291
|
+
const cached = readCache(storage, key, now, opts.ttlMs ?? CACHE_TTL_MS);
|
|
292
|
+
if (cached)
|
|
293
|
+
return cached;
|
|
294
|
+
const seen = new Set();
|
|
295
|
+
const actions = [];
|
|
296
|
+
let offOrigin = 0;
|
|
297
|
+
for (const record of await queryTypedRecords(client, 'Action')) {
|
|
298
|
+
const action = parseAction(record);
|
|
299
|
+
if (!action || seen.has(action['@id']))
|
|
300
|
+
continue;
|
|
301
|
+
seen.add(action['@id']);
|
|
302
|
+
if (!onOrigin(action, origin)) {
|
|
303
|
+
offOrigin++;
|
|
304
|
+
continue;
|
|
305
|
+
}
|
|
306
|
+
actions.push(action);
|
|
307
|
+
}
|
|
308
|
+
if (offOrigin) {
|
|
309
|
+
console.warn(`[nxis-mcp] ignored ${offOrigin} site action(s) whose page is not on ${origin}`);
|
|
310
|
+
}
|
|
311
|
+
writeCache(storage, key, { fetchedAt: now, actions });
|
|
312
|
+
return actions;
|
|
313
|
+
}
|
|
314
|
+
//# sourceMappingURL=site-actions.js.map
|