@jini-ai/agentic 0.4.3 → 0.4.5
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/CHANGELOG.md +4 -0
- package/dist/core/a2ui/json-pointer.js +94 -73
- package/dist/core/a2ui/resolve.js +45 -23
- package/dist/core/a2ui/tree.js +8 -5
- package/dist/core/capability.js +21 -12
- package/dist/core/dom/dom-page-driver.js +89 -74
- package/dist/core/gen-ui/encoder.js +31 -31
- package/dist/core/guards.js +13 -6
- package/dist/core/handle.d.ts +13 -0
- package/dist/core/handle.js +19 -0
- package/dist/core/index.d.ts +2 -1
- package/dist/core/index.js +2 -1
- package/dist/core/mcp-ui-apps.js +12 -6
- package/dist/core/page-executor.js +131 -111
- package/dist/core/site-guide/index.d.ts +4 -0
- package/dist/core/site-guide/index.js +4 -0
- package/dist/core/site-guide/site-guide.d.ts +18 -0
- package/dist/core/site-guide/site-guide.js +162 -0
- package/dist/core/site-guide/topic-schema.d.ts +16 -0
- package/dist/core/site-guide/topic-schema.js +68 -0
- package/dist/core/site-guide/types.d.ts +83 -0
- package/dist/core/site-guide/types.js +2 -0
- package/dist/skills/install/archive.js +42 -30
- package/dist/skills/install/github.js +102 -63
- package/dist/skills/install/index.d.ts +1 -0
- package/dist/skills/install/index.js +1 -0
- package/dist/skills/install/install-service.d.ts +20 -1
- package/dist/skills/install/install-service.js +23 -11
- package/dist/skills/install/layout.d.ts +8 -0
- package/dist/skills/install/layout.js +26 -1
- package/dist/skills/install/live-registration.js +1 -0
- package/dist/skills/install/node-filesystem.js +3 -1
- package/dist/skills/install/state.js +18 -7
- package/dist/skills/install/validation.d.ts +5 -1
- package/dist/skills/install/validation.js +85 -52
- package/dist/skills/install/yauzl-archive-reader.d.ts +29 -0
- package/dist/skills/install/yauzl-archive-reader.js +31 -0
- package/package.json +4 -4
- package/dist/a2ui/agent-to-renderer.d.ts +0 -613
- package/dist/a2ui/agent-to-renderer.js +0 -191
- package/dist/a2ui/catalog.d.ts +0 -73
- package/dist/a2ui/catalog.js +0 -304
- package/dist/a2ui/common-types.d.ts +0 -401
- package/dist/a2ui/common-types.js +0 -130
- package/dist/a2ui/index.d.ts +0 -16
- package/dist/a2ui/index.js +0 -16
- package/dist/a2ui/interpreter.d.ts +0 -71
- package/dist/a2ui/interpreter.js +0 -337
- package/dist/a2ui/json-pointer.d.ts +0 -48
- package/dist/a2ui/json-pointer.js +0 -174
- package/dist/a2ui/renderer-to-agent.d.ts +0 -342
- package/dist/a2ui/renderer-to-agent.js +0 -111
- package/dist/a2ui/resolve.d.ts +0 -28
- package/dist/a2ui/resolve.js +0 -144
- package/dist/a2ui/tree.d.ts +0 -66
- package/dist/a2ui/tree.js +0 -74
- package/dist/ag-ui.d.ts +0 -82
- package/dist/ag-ui.js +0 -85
- package/dist/agui/encoder.d.ts +0 -50
- package/dist/agui/encoder.js +0 -97
- package/dist/agui/events.d.ts +0 -59
- package/dist/agui/events.js +0 -15
- package/dist/capability.d.ts +0 -81
- package/dist/capability.js +0 -79
- package/dist/dom/dom-page-driver.d.ts +0 -36
- package/dist/dom/dom-page-driver.js +0 -601
- package/dist/dom/index.d.ts +0 -17
- package/dist/dom/index.js +0 -17
- package/dist/dom/model-context.d.ts +0 -69
- package/dist/dom/model-context.js +0 -44
- package/dist/element-handles.d.ts +0 -120
- package/dist/element-handles.js +0 -39
- package/dist/gen-ui/encoder.d.ts +0 -50
- package/dist/gen-ui/encoder.js +0 -97
- package/dist/gen-ui/events.d.ts +0 -59
- package/dist/gen-ui/events.js +0 -15
- package/dist/gen-ui/index.d.ts +0 -18
- package/dist/gen-ui/index.js +0 -17
- package/dist/guards.d.ts +0 -96
- package/dist/guards.js +0 -186
- package/dist/handle.d.ts +0 -41
- package/dist/handle.js +0 -41
- package/dist/mcp-ui-apps.d.ts +0 -223
- package/dist/mcp-ui-apps.js +0 -252
- package/dist/mcp-ui.d.ts +0 -133
- package/dist/mcp-ui.js +0 -134
- package/dist/page-capabilities.d.ts +0 -14
- package/dist/page-capabilities.js +0 -129
- package/dist/page-driver.d.ts +0 -120
- package/dist/page-driver.js +0 -2
- package/dist/page-executor.d.ts +0 -94
- package/dist/page-executor.js +0 -320
- package/dist/webmcp.d.ts +0 -202
- package/dist/webmcp.js +0 -173
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
/** `stage_start` / `stage_end` share one shape; `iteration` is omitted entirely (not set to `undefined`) when absent. */
|
|
2
|
+
function encodeStage(payload, base) {
|
|
3
|
+
return {
|
|
4
|
+
...base,
|
|
5
|
+
kind: 'run.lifecycle',
|
|
6
|
+
status: payload.type === 'stage_start' ? 'pipeline_stage_started' : 'pipeline_stage_completed',
|
|
7
|
+
stageId: payload.stageId,
|
|
8
|
+
...(payload.iteration !== undefined ? { iteration: payload.iteration } : {}),
|
|
9
|
+
};
|
|
10
|
+
}
|
|
1
11
|
/**
|
|
2
12
|
* Creates a fresh `GenUiEncoder` with its own, empty tool-call correlation map.
|
|
3
13
|
* @complexity `encode` is O(1) per call (one `Map` get/set/delete); memory is O(k) in the number
|
|
@@ -10,6 +20,24 @@ export function createGenUiEncoder({ clock }, _optional = {}) {
|
|
|
10
20
|
const ts = ctx.now ? ctx.now({}) : clock.nowMs();
|
|
11
21
|
return ctx.seq !== undefined ? { runId: ctx.runId, ts, seq: ctx.seq } : { runId: ctx.runId, ts };
|
|
12
22
|
}
|
|
23
|
+
/** Resolves a `tool_result` against (and removes) its correlated `tool_use`. */
|
|
24
|
+
function encodeToolResult(payload, base) {
|
|
25
|
+
const pending = pendingToolCalls.get(payload.toolUseId);
|
|
26
|
+
pendingToolCalls.delete(payload.toolUseId);
|
|
27
|
+
return {
|
|
28
|
+
...base,
|
|
29
|
+
kind: 'tool_call',
|
|
30
|
+
// A tool_result with no matching prior tool_use (never observed, or already resolved/
|
|
31
|
+
// cleared — e.g. after a run-ending 'end' event, see below) falls back to an 'unknown'
|
|
32
|
+
// name and null args rather than throwing: the result itself is still real and worth
|
|
33
|
+
// relaying even when its originating call wasn't captured.
|
|
34
|
+
toolName: pending?.toolName ?? 'unknown',
|
|
35
|
+
args: pending?.args ?? null,
|
|
36
|
+
callId: payload.toolUseId,
|
|
37
|
+
status: payload.isError ? 'failed' : 'completed',
|
|
38
|
+
result: payload.content,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
13
41
|
function encodeAgentPayload(payload, ctx) {
|
|
14
42
|
const base = baseFields(ctx);
|
|
15
43
|
switch (payload.type) {
|
|
@@ -21,42 +49,14 @@ export function createGenUiEncoder({ clock }, _optional = {}) {
|
|
|
21
49
|
// and there is no protocol-level guarantee ids are never reused within one run.
|
|
22
50
|
pendingToolCalls.set(payload.id, { toolName: payload.name, args: payload.input });
|
|
23
51
|
return { ...base, kind: 'tool_call', toolName: payload.name, args: payload.input, callId: payload.id, status: 'started' };
|
|
24
|
-
case 'tool_result':
|
|
25
|
-
|
|
26
|
-
pendingToolCalls.delete(payload.toolUseId);
|
|
27
|
-
return {
|
|
28
|
-
...base,
|
|
29
|
-
kind: 'tool_call',
|
|
30
|
-
// A tool_result with no matching prior tool_use (never observed, or already resolved/
|
|
31
|
-
// cleared — e.g. after a run-ending 'end' event, see below) falls back to an 'unknown'
|
|
32
|
-
// name and null args rather than throwing: the result itself is still real and worth
|
|
33
|
-
// relaying even when its originating call wasn't captured.
|
|
34
|
-
toolName: pending?.toolName ?? 'unknown',
|
|
35
|
-
args: pending?.args ?? null,
|
|
36
|
-
callId: payload.toolUseId,
|
|
37
|
-
status: payload.isError ? 'failed' : 'completed',
|
|
38
|
-
result: payload.content,
|
|
39
|
-
};
|
|
40
|
-
}
|
|
52
|
+
case 'tool_result':
|
|
53
|
+
return encodeToolResult(payload, base);
|
|
41
54
|
// Generalization (see archived provenance ledger): pipeline_stage_started/completed generalized into a
|
|
42
55
|
// named-stage boundary any multi-step driver can emit, flowing through the same 'agent'
|
|
43
56
|
// channel as tool_use/tool_result.
|
|
44
57
|
case 'stage_start':
|
|
45
|
-
return {
|
|
46
|
-
...base,
|
|
47
|
-
kind: 'run.lifecycle',
|
|
48
|
-
status: 'pipeline_stage_started',
|
|
49
|
-
stageId: payload.stageId,
|
|
50
|
-
...(payload.iteration !== undefined ? { iteration: payload.iteration } : {}),
|
|
51
|
-
};
|
|
52
58
|
case 'stage_end':
|
|
53
|
-
return
|
|
54
|
-
...base,
|
|
55
|
-
kind: 'run.lifecycle',
|
|
56
|
-
status: 'pipeline_stage_completed',
|
|
57
|
-
stageId: payload.stageId,
|
|
58
|
-
...(payload.iteration !== undefined ? { iteration: payload.iteration } : {}),
|
|
59
|
-
};
|
|
59
|
+
return encodeStage(payload, base);
|
|
60
60
|
// Generalization (see archived provenance ledger): genui_surface_request/response generalized into a
|
|
61
61
|
// generic human-in-the-loop ask/answer pair.
|
|
62
62
|
case 'surface_request':
|
package/dist/core/guards.js
CHANGED
|
@@ -81,6 +81,13 @@ export function findFieldReadRefusal({ field }, _optional = {}) {
|
|
|
81
81
|
const type = field.type?.toLowerCase();
|
|
82
82
|
if (type !== undefined && DENIED_TYPES.has(type))
|
|
83
83
|
return 'denied-type';
|
|
84
|
+
if (hasDeniedAutocomplete({ field }))
|
|
85
|
+
return 'denied-autocomplete';
|
|
86
|
+
if (hasSuspiciousFieldName({ field }))
|
|
87
|
+
return 'suspicious-name';
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
function hasDeniedAutocomplete({ field }, _optional = {}) {
|
|
84
91
|
const autocomplete = field.autocomplete?.toLowerCase();
|
|
85
92
|
if (autocomplete !== undefined) {
|
|
86
93
|
// The real grammar is space-separated tokens, but a comma-joined value
|
|
@@ -90,10 +97,13 @@ export function findFieldReadRefusal({ field }, _optional = {}) {
|
|
|
90
97
|
// worked as a plain text input while looking unguarded.
|
|
91
98
|
for (const token of autocomplete.split(/[\s,]+/)) {
|
|
92
99
|
if (DENIED_AUTOCOMPLETE.has(token))
|
|
93
|
-
return
|
|
100
|
+
return true;
|
|
94
101
|
}
|
|
95
102
|
}
|
|
96
|
-
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
function hasSuspiciousFieldName({ field }, _optional = {}) {
|
|
106
|
+
return (SUSPICIOUS_NAME.test(squashSeparators(field.name ?? ''))
|
|
97
107
|
|| SUSPICIOUS_NAME.test(squashSeparators(field.id ?? ''))
|
|
98
108
|
// `name`/`id` are the *machine* names; a form builder or CMS that emits `name="field_47"`
|
|
99
109
|
// next to a visible "Card number" label left the guard nothing to judge until this existed.
|
|
@@ -102,10 +112,7 @@ export function findFieldReadRefusal({ field }, _optional = {}) {
|
|
|
102
112
|
// deletes the separators, so a join would let two innocuous labels form a trigger word across
|
|
103
113
|
// their boundary ("Address" + "Snapshot" -> "addresssnapshot", which contains "ssn"). This
|
|
104
114
|
// guard is deliberately over-inclusive, but not at the cost of matching text no page wrote.
|
|
105
|
-
|| (field.accessibleLabels ?? []).some((label) => SUSPICIOUS_NAME.test(squashSeparators(label))))
|
|
106
|
-
return 'suspicious-name';
|
|
107
|
-
}
|
|
108
|
-
return null;
|
|
115
|
+
|| (field.accessibleLabels ?? []).some((label) => SUSPICIOUS_NAME.test(squashSeparators(label))));
|
|
109
116
|
}
|
|
110
117
|
/**
|
|
111
118
|
* Why an automated fill must be refused for this field, or `null` when it is safe to write.
|
package/dist/core/handle.d.ts
CHANGED
|
@@ -40,6 +40,19 @@ export type AgentHandleProps = {
|
|
|
40
40
|
export declare function agentHandle({ handle }: {
|
|
41
41
|
handle: string;
|
|
42
42
|
}, options?: AgentHandleOptions): AgentHandleProps;
|
|
43
|
+
/**
|
|
44
|
+
* Derives a bounded handle segment from an opaque id without rewriting that id.
|
|
45
|
+
* Short valid ids keep their spelling; other ids use a stable 64-bit FNV-1a fingerprint
|
|
46
|
+
* of their UTF-16 code units. The `h-<16 hex digits>` namespace is reserved for fingerprints.
|
|
47
|
+
* Unlike list collision suffixes, this segment does not depend on filtering or row order.
|
|
48
|
+
* This is display identity, not a cryptographic or authorization primitive.
|
|
49
|
+
* @param required - The original id; arbitrary characters, empty and long strings are accepted.
|
|
50
|
+
* @returns A valid segment of at most 40 characters; callers reserve space for their fixed prefix.
|
|
51
|
+
* @complexity O(n) time in id characters, O(1) auxiliary space. No effects or errors.
|
|
52
|
+
*/
|
|
53
|
+
export declare function agentHandleSegment({ value }: {
|
|
54
|
+
value: string;
|
|
55
|
+
}, _optional?: Record<string, never>): string;
|
|
43
56
|
/**
|
|
44
57
|
* The handle for one sub-element published under `base` — `<base>-<action>`.
|
|
45
58
|
*
|
package/dist/core/handle.js
CHANGED
|
@@ -38,6 +38,25 @@ export function agentHandle({ handle }, options = {}) {
|
|
|
38
38
|
props[AGENT_PAGE_ATTRIBUTE] = options.page;
|
|
39
39
|
return props;
|
|
40
40
|
}
|
|
41
|
+
/**
|
|
42
|
+
* Derives a bounded handle segment from an opaque id without rewriting that id.
|
|
43
|
+
* Short valid ids keep their spelling; other ids use a stable 64-bit FNV-1a fingerprint
|
|
44
|
+
* of their UTF-16 code units. The `h-<16 hex digits>` namespace is reserved for fingerprints.
|
|
45
|
+
* Unlike list collision suffixes, this segment does not depend on filtering or row order.
|
|
46
|
+
* This is display identity, not a cryptographic or authorization primitive.
|
|
47
|
+
* @param required - The original id; arbitrary characters, empty and long strings are accepted.
|
|
48
|
+
* @returns A valid segment of at most 40 characters; callers reserve space for their fixed prefix.
|
|
49
|
+
* @complexity O(n) time in id characters, O(1) auxiliary space. No effects or errors.
|
|
50
|
+
*/
|
|
51
|
+
export function agentHandleSegment({ value }, _optional = {}) {
|
|
52
|
+
if (value.length <= 40 && isValidElementHandle({ handle: value }) && !/^h-[0-9a-f]{16}$/.test(value))
|
|
53
|
+
return value;
|
|
54
|
+
let hash = 0xcbf29ce484222325n;
|
|
55
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
56
|
+
hash = BigInt.asUintN(64, (hash ^ BigInt(value.charCodeAt(index))) * 0x100000001b3n);
|
|
57
|
+
}
|
|
58
|
+
return `h-${hash.toString(16).padStart(16, '0')}`;
|
|
59
|
+
}
|
|
41
60
|
/**
|
|
42
61
|
* The handle for one sub-element published under `base` — `<base>-<action>`.
|
|
43
62
|
*
|
package/dist/core/index.d.ts
CHANGED
|
@@ -21,7 +21,7 @@ export type { PageDriver, FindElementsFilter, PageSummary } from './page-driver.
|
|
|
21
21
|
export { executePageCapability, projectElementState, DEFAULT_HIGHLIGHT_MS, MAX_HIGHLIGHT_MS, MAX_STATEFUL_ELEMENTS, type FindElementsResult, type PageElementResult, type PageActivitySnapshot, type PageWriteObservation, } from './page-executor.js';
|
|
22
22
|
export { PAGE_CAPABILITIES } from './page-capabilities.js';
|
|
23
23
|
export { AGENT_ELEMENT_ATTRIBUTE, AGENT_ROLE_ATTRIBUTE, AGENT_LABEL_ATTRIBUTE, AGENT_PAGE_ATTRIBUTE, AGENT_PRIVATE_ATTRIBUTE, AGENT_ELEMENT_ROLES, isValidElementHandle, resolveHandleSelector, type AgentElementRole, type AgentElementDescriptor, type AgentElementRawState, type AgentElementState, } from './element-handles.js';
|
|
24
|
-
export { agentHandle, agentSubHandle, agentHandleProps, type AgentHandleOptions, type AgentHandleProps, type AgentHandlePropsOptions, } from './handle.js';
|
|
24
|
+
export { agentHandle, agentHandleSegment, agentSubHandle, agentHandleProps, type AgentHandleOptions, type AgentHandleProps, type AgentHandlePropsOptions, } from './handle.js';
|
|
25
25
|
export { buildAgentListHandles } from './list-handles.js';
|
|
26
26
|
export { findFieldFillRefusal, findFieldReadRefusal, describeFieldRefusal, describeFieldReadRefusal, normalizeAgentLabel, MAX_AGENT_LABEL_LENGTH, type FieldDescriptor, type FieldRefusal, type FieldReadRefusal, type NormalizedLabel, } from './guards.js';
|
|
27
27
|
/**
|
|
@@ -48,4 +48,5 @@ export { createGenUiEncoder, type GenUiEncodeContext, type GenUiEncoder, type Ge
|
|
|
48
48
|
export { MCP_UI_VIEW_METHODS, MCP_UI_HOST_NOTIFICATIONS, MCP_UI_VIEW_NOTIFICATIONS, MCP_UI_HOST_REQUESTS, MCP_UI_SANDBOX_NOTE, JINI_PAGE_ACTION_METHOD, JSON_RPC_ERROR_CODES, isJsonRpcMessage, isJsonRpcRequest, createJsonRpcRequest, createJsonRpcNotification, createJsonRpcResult, createJsonRpcError, createPageActionRequest, type JsonRpcMessage, type JsonRpcRequest, type JsonRpcNotification, type JsonRpcResponse, type JsonRpcError, } from './mcp-ui-apps.js';
|
|
49
49
|
export { defaultAgenticMessages } from "./messages.js";
|
|
50
50
|
export { createAnnotatedActionsScript } from './annotated-actions-script.js';
|
|
51
|
+
export * from './site-guide/index.js';
|
|
51
52
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/core/index.js
CHANGED
|
@@ -20,7 +20,7 @@ export { findCapability, findCapabilityInputError, availableCapabilities, } from
|
|
|
20
20
|
export { executePageCapability, projectElementState, DEFAULT_HIGHLIGHT_MS, MAX_HIGHLIGHT_MS, MAX_STATEFUL_ELEMENTS, } from './page-executor.js';
|
|
21
21
|
export { PAGE_CAPABILITIES } from './page-capabilities.js';
|
|
22
22
|
export { AGENT_ELEMENT_ATTRIBUTE, AGENT_ROLE_ATTRIBUTE, AGENT_LABEL_ATTRIBUTE, AGENT_PAGE_ATTRIBUTE, AGENT_PRIVATE_ATTRIBUTE, AGENT_ELEMENT_ROLES, isValidElementHandle, resolveHandleSelector, } from './element-handles.js';
|
|
23
|
-
export { agentHandle, agentSubHandle, agentHandleProps, } from './handle.js';
|
|
23
|
+
export { agentHandle, agentHandleSegment, agentSubHandle, agentHandleProps, } from './handle.js';
|
|
24
24
|
export { buildAgentListHandles } from './list-handles.js';
|
|
25
25
|
export { findFieldFillRefusal, findFieldReadRefusal, describeFieldRefusal, describeFieldReadRefusal, normalizeAgentLabel, MAX_AGENT_LABEL_LENGTH, } from './guards.js';
|
|
26
26
|
/**
|
|
@@ -47,4 +47,5 @@ export { createGenUiEncoder, } from './gen-ui/index.js';
|
|
|
47
47
|
export { MCP_UI_VIEW_METHODS, MCP_UI_HOST_NOTIFICATIONS, MCP_UI_VIEW_NOTIFICATIONS, MCP_UI_HOST_REQUESTS, MCP_UI_SANDBOX_NOTE, JINI_PAGE_ACTION_METHOD, JSON_RPC_ERROR_CODES, isJsonRpcMessage, isJsonRpcRequest, createJsonRpcRequest, createJsonRpcNotification, createJsonRpcResult, createJsonRpcError, createPageActionRequest, } from './mcp-ui-apps.js';
|
|
48
48
|
export { defaultAgenticMessages } from "./messages.js";
|
|
49
49
|
export { createAnnotatedActionsScript } from './annotated-actions-script.js';
|
|
50
|
+
export * from './site-guide/index.js';
|
|
50
51
|
//# sourceMappingURL=index.js.map
|
package/dist/core/mcp-ui-apps.js
CHANGED
|
@@ -194,14 +194,10 @@ export function isJsonRpcMessage(required, _optional = {}) {
|
|
|
194
194
|
const { value } = required;
|
|
195
195
|
if (!isRecord(value) || value['jsonrpc'] !== '2.0')
|
|
196
196
|
return false;
|
|
197
|
-
if (
|
|
197
|
+
if (!hasValidJsonRpcId({ value }))
|
|
198
198
|
return false;
|
|
199
199
|
if ('method' in value) {
|
|
200
|
-
|
|
201
|
-
return typeof value['method'] === 'string'
|
|
202
|
-
&& hasValidParams(value)
|
|
203
|
-
&& !('result' in value)
|
|
204
|
-
&& !('error' in value);
|
|
200
|
+
return isJsonRpcCallShape({ value });
|
|
205
201
|
}
|
|
206
202
|
// Response: correlated by `id`, carrying exactly one of `result` / `error`.
|
|
207
203
|
if (!('id' in value))
|
|
@@ -212,6 +208,16 @@ export function isJsonRpcMessage(required, _optional = {}) {
|
|
|
212
208
|
return false;
|
|
213
209
|
return hasResult || isJsonRpcErrorObject(value['error']);
|
|
214
210
|
}
|
|
211
|
+
function hasValidJsonRpcId({ value }, _optional = {}) {
|
|
212
|
+
return !('id' in value) || typeof value['id'] === 'number' || typeof value['id'] === 'string';
|
|
213
|
+
}
|
|
214
|
+
function isJsonRpcCallShape({ value }, _optional = {}) {
|
|
215
|
+
// Request or notification: `id` present or not, but never a response's members alongside.
|
|
216
|
+
return typeof value['method'] === 'string'
|
|
217
|
+
&& hasValidParams(value)
|
|
218
|
+
&& !('result' in value)
|
|
219
|
+
&& !('error' in value);
|
|
220
|
+
}
|
|
215
221
|
/** Whether a validated message expects a response (as opposed to being a notification). */
|
|
216
222
|
export function isJsonRpcRequest(required, _optional = {}) {
|
|
217
223
|
const { message } = required;
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* Nothing here touches the DOM. That is what makes the interesting half testable without a
|
|
12
12
|
* browser: "does it refuse to type into a password box" is a unit test, not an integration test.
|
|
13
13
|
*/
|
|
14
|
-
import { findCapabilityInputError, } from './capability.js';
|
|
14
|
+
import { findCapability, findCapabilityInputError, } from './capability.js';
|
|
15
15
|
import { AGENT_ELEMENT_ROLES, isValidElementHandle, } from './element-handles.js';
|
|
16
16
|
import { describeFieldReadRefusal, describeFieldRefusal, findFieldFillRefusal, findFieldReadRefusal, normalizeAgentLabel, } from './guards.js';
|
|
17
17
|
import { PAGE_CAPABILITIES } from './page-capabilities.js';
|
|
@@ -69,19 +69,28 @@ export function projectElementState({ raw }, _optional = {}) {
|
|
|
69
69
|
// what the option is called; withholding the label too would blind a caller to the page's own
|
|
70
70
|
// ontology for no reduction in what actually leaks.
|
|
71
71
|
const refusal = raw.field !== undefined ? findFieldReadRefusal({ field: raw.field }) : null;
|
|
72
|
+
const withheldText = findWithheldText({ raw, refusal });
|
|
73
|
+
const base = {
|
|
74
|
+
text: withheldText === undefined ? text.text : '',
|
|
75
|
+
textTruncated: withheldText === undefined ? text.truncated : false,
|
|
76
|
+
...(withheldText !== undefined ? { textWithheld: withheldText } : {}),
|
|
77
|
+
...projectOptionalState({ raw, refusal }),
|
|
78
|
+
};
|
|
79
|
+
return projectFieldValue({ raw, refusal, base });
|
|
80
|
+
}
|
|
81
|
+
function findWithheldText({ raw, refusal }, _optional = {}) {
|
|
72
82
|
// The one element whose `text` is NOT page-authored chrome: an editable region, whose contents
|
|
73
83
|
// are the user's data and whose driver has no `value` to report them as. It is gated by exactly
|
|
74
84
|
// the rules `value` is gated by below, including the "arrived with nothing to check it against"
|
|
75
85
|
// one — an unverifiable secret is withheld, not trusted.
|
|
76
|
-
|
|
86
|
+
return raw.textIsValue !== true
|
|
77
87
|
? undefined
|
|
78
88
|
: raw.field === undefined
|
|
79
89
|
? 'this element reported contents without the attributes needed to check them for secrets'
|
|
80
90
|
: refusal !== null ? describeFieldReadRefusal({ refusal }) : undefined;
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
...(withheldText !== undefined ? { textWithheld: withheldText } : {}),
|
|
91
|
+
}
|
|
92
|
+
function projectOptionalState({ raw, refusal }, _optional = {}) {
|
|
93
|
+
return {
|
|
85
94
|
...(raw.checked !== undefined && refusal === null ? { checked: raw.checked } : {}),
|
|
86
95
|
...(raw.disabled !== undefined ? { disabled: raw.disabled } : {}),
|
|
87
96
|
...(raw.visible !== undefined ? { visible: raw.visible } : {}),
|
|
@@ -91,6 +100,8 @@ export function projectElementState({ raw }, _optional = {}) {
|
|
|
91
100
|
? { options: raw.options.map((option) => normalizeAgentLabel({ raw: option }).text) }
|
|
92
101
|
: {}),
|
|
93
102
|
};
|
|
103
|
+
}
|
|
104
|
+
function projectFieldValue({ raw, refusal, base }, _optional = {}) {
|
|
94
105
|
if (raw.value === undefined)
|
|
95
106
|
return base;
|
|
96
107
|
if (raw.field === undefined) {
|
|
@@ -176,66 +187,10 @@ async function summarizePage(driver) {
|
|
|
176
187
|
* purpose: a caller must be told why, not handed a silent no-op it will retry forever.
|
|
177
188
|
*/
|
|
178
189
|
export async function executePageCapability({ driver, capabilityId, input }, _optional = {}) {
|
|
179
|
-
|
|
180
|
-
if (capability === undefined)
|
|
181
|
-
throw new Error(`unknown page capability: ${capabilityId}`);
|
|
182
|
-
const inputError = findCapabilityInputError({ capability, input });
|
|
183
|
-
if (inputError !== null) {
|
|
184
|
-
// The schema rides along with the refusal rather than making a caller spend a second
|
|
185
|
-
// round trip on describe_tool to learn what it already tried to guess — ai-control-plane.md
|
|
186
|
-
// §29.4's "schema-on-error" requirement, empirically forced by a real run: page.fill was
|
|
187
|
-
// called with `handle` instead of the argument's old name, `element`, twice before the caller
|
|
188
|
-
// gave up and looked the schema up separately. One JSON-Schema object, embedded once, turned
|
|
189
|
-
// that into a same-turn self-correction for every page.* verb this gate guards, not just the
|
|
190
|
-
// one that got measured — self-correction is why that mismatch was survivable at all, given
|
|
191
|
-
// every verb here needs a handle from `page.find_elements`, whose own response has always
|
|
192
|
-
// called this same value `handle` (`AgentElementDescriptor.handle`). The argument itself was
|
|
193
|
-
// later renamed from `element` to `handle` to match, closing the gap this comment describes
|
|
194
|
-
// rather than merely papering over it turn by turn.
|
|
195
|
-
throw new Error(`${capabilityId}: ${inputError}. Expected input: ${JSON.stringify(capability.inputSchema)}`);
|
|
196
|
-
}
|
|
190
|
+
validatePageCapability({ capabilityId, input });
|
|
197
191
|
switch (capabilityId) {
|
|
198
|
-
case 'page.find_elements':
|
|
199
|
-
|
|
200
|
-
// The schema's enum check has already run; this narrows the type for the driver.
|
|
201
|
-
const filter = {
|
|
202
|
-
...(isAgentElementRole(role) ? { role } : {}),
|
|
203
|
-
...(typeof input['query'] === 'string' ? { query: input['query'] } : {}),
|
|
204
|
-
};
|
|
205
|
-
const [found, rawPages] = await Promise.all([driver.findElements({}, filter), driver.listPages({})]);
|
|
206
|
-
const withState = input['withState'] === true;
|
|
207
|
-
const observing = withState && driver.describeState !== undefined;
|
|
208
|
-
const elements = await Promise.all(found.map(async (element, index) => {
|
|
209
|
-
const label = normalizeAgentLabel({ raw: element.label });
|
|
210
|
-
const base = {
|
|
211
|
-
handle: element.handle,
|
|
212
|
-
role: element.role,
|
|
213
|
-
label: label.text,
|
|
214
|
-
labelTruncated: label.truncated,
|
|
215
|
-
page: element.page,
|
|
216
|
-
};
|
|
217
|
-
if (!observing || index >= MAX_STATEFUL_ELEMENTS)
|
|
218
|
-
return base;
|
|
219
|
-
const state = await observeElement(driver, element.handle);
|
|
220
|
-
return state === undefined ? base : { ...base, state };
|
|
221
|
-
}));
|
|
222
|
-
// `label` is page-authored the same way an element's is — bounded here for the same reason
|
|
223
|
-
// `page.navigate`'s refusal message bounds it below, rather than trusting a host to have kept
|
|
224
|
-
// every `data-agent-page` label short and clean.
|
|
225
|
-
const pages = rawPages.map((page) => ({
|
|
226
|
-
id: page.id,
|
|
227
|
-
label: normalizeAgentLabel({ raw: page.label }).text,
|
|
228
|
-
}));
|
|
229
|
-
return {
|
|
230
|
-
elements,
|
|
231
|
-
pages,
|
|
232
|
-
untrustedFields: observing
|
|
233
|
-
? ['elements[].label', 'elements[].state.text', 'elements[].state.value', 'pages[].label']
|
|
234
|
-
: ['elements[].label', 'pages[].label'],
|
|
235
|
-
...(withState && !observing ? { stateUnavailable: true } : {}),
|
|
236
|
-
...(observing && found.length > MAX_STATEFUL_ELEMENTS ? { stateTruncated: true } : {}),
|
|
237
|
-
};
|
|
238
|
-
}
|
|
192
|
+
case 'page.find_elements':
|
|
193
|
+
return findPageElements({ driver, input });
|
|
239
194
|
case 'page.highlight': {
|
|
240
195
|
const handle = requireHandle(input, 'handle');
|
|
241
196
|
const durationMs = clampHighlightDuration(input['durationMs']);
|
|
@@ -252,30 +207,15 @@ export async function executePageCapability({ driver, capabilityId, input }, _op
|
|
|
252
207
|
const observation = await observeWrite(driver, handle, () => driver.click({ handle }));
|
|
253
208
|
return { clicked: handle, ...observation };
|
|
254
209
|
}
|
|
255
|
-
case 'page.fill':
|
|
256
|
-
|
|
257
|
-
// Declared required and typed `string` in the manifest, and `findCapabilityInputError`
|
|
258
|
-
// enforced both above — re-checking here would be an unreachable branch, not a safeguard.
|
|
259
|
-
const text = input['text'];
|
|
260
|
-
// Ask the page what this field actually is before writing to it. A handle proves the page
|
|
261
|
-
// published the element; it says nothing about whether it holds a password.
|
|
262
|
-
const field = await driver.describeField({ handle });
|
|
263
|
-
if (field === null)
|
|
264
|
-
throw new Error(`"${handle}" is not a fillable field`);
|
|
265
|
-
const refusal = findFieldFillRefusal({ field });
|
|
266
|
-
if (refusal !== null) {
|
|
267
|
-
throw new Error(`refusing to fill "${handle}": ${describeFieldRefusal({ refusal })}`);
|
|
268
|
-
}
|
|
269
|
-
const observation = await observeWrite(driver, handle, () => driver.fill({ handle, text }));
|
|
270
|
-
return { filled: handle, ...observation };
|
|
271
|
-
}
|
|
210
|
+
case 'page.fill':
|
|
211
|
+
return fillPageField({ driver, input });
|
|
272
212
|
case 'page.select_option': {
|
|
273
213
|
const handle = requireHandle(input, 'handle');
|
|
274
214
|
// Required and string-typed by the manifest, already enforced above. See page.fill.
|
|
275
215
|
const option = input['option'];
|
|
276
216
|
// Optional and boolean-typed by the manifest; absent means "select it", same as before this
|
|
277
217
|
// argument existed.
|
|
278
|
-
const selected = input['selected']
|
|
218
|
+
const selected = input['selected'] !== false;
|
|
279
219
|
// No field guard: a dropdown's options are authored by the page, so choosing one reveals
|
|
280
220
|
// nothing the page had not already published, and none of the credential field types this
|
|
281
221
|
// surface refuses can be a `<select>`.
|
|
@@ -284,37 +224,117 @@ export async function executePageCapability({ driver, capabilityId, input }, _op
|
|
|
284
224
|
// "the handle this call acted on" for every verb in this switch (`clicked`, `filled`, …).
|
|
285
225
|
return { selected: handle, option, optionSelected: selected, ...observation };
|
|
286
226
|
}
|
|
287
|
-
case 'page.navigate':
|
|
288
|
-
|
|
289
|
-
const page = input['page'];
|
|
290
|
-
const pages = await driver.listPages({});
|
|
291
|
-
if (!pages.some((candidate) => candidate.id === page)) {
|
|
292
|
-
// Every other piece of page-authored (or, here, caller-authored-but-page-shaped) text
|
|
293
|
-
// this system hands to a model goes through normalizeAgentLabel first — bounded, control-
|
|
294
|
-
// and bidi-stripped. This refusal was the one path that skipped it: the raw `page`
|
|
295
|
-
// argument, and the `pages` this host actually publishes (itself read verbatim off
|
|
296
|
-
// `data-agent-page` attributes with no length bound), were interpolated straight into the
|
|
297
|
-
// thrown message. A 5000+-char bidi payload survived verbatim as a result. The lookup
|
|
298
|
-
// above still runs on the raw values — only the text that reaches the message is bounded.
|
|
299
|
-
//
|
|
300
|
-
// `id (label)`, not just the id: a refusal is the one moment a caller that guessed wrong
|
|
301
|
-
// is looking straight at the real menu, so it is worth spending the label here even though
|
|
302
|
-
// `page.find_elements` already carries the same information — a caller that jumped
|
|
303
|
-
// straight to `page.navigate` without calling that first still lands on the right id.
|
|
304
|
-
const safePage = normalizeAgentLabel({ raw: page }).text;
|
|
305
|
-
const safePages = pages.map((candidate) => `${normalizeAgentLabel({ raw: candidate.id }).text} (${normalizeAgentLabel({ raw: candidate.label }).text})`);
|
|
306
|
-
throw new Error(`"${safePage}" is not a published page. Available: ${safePages.length > 0 ? safePages.join(', ') : '(none)'}`);
|
|
307
|
-
}
|
|
308
|
-
const before = await summarizePage(driver);
|
|
309
|
-
await driver.navigate({ page });
|
|
310
|
-
if (driver.settle !== undefined)
|
|
311
|
-
await driver.settle({});
|
|
312
|
-
const after = await summarizePage(driver);
|
|
313
|
-
return { navigatedTo: page, before, after };
|
|
314
|
-
}
|
|
227
|
+
case 'page.navigate':
|
|
228
|
+
return navigatePage({ driver, input });
|
|
315
229
|
/* c8 ignore next 2 -- unreachable: the id was matched against PAGE_CAPABILITIES above. */
|
|
316
230
|
default:
|
|
317
231
|
throw new Error(`unhandled page capability: ${capabilityId}`);
|
|
318
232
|
}
|
|
319
233
|
}
|
|
234
|
+
function validatePageCapability({ capabilityId, input }, _optional = {}) {
|
|
235
|
+
const capability = findCapability({ capabilities: PAGE_CAPABILITIES, id: capabilityId });
|
|
236
|
+
if (capability === undefined)
|
|
237
|
+
throw new Error(`unknown page capability: ${capabilityId}`);
|
|
238
|
+
const inputError = findCapabilityInputError({ capability, input });
|
|
239
|
+
if (inputError !== null) {
|
|
240
|
+
// The schema rides along with the refusal rather than making a caller spend a second
|
|
241
|
+
// round trip on describe_tool to learn what it already tried to guess — ai-control-plane.md
|
|
242
|
+
// §29.4's "schema-on-error" requirement, empirically forced by a real run: page.fill was
|
|
243
|
+
// called with `handle` instead of the argument's old name, `element`, twice before the caller
|
|
244
|
+
// gave up and looked the schema up separately. One JSON-Schema object, embedded once, turned
|
|
245
|
+
// that into a same-turn self-correction for every page.* verb this gate guards, not just the
|
|
246
|
+
// one that got measured — self-correction is why that mismatch was survivable at all, given
|
|
247
|
+
// every verb here needs a handle from `page.find_elements`, whose own response has always
|
|
248
|
+
// called this same value `handle` (`AgentElementDescriptor.handle`). The argument itself was
|
|
249
|
+
// later renamed from `element` to `handle` to match, closing the gap this comment describes
|
|
250
|
+
// rather than merely papering over it turn by turn.
|
|
251
|
+
throw new Error(`${capabilityId}: ${inputError}. Expected input: ${JSON.stringify(capability.inputSchema)}`);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
async function findPageElements({ driver, input }, _optional = {}) {
|
|
255
|
+
const role = input['role'];
|
|
256
|
+
// The schema's enum check has already run; this narrows the type for the driver.
|
|
257
|
+
const filter = {
|
|
258
|
+
...(isAgentElementRole(role) ? { role } : {}),
|
|
259
|
+
...(typeof input['query'] === 'string' ? { query: input['query'] } : {}),
|
|
260
|
+
};
|
|
261
|
+
const [found, rawPages] = await Promise.all([driver.findElements({}, filter), driver.listPages({})]);
|
|
262
|
+
const withState = input['withState'] === true;
|
|
263
|
+
const observing = withState && driver.describeState !== undefined;
|
|
264
|
+
const elements = await Promise.all(found.map(async (element, index) => {
|
|
265
|
+
const label = normalizeAgentLabel({ raw: element.label });
|
|
266
|
+
const base = {
|
|
267
|
+
handle: element.handle,
|
|
268
|
+
role: element.role,
|
|
269
|
+
label: label.text,
|
|
270
|
+
labelTruncated: label.truncated,
|
|
271
|
+
page: element.page,
|
|
272
|
+
};
|
|
273
|
+
if (!observing || index >= MAX_STATEFUL_ELEMENTS)
|
|
274
|
+
return base;
|
|
275
|
+
const state = await observeElement(driver, element.handle);
|
|
276
|
+
return state === undefined ? base : { ...base, state };
|
|
277
|
+
}));
|
|
278
|
+
// `label` is page-authored the same way an element's is — bounded here for the same reason
|
|
279
|
+
// `page.navigate`'s refusal message bounds it below, rather than trusting a host to have kept
|
|
280
|
+
// every `data-agent-page` label short and clean.
|
|
281
|
+
const pages = rawPages.map((page) => ({
|
|
282
|
+
id: page.id,
|
|
283
|
+
label: normalizeAgentLabel({ raw: page.label }).text,
|
|
284
|
+
}));
|
|
285
|
+
return {
|
|
286
|
+
elements,
|
|
287
|
+
pages,
|
|
288
|
+
untrustedFields: observing
|
|
289
|
+
? ['elements[].label', 'elements[].state.text', 'elements[].state.value', 'pages[].label']
|
|
290
|
+
: ['elements[].label', 'pages[].label'],
|
|
291
|
+
...(withState && !observing ? { stateUnavailable: true } : {}),
|
|
292
|
+
...(observing && found.length > MAX_STATEFUL_ELEMENTS ? { stateTruncated: true } : {}),
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
async function fillPageField({ driver, input }, _optional = {}) {
|
|
296
|
+
const handle = requireHandle(input, 'handle');
|
|
297
|
+
// Declared required and typed `string` in the manifest, and `findCapabilityInputError`
|
|
298
|
+
// enforced both above — re-checking here would be an unreachable branch, not a safeguard.
|
|
299
|
+
const text = input['text'];
|
|
300
|
+
// Ask the page what this field actually is before writing to it. A handle proves the page
|
|
301
|
+
// published the element; it says nothing about whether it holds a password.
|
|
302
|
+
const field = await driver.describeField({ handle });
|
|
303
|
+
if (field === null)
|
|
304
|
+
throw new Error(`"${handle}" is not a fillable field`);
|
|
305
|
+
const refusal = findFieldFillRefusal({ field });
|
|
306
|
+
if (refusal !== null) {
|
|
307
|
+
throw new Error(`refusing to fill "${handle}": ${describeFieldRefusal({ refusal })}`);
|
|
308
|
+
}
|
|
309
|
+
const observation = await observeWrite(driver, handle, () => driver.fill({ handle, text }));
|
|
310
|
+
return { filled: handle, ...observation };
|
|
311
|
+
}
|
|
312
|
+
async function navigatePage({ driver, input }, _optional = {}) {
|
|
313
|
+
// Required and string-typed by the manifest, already enforced above. See page.fill.
|
|
314
|
+
const page = input['page'];
|
|
315
|
+
const pages = await driver.listPages({});
|
|
316
|
+
if (!pages.some((candidate) => candidate.id === page)) {
|
|
317
|
+
// Every other piece of page-authored (or, here, caller-authored-but-page-shaped) text
|
|
318
|
+
// this system hands to a model goes through normalizeAgentLabel first — bounded, control-
|
|
319
|
+
// and bidi-stripped. This refusal was the one path that skipped it: the raw `page`
|
|
320
|
+
// argument, and the `pages` this host actually publishes (itself read verbatim off
|
|
321
|
+
// `data-agent-page` attributes with no length bound), were interpolated straight into the
|
|
322
|
+
// thrown message. A 5000+-char bidi payload survived verbatim as a result. The lookup
|
|
323
|
+
// above still runs on the raw values — only the text that reaches the message is bounded.
|
|
324
|
+
//
|
|
325
|
+
// `id (label)`, not just the id: a refusal is the one moment a caller that guessed wrong
|
|
326
|
+
// is looking straight at the real menu, so it is worth spending the label here even though
|
|
327
|
+
// `page.find_elements` already carries the same information — a caller that jumped
|
|
328
|
+
// straight to `page.navigate` without calling that first still lands on the right id.
|
|
329
|
+
const safePage = normalizeAgentLabel({ raw: page }).text;
|
|
330
|
+
const safePages = pages.map((candidate) => `${normalizeAgentLabel({ raw: candidate.id }).text} (${normalizeAgentLabel({ raw: candidate.label }).text})`);
|
|
331
|
+
throw new Error(`"${safePage}" is not a published page. Available: ${safePages.length > 0 ? safePages.join(', ') : '(none)'}`);
|
|
332
|
+
}
|
|
333
|
+
const before = await summarizePage(driver);
|
|
334
|
+
await driver.navigate({ page });
|
|
335
|
+
if (driver.settle !== undefined)
|
|
336
|
+
await driver.settle({});
|
|
337
|
+
const after = await summarizePage(driver);
|
|
338
|
+
return { navigatedTo: page, before, after };
|
|
339
|
+
}
|
|
320
340
|
//# sourceMappingURL=page-executor.js.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { SiteGuide, SiteGuidePorts } from './types.js';
|
|
2
|
+
export declare class SiteGuideInputError extends Error {
|
|
3
|
+
constructor({ message }: {
|
|
4
|
+
readonly message: string;
|
|
5
|
+
}, _options?: Record<string, never>);
|
|
6
|
+
}
|
|
7
|
+
/** Build a neutral read-only guide over host ports. No product state or plugin loader is ambient.
|
|
8
|
+
* Authorization and contributions are read fresh for each request. Errors from enrichment/facts
|
|
9
|
+
* yield explicit unavailable status without copying arbitrary error messages into model context.
|
|
10
|
+
* @param ports - Facts, authorization, optional enrichment, and host minimal capability cards.
|
|
11
|
+
* @returns One overview/explain service. Invalid input and hidden topics throw SiteGuideInputError.
|
|
12
|
+
* @complexity O(t + p) per request, topics plus distinct permissions; one bulk facts read.
|
|
13
|
+
* @example createSiteGuide({ facts, authorize, builtInTopics: cards }).run({ caller, operation: 'overview' });
|
|
14
|
+
*/
|
|
15
|
+
export declare function createSiteGuide(ports: SiteGuidePorts, options?: {
|
|
16
|
+
readonly readTimeoutMs?: number;
|
|
17
|
+
}): SiteGuide;
|
|
18
|
+
//# sourceMappingURL=site-guide.d.ts.map
|