@rtrvr-ai/rover 3.0.1 → 4.0.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 +110 -12
- package/dist/agentDiscovery.d.ts +304 -0
- package/dist/agentDiscovery.js +1040 -0
- package/dist/embed.js +3707 -1565
- package/dist/index.d.ts +15 -2
- package/dist/ownerInstall.d.ts +150 -0
- package/dist/ownerInstall.js +340 -0
- package/dist/previewBootstrap.d.ts +9 -0
- package/dist/previewBootstrap.js +72 -2
- package/dist/rover.js +3707 -1565
- package/dist/worker/rover-worker.js +11 -11
- package/package.json +13 -1
|
@@ -0,0 +1,1040 @@
|
|
|
1
|
+
import { toBaseUrl } from './serverRuntime.js';
|
|
2
|
+
import { resolveAiLaunchAccess } from './deepLink.js';
|
|
3
|
+
import { createRoverAgentDiscoverySnapshot } from '@rover/shared/lib/agent-discovery.js';
|
|
4
|
+
export const DEFAULT_AGENT_CARD_PATH = '/.well-known/agent-card.json';
|
|
5
|
+
export const DEFAULT_ROVER_SITE_PATH = '/.well-known/rover-site.json';
|
|
6
|
+
export const DEFAULT_LLMS_PATH = '/llms.txt';
|
|
7
|
+
export const ROVER_WEBMCP_DISCOVERY_GLOBAL = '__ROVER_WEBMCP_TOOL_DEFS__';
|
|
8
|
+
export const ROVER_DISCOVERY_ACTION_SHEET_MAX_ACTIONS = 3;
|
|
9
|
+
const ROVER_DISCOVERY_SHORTCUT_PROMPT_MAX_CHARS = 2000;
|
|
10
|
+
const DEFAULT_SKILL_OUTPUT_SCHEMA = {
|
|
11
|
+
type: 'object',
|
|
12
|
+
properties: {
|
|
13
|
+
status: {
|
|
14
|
+
type: 'string',
|
|
15
|
+
description: 'Task status returned by Rover.',
|
|
16
|
+
},
|
|
17
|
+
summary: {
|
|
18
|
+
type: 'string',
|
|
19
|
+
description: 'High-level summary of what Rover completed or observed.',
|
|
20
|
+
},
|
|
21
|
+
task: {
|
|
22
|
+
type: 'string',
|
|
23
|
+
description: 'Canonical Rover task URL.',
|
|
24
|
+
},
|
|
25
|
+
workflow: {
|
|
26
|
+
type: 'string',
|
|
27
|
+
description: 'Canonical Rover workflow URL when delegation occurs.',
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
const DEFAULT_TOOL_OUTPUT_SCHEMA = {
|
|
32
|
+
type: 'object',
|
|
33
|
+
properties: {
|
|
34
|
+
success: {
|
|
35
|
+
type: 'boolean',
|
|
36
|
+
description: 'Whether the tool completed successfully.',
|
|
37
|
+
},
|
|
38
|
+
summary: {
|
|
39
|
+
type: 'string',
|
|
40
|
+
description: 'Human-readable summary of the explicit site action.',
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
function text(value, max = 0) {
|
|
45
|
+
const out = String(value || '').replace(/\s+/g, ' ').trim();
|
|
46
|
+
if (!max || out.length <= max)
|
|
47
|
+
return out;
|
|
48
|
+
return out.slice(0, max).trim();
|
|
49
|
+
}
|
|
50
|
+
function asObject(value) {
|
|
51
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? value : undefined;
|
|
52
|
+
}
|
|
53
|
+
function uniqueStrings(input, options) {
|
|
54
|
+
if (!Array.isArray(input))
|
|
55
|
+
return [];
|
|
56
|
+
const seen = new Set();
|
|
57
|
+
const out = [];
|
|
58
|
+
const max = Math.max(1, Number(options?.max) || 24);
|
|
59
|
+
const maxLen = Math.max(8, Number(options?.maxLen) || 160);
|
|
60
|
+
for (const raw of input) {
|
|
61
|
+
const value = text(raw, maxLen);
|
|
62
|
+
if (!value)
|
|
63
|
+
continue;
|
|
64
|
+
const key = value.toLowerCase();
|
|
65
|
+
if (seen.has(key))
|
|
66
|
+
continue;
|
|
67
|
+
seen.add(key);
|
|
68
|
+
out.push(value);
|
|
69
|
+
if (out.length >= max)
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
return out;
|
|
73
|
+
}
|
|
74
|
+
function normalizeSchema(value) {
|
|
75
|
+
const schema = asObject(value);
|
|
76
|
+
return schema ? { ...schema } : undefined;
|
|
77
|
+
}
|
|
78
|
+
function normalizeSiteUrl(siteUrl) {
|
|
79
|
+
try {
|
|
80
|
+
const url = new URL(siteUrl);
|
|
81
|
+
url.hash = '';
|
|
82
|
+
url.search = '';
|
|
83
|
+
return url.toString();
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
return text(siteUrl) || 'https://example.com/';
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function normalizeRoutePath(value) {
|
|
90
|
+
const raw = text(value, 512);
|
|
91
|
+
if (!raw)
|
|
92
|
+
return undefined;
|
|
93
|
+
if (raw === '*' || raw === '/*')
|
|
94
|
+
return '*';
|
|
95
|
+
if (/^https?:\/\//i.test(raw)) {
|
|
96
|
+
try {
|
|
97
|
+
return new URL(raw).pathname || '/';
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
return undefined;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return raw.startsWith('/') ? raw : `/${raw}`;
|
|
104
|
+
}
|
|
105
|
+
function normalizeResultModes(input) {
|
|
106
|
+
if (!Array.isArray(input))
|
|
107
|
+
return [];
|
|
108
|
+
const out = [];
|
|
109
|
+
const seen = new Set();
|
|
110
|
+
for (const value of input) {
|
|
111
|
+
const mode = value === 'text'
|
|
112
|
+
|| value === 'markdown'
|
|
113
|
+
|| value === 'json'
|
|
114
|
+
|| value === 'observation'
|
|
115
|
+
|| value === 'artifacts'
|
|
116
|
+
? value
|
|
117
|
+
: undefined;
|
|
118
|
+
if (!mode || seen.has(mode))
|
|
119
|
+
continue;
|
|
120
|
+
seen.add(mode);
|
|
121
|
+
out.push(mode);
|
|
122
|
+
}
|
|
123
|
+
return out;
|
|
124
|
+
}
|
|
125
|
+
function normalizeDiscoveryMode(value, fallback = 'beacon') {
|
|
126
|
+
return value === 'silent' || value === 'beacon' || value === 'integrated' || value === 'debug'
|
|
127
|
+
? value
|
|
128
|
+
: fallback;
|
|
129
|
+
}
|
|
130
|
+
function normalizeDiscoveryBranding(value, fallback = 'site') {
|
|
131
|
+
return value === 'site' || value === 'co' || value === 'rover'
|
|
132
|
+
? value
|
|
133
|
+
: fallback;
|
|
134
|
+
}
|
|
135
|
+
function normalizeHostSurface(value, fallback = 'auto') {
|
|
136
|
+
return value === 'auto'
|
|
137
|
+
|| value === 'existing-assistant'
|
|
138
|
+
|| value === 'floating-corner'
|
|
139
|
+
|| value === 'inline-primary'
|
|
140
|
+
? value
|
|
141
|
+
: fallback;
|
|
142
|
+
}
|
|
143
|
+
function normalizeActionReveal(value, fallback = 'click') {
|
|
144
|
+
return value === 'click'
|
|
145
|
+
|| value === 'focus'
|
|
146
|
+
|| value === 'keyboard'
|
|
147
|
+
|| value === 'agent-handshake'
|
|
148
|
+
? value
|
|
149
|
+
: fallback;
|
|
150
|
+
}
|
|
151
|
+
function defaultAgentModeEntryHints(policy) {
|
|
152
|
+
if (policy.mode === 'silent') {
|
|
153
|
+
return [
|
|
154
|
+
'Read rover-site.json, the page manifest, or the well-known agent card before falling back to raw DOM guesses.',
|
|
155
|
+
];
|
|
156
|
+
}
|
|
157
|
+
if (policy.hostSurface === 'existing-assistant' || policy.mode === 'integrated') {
|
|
158
|
+
return [
|
|
159
|
+
'Use the site assistant or chat entrypoint first when it is present on the page.',
|
|
160
|
+
'Prefer Rover task and shortcut surfaces over selector or screenshot guessing once the assistant surface is active.',
|
|
161
|
+
];
|
|
162
|
+
}
|
|
163
|
+
return [
|
|
164
|
+
'Use the page beacon or task endpoint before attempting generic DOM automation.',
|
|
165
|
+
'When Rover is already active, prefer the current-page actions over screenshot loops.',
|
|
166
|
+
];
|
|
167
|
+
}
|
|
168
|
+
function resolveDiscoverySurfacePolicy(config) {
|
|
169
|
+
const input = config.discoverySurface || {};
|
|
170
|
+
const mode = normalizeDiscoveryMode(input.mode, config.visibleCue === false ? 'silent' : 'beacon');
|
|
171
|
+
const hostSurface = normalizeHostSurface(input.hostSurface, 'auto');
|
|
172
|
+
const actionReveal = normalizeActionReveal(input.actionReveal, 'click');
|
|
173
|
+
const beaconLabel = text(input.beaconLabel
|
|
174
|
+
|| config.pageContext?.beaconLabel
|
|
175
|
+
|| config.pageContext?.visibleCueLabel, 180) || undefined;
|
|
176
|
+
const seed = {
|
|
177
|
+
mode,
|
|
178
|
+
branding: normalizeDiscoveryBranding(input.branding, 'site'),
|
|
179
|
+
hostSurface,
|
|
180
|
+
actionReveal,
|
|
181
|
+
beaconLabel,
|
|
182
|
+
agentModeEntryHints: uniqueStrings(input.agentModeEntryHints, { max: 8, maxLen: 240 }),
|
|
183
|
+
};
|
|
184
|
+
return {
|
|
185
|
+
...seed,
|
|
186
|
+
agentModeEntryHints: seed.agentModeEntryHints.length ? seed.agentModeEntryHints : defaultAgentModeEntryHints(seed),
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
export function sanitizeRoverAgentDiscoveryRuntimeConfig(raw) {
|
|
190
|
+
const input = asObject(raw);
|
|
191
|
+
if (!input)
|
|
192
|
+
return undefined;
|
|
193
|
+
const surfaceInput = asObject(input.discoverySurface);
|
|
194
|
+
const discoverySurface = {};
|
|
195
|
+
if (surfaceInput) {
|
|
196
|
+
const mode = normalizeDiscoveryMode(surfaceInput.mode, 'beacon');
|
|
197
|
+
if (surfaceInput.mode === 'silent' || surfaceInput.mode === 'beacon' || surfaceInput.mode === 'integrated' || surfaceInput.mode === 'debug') {
|
|
198
|
+
discoverySurface.mode = mode;
|
|
199
|
+
}
|
|
200
|
+
if (surfaceInput.branding === 'site' || surfaceInput.branding === 'co' || surfaceInput.branding === 'rover') {
|
|
201
|
+
discoverySurface.branding = surfaceInput.branding;
|
|
202
|
+
}
|
|
203
|
+
if (surfaceInput.hostSurface === 'auto'
|
|
204
|
+
|| surfaceInput.hostSurface === 'existing-assistant'
|
|
205
|
+
|| surfaceInput.hostSurface === 'floating-corner'
|
|
206
|
+
|| surfaceInput.hostSurface === 'inline-primary') {
|
|
207
|
+
discoverySurface.hostSurface = surfaceInput.hostSurface;
|
|
208
|
+
}
|
|
209
|
+
if (surfaceInput.actionReveal === 'click'
|
|
210
|
+
|| surfaceInput.actionReveal === 'focus'
|
|
211
|
+
|| surfaceInput.actionReveal === 'keyboard'
|
|
212
|
+
|| surfaceInput.actionReveal === 'agent-handshake') {
|
|
213
|
+
discoverySurface.actionReveal = surfaceInput.actionReveal;
|
|
214
|
+
}
|
|
215
|
+
const beaconLabel = text(surfaceInput.beaconLabel
|
|
216
|
+
|| surfaceInput.visibleCueLabel, 180) || undefined;
|
|
217
|
+
if (beaconLabel)
|
|
218
|
+
discoverySurface.beaconLabel = beaconLabel;
|
|
219
|
+
const agentModeEntryHints = uniqueStrings(surfaceInput.agentModeEntryHints, { max: 8, maxLen: 240 });
|
|
220
|
+
if (agentModeEntryHints.length > 0)
|
|
221
|
+
discoverySurface.agentModeEntryHints = agentModeEntryHints;
|
|
222
|
+
}
|
|
223
|
+
const next = {};
|
|
224
|
+
if (typeof input.enabled === 'boolean')
|
|
225
|
+
next.enabled = input.enabled;
|
|
226
|
+
else if (typeof input.visibleCue === 'boolean')
|
|
227
|
+
next.enabled = input.visibleCue;
|
|
228
|
+
if (typeof input.siteUrl === 'string' && text(input.siteUrl))
|
|
229
|
+
next.siteUrl = text(input.siteUrl);
|
|
230
|
+
if (typeof input.siteId === 'string' && text(input.siteId, 160))
|
|
231
|
+
next.siteId = text(input.siteId, 160);
|
|
232
|
+
if (typeof input.apiBase === 'string' && text(input.apiBase, 240))
|
|
233
|
+
next.apiBase = text(input.apiBase, 240);
|
|
234
|
+
if (typeof input.siteName === 'string' && text(input.siteName, 160))
|
|
235
|
+
next.siteName = text(input.siteName, 160);
|
|
236
|
+
if (typeof input.description === 'string' && text(input.description, 400))
|
|
237
|
+
next.description = text(input.description, 400);
|
|
238
|
+
if (typeof input.version === 'string' && text(input.version, 120))
|
|
239
|
+
next.version = text(input.version, 120);
|
|
240
|
+
if (typeof input.agentCardUrl === 'string' && text(input.agentCardUrl, 240))
|
|
241
|
+
next.agentCardUrl = text(input.agentCardUrl, 240);
|
|
242
|
+
if (typeof input.roverSiteUrl === 'string' && text(input.roverSiteUrl, 240))
|
|
243
|
+
next.roverSiteUrl = text(input.roverSiteUrl, 240);
|
|
244
|
+
if (typeof input.llmsUrl === 'string' && text(input.llmsUrl, 240))
|
|
245
|
+
next.llmsUrl = text(input.llmsUrl, 240);
|
|
246
|
+
if (typeof input.visibleCue === 'boolean')
|
|
247
|
+
next.visibleCue = input.visibleCue;
|
|
248
|
+
if (typeof input.hostSurfaceSelector === 'string' && text(input.hostSurfaceSelector, 240)) {
|
|
249
|
+
next.hostSurfaceSelector = text(input.hostSurfaceSelector, 240);
|
|
250
|
+
}
|
|
251
|
+
if (input.preferExecution === 'auto' || input.preferExecution === 'browser' || input.preferExecution === 'cloud') {
|
|
252
|
+
next.preferExecution = input.preferExecution;
|
|
253
|
+
}
|
|
254
|
+
if (Object.keys(discoverySurface).length > 0)
|
|
255
|
+
next.discoverySurface = discoverySurface;
|
|
256
|
+
return Object.keys(next).length ? next : undefined;
|
|
257
|
+
}
|
|
258
|
+
function defaultAllowedExecutionModes(preferred) {
|
|
259
|
+
if (preferred === 'browser')
|
|
260
|
+
return ['browser'];
|
|
261
|
+
if (preferred === 'cloud')
|
|
262
|
+
return ['cloud'];
|
|
263
|
+
return ['auto', 'browser', 'cloud'];
|
|
264
|
+
}
|
|
265
|
+
function normalizeCapabilityRecord(value, defaults) {
|
|
266
|
+
const capability = asObject(value);
|
|
267
|
+
if (!capability)
|
|
268
|
+
return null;
|
|
269
|
+
const capabilityId = text(capability.capabilityId || capability.id, 120);
|
|
270
|
+
const label = text(capability.label || capability.name, 180);
|
|
271
|
+
const description = text(capability.description, 480);
|
|
272
|
+
if (!capabilityId || !label || !description)
|
|
273
|
+
return null;
|
|
274
|
+
const allowedExecutionModes = Array.isArray(capability.allowedExecutionModes)
|
|
275
|
+
? capability.allowedExecutionModes.filter((mode) => mode === 'auto' || mode === 'browser' || mode === 'cloud')
|
|
276
|
+
: [];
|
|
277
|
+
const resultModes = normalizeResultModes(capability.resultModes);
|
|
278
|
+
return {
|
|
279
|
+
capabilityId,
|
|
280
|
+
version: text(capability.version, 80) || defaults.version,
|
|
281
|
+
label,
|
|
282
|
+
description,
|
|
283
|
+
inputSchema: normalizeSchema(capability.inputSchema),
|
|
284
|
+
outputSchema: normalizeSchema(capability.outputSchema),
|
|
285
|
+
sideEffect: capability.sideEffect === 'none'
|
|
286
|
+
|| capability.sideEffect === 'read'
|
|
287
|
+
|| capability.sideEffect === 'write'
|
|
288
|
+
|| capability.sideEffect === 'transactional'
|
|
289
|
+
? capability.sideEffect
|
|
290
|
+
: undefined,
|
|
291
|
+
requiresConfirmation: typeof capability.requiresConfirmation === 'boolean' ? capability.requiresConfirmation : undefined,
|
|
292
|
+
preferredInterface: normalizeAnnotations({ preferredInterface: capability.preferredInterface }).preferredInterface,
|
|
293
|
+
allowedExecutionModes: allowedExecutionModes.length ? allowedExecutionModes : defaults.allowedExecutionModes,
|
|
294
|
+
resultModes: resultModes.length ? resultModes : defaults.resultModes,
|
|
295
|
+
pageScope: uniqueStrings(capability.pageScope, { max: 24, maxLen: 80 }),
|
|
296
|
+
analyticsTags: uniqueStrings(capability.analyticsTags, { max: 24, maxLen: 64 }),
|
|
297
|
+
rover: asObject(capability.rover) ? { ...capability.rover } : undefined,
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
function normalizePageDefinition(value, defaults) {
|
|
301
|
+
const page = asObject(value);
|
|
302
|
+
if (!page)
|
|
303
|
+
return null;
|
|
304
|
+
const pageId = text(page.pageId || page.id, 120);
|
|
305
|
+
if (!pageId)
|
|
306
|
+
return null;
|
|
307
|
+
const beaconLabel = text(page.beaconLabel || page.visibleCueLabel || defaults?.beaconLabel, 180) || undefined;
|
|
308
|
+
return {
|
|
309
|
+
pageId,
|
|
310
|
+
route: normalizeRoutePath(page.route),
|
|
311
|
+
label: text(page.label, 180) || undefined,
|
|
312
|
+
capabilityIds: uniqueStrings(page.capabilityIds, { max: 48, maxLen: 120 }),
|
|
313
|
+
entityHints: uniqueStrings(page.entityHints, { max: 24, maxLen: 120 }),
|
|
314
|
+
formHints: uniqueStrings(page.formHints, { max: 24, maxLen: 120 }),
|
|
315
|
+
visibleCueLabel: beaconLabel,
|
|
316
|
+
beaconLabel,
|
|
317
|
+
discoveryMode: normalizeDiscoveryMode(page.discoveryMode, defaults?.mode || 'beacon'),
|
|
318
|
+
hostSurface: normalizeHostSurface(page.hostSurface, defaults?.hostSurface || 'auto'),
|
|
319
|
+
actionReveal: normalizeActionReveal(page.actionReveal, defaults?.actionReveal || 'click'),
|
|
320
|
+
agentModeEntryHints: uniqueStrings(Array.isArray(page.agentModeEntryHints) && page.agentModeEntryHints.length
|
|
321
|
+
? page.agentModeEntryHints
|
|
322
|
+
: defaults?.agentModeEntryHints, { max: 8, maxLen: 240 }),
|
|
323
|
+
capabilitySummary: uniqueStrings(page.capabilitySummary, { max: 12, maxLen: 180 }),
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
function buildTaskEndpoint(apiBase) {
|
|
327
|
+
return `${toBaseUrl(apiBase)}/v1/tasks`;
|
|
328
|
+
}
|
|
329
|
+
function buildWorkflowEndpoint(apiBase) {
|
|
330
|
+
return `${toBaseUrl(apiBase)}/v1/workflows`;
|
|
331
|
+
}
|
|
332
|
+
function buildDeepLink(siteUrl, shortcutId) {
|
|
333
|
+
try {
|
|
334
|
+
const url = new URL(siteUrl);
|
|
335
|
+
url.searchParams.set('rover_shortcut', shortcutId);
|
|
336
|
+
return url.toString();
|
|
337
|
+
}
|
|
338
|
+
catch {
|
|
339
|
+
const sep = siteUrl.includes('?') ? '&' : '?';
|
|
340
|
+
return `${siteUrl}${sep}rover_shortcut=${encodeURIComponent(shortcutId)}`;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
function humanizeName(name) {
|
|
344
|
+
return text(name)
|
|
345
|
+
.replace(/[_-]+/g, ' ')
|
|
346
|
+
.replace(/\s+/g, ' ')
|
|
347
|
+
.replace(/\b\w/g, char => char.toUpperCase());
|
|
348
|
+
}
|
|
349
|
+
function parametersToSchema(parameters, required = []) {
|
|
350
|
+
if (!parameters || typeof parameters !== 'object')
|
|
351
|
+
return undefined;
|
|
352
|
+
const properties = {};
|
|
353
|
+
for (const [key, value] of Object.entries(parameters)) {
|
|
354
|
+
const param = asObject(value);
|
|
355
|
+
if (!param)
|
|
356
|
+
continue;
|
|
357
|
+
const type = text(param.type) || 'string';
|
|
358
|
+
const schema = {
|
|
359
|
+
type: ['string', 'number', 'integer', 'boolean', 'array', 'object'].includes(type) ? type : 'string',
|
|
360
|
+
};
|
|
361
|
+
if (param.description)
|
|
362
|
+
schema.description = text(param.description, 240);
|
|
363
|
+
if (param.enum && Array.isArray(param.enum)) {
|
|
364
|
+
const enumValues = uniqueStrings(param.enum, { max: 24, maxLen: 80 });
|
|
365
|
+
if (enumValues.length)
|
|
366
|
+
schema.enum = enumValues;
|
|
367
|
+
}
|
|
368
|
+
if (type === 'array' && param.items) {
|
|
369
|
+
schema.items = normalizeSchema(param.items) || {
|
|
370
|
+
type: text(param.items?.type) || 'string',
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
if (type === 'object' && param.properties && typeof param.properties === 'object') {
|
|
374
|
+
schema.properties = {};
|
|
375
|
+
for (const [nestedKey, nestedValue] of Object.entries(param.properties)) {
|
|
376
|
+
const nested = asObject(nestedValue);
|
|
377
|
+
if (!nested)
|
|
378
|
+
continue;
|
|
379
|
+
schema.properties[nestedKey] = {
|
|
380
|
+
type: text(nested.type) || 'string',
|
|
381
|
+
...(nested.description ? { description: text(nested.description, 240) } : {}),
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
if (Array.isArray(param.required)) {
|
|
385
|
+
const nestedRequired = uniqueStrings(param.required, { max: 50, maxLen: 80 });
|
|
386
|
+
if (nestedRequired.length)
|
|
387
|
+
schema.required = nestedRequired;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
properties[key] = schema;
|
|
391
|
+
}
|
|
392
|
+
return {
|
|
393
|
+
type: 'object',
|
|
394
|
+
properties,
|
|
395
|
+
...(required.length ? { required: uniqueStrings(required, { max: 50, maxLen: 80 }) } : {}),
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
function normalizeAnnotations(value) {
|
|
399
|
+
const annotations = asObject(value) || {};
|
|
400
|
+
return {
|
|
401
|
+
category: text(annotations.category, 40) || undefined,
|
|
402
|
+
tags: uniqueStrings(annotations.tags, { max: 16, maxLen: 40 }),
|
|
403
|
+
examples: uniqueStrings(annotations.examples, { max: 6, maxLen: 200 }),
|
|
404
|
+
whenToUse: text(annotations.whenToUse, 280) || undefined,
|
|
405
|
+
whyUse: text(annotations.whyUse, 280) || undefined,
|
|
406
|
+
sideEffect: annotations.sideEffect === 'none'
|
|
407
|
+
|| annotations.sideEffect === 'read'
|
|
408
|
+
|| annotations.sideEffect === 'write'
|
|
409
|
+
|| annotations.sideEffect === 'transactional'
|
|
410
|
+
? annotations.sideEffect
|
|
411
|
+
: undefined,
|
|
412
|
+
requiresConfirmation: typeof annotations.requiresConfirmation === 'boolean'
|
|
413
|
+
? annotations.requiresConfirmation
|
|
414
|
+
: undefined,
|
|
415
|
+
preferredInterface: annotations.preferredInterface === 'task'
|
|
416
|
+
|| annotations.preferredInterface === 'shortcut'
|
|
417
|
+
|| annotations.preferredInterface === 'client_tool'
|
|
418
|
+
|| annotations.preferredInterface === 'webmcp'
|
|
419
|
+
? annotations.preferredInterface
|
|
420
|
+
: undefined,
|
|
421
|
+
priority: annotations.priority === 'primary' || annotations.priority === 'secondary'
|
|
422
|
+
? annotations.priority
|
|
423
|
+
: undefined,
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
function inferSideEffect(name, preferredInterface, annotations) {
|
|
427
|
+
if (annotations?.sideEffect)
|
|
428
|
+
return annotations.sideEffect;
|
|
429
|
+
if (preferredInterface === 'shortcut')
|
|
430
|
+
return 'transactional';
|
|
431
|
+
const normalized = name.toLowerCase();
|
|
432
|
+
if (/^(get|read|list|show|find|search|compare|inspect|lookup)/.test(normalized))
|
|
433
|
+
return 'read';
|
|
434
|
+
if (/^(save|create|reply|vote|leave|submit|answer|book|start|checkout|handoff)/.test(normalized))
|
|
435
|
+
return 'write';
|
|
436
|
+
if (normalized.includes('run_task'))
|
|
437
|
+
return 'transactional';
|
|
438
|
+
return 'read';
|
|
439
|
+
}
|
|
440
|
+
function inferRequiresConfirmation(sideEffect, annotations) {
|
|
441
|
+
if (typeof annotations?.requiresConfirmation === 'boolean')
|
|
442
|
+
return annotations.requiresConfirmation;
|
|
443
|
+
return sideEffect === 'write' || sideEffect === 'transactional';
|
|
444
|
+
}
|
|
445
|
+
function buildToolDescription(title, description, annotations, preferredInterface) {
|
|
446
|
+
const parts = [];
|
|
447
|
+
if (title)
|
|
448
|
+
parts.push(text(title, 120));
|
|
449
|
+
if (description)
|
|
450
|
+
parts.push(text(description, 320));
|
|
451
|
+
if (annotations.whenToUse)
|
|
452
|
+
parts.push(`When to use: ${annotations.whenToUse}`);
|
|
453
|
+
if (annotations.whyUse)
|
|
454
|
+
parts.push(`Why use this path: ${annotations.whyUse}`);
|
|
455
|
+
if (!description && preferredInterface === 'shortcut') {
|
|
456
|
+
parts.push('Use this explicit Rover shortcut when the user intent matches this site journey instead of improvising with raw DOM actions.');
|
|
457
|
+
}
|
|
458
|
+
return parts.join(' ').trim();
|
|
459
|
+
}
|
|
460
|
+
function normalizePublishedShortcut(shortcut) {
|
|
461
|
+
const id = text(shortcut.id, 80);
|
|
462
|
+
const label = text(shortcut.label, 120);
|
|
463
|
+
const prompt = text(shortcut.prompt, ROVER_DISCOVERY_SHORTCUT_PROMPT_MAX_CHARS);
|
|
464
|
+
if (!id || !label || !prompt || shortcut.enabled === false)
|
|
465
|
+
return null;
|
|
466
|
+
const description = text(shortcut.description, 320);
|
|
467
|
+
return {
|
|
468
|
+
id,
|
|
469
|
+
label,
|
|
470
|
+
prompt,
|
|
471
|
+
...(description ? { description } : {}),
|
|
472
|
+
...(shortcut.routing ? { routing: shortcut.routing } : {}),
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
function buildShortcutSkill(shortcut, config, taskEndpoint) {
|
|
476
|
+
const publishedShortcut = normalizePublishedShortcut(shortcut);
|
|
477
|
+
if (!publishedShortcut)
|
|
478
|
+
return null;
|
|
479
|
+
const { id, label, prompt } = publishedShortcut;
|
|
480
|
+
const tags = uniqueStrings([...(shortcut.tags || []), 'shortcut', 'rover'], { max: 16, maxLen: 40 });
|
|
481
|
+
const examples = uniqueStrings([...(shortcut.examples || []), prompt], { max: 6, maxLen: 220 });
|
|
482
|
+
const annotations = normalizeAnnotations({
|
|
483
|
+
tags,
|
|
484
|
+
examples,
|
|
485
|
+
sideEffect: shortcut.sideEffect,
|
|
486
|
+
requiresConfirmation: shortcut.requiresConfirmation,
|
|
487
|
+
preferredInterface: shortcut.preferredInterface || 'shortcut',
|
|
488
|
+
priority: 'primary',
|
|
489
|
+
});
|
|
490
|
+
const preferredInterface = 'shortcut';
|
|
491
|
+
const sideEffect = inferSideEffect(id, preferredInterface, annotations);
|
|
492
|
+
const requiresConfirmation = inferRequiresConfirmation(sideEffect, annotations);
|
|
493
|
+
return {
|
|
494
|
+
id,
|
|
495
|
+
name: label,
|
|
496
|
+
description: buildToolDescription(label, shortcut.description || `Run the "${label}" site flow with Rover using the exact shortcut id "${id}".`, {
|
|
497
|
+
...annotations,
|
|
498
|
+
whenToUse: annotations.whenToUse
|
|
499
|
+
|| 'Use this when the user wants this exact site outcome and you want a stable path that avoids brittle DOM guessing.',
|
|
500
|
+
whyUse: annotations.whyUse
|
|
501
|
+
|| 'Rover shortcuts are explicit site-owned entrypoints with structured task progress and cleaner recovery than generic DOM automation.',
|
|
502
|
+
}, preferredInterface),
|
|
503
|
+
tags,
|
|
504
|
+
examples,
|
|
505
|
+
inputSchema: normalizeSchema(shortcut.inputSchema) || {
|
|
506
|
+
type: 'object',
|
|
507
|
+
additionalProperties: false,
|
|
508
|
+
description: 'This shortcut is parameterless. Invoke it directly by id.',
|
|
509
|
+
},
|
|
510
|
+
outputSchema: normalizeSchema(shortcut.outputSchema) || { ...DEFAULT_SKILL_OUTPUT_SCHEMA },
|
|
511
|
+
sideEffect,
|
|
512
|
+
requiresConfirmation,
|
|
513
|
+
preferredInterface,
|
|
514
|
+
category: 'primary',
|
|
515
|
+
rover: {
|
|
516
|
+
shortcutId: id,
|
|
517
|
+
prompt,
|
|
518
|
+
routing: shortcut.routing,
|
|
519
|
+
deepLink: buildDeepLink(config.siteUrl, id),
|
|
520
|
+
task: {
|
|
521
|
+
endpoint: taskEndpoint,
|
|
522
|
+
payload: {
|
|
523
|
+
url: normalizeSiteUrl(config.siteUrl),
|
|
524
|
+
shortcut: id,
|
|
525
|
+
},
|
|
526
|
+
preferExecution: config.preferExecution || 'auto',
|
|
527
|
+
},
|
|
528
|
+
source: 'shortcut',
|
|
529
|
+
},
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
function buildToolSkill(tool, preferredInterface) {
|
|
533
|
+
const name = text(tool.name, 120);
|
|
534
|
+
if (!name || tool.llmCallable === false)
|
|
535
|
+
return null;
|
|
536
|
+
const annotations = normalizeAnnotations(tool.annotations);
|
|
537
|
+
const sideEffect = inferSideEffect(name, preferredInterface, annotations);
|
|
538
|
+
const requiresConfirmation = inferRequiresConfirmation(sideEffect, annotations);
|
|
539
|
+
const tags = uniqueStrings([...(annotations.tags || []), preferredInterface, 'rover'], { max: 16, maxLen: 40 });
|
|
540
|
+
const examples = uniqueStrings(annotations.examples || [], { max: 6, maxLen: 220 });
|
|
541
|
+
return {
|
|
542
|
+
id: name,
|
|
543
|
+
name: text(tool.title, 120) || humanizeName(name),
|
|
544
|
+
description: buildToolDescription(text(tool.title, 120), tool.description, {
|
|
545
|
+
...annotations,
|
|
546
|
+
whenToUse: annotations.whenToUse
|
|
547
|
+
|| (preferredInterface === 'webmcp'
|
|
548
|
+
? 'Use this explicit WebMCP tool when the browser exposes it instead of simulating DOM actions for the same outcome.'
|
|
549
|
+
: 'Use this explicit site tool when it matches the user goal instead of reconstructing the same action through generic DOM automation.'),
|
|
550
|
+
whyUse: annotations.whyUse
|
|
551
|
+
|| 'Explicit site tools expose stable schemas, clearer validation errors, and structured results that are easier to recover from than brittle DOM inference.',
|
|
552
|
+
}, preferredInterface),
|
|
553
|
+
...(tags.length ? { tags } : {}),
|
|
554
|
+
...(examples.length ? { examples } : {}),
|
|
555
|
+
inputSchema: normalizeSchema(tool.schema) || parametersToSchema(tool.parameters, tool.required || []) || {
|
|
556
|
+
type: 'object',
|
|
557
|
+
additionalProperties: false,
|
|
558
|
+
},
|
|
559
|
+
outputSchema: normalizeSchema(tool.outputSchema) || { ...DEFAULT_TOOL_OUTPUT_SCHEMA },
|
|
560
|
+
sideEffect,
|
|
561
|
+
requiresConfirmation,
|
|
562
|
+
preferredInterface,
|
|
563
|
+
category: annotations.priority === 'primary' ? 'primary' : 'secondary',
|
|
564
|
+
rover: {
|
|
565
|
+
toolName: name,
|
|
566
|
+
source: preferredInterface === 'webmcp' ? 'webmcp' : 'client_tool',
|
|
567
|
+
},
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
function dedupeSkills(skills) {
|
|
571
|
+
const out = [];
|
|
572
|
+
const seen = new Set();
|
|
573
|
+
for (const skill of skills) {
|
|
574
|
+
const id = text(skill.id, 120);
|
|
575
|
+
if (!id || seen.has(id.toLowerCase()))
|
|
576
|
+
continue;
|
|
577
|
+
seen.add(id.toLowerCase());
|
|
578
|
+
out.push({
|
|
579
|
+
...skill,
|
|
580
|
+
id,
|
|
581
|
+
});
|
|
582
|
+
}
|
|
583
|
+
return out;
|
|
584
|
+
}
|
|
585
|
+
function dedupeCapabilities(capabilities) {
|
|
586
|
+
const out = [];
|
|
587
|
+
const seen = new Set();
|
|
588
|
+
for (const capability of capabilities) {
|
|
589
|
+
const key = text(capability.capabilityId, 120);
|
|
590
|
+
if (!key || seen.has(key.toLowerCase()))
|
|
591
|
+
continue;
|
|
592
|
+
seen.add(key.toLowerCase());
|
|
593
|
+
out.push({
|
|
594
|
+
...capability,
|
|
595
|
+
capabilityId: key,
|
|
596
|
+
pageScope: uniqueStrings(capability.pageScope, { max: 24, maxLen: 80 }),
|
|
597
|
+
analyticsTags: uniqueStrings(capability.analyticsTags, { max: 24, maxLen: 64 }),
|
|
598
|
+
allowedExecutionModes: Array.from(new Set(capability.allowedExecutionModes || [])),
|
|
599
|
+
resultModes: Array.from(new Set(capability.resultModes || [])),
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
return out;
|
|
603
|
+
}
|
|
604
|
+
function dedupePages(pages, defaults) {
|
|
605
|
+
const out = [];
|
|
606
|
+
const seen = new Set();
|
|
607
|
+
for (const page of pages) {
|
|
608
|
+
const pageId = text(page.pageId, 120);
|
|
609
|
+
if (!pageId || seen.has(pageId.toLowerCase()))
|
|
610
|
+
continue;
|
|
611
|
+
seen.add(pageId.toLowerCase());
|
|
612
|
+
const beaconLabel = text(page.beaconLabel || page.visibleCueLabel || defaults?.beaconLabel, 180) || undefined;
|
|
613
|
+
out.push({
|
|
614
|
+
pageId,
|
|
615
|
+
route: normalizeRoutePath(page.route),
|
|
616
|
+
label: text(page.label, 180) || undefined,
|
|
617
|
+
capabilityIds: uniqueStrings(page.capabilityIds, { max: 48, maxLen: 120 }),
|
|
618
|
+
entityHints: uniqueStrings(page.entityHints, { max: 24, maxLen: 120 }),
|
|
619
|
+
formHints: uniqueStrings(page.formHints, { max: 24, maxLen: 120 }),
|
|
620
|
+
visibleCueLabel: beaconLabel,
|
|
621
|
+
beaconLabel,
|
|
622
|
+
discoveryMode: normalizeDiscoveryMode(page.discoveryMode, defaults?.mode || 'beacon'),
|
|
623
|
+
hostSurface: normalizeHostSurface(page.hostSurface, defaults?.hostSurface || 'auto'),
|
|
624
|
+
actionReveal: normalizeActionReveal(page.actionReveal, defaults?.actionReveal || 'click'),
|
|
625
|
+
agentModeEntryHints: uniqueStrings(Array.isArray(page.agentModeEntryHints) && page.agentModeEntryHints.length
|
|
626
|
+
? page.agentModeEntryHints
|
|
627
|
+
: defaults?.agentModeEntryHints, { max: 8, maxLen: 240 }),
|
|
628
|
+
capabilitySummary: uniqueStrings(page.capabilitySummary, { max: 12, maxLen: 180 }),
|
|
629
|
+
});
|
|
630
|
+
}
|
|
631
|
+
return out;
|
|
632
|
+
}
|
|
633
|
+
function applyPageCapabilitySummary(page, capabilities, defaults) {
|
|
634
|
+
const capabilityIds = uniqueStrings(page.capabilityIds, { max: 48, maxLen: 120 });
|
|
635
|
+
const capabilitySummary = uniqueStrings(Array.isArray(page.capabilitySummary) && page.capabilitySummary.length
|
|
636
|
+
? page.capabilitySummary
|
|
637
|
+
: capabilityIds
|
|
638
|
+
.map(capabilityId => capabilities.find(capability => capability.capabilityId === capabilityId)?.label)
|
|
639
|
+
.filter((value) => !!value), { max: 12, maxLen: 180 });
|
|
640
|
+
const beaconLabel = text(page.beaconLabel || page.visibleCueLabel || defaults.beaconLabel, 180) || undefined;
|
|
641
|
+
const agentModeEntryHints = uniqueStrings(Array.isArray(page.agentModeEntryHints) && page.agentModeEntryHints.length
|
|
642
|
+
? page.agentModeEntryHints
|
|
643
|
+
: defaults.agentModeEntryHints, { max: 8, maxLen: 240 });
|
|
644
|
+
return {
|
|
645
|
+
...page,
|
|
646
|
+
capabilityIds,
|
|
647
|
+
visibleCueLabel: beaconLabel,
|
|
648
|
+
beaconLabel,
|
|
649
|
+
discoveryMode: normalizeDiscoveryMode(page.discoveryMode, defaults.mode),
|
|
650
|
+
hostSurface: normalizeHostSurface(page.hostSurface, defaults.hostSurface),
|
|
651
|
+
actionReveal: normalizeActionReveal(page.actionReveal, defaults.actionReveal),
|
|
652
|
+
agentModeEntryHints,
|
|
653
|
+
capabilitySummary,
|
|
654
|
+
};
|
|
655
|
+
}
|
|
656
|
+
function buildCapabilityFromSkill(skill, config) {
|
|
657
|
+
const preferredExecution = config.preferExecution || 'auto';
|
|
658
|
+
return {
|
|
659
|
+
capabilityId: skill.id,
|
|
660
|
+
version: text(config.version, 80) || '1.0.0',
|
|
661
|
+
label: skill.name,
|
|
662
|
+
description: skill.description,
|
|
663
|
+
inputSchema: normalizeSchema(skill.inputSchema),
|
|
664
|
+
outputSchema: normalizeSchema(skill.outputSchema) || { ...DEFAULT_SKILL_OUTPUT_SCHEMA },
|
|
665
|
+
sideEffect: skill.sideEffect,
|
|
666
|
+
requiresConfirmation: skill.requiresConfirmation,
|
|
667
|
+
preferredInterface: skill.preferredInterface,
|
|
668
|
+
allowedExecutionModes: defaultAllowedExecutionModes(preferredExecution),
|
|
669
|
+
resultModes: ['text', 'json', 'observation', 'artifacts'],
|
|
670
|
+
pageScope: uniqueStrings(skill.rover?.shortcutId ? ['site', skill.rover.shortcutId] : ['site'], { max: 8, maxLen: 80 }),
|
|
671
|
+
analyticsTags: uniqueStrings(skill.tags, { max: 24, maxLen: 64 }),
|
|
672
|
+
rover: skill.rover ? { ...skill.rover } : undefined,
|
|
673
|
+
};
|
|
674
|
+
}
|
|
675
|
+
function resolveCurrentPageDefinition(config, capabilities, discoverySurface) {
|
|
676
|
+
const siteUrl = normalizeSiteUrl(config.siteUrl);
|
|
677
|
+
let pathname = '/';
|
|
678
|
+
try {
|
|
679
|
+
pathname = new URL(siteUrl).pathname || '/';
|
|
680
|
+
}
|
|
681
|
+
catch {
|
|
682
|
+
pathname = '/';
|
|
683
|
+
}
|
|
684
|
+
const normalizedCurrent = normalizePageDefinition({
|
|
685
|
+
pageId: config.pageContext?.pageId
|
|
686
|
+
|| pathname.replace(/^\/+|\/+$/g, '').replace(/[^a-z0-9/_-]+/gi, '-').replace(/\//g, '__')
|
|
687
|
+
|| 'home',
|
|
688
|
+
route: config.pageContext?.route || pathname,
|
|
689
|
+
label: config.pageContext?.label,
|
|
690
|
+
capabilityIds: config.pageContext?.capabilityIds,
|
|
691
|
+
entityHints: config.pageContext?.entityHints,
|
|
692
|
+
formHints: config.pageContext?.formHints,
|
|
693
|
+
visibleCueLabel: config.pageContext?.visibleCueLabel,
|
|
694
|
+
beaconLabel: config.pageContext?.beaconLabel,
|
|
695
|
+
discoveryMode: config.pageContext?.discoveryMode,
|
|
696
|
+
hostSurface: config.pageContext?.hostSurface,
|
|
697
|
+
actionReveal: config.pageContext?.actionReveal,
|
|
698
|
+
agentModeEntryHints: config.pageContext?.agentModeEntryHints,
|
|
699
|
+
capabilitySummary: config.pageContext?.capabilitySummary,
|
|
700
|
+
}, discoverySurface);
|
|
701
|
+
return normalizedCurrent || {
|
|
702
|
+
pageId: 'home',
|
|
703
|
+
route: pathname,
|
|
704
|
+
label: undefined,
|
|
705
|
+
capabilityIds: capabilities.slice(0, 12).map(capability => capability.capabilityId),
|
|
706
|
+
entityHints: [],
|
|
707
|
+
formHints: [],
|
|
708
|
+
visibleCueLabel: discoverySurface.beaconLabel,
|
|
709
|
+
beaconLabel: discoverySurface.beaconLabel,
|
|
710
|
+
discoveryMode: discoverySurface.mode,
|
|
711
|
+
hostSurface: discoverySurface.hostSurface,
|
|
712
|
+
actionReveal: discoverySurface.actionReveal,
|
|
713
|
+
agentModeEntryHints: discoverySurface.agentModeEntryHints,
|
|
714
|
+
capabilitySummary: capabilities.slice(0, 6).map(capability => capability.label),
|
|
715
|
+
};
|
|
716
|
+
}
|
|
717
|
+
function buildCapabilityGraph(config, skills, discoverySurface) {
|
|
718
|
+
const derivedCapabilities = skills.map(skill => buildCapabilityFromSkill(skill, config));
|
|
719
|
+
const explicitCapabilities = (config.capabilities || [])
|
|
720
|
+
.map(capability => normalizeCapabilityRecord(capability, {
|
|
721
|
+
version: text(config.version, 80) || '1.0.0',
|
|
722
|
+
allowedExecutionModes: defaultAllowedExecutionModes(config.preferExecution || 'auto'),
|
|
723
|
+
resultModes: ['text', 'json', 'observation', 'artifacts'],
|
|
724
|
+
}))
|
|
725
|
+
.filter((capability) => !!capability);
|
|
726
|
+
const capabilities = dedupeCapabilities([
|
|
727
|
+
...explicitCapabilities,
|
|
728
|
+
...derivedCapabilities,
|
|
729
|
+
]);
|
|
730
|
+
const currentPage = resolveCurrentPageDefinition(config, capabilities, discoverySurface);
|
|
731
|
+
const explicitPages = (config.pages || [])
|
|
732
|
+
.map(page => normalizePageDefinition(page, discoverySurface))
|
|
733
|
+
.filter((page) => !!page);
|
|
734
|
+
if (!currentPage.capabilityIds?.length) {
|
|
735
|
+
currentPage.capabilityIds = capabilities.slice(0, 12).map(capability => capability.capabilityId);
|
|
736
|
+
}
|
|
737
|
+
const pages = dedupePages([
|
|
738
|
+
...explicitPages,
|
|
739
|
+
currentPage,
|
|
740
|
+
], discoverySurface).map(page => applyPageCapabilitySummary(page, capabilities, discoverySurface));
|
|
741
|
+
const resolvedCurrentPage = pages.find(page => page.pageId === currentPage.pageId)
|
|
742
|
+
|| applyPageCapabilitySummary(currentPage, capabilities, discoverySurface);
|
|
743
|
+
return {
|
|
744
|
+
capabilities,
|
|
745
|
+
pages,
|
|
746
|
+
currentPage: resolvedCurrentPage,
|
|
747
|
+
};
|
|
748
|
+
}
|
|
749
|
+
function escapeHtmlAttr(value) {
|
|
750
|
+
return value
|
|
751
|
+
.replace(/&/g, '&')
|
|
752
|
+
.replace(/"/g, '"')
|
|
753
|
+
.replace(/</g, '<')
|
|
754
|
+
.replace(/>/g, '>');
|
|
755
|
+
}
|
|
756
|
+
function escapeScriptJson(value) {
|
|
757
|
+
return value
|
|
758
|
+
.replace(/</g, '\\u003c')
|
|
759
|
+
.replace(/>/g, '\\u003e')
|
|
760
|
+
.replace(/&/g, '\\u0026');
|
|
761
|
+
}
|
|
762
|
+
function buildInlineDataUrl(json) {
|
|
763
|
+
return `data:application/json;charset=utf-8,${encodeURIComponent(json)}`;
|
|
764
|
+
}
|
|
765
|
+
export function createRoverAgentCard(config) {
|
|
766
|
+
const siteUrl = normalizeSiteUrl(config.siteUrl);
|
|
767
|
+
const taskEndpoint = buildTaskEndpoint(config.apiBase);
|
|
768
|
+
const workflowEndpoint = buildWorkflowEndpoint(config.apiBase);
|
|
769
|
+
const serviceDescUrl = text(config.agentCardUrl) || DEFAULT_AGENT_CARD_PATH;
|
|
770
|
+
const roverSiteUrl = text(config.roverSiteUrl) || DEFAULT_ROVER_SITE_PATH;
|
|
771
|
+
const llmsUrl = text(config.llmsUrl);
|
|
772
|
+
const launchAccess = resolveAiLaunchAccess(config.aiAccess);
|
|
773
|
+
const promptLaunchEnabled = launchAccess.promptLaunchEnabled;
|
|
774
|
+
const shortcutLaunchEnabled = launchAccess.shortcutLaunchEnabled;
|
|
775
|
+
const publicTaskEnabled = promptLaunchEnabled || shortcutLaunchEnabled;
|
|
776
|
+
const cloudBrowserAllowed = config.aiAccess?.allowCloudBrowser !== false;
|
|
777
|
+
const delegatedHandoffs = config.aiAccess?.allowDelegatedHandoffs === true;
|
|
778
|
+
const shortcutSkills = (config.shortcuts || [])
|
|
779
|
+
.map(shortcut => buildShortcutSkill(shortcut, config, taskEndpoint))
|
|
780
|
+
.filter((skill) => !!skill);
|
|
781
|
+
const toolSkills = (config.tools || [])
|
|
782
|
+
.map(tool => buildToolSkill(tool, 'client_tool'))
|
|
783
|
+
.filter((skill) => !!skill);
|
|
784
|
+
const webmcpSkills = (config.webmcpTools || [])
|
|
785
|
+
.map(tool => buildToolSkill(tool, 'webmcp'))
|
|
786
|
+
.filter((skill) => !!skill);
|
|
787
|
+
const skills = dedupeSkills([
|
|
788
|
+
...shortcutSkills,
|
|
789
|
+
...(config.additionalSkills || []),
|
|
790
|
+
...toolSkills,
|
|
791
|
+
...webmcpSkills,
|
|
792
|
+
]);
|
|
793
|
+
const discoverySurface = resolveDiscoverySurfacePolicy(config);
|
|
794
|
+
const capabilityGraph = buildCapabilityGraph(config, skills, discoverySurface);
|
|
795
|
+
const effectiveDiscoverySurface = {
|
|
796
|
+
mode: normalizeDiscoveryMode(capabilityGraph.currentPage.discoveryMode, discoverySurface.mode),
|
|
797
|
+
branding: discoverySurface.branding,
|
|
798
|
+
hostSurface: normalizeHostSurface(capabilityGraph.currentPage.hostSurface, discoverySurface.hostSurface),
|
|
799
|
+
actionReveal: normalizeActionReveal(capabilityGraph.currentPage.actionReveal, discoverySurface.actionReveal),
|
|
800
|
+
beaconLabel: text(capabilityGraph.currentPage.beaconLabel
|
|
801
|
+
|| capabilityGraph.currentPage.visibleCueLabel
|
|
802
|
+
|| discoverySurface.beaconLabel, 180) || undefined,
|
|
803
|
+
agentModeEntryHints: uniqueStrings(capabilityGraph.currentPage.agentModeEntryHints?.length
|
|
804
|
+
? capabilityGraph.currentPage.agentModeEntryHints
|
|
805
|
+
: discoverySurface.agentModeEntryHints, { max: 8, maxLen: 240 }),
|
|
806
|
+
compactActionMaxActions: ROVER_DISCOVERY_ACTION_SHEET_MAX_ACTIONS,
|
|
807
|
+
};
|
|
808
|
+
const siteName = text(config.siteName, 120)
|
|
809
|
+
|| (() => {
|
|
810
|
+
try {
|
|
811
|
+
return new URL(siteUrl).hostname;
|
|
812
|
+
}
|
|
813
|
+
catch {
|
|
814
|
+
return 'Rover Site';
|
|
815
|
+
}
|
|
816
|
+
})();
|
|
817
|
+
const description = text(config.description, 320)
|
|
818
|
+
|| `Structured Rover entrypoints for ${siteName}. Prefer the published shortcuts and explicit tools over raw DOM automation whenever they match the user's goal.`;
|
|
819
|
+
return {
|
|
820
|
+
name: siteName,
|
|
821
|
+
description,
|
|
822
|
+
url: taskEndpoint,
|
|
823
|
+
version: text(config.version, 80) || '1.0.0',
|
|
824
|
+
defaultInputModes: ['text/plain', 'application/json'],
|
|
825
|
+
defaultOutputModes: ['text/plain', 'application/json'],
|
|
826
|
+
capabilities: {
|
|
827
|
+
streaming: publicTaskEnabled,
|
|
828
|
+
publicTasks: publicTaskEnabled,
|
|
829
|
+
stateTransitions: publicTaskEnabled,
|
|
830
|
+
delegatedHandoffs,
|
|
831
|
+
webmcp: webmcpSkills.length > 0,
|
|
832
|
+
},
|
|
833
|
+
skills,
|
|
834
|
+
interfaces: [
|
|
835
|
+
{
|
|
836
|
+
type: 'task',
|
|
837
|
+
url: taskEndpoint,
|
|
838
|
+
description: 'Canonical Rover ATP task creation endpoint.',
|
|
839
|
+
available: promptLaunchEnabled,
|
|
840
|
+
},
|
|
841
|
+
{
|
|
842
|
+
type: 'workflow',
|
|
843
|
+
url: workflowEndpoint,
|
|
844
|
+
description: 'Aggregated Rover workflow resource for delegated tasks.',
|
|
845
|
+
available: true,
|
|
846
|
+
},
|
|
847
|
+
{
|
|
848
|
+
type: 'site',
|
|
849
|
+
url: siteUrl,
|
|
850
|
+
description: 'Human-visible website entrypoint.',
|
|
851
|
+
available: true,
|
|
852
|
+
},
|
|
853
|
+
{
|
|
854
|
+
type: 'deep_link',
|
|
855
|
+
url: siteUrl,
|
|
856
|
+
description: 'Browser-first Rover deep link surface using rover= or rover_shortcut= query params.',
|
|
857
|
+
available: publicTaskEnabled,
|
|
858
|
+
},
|
|
859
|
+
{
|
|
860
|
+
type: 'webmcp',
|
|
861
|
+
url: siteUrl,
|
|
862
|
+
description: 'WebMCP tool surface when the browser host supports navigator.modelContext.',
|
|
863
|
+
available: webmcpSkills.length > 0,
|
|
864
|
+
},
|
|
865
|
+
],
|
|
866
|
+
extensions: {
|
|
867
|
+
rover: {
|
|
868
|
+
siteId: text(config.siteId, 120) || undefined,
|
|
869
|
+
siteUrl,
|
|
870
|
+
taskEndpoint,
|
|
871
|
+
workflowEndpoint,
|
|
872
|
+
serviceDescUrl,
|
|
873
|
+
roverSiteUrl,
|
|
874
|
+
...(llmsUrl ? { llmsUrl } : {}),
|
|
875
|
+
preferredExecution: config.preferExecution || 'auto',
|
|
876
|
+
promptLaunchEnabled,
|
|
877
|
+
shortcutLaunchEnabled,
|
|
878
|
+
cloudBrowserAllowed,
|
|
879
|
+
delegatedHandoffs,
|
|
880
|
+
instructions: [
|
|
881
|
+
'Prefer exact Rover shortcuts and explicit site tools over raw DOM automation when the user goal matches a published skill.',
|
|
882
|
+
'Use POST /v1/tasks when you need structured progress, continuation input, or a stable final result channel.',
|
|
883
|
+
'Fall back to generic DOM automation only when no matching Rover shortcut or explicit tool exists for the requested outcome.',
|
|
884
|
+
],
|
|
885
|
+
capabilitiesGraph: capabilityGraph.capabilities,
|
|
886
|
+
pages: capabilityGraph.pages,
|
|
887
|
+
currentPage: capabilityGraph.currentPage,
|
|
888
|
+
discoverySurface: effectiveDiscoverySurface,
|
|
889
|
+
shortcuts: (config.shortcuts || [])
|
|
890
|
+
.map(shortcut => normalizePublishedShortcut(shortcut))
|
|
891
|
+
.filter((shortcut) => !!shortcut),
|
|
892
|
+
webmcp: {
|
|
893
|
+
available: webmcpSkills.length > 0,
|
|
894
|
+
tools: webmcpSkills.map(skill => skill.id),
|
|
895
|
+
},
|
|
896
|
+
},
|
|
897
|
+
},
|
|
898
|
+
};
|
|
899
|
+
}
|
|
900
|
+
export function createRoverSiteProfile(config) {
|
|
901
|
+
const card = createRoverAgentCard(config);
|
|
902
|
+
const rover = card.extensions?.rover;
|
|
903
|
+
return {
|
|
904
|
+
identity: {
|
|
905
|
+
siteId: rover?.siteId,
|
|
906
|
+
name: card.name,
|
|
907
|
+
description: card.description,
|
|
908
|
+
siteUrl: rover?.siteUrl || normalizeSiteUrl(config.siteUrl),
|
|
909
|
+
version: card.version,
|
|
910
|
+
},
|
|
911
|
+
actions: rover?.capabilitiesGraph || [],
|
|
912
|
+
pages: rover?.pages || [],
|
|
913
|
+
policies: {
|
|
914
|
+
preferredExecution: rover?.preferredExecution || 'auto',
|
|
915
|
+
promptLaunchEnabled: rover?.promptLaunchEnabled !== false,
|
|
916
|
+
shortcutLaunchEnabled: rover?.shortcutLaunchEnabled !== false,
|
|
917
|
+
cloudBrowserAllowed: rover?.cloudBrowserAllowed !== false,
|
|
918
|
+
delegatedHandoffs: rover?.delegatedHandoffs === true,
|
|
919
|
+
},
|
|
920
|
+
auth: {
|
|
921
|
+
taskEndpoint: rover?.taskEndpoint || buildTaskEndpoint(config.apiBase),
|
|
922
|
+
workflowEndpoint: rover?.workflowEndpoint || buildWorkflowEndpoint(config.apiBase),
|
|
923
|
+
acceptsHttpMessageSignatures: true,
|
|
924
|
+
supportsUnsignedSelfReportedIdentity: true,
|
|
925
|
+
},
|
|
926
|
+
analytics: {
|
|
927
|
+
layer: 'roverbook',
|
|
928
|
+
taskIdField: 'taskId',
|
|
929
|
+
workflowIdField: 'workflowId',
|
|
930
|
+
capabilityIdField: 'capabilityId',
|
|
931
|
+
pageIdField: 'pageId',
|
|
932
|
+
},
|
|
933
|
+
currentPage: rover?.currentPage,
|
|
934
|
+
display: {
|
|
935
|
+
mode: rover?.discoverySurface.mode || 'beacon',
|
|
936
|
+
branding: rover?.discoverySurface.branding || 'site',
|
|
937
|
+
hostSurface: rover?.discoverySurface.hostSurface || 'auto',
|
|
938
|
+
actionReveal: rover?.discoverySurface.actionReveal || 'click',
|
|
939
|
+
beaconLabel: rover?.discoverySurface.beaconLabel,
|
|
940
|
+
agentModeEntryHints: rover?.discoverySurface.agentModeEntryHints || [],
|
|
941
|
+
compactActionMaxActions: rover?.discoverySurface.compactActionMaxActions || ROVER_DISCOVERY_ACTION_SHEET_MAX_ACTIONS,
|
|
942
|
+
},
|
|
943
|
+
artifacts: {
|
|
944
|
+
agentCardUrl: rover?.serviceDescUrl || text(config.agentCardUrl) || DEFAULT_AGENT_CARD_PATH,
|
|
945
|
+
roverSiteUrl: rover?.roverSiteUrl || text(config.roverSiteUrl) || DEFAULT_ROVER_SITE_PATH,
|
|
946
|
+
...(rover?.llmsUrl ? { llmsUrl: rover.llmsUrl } : {}),
|
|
947
|
+
siteUrl: rover?.siteUrl || normalizeSiteUrl(config.siteUrl),
|
|
948
|
+
},
|
|
949
|
+
interfaces: card.interfaces,
|
|
950
|
+
};
|
|
951
|
+
}
|
|
952
|
+
export function buildRoverAgentDiscoveryPayloads(config) {
|
|
953
|
+
const cardJson = createRoverAgentCardJson(config);
|
|
954
|
+
const card = createRoverAgentCard(config);
|
|
955
|
+
const roverSite = createRoverSiteProfile(config);
|
|
956
|
+
const roverSiteJson = JSON.stringify(roverSite, null, 2);
|
|
957
|
+
const inlineCardUrl = buildInlineDataUrl(cardJson);
|
|
958
|
+
const serviceDescHref = text(config.agentCardUrl) || inlineCardUrl;
|
|
959
|
+
const roverSiteHref = text(config.roverSiteUrl) || DEFAULT_ROVER_SITE_PATH;
|
|
960
|
+
const pageManifest = card.extensions?.rover.currentPage || {
|
|
961
|
+
pageId: 'home',
|
|
962
|
+
route: '/',
|
|
963
|
+
capabilityIds: [],
|
|
964
|
+
};
|
|
965
|
+
const pageManifestJson = JSON.stringify(pageManifest, null, 2);
|
|
966
|
+
const marker = {
|
|
967
|
+
task: card.extensions?.rover.taskEndpoint,
|
|
968
|
+
card: serviceDescHref,
|
|
969
|
+
roverSite: roverSiteHref,
|
|
970
|
+
site: card.extensions?.rover.siteUrl,
|
|
971
|
+
workflow: card.extensions?.rover.workflowEndpoint,
|
|
972
|
+
page: pageManifest.pageId,
|
|
973
|
+
preferExecution: card.extensions?.rover.preferredExecution,
|
|
974
|
+
discoveryMode: card.extensions?.rover.discoverySurface.mode,
|
|
975
|
+
hostSurface: card.extensions?.rover.discoverySurface.hostSurface,
|
|
976
|
+
actionReveal: card.extensions?.rover.discoverySurface.actionReveal,
|
|
977
|
+
beaconLabel: card.extensions?.rover.discoverySurface.beaconLabel,
|
|
978
|
+
skills: card.skills.slice(0, 24).map(skill => ({
|
|
979
|
+
id: skill.id,
|
|
980
|
+
name: skill.name,
|
|
981
|
+
})),
|
|
982
|
+
capabilities: (card.extensions?.rover.capabilitiesGraph || []).slice(0, 24).map(capability => ({
|
|
983
|
+
capabilityId: capability.capabilityId,
|
|
984
|
+
label: capability.label,
|
|
985
|
+
})),
|
|
986
|
+
};
|
|
987
|
+
return {
|
|
988
|
+
card,
|
|
989
|
+
cardJson,
|
|
990
|
+
serviceDescHref,
|
|
991
|
+
roverSite,
|
|
992
|
+
roverSiteJson,
|
|
993
|
+
roverSiteHref,
|
|
994
|
+
pageManifest,
|
|
995
|
+
pageManifestJson,
|
|
996
|
+
llmsUrl: text(config.llmsUrl || card.extensions?.rover.llmsUrl) || undefined,
|
|
997
|
+
marker,
|
|
998
|
+
markerJson: escapeScriptJson(JSON.stringify(marker)),
|
|
999
|
+
};
|
|
1000
|
+
}
|
|
1001
|
+
export function createRoverAgentCardJson(config, options) {
|
|
1002
|
+
return JSON.stringify(createRoverAgentCard(config), null, options?.pretty === false ? undefined : 2);
|
|
1003
|
+
}
|
|
1004
|
+
export function createRoverWellKnownAgentCard(config, options) {
|
|
1005
|
+
return createRoverAgentCardJson(config, options);
|
|
1006
|
+
}
|
|
1007
|
+
export function createRoverSiteProfileJson(config, options) {
|
|
1008
|
+
return JSON.stringify(createRoverSiteProfile(config), null, options?.pretty === false ? undefined : 2);
|
|
1009
|
+
}
|
|
1010
|
+
export function createRoverWellKnownSiteProfile(config, options) {
|
|
1011
|
+
return createRoverSiteProfileJson(config, options);
|
|
1012
|
+
}
|
|
1013
|
+
export function createRoverServiceDescLinkHeader(config) {
|
|
1014
|
+
const parts = [
|
|
1015
|
+
`<${text(config.agentCardUrl) || DEFAULT_AGENT_CARD_PATH}>; rel="service-desc"; type="application/json"`,
|
|
1016
|
+
];
|
|
1017
|
+
const llmsUrl = text(config.llmsUrl);
|
|
1018
|
+
if (llmsUrl) {
|
|
1019
|
+
parts.push(`<${llmsUrl}>; rel="service-doc"; type="text/markdown"`);
|
|
1020
|
+
}
|
|
1021
|
+
return parts.join(', ');
|
|
1022
|
+
}
|
|
1023
|
+
export function createRoverAgentDiscoveryTags(config) {
|
|
1024
|
+
const { cardJson, llmsUrl, markerJson, pageManifestJson, roverSiteJson, serviceDescHref, } = buildRoverAgentDiscoveryPayloads(config);
|
|
1025
|
+
const escapedCardJson = escapeScriptJson(cardJson);
|
|
1026
|
+
const escapedRoverSiteJson = escapeScriptJson(roverSiteJson);
|
|
1027
|
+
const escapedPageManifestJson = escapeScriptJson(pageManifestJson);
|
|
1028
|
+
const lines = [
|
|
1029
|
+
`<script type="application/agent+json">${markerJson}</script>`,
|
|
1030
|
+
`<link rel="service-desc" href="${escapeHtmlAttr(serviceDescHref)}" type="application/json" />`,
|
|
1031
|
+
];
|
|
1032
|
+
if (llmsUrl) {
|
|
1033
|
+
lines.push(`<link rel="service-doc" href="${escapeHtmlAttr(llmsUrl)}" type="text/markdown" />`);
|
|
1034
|
+
}
|
|
1035
|
+
lines.push(`<script type="application/rover-site+json">${escapedRoverSiteJson}</script>`);
|
|
1036
|
+
lines.push(`<script type="application/rover-page+json">${escapedPageManifestJson}</script>`);
|
|
1037
|
+
lines.push(`<script type="application/agent-card+json">${escapedCardJson}</script>`);
|
|
1038
|
+
return lines.join('\n');
|
|
1039
|
+
}
|
|
1040
|
+
export { createRoverAgentDiscoverySnapshot };
|