@syrin/iris 0.5.0 → 0.6.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +2587 -1027
- package/dist/index.js +414 -114
- package/dist/next.js +5 -1
- package/dist/server.d.ts +9 -0
- package/dist/server.js +1903 -1005
- package/dist/test.js +1918 -1074
- package/dist/vite.d.ts +43 -0
- package/dist/vite.js +144 -0
- package/package.json +15 -10
package/dist/index.js
CHANGED
|
@@ -2,6 +2,30 @@
|
|
|
2
2
|
var IRIS_DEFAULT_PORT = 4400;
|
|
3
3
|
var IRIS_WS_PATH = "/iris";
|
|
4
4
|
var IRIS_PROTOCOL_VERSION = 1;
|
|
5
|
+
var TRANSPORT_LIMITS = {
|
|
6
|
+
MAX_MESSAGE_BYTES: 1024 * 1024,
|
|
7
|
+
MAX_MESSAGES_PER_SECOND: 1e3,
|
|
8
|
+
MAX_SESSIONS: 32,
|
|
9
|
+
MAX_PENDING_CONNECTIONS: 16,
|
|
10
|
+
HELLO_TIMEOUT_MS: 5e3,
|
|
11
|
+
MAX_BUFFER_BYTES: 8 * 1024 * 1024,
|
|
12
|
+
MAX_SESSION_ID_LENGTH: 128,
|
|
13
|
+
MAX_URL_LENGTH: 4096,
|
|
14
|
+
MAX_TITLE_LENGTH: 512,
|
|
15
|
+
MAX_ADAPTERS: 32,
|
|
16
|
+
MAX_ADAPTER_NAME_LENGTH: 128,
|
|
17
|
+
MAX_TOKEN_LENGTH: 512,
|
|
18
|
+
MAX_COMMAND_ID_LENGTH: 128,
|
|
19
|
+
MAX_COMMAND_NAME_LENGTH: 128,
|
|
20
|
+
MAX_REF_LENGTH: 128,
|
|
21
|
+
MAX_ERROR_LENGTH: 4096,
|
|
22
|
+
MAX_SERIALIZE_DEPTH: 8,
|
|
23
|
+
MAX_COLLECTION_ITEMS: 200,
|
|
24
|
+
MAX_OBJECT_KEYS: 200,
|
|
25
|
+
MAX_STRING_LENGTH: 64 * 1024
|
|
26
|
+
};
|
|
27
|
+
var REDACTED_VALUE = "[REDACTED]";
|
|
28
|
+
var DANGEROUS_ACTION_CONFIRM_ARG = "confirmDangerous";
|
|
5
29
|
var UpdateCheckIntervalMs = 24 * 60 * 60 * 1e3;
|
|
6
30
|
var RunKind = {
|
|
7
31
|
FLOW_REPLAY: "flow_replay",
|
|
@@ -43,6 +67,11 @@ var RecorderPhase = {
|
|
|
43
67
|
ANNOTATING: "annotating"
|
|
44
68
|
// recording paused, awaiting an annotation target/kind
|
|
45
69
|
};
|
|
70
|
+
var RING_BUFFER_DEFAULTS = {
|
|
71
|
+
MAX_EVENTS: 2e3,
|
|
72
|
+
MAX_AGE_MS: 6e4,
|
|
73
|
+
MAX_BYTES: TRANSPORT_LIMITS.MAX_BUFFER_BYTES
|
|
74
|
+
};
|
|
46
75
|
var EventType = {
|
|
47
76
|
DOM_ADDED: "dom.added",
|
|
48
77
|
DOM_REMOVED: "dom.removed",
|
|
@@ -207,136 +236,209 @@ var MessageKind = {
|
|
|
207
236
|
EVENT: "event"
|
|
208
237
|
};
|
|
209
238
|
|
|
210
|
-
// ../protocol/dist/
|
|
239
|
+
// ../protocol/dist/messages.js
|
|
211
240
|
import { z } from "zod";
|
|
212
|
-
var
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
241
|
+
var sessionIdSchema = z.string().min(1).max(TRANSPORT_LIMITS.MAX_SESSION_ID_LENGTH);
|
|
242
|
+
var refSchema = z.string().max(TRANSPORT_LIMITS.MAX_REF_LENGTH);
|
|
243
|
+
var HumanControlDataSchema = z.object({
|
|
244
|
+
kind: z.nativeEnum(HumanControlKind),
|
|
245
|
+
text: z.string().optional()
|
|
246
|
+
});
|
|
247
|
+
var IrisEventSchema = z.object({
|
|
248
|
+
t: z.number(),
|
|
249
|
+
type: z.nativeEnum(EventType),
|
|
250
|
+
sessionId: sessionIdSchema,
|
|
251
|
+
/** Stable element reference this event concerns, when applicable (e.g. "e7"). */
|
|
252
|
+
ref: refSchema.optional(),
|
|
253
|
+
/** Event-type-specific payload. Kept open here; refined per observer at the edges. */
|
|
254
|
+
data: z.record(z.unknown()).default({})
|
|
255
|
+
});
|
|
256
|
+
var HelloMessageSchema = z.object({
|
|
257
|
+
kind: z.literal(MessageKind.HELLO),
|
|
258
|
+
protocolVersion: z.literal(IRIS_PROTOCOL_VERSION),
|
|
259
|
+
sessionId: sessionIdSchema,
|
|
260
|
+
url: z.string().max(TRANSPORT_LIMITS.MAX_URL_LENGTH),
|
|
261
|
+
title: z.string().max(TRANSPORT_LIMITS.MAX_TITLE_LENGTH),
|
|
262
|
+
adapters: z.array(z.string().max(TRANSPORT_LIMITS.MAX_ADAPTER_NAME_LENGTH)).max(TRANSPORT_LIMITS.MAX_ADAPTERS),
|
|
263
|
+
/** Optional browser/bridge pairing token. Required when the bridge configures one. */
|
|
264
|
+
token: z.string().max(TRANSPORT_LIMITS.MAX_TOKEN_LENGTH).optional(),
|
|
265
|
+
/** Whether the app has advertised a capability registry (iris.describe). */
|
|
266
|
+
hasCapabilities: z.boolean().optional()
|
|
267
|
+
});
|
|
268
|
+
var CommandMessageSchema = z.object({
|
|
269
|
+
kind: z.literal(MessageKind.COMMAND),
|
|
270
|
+
id: z.string().min(1).max(TRANSPORT_LIMITS.MAX_COMMAND_ID_LENGTH),
|
|
271
|
+
sessionId: sessionIdSchema.optional(),
|
|
272
|
+
name: z.string().min(1).max(TRANSPORT_LIMITS.MAX_COMMAND_NAME_LENGTH),
|
|
273
|
+
args: z.record(z.unknown()).default({})
|
|
274
|
+
});
|
|
275
|
+
var CommandResultSchema = z.object({
|
|
276
|
+
kind: z.literal(MessageKind.COMMAND_RESULT),
|
|
277
|
+
id: z.string().min(1).max(TRANSPORT_LIMITS.MAX_COMMAND_ID_LENGTH),
|
|
278
|
+
ok: z.boolean(),
|
|
279
|
+
result: z.unknown().optional(),
|
|
280
|
+
error: z.string().max(TRANSPORT_LIMITS.MAX_ERROR_LENGTH).optional()
|
|
281
|
+
});
|
|
282
|
+
var EventMessageSchema = z.object({
|
|
283
|
+
kind: z.literal(MessageKind.EVENT),
|
|
284
|
+
event: IrisEventSchema
|
|
285
|
+
});
|
|
286
|
+
var IrisMessageSchema = z.discriminatedUnion("kind", [
|
|
287
|
+
HelloMessageSchema,
|
|
288
|
+
CommandMessageSchema,
|
|
289
|
+
CommandResultSchema,
|
|
290
|
+
EventMessageSchema
|
|
291
|
+
]);
|
|
292
|
+
|
|
293
|
+
// ../protocol/dist/security.js
|
|
294
|
+
var DANGEROUS_ACTION = /\b(delete|remove|destroy|erase|drop|terminate|revoke|reset|logout|log out|sign out|close account|cancel subscription|purchase|buy|pay|place order|confirm order|deploy|publish|send|transfer|withdraw|refund)\b/i;
|
|
295
|
+
function isLoopbackHostname(hostname) {
|
|
296
|
+
const normalized = hostname.toLowerCase().replace(/^\[|\]$/g, "");
|
|
297
|
+
if (normalized === "localhost" || normalized === "::1" || normalized === "0:0:0:0:0:0:0:1") {
|
|
298
|
+
return true;
|
|
299
|
+
}
|
|
300
|
+
const octets = normalized.split(".");
|
|
301
|
+
return octets.length === 4 && octets[0] === "127" && octets.every((octet) => {
|
|
302
|
+
if (!/^\d{1,3}$/.test(octet))
|
|
303
|
+
return false;
|
|
304
|
+
const value = Number(octet);
|
|
305
|
+
return value >= 0 && value <= 255;
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
function isDangerousActionText(text) {
|
|
309
|
+
return DANGEROUS_ACTION.test(text.replace(/[_-]+/g, " "));
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// ../protocol/dist/types.js
|
|
313
|
+
import { z as z2 } from "zod";
|
|
314
|
+
var ElementQuerySchema = z2.object({
|
|
315
|
+
by: z2.nativeEnum(QueryBy).optional(),
|
|
316
|
+
value: z2.string().optional(),
|
|
317
|
+
role: z2.string().optional(),
|
|
318
|
+
name: z2.string().optional(),
|
|
319
|
+
text: z2.string().optional(),
|
|
320
|
+
label: z2.string().optional(),
|
|
321
|
+
placeholder: z2.string().optional(),
|
|
322
|
+
testid: z2.string().optional(),
|
|
323
|
+
alt: z2.string().optional(),
|
|
222
324
|
/** CSS selector or ref to scope the search. */
|
|
223
|
-
scope:
|
|
325
|
+
scope: z2.string().optional()
|
|
224
326
|
});
|
|
225
|
-
var CapabilityFlowSchema =
|
|
226
|
-
name:
|
|
227
|
-
steps:
|
|
327
|
+
var CapabilityFlowSchema = z2.object({
|
|
328
|
+
name: z2.string(),
|
|
329
|
+
steps: z2.array(z2.string())
|
|
228
330
|
});
|
|
229
|
-
var CapabilitiesSchema =
|
|
230
|
-
testids:
|
|
231
|
-
signals:
|
|
232
|
-
stores:
|
|
233
|
-
flows:
|
|
331
|
+
var CapabilitiesSchema = z2.object({
|
|
332
|
+
testids: z2.array(z2.string()),
|
|
333
|
+
signals: z2.array(z2.string()),
|
|
334
|
+
stores: z2.array(z2.string()),
|
|
335
|
+
flows: z2.array(CapabilityFlowSchema)
|
|
234
336
|
});
|
|
235
|
-
var ContractFileSchema =
|
|
236
|
-
version:
|
|
237
|
-
generatedAt:
|
|
337
|
+
var ContractFileSchema = z2.object({
|
|
338
|
+
version: z2.number(),
|
|
339
|
+
generatedAt: z2.number(),
|
|
238
340
|
capabilities: CapabilitiesSchema
|
|
239
341
|
});
|
|
240
|
-
var RunEvidenceSchema =
|
|
241
|
-
consoleErrors:
|
|
242
|
-
networkErrors:
|
|
243
|
-
driftSteps:
|
|
342
|
+
var RunEvidenceSchema = z2.object({
|
|
343
|
+
consoleErrors: z2.number().optional(),
|
|
344
|
+
networkErrors: z2.number().optional(),
|
|
345
|
+
driftSteps: z2.number().optional()
|
|
244
346
|
});
|
|
245
|
-
var RunRecordSchema =
|
|
246
|
-
kind:
|
|
247
|
-
name:
|
|
248
|
-
status:
|
|
249
|
-
at:
|
|
250
|
-
summary:
|
|
347
|
+
var RunRecordSchema = z2.object({
|
|
348
|
+
kind: z2.nativeEnum(RunKind),
|
|
349
|
+
name: z2.string(),
|
|
350
|
+
status: z2.nativeEnum(RunStatus),
|
|
351
|
+
at: z2.number(),
|
|
352
|
+
summary: z2.string().optional(),
|
|
251
353
|
evidence: RunEvidenceSchema.optional(),
|
|
252
|
-
durationMs:
|
|
354
|
+
durationMs: z2.number().optional()
|
|
253
355
|
});
|
|
254
|
-
var ProjectLearnedSchema =
|
|
255
|
-
flows:
|
|
256
|
-
routes:
|
|
356
|
+
var ProjectLearnedSchema = z2.object({
|
|
357
|
+
flows: z2.array(z2.string()).optional(),
|
|
358
|
+
routes: z2.array(z2.string()).optional()
|
|
257
359
|
});
|
|
258
|
-
var ProjectFileSchema =
|
|
259
|
-
version:
|
|
360
|
+
var ProjectFileSchema = z2.object({
|
|
361
|
+
version: z2.number(),
|
|
260
362
|
learned: ProjectLearnedSchema.optional(),
|
|
261
|
-
runs:
|
|
363
|
+
runs: z2.array(RunRecordSchema)
|
|
262
364
|
});
|
|
263
|
-
var FlowAnchorSchema =
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
kind:
|
|
267
|
-
role:
|
|
268
|
-
name:
|
|
365
|
+
var FlowAnchorSchema = z2.discriminatedUnion("kind", [
|
|
366
|
+
z2.object({ kind: z2.literal(AnchorKind.TESTID), value: z2.string().min(1) }),
|
|
367
|
+
z2.object({
|
|
368
|
+
kind: z2.literal(AnchorKind.ROLE),
|
|
369
|
+
role: z2.string().min(1),
|
|
370
|
+
name: z2.string().optional()
|
|
269
371
|
}),
|
|
270
|
-
|
|
372
|
+
z2.object({ kind: z2.literal(AnchorKind.SIGNAL), name: z2.string().min(1) })
|
|
271
373
|
]);
|
|
272
|
-
var FlowExpectSchema =
|
|
273
|
-
signal:
|
|
374
|
+
var FlowExpectSchema = z2.object({
|
|
375
|
+
signal: z2.string().optional(),
|
|
274
376
|
/**
|
|
275
377
|
* Optional payload shape an `assert-signal` annotation requires the signal
|
|
276
378
|
* to match (the predicate DSL's signal.dataMatches). Additive/optional — a flow file with a
|
|
277
379
|
* bare `signal` still parses, and the on-disk version stays FLOW_FILE_VERSION 1.
|
|
278
380
|
*/
|
|
279
|
-
signalData:
|
|
280
|
-
net:
|
|
281
|
-
method:
|
|
282
|
-
urlContains:
|
|
283
|
-
status:
|
|
381
|
+
signalData: z2.record(z2.unknown()).optional(),
|
|
382
|
+
net: z2.object({
|
|
383
|
+
method: z2.string().optional(),
|
|
384
|
+
urlContains: z2.string().optional(),
|
|
385
|
+
status: z2.number().optional()
|
|
284
386
|
}).optional(),
|
|
285
|
-
element:
|
|
286
|
-
testid:
|
|
287
|
-
role:
|
|
288
|
-
name:
|
|
387
|
+
element: z2.object({
|
|
388
|
+
testid: z2.string().optional(),
|
|
389
|
+
role: z2.string().optional(),
|
|
390
|
+
name: z2.string().optional()
|
|
289
391
|
}).optional()
|
|
290
392
|
});
|
|
291
|
-
var baseFlowStep =
|
|
292
|
-
tool:
|
|
393
|
+
var baseFlowStep = z2.object({
|
|
394
|
+
tool: z2.string(),
|
|
293
395
|
anchor: FlowAnchorSchema,
|
|
294
|
-
action:
|
|
295
|
-
args:
|
|
396
|
+
action: z2.nativeEnum(ActionType).optional(),
|
|
397
|
+
args: z2.record(z2.unknown()).optional(),
|
|
296
398
|
expect: FlowExpectSchema.optional(),
|
|
297
|
-
degraded:
|
|
399
|
+
degraded: z2.boolean().optional()
|
|
298
400
|
});
|
|
299
401
|
var FlowStepSchema = baseFlowStep.extend({
|
|
300
|
-
steps:
|
|
402
|
+
steps: z2.lazy(() => z2.array(FlowStepSchema).optional())
|
|
301
403
|
});
|
|
302
|
-
var FlowFileSchema =
|
|
303
|
-
version:
|
|
304
|
-
name:
|
|
404
|
+
var FlowFileSchema = z2.object({
|
|
405
|
+
version: z2.literal(FLOW_FILE_VERSION),
|
|
406
|
+
name: z2.string(),
|
|
305
407
|
// FUTURE: fixtures/preconditions — schema slot reserved, unpopulated this cut. The recorder
|
|
306
408
|
// never writes it and no fixture runner exists.
|
|
307
|
-
fixture:
|
|
409
|
+
fixture: z2.string().optional(),
|
|
308
410
|
/** From the injected clock (ms) — deterministic in tests, byte-stable on disk. */
|
|
309
|
-
createdAt:
|
|
310
|
-
steps:
|
|
411
|
+
createdAt: z2.number(),
|
|
412
|
+
steps: z2.array(FlowStepSchema),
|
|
311
413
|
success: FlowExpectSchema.optional(),
|
|
312
414
|
/**
|
|
313
415
|
* Anchors whose CONTENT must not be asserted (e.g. LLM output). Replay asserts
|
|
314
416
|
* presence, not words. Compiled from a `mark-dynamic` annotation.
|
|
315
417
|
*/
|
|
316
|
-
dynamic:
|
|
418
|
+
dynamic: z2.array(FlowAnchorSchema).optional()
|
|
317
419
|
});
|
|
318
|
-
var RecordedFlowSchema =
|
|
319
|
-
name:
|
|
420
|
+
var RecordedFlowSchema = z2.object({
|
|
421
|
+
name: z2.string(),
|
|
320
422
|
flow: FlowFileSchema
|
|
321
423
|
});
|
|
322
|
-
var AnnotationSchema =
|
|
323
|
-
|
|
324
|
-
kind:
|
|
325
|
-
name:
|
|
326
|
-
dataMatches:
|
|
424
|
+
var AnnotationSchema = z2.discriminatedUnion("kind", [
|
|
425
|
+
z2.object({
|
|
426
|
+
kind: z2.literal(AnnotationKind.ASSERT_SIGNAL),
|
|
427
|
+
name: z2.string().min(1),
|
|
428
|
+
dataMatches: z2.record(z2.unknown()).optional()
|
|
327
429
|
}),
|
|
328
|
-
|
|
329
|
-
kind:
|
|
330
|
-
testid:
|
|
430
|
+
z2.object({
|
|
431
|
+
kind: z2.literal(AnnotationKind.ASSERT_VISIBLE),
|
|
432
|
+
testid: z2.string().min(1)
|
|
331
433
|
}),
|
|
332
|
-
|
|
333
|
-
kind:
|
|
334
|
-
testid:
|
|
434
|
+
z2.object({
|
|
435
|
+
kind: z2.literal(AnnotationKind.MARK_DYNAMIC),
|
|
436
|
+
testid: z2.string().min(1)
|
|
335
437
|
}),
|
|
336
|
-
|
|
337
|
-
kind:
|
|
338
|
-
signal:
|
|
339
|
-
testid:
|
|
438
|
+
z2.object({
|
|
439
|
+
kind: z2.literal(AnnotationKind.SUCCESS_STATE),
|
|
440
|
+
signal: z2.string().min(1).optional(),
|
|
441
|
+
testid: z2.string().min(1).optional()
|
|
340
442
|
})
|
|
341
443
|
]);
|
|
342
444
|
|
|
@@ -371,6 +473,96 @@ var RefRegistry = class {
|
|
|
371
473
|
};
|
|
372
474
|
var refs = new RefRegistry();
|
|
373
475
|
|
|
476
|
+
// ../browser/dist/security/serialization.js
|
|
477
|
+
var TRUNCATED_VALUE = "[TRUNCATED]";
|
|
478
|
+
var UNSERIALIZABLE_VALUE = "[UNSERIALIZABLE]";
|
|
479
|
+
var OMIT_VALUE = /* @__PURE__ */ Symbol("omit");
|
|
480
|
+
var MAX_KEY_LENGTH = 256;
|
|
481
|
+
var MAX_TOTAL_CHARACTERS = Math.floor(TRANSPORT_LIMITS.MAX_MESSAGE_BYTES / 8);
|
|
482
|
+
var MAX_TOTAL_NODES = TRANSPORT_LIMITS.MAX_COLLECTION_ITEMS * 5;
|
|
483
|
+
var SENSITIVE_KEY = /password|passwd|passcode|secret|token|authorization|api[-_]?key|access[-_]?key|private[-_]?key|client[-_]?secret|credit[-_]?card|card[-_]?number|cvv|cvc|ssn/i;
|
|
484
|
+
function isSensitiveKey(key) {
|
|
485
|
+
return SENSITIVE_KEY.test(key);
|
|
486
|
+
}
|
|
487
|
+
function boundedString(value, state, max) {
|
|
488
|
+
const allowed = Math.max(0, Math.min(max, state.remainingCharacters));
|
|
489
|
+
if (value.length <= allowed) {
|
|
490
|
+
state.remainingCharacters -= value.length;
|
|
491
|
+
return value;
|
|
492
|
+
}
|
|
493
|
+
const truncated = allowed <= TRUNCATED_VALUE.length ? TRUNCATED_VALUE.slice(0, allowed) : `${value.slice(0, allowed - TRUNCATED_VALUE.length)}${TRUNCATED_VALUE}`;
|
|
494
|
+
state.remainingCharacters -= truncated.length;
|
|
495
|
+
return truncated;
|
|
496
|
+
}
|
|
497
|
+
function sanitize(value, state, depth, key) {
|
|
498
|
+
if (key !== void 0 && isSensitiveKey(key))
|
|
499
|
+
return REDACTED_VALUE;
|
|
500
|
+
if (depth > TRANSPORT_LIMITS.MAX_SERIALIZE_DEPTH || state.nodes >= MAX_TOTAL_NODES) {
|
|
501
|
+
return TRUNCATED_VALUE;
|
|
502
|
+
}
|
|
503
|
+
state.nodes += 1;
|
|
504
|
+
if (value === null || typeof value === "boolean")
|
|
505
|
+
return value;
|
|
506
|
+
if (typeof value === "string") {
|
|
507
|
+
return boundedString(value, state, key?.toLowerCase() === "error" ? TRANSPORT_LIMITS.MAX_ERROR_LENGTH : TRANSPORT_LIMITS.MAX_STRING_LENGTH);
|
|
508
|
+
}
|
|
509
|
+
if (typeof value === "number")
|
|
510
|
+
return Number.isFinite(value) ? value : null;
|
|
511
|
+
if (typeof value === "bigint")
|
|
512
|
+
return value.toString();
|
|
513
|
+
if (typeof value === "undefined" || typeof value === "function" || typeof value === "symbol") {
|
|
514
|
+
return OMIT_VALUE;
|
|
515
|
+
}
|
|
516
|
+
if (value instanceof Date)
|
|
517
|
+
return value.toISOString();
|
|
518
|
+
if (value instanceof Error) {
|
|
519
|
+
return {
|
|
520
|
+
name: boundedString(value.name, state, 256),
|
|
521
|
+
message: boundedString(value.message, state, TRANSPORT_LIMITS.MAX_ERROR_LENGTH)
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
if (state.seen.has(value))
|
|
525
|
+
return "[CIRCULAR]";
|
|
526
|
+
state.seen.add(value);
|
|
527
|
+
try {
|
|
528
|
+
if (Array.isArray(value)) {
|
|
529
|
+
return value.slice(0, TRANSPORT_LIMITS.MAX_COLLECTION_ITEMS).map((item) => {
|
|
530
|
+
const sanitized = sanitize(item, state, depth + 1);
|
|
531
|
+
return sanitized === OMIT_VALUE ? null : sanitized;
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
const out = /* @__PURE__ */ Object.create(null);
|
|
535
|
+
for (const rawKey of Object.keys(value).slice(0, TRANSPORT_LIMITS.MAX_OBJECT_KEYS)) {
|
|
536
|
+
const safeKey = boundedString(rawKey, state, MAX_KEY_LENGTH);
|
|
537
|
+
try {
|
|
538
|
+
const sanitized = sanitize(value[rawKey], state, depth + 1, rawKey);
|
|
539
|
+
if (sanitized !== OMIT_VALUE)
|
|
540
|
+
out[safeKey] = sanitized;
|
|
541
|
+
} catch {
|
|
542
|
+
out[safeKey] = UNSERIALIZABLE_VALUE;
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
return out;
|
|
546
|
+
} finally {
|
|
547
|
+
state.seen.delete(value);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
function sanitizeForTransport(value) {
|
|
551
|
+
const sanitized = sanitize(value, {
|
|
552
|
+
seen: /* @__PURE__ */ new WeakSet(),
|
|
553
|
+
remainingCharacters: MAX_TOTAL_CHARACTERS,
|
|
554
|
+
nodes: 0
|
|
555
|
+
}, 0);
|
|
556
|
+
return sanitized === OMIT_VALUE ? null : sanitized;
|
|
557
|
+
}
|
|
558
|
+
function safeStringify(value) {
|
|
559
|
+
try {
|
|
560
|
+
return JSON.stringify(sanitizeForTransport(value));
|
|
561
|
+
} catch {
|
|
562
|
+
return JSON.stringify(UNSERIALIZABLE_VALUE);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
374
566
|
// ../browser/dist/dom/a11y.js
|
|
375
567
|
var NAME_FROM_CONTENT = /* @__PURE__ */ new Set([
|
|
376
568
|
"button",
|
|
@@ -537,6 +729,17 @@ function getStates(el) {
|
|
|
537
729
|
}
|
|
538
730
|
function getValue(el) {
|
|
539
731
|
if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement || el instanceof HTMLSelectElement) {
|
|
732
|
+
const autocomplete = el.getAttribute("autocomplete") ?? "";
|
|
733
|
+
const identifiers = [
|
|
734
|
+
el.getAttribute("name") ?? "",
|
|
735
|
+
el.id,
|
|
736
|
+
el.getAttribute("data-testid") ?? "",
|
|
737
|
+
el.getAttribute("aria-label") ?? ""
|
|
738
|
+
];
|
|
739
|
+
const sensitiveAutocomplete = /current-password|new-password|cc-number|cc-csc|one-time-code/i.test(autocomplete);
|
|
740
|
+
if (el instanceof HTMLInputElement && el.type.toLowerCase() === "password" || sensitiveAutocomplete || identifiers.some(isSensitiveKey)) {
|
|
741
|
+
return REDACTED_VALUE;
|
|
742
|
+
}
|
|
540
743
|
return el.value;
|
|
541
744
|
}
|
|
542
745
|
const valueNow = el.getAttribute("aria-valuenow");
|
|
@@ -1041,6 +1244,30 @@ var result = (ref, action, effect, settled, settleReason, warning) => {
|
|
|
1041
1244
|
var FILL_LIKE = /* @__PURE__ */ new Set([ActionType.FILL, ActionType.TYPE, ActionType.CLEAR]);
|
|
1042
1245
|
var isFillLike = (action) => FILL_LIKE.has(action);
|
|
1043
1246
|
var CLICK_LIKE = /* @__PURE__ */ new Set([ActionType.CLICK, ActionType.DBLCLICK]);
|
|
1247
|
+
function dangerousActionContext(el) {
|
|
1248
|
+
const form = el.closest("form");
|
|
1249
|
+
return [
|
|
1250
|
+
getAccessibleName(el),
|
|
1251
|
+
el.textContent ?? "",
|
|
1252
|
+
el.getAttribute("value") ?? "",
|
|
1253
|
+
el.getAttribute("title") ?? "",
|
|
1254
|
+
el.getAttribute("aria-label") ?? "",
|
|
1255
|
+
el.getAttribute("href") ?? "",
|
|
1256
|
+
form?.getAttribute("action") ?? "",
|
|
1257
|
+
form?.textContent ?? ""
|
|
1258
|
+
].join(" ");
|
|
1259
|
+
}
|
|
1260
|
+
function requiresDangerousConfirmation(text) {
|
|
1261
|
+
return isDangerousActionText(text);
|
|
1262
|
+
}
|
|
1263
|
+
function assertActionAllowed(el, action, args) {
|
|
1264
|
+
const canTrigger = action === ActionType.CLICK || action === ActionType.DBLCLICK || action === ActionType.DRAG || action === ActionType.SUBMIT || action === ActionType.PRESS && asString(args["key"], "Enter") === "Enter";
|
|
1265
|
+
const dragTarget = action === ActionType.DRAG ? refs.resolve(asString(args["toRef"])) : null;
|
|
1266
|
+
const context = dragTarget instanceof HTMLElement ? `${dangerousActionContext(el)} ${dangerousActionContext(dragTarget)}` : dangerousActionContext(el);
|
|
1267
|
+
if (canTrigger && requiresDangerousConfirmation(context) && args[DANGEROUS_ACTION_CONFIRM_ARG] !== true) {
|
|
1268
|
+
throw new Error(`potentially destructive action blocked; retry with args.${DANGEROUS_ACTION_CONFIRM_ARG}=true`);
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1044
1271
|
var NO_GEOMETRY = { occluded: false, occludedBy: null, scrolledIntoView: false };
|
|
1045
1272
|
function fireClickSequence(el) {
|
|
1046
1273
|
const doc = el.ownerDocument;
|
|
@@ -1207,6 +1434,7 @@ async function dispatchFor(el, action, args) {
|
|
|
1207
1434
|
}
|
|
1208
1435
|
async function executeAction(ref, action, args = {}) {
|
|
1209
1436
|
const el = requireElement(ref);
|
|
1437
|
+
assertActionAllowed(el, action, args);
|
|
1210
1438
|
const visible = isVisible(el);
|
|
1211
1439
|
const enabled = enabledOf(el);
|
|
1212
1440
|
const prevFocus = activeRef(el);
|
|
@@ -1313,7 +1541,10 @@ async function dragElement(source, target, data) {
|
|
|
1313
1541
|
}
|
|
1314
1542
|
return dropPrevented;
|
|
1315
1543
|
}
|
|
1316
|
-
async function dispatchWebMcp(tool, params) {
|
|
1544
|
+
async function dispatchWebMcp(tool, params, confirmDangerous = false) {
|
|
1545
|
+
if (requiresDangerousConfirmation(tool) && !confirmDangerous) {
|
|
1546
|
+
throw new Error(`potentially destructive WebMCP tool blocked; retry with ${DANGEROUS_ACTION_CONFIRM_ARG}=true`);
|
|
1547
|
+
}
|
|
1317
1548
|
const mc = navigator.modelContext;
|
|
1318
1549
|
if (mc === void 0 || typeof mc.callTool !== "function") {
|
|
1319
1550
|
throw new Error("WebMCP (navigator.modelContext) not available on this page");
|
|
@@ -1360,7 +1591,7 @@ function readStores(only) {
|
|
|
1360
1591
|
if (only !== void 0 && name !== only)
|
|
1361
1592
|
continue;
|
|
1362
1593
|
try {
|
|
1363
|
-
out[name] = getter();
|
|
1594
|
+
out[name] = sanitizeForTransport(getter());
|
|
1364
1595
|
} catch (error) {
|
|
1365
1596
|
out[name] = { __error: error instanceof Error ? error.message : String(error) };
|
|
1366
1597
|
}
|
|
@@ -1509,6 +1740,9 @@ function inspect(ref) {
|
|
|
1509
1740
|
return {
|
|
1510
1741
|
...describe(el),
|
|
1511
1742
|
tag: el.tagName.toLowerCase(),
|
|
1743
|
+
href: el.getAttribute("href") ?? void 0,
|
|
1744
|
+
formAction: el instanceof HTMLButtonElement || el instanceof HTMLInputElement ? el.form?.getAttribute("action") ?? void 0 : void 0,
|
|
1745
|
+
formText: el instanceof HTMLButtonElement || el instanceof HTMLInputElement ? el.form?.textContent ?? void 0 : void 0,
|
|
1512
1746
|
box: { x: rect.x, y: rect.y, width: rect.width, height: rect.height },
|
|
1513
1747
|
styles,
|
|
1514
1748
|
component
|
|
@@ -1553,6 +1787,16 @@ function listAnimations() {
|
|
|
1553
1787
|
});
|
|
1554
1788
|
return { animations };
|
|
1555
1789
|
}
|
|
1790
|
+
function resolveNavigationUrl(rawUrl, baseUrl) {
|
|
1791
|
+
if (rawUrl.length === 0 || rawUrl.length > TRANSPORT_LIMITS.MAX_URL_LENGTH)
|
|
1792
|
+
return null;
|
|
1793
|
+
try {
|
|
1794
|
+
const url = new URL(rawUrl, baseUrl);
|
|
1795
|
+
return url.protocol === "http:" || url.protocol === "https:" ? url.toString() : null;
|
|
1796
|
+
} catch {
|
|
1797
|
+
return null;
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
1556
1800
|
function createCommandRegistry() {
|
|
1557
1801
|
const reg = /* @__PURE__ */ new Map();
|
|
1558
1802
|
reg.set(IrisCommand.SNAPSHOT, (args) => buildSnapshot({
|
|
@@ -1565,7 +1809,7 @@ function createCommandRegistry() {
|
|
|
1565
1809
|
const action = str(args["action"]) ?? "";
|
|
1566
1810
|
if (action === ActionType.WEBMCP) {
|
|
1567
1811
|
const inner = record(args["args"]);
|
|
1568
|
-
return dispatchWebMcp(str(inner["tool"]) ?? "", record(inner["params"]));
|
|
1812
|
+
return dispatchWebMcp(str(inner["tool"]) ?? "", record(inner["params"]), inner[DANGEROUS_ACTION_CONFIRM_ARG] === true);
|
|
1569
1813
|
}
|
|
1570
1814
|
return executeAction(str(args["ref"]) ?? "", action, record(args["args"]));
|
|
1571
1815
|
});
|
|
@@ -1592,9 +1836,12 @@ function createCommandRegistry() {
|
|
|
1592
1836
|
return scrollContainer(str(args["ref"]), typeof dy === "number" ? dy : void 0, typeof fraction === "number" ? fraction : void 0);
|
|
1593
1837
|
});
|
|
1594
1838
|
reg.set(IrisCommand.NAVIGATE, (args) => {
|
|
1595
|
-
const
|
|
1596
|
-
if (
|
|
1839
|
+
const rawUrl = str(args["url"]);
|
|
1840
|
+
if (rawUrl === void 0 || rawUrl.length === 0)
|
|
1597
1841
|
return { ok: false, reason: "url required" };
|
|
1842
|
+
const url = resolveNavigationUrl(rawUrl, window.location.href);
|
|
1843
|
+
if (url === null)
|
|
1844
|
+
return { ok: false, reason: "only http(s) navigation is allowed" };
|
|
1598
1845
|
window.location.assign(url);
|
|
1599
1846
|
return { ok: true, url };
|
|
1600
1847
|
});
|
|
@@ -1687,12 +1934,23 @@ var Transport = class {
|
|
|
1687
1934
|
} catch {
|
|
1688
1935
|
return;
|
|
1689
1936
|
}
|
|
1690
|
-
const
|
|
1691
|
-
if (
|
|
1937
|
+
const result2 = CommandMessageSchema.safeParse(parsed);
|
|
1938
|
+
if (!result2.success)
|
|
1939
|
+
return;
|
|
1940
|
+
const command = result2.data;
|
|
1941
|
+
const currentSessionId = this.#deps.hello().sessionId;
|
|
1942
|
+
if (command.sessionId !== void 0 && command.sessionId !== currentSessionId)
|
|
1692
1943
|
return;
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1944
|
+
let outcome;
|
|
1945
|
+
try {
|
|
1946
|
+
outcome = await this.#deps.handleCommand(command);
|
|
1947
|
+
} catch (error) {
|
|
1948
|
+
outcome = {
|
|
1949
|
+
ok: false,
|
|
1950
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1951
|
+
};
|
|
1952
|
+
}
|
|
1953
|
+
this.#sendRaw(safeStringify({
|
|
1696
1954
|
kind: MessageKind.COMMAND_RESULT,
|
|
1697
1955
|
id: command.id,
|
|
1698
1956
|
ok: outcome.ok,
|
|
@@ -1701,7 +1959,7 @@ var Transport = class {
|
|
|
1701
1959
|
}));
|
|
1702
1960
|
}
|
|
1703
1961
|
sendEvent(event) {
|
|
1704
|
-
this.#sendRaw(
|
|
1962
|
+
this.#sendRaw(safeStringify({ kind: MessageKind.EVENT, event }));
|
|
1705
1963
|
}
|
|
1706
1964
|
#sendRaw(text) {
|
|
1707
1965
|
if (this.#ws !== void 0 && this.#ws.readyState === WebSocket.OPEN) {
|
|
@@ -1820,13 +2078,14 @@ function methodOf(input, init) {
|
|
|
1820
2078
|
return "GET";
|
|
1821
2079
|
}
|
|
1822
2080
|
function installNetwork(emit) {
|
|
1823
|
-
const origFetch = window.fetch
|
|
2081
|
+
const origFetch = window.fetch;
|
|
2082
|
+
const callFetch = origFetch.bind(window);
|
|
1824
2083
|
window.fetch = async (input, init) => {
|
|
1825
2084
|
const start = performance.now();
|
|
1826
2085
|
const method = methodOf(input, init);
|
|
1827
2086
|
const url = urlOf(input);
|
|
1828
2087
|
try {
|
|
1829
|
-
const res = await
|
|
2088
|
+
const res = await callFetch(input, init);
|
|
1830
2089
|
emit(EventType.NET_REQUEST, {
|
|
1831
2090
|
method,
|
|
1832
2091
|
url,
|
|
@@ -1892,8 +2151,10 @@ function snapshotLocation() {
|
|
|
1892
2151
|
};
|
|
1893
2152
|
}
|
|
1894
2153
|
function installRoute(emit) {
|
|
1895
|
-
const origPush = history.pushState
|
|
1896
|
-
const origReplace = history.replaceState
|
|
2154
|
+
const origPush = history.pushState;
|
|
2155
|
+
const origReplace = history.replaceState;
|
|
2156
|
+
const callPush = origPush.bind(history);
|
|
2157
|
+
const callReplace = origReplace.bind(history);
|
|
1897
2158
|
const fire = (from) => {
|
|
1898
2159
|
const to = snapshotLocation();
|
|
1899
2160
|
if (to.href === from)
|
|
@@ -1908,12 +2169,12 @@ function installRoute(emit) {
|
|
|
1908
2169
|
};
|
|
1909
2170
|
history.pushState = (data, unused, url) => {
|
|
1910
2171
|
const from = location.href;
|
|
1911
|
-
|
|
2172
|
+
callPush(data, unused, url ?? null);
|
|
1912
2173
|
fire(from);
|
|
1913
2174
|
};
|
|
1914
2175
|
history.replaceState = (data, unused, url) => {
|
|
1915
2176
|
const from = location.href;
|
|
1916
|
-
|
|
2177
|
+
callReplace(data, unused, url ?? null);
|
|
1917
2178
|
fire(from);
|
|
1918
2179
|
};
|
|
1919
2180
|
let lastHref = location.href;
|
|
@@ -1943,22 +2204,19 @@ function stringifyArgs(args) {
|
|
|
1943
2204
|
return a;
|
|
1944
2205
|
if (a instanceof Error)
|
|
1945
2206
|
return a.message;
|
|
1946
|
-
|
|
1947
|
-
return JSON.stringify(a);
|
|
1948
|
-
} catch {
|
|
1949
|
-
return String(a);
|
|
1950
|
-
}
|
|
2207
|
+
return safeStringify(a);
|
|
1951
2208
|
}).join(" ");
|
|
1952
2209
|
}
|
|
1953
2210
|
function installConsole(emit) {
|
|
1954
2211
|
const methods = ["log", "warn", "error"];
|
|
1955
2212
|
const originals2 = /* @__PURE__ */ new Map();
|
|
1956
2213
|
for (const method of methods) {
|
|
1957
|
-
const original = console[method]
|
|
2214
|
+
const original = console[method];
|
|
1958
2215
|
originals2.set(method, original);
|
|
2216
|
+
const callOriginal = original.bind(console);
|
|
1959
2217
|
console[method] = (...args) => {
|
|
1960
2218
|
emit(METHOD_EVENT[method], { message: stringifyArgs(args) });
|
|
1961
|
-
|
|
2219
|
+
callOriginal(...args);
|
|
1962
2220
|
};
|
|
1963
2221
|
}
|
|
1964
2222
|
const onError = (event) => {
|
|
@@ -3463,6 +3721,40 @@ function installRecorder(deps) {
|
|
|
3463
3721
|
}
|
|
3464
3722
|
|
|
3465
3723
|
// ../browser/dist/iris.js
|
|
3724
|
+
function connectionPolicy(pageHostname, bridgeUrl, allowNonLocalhost, token) {
|
|
3725
|
+
let bridge;
|
|
3726
|
+
try {
|
|
3727
|
+
bridge = new URL(bridgeUrl);
|
|
3728
|
+
} catch {
|
|
3729
|
+
return { allowed: false, reason: "invalid Iris bridge URL" };
|
|
3730
|
+
}
|
|
3731
|
+
if (bridge.protocol !== "ws:" && bridge.protocol !== "wss:") {
|
|
3732
|
+
return { allowed: false, reason: "Iris bridge URL must use ws:// or wss://" };
|
|
3733
|
+
}
|
|
3734
|
+
if ((token?.length ?? 0) > TRANSPORT_LIMITS.MAX_TOKEN_LENGTH) {
|
|
3735
|
+
return {
|
|
3736
|
+
allowed: false,
|
|
3737
|
+
reason: `Iris pairing token exceeds ${String(TRANSPORT_LIMITS.MAX_TOKEN_LENGTH)} characters`
|
|
3738
|
+
};
|
|
3739
|
+
}
|
|
3740
|
+
const remoteBridge = !isLoopbackHostname(bridge.hostname);
|
|
3741
|
+
if (remoteBridge && bridge.protocol !== "wss:") {
|
|
3742
|
+
return { allowed: false, reason: "a non-local Iris bridge must use wss://" };
|
|
3743
|
+
}
|
|
3744
|
+
const remote = !isLoopbackHostname(pageHostname) || remoteBridge;
|
|
3745
|
+
if (!remote)
|
|
3746
|
+
return { allowed: true };
|
|
3747
|
+
if (!allowNonLocalhost) {
|
|
3748
|
+
return {
|
|
3749
|
+
allowed: false,
|
|
3750
|
+
reason: "Iris is disabled outside localhost unless allowNonLocalhost is explicitly enabled"
|
|
3751
|
+
};
|
|
3752
|
+
}
|
|
3753
|
+
if (token === void 0 || token.length === 0) {
|
|
3754
|
+
return { allowed: false, reason: "a pairing token is required outside localhost" };
|
|
3755
|
+
}
|
|
3756
|
+
return { allowed: true };
|
|
3757
|
+
}
|
|
3466
3758
|
function str2(value, fallback = "") {
|
|
3467
3759
|
return typeof value === "string" ? value : fallback;
|
|
3468
3760
|
}
|
|
@@ -3491,6 +3783,7 @@ var Iris = class {
|
|
|
3491
3783
|
#presenter;
|
|
3492
3784
|
#recorder;
|
|
3493
3785
|
#eventCount = 0;
|
|
3786
|
+
#token;
|
|
3494
3787
|
/** Act-row log handle for the in-flight act/act_sequence, so its outcome stamps the right row. */
|
|
3495
3788
|
#actHandle;
|
|
3496
3789
|
connect(options = {}) {
|
|
@@ -3498,10 +3791,16 @@ var Iris = class {
|
|
|
3498
3791
|
return;
|
|
3499
3792
|
if (typeof window === "undefined" || typeof document === "undefined")
|
|
3500
3793
|
return;
|
|
3501
|
-
|
|
3794
|
+
const url = options.url ?? `ws://localhost:${String(IRIS_DEFAULT_PORT)}${IRIS_WS_PATH}`;
|
|
3795
|
+
const policy = connectionPolicy(window.location.hostname, url, options.allowNonLocalhost === true, options.token);
|
|
3796
|
+
if (!policy.allowed) {
|
|
3797
|
+
globalThis.console.warn(`[Iris] ${policy.reason ?? "connection blocked"}`);
|
|
3798
|
+
return;
|
|
3799
|
+
}
|
|
3800
|
+
this.#session = resolveSessionLabel(options.session, () => typeof globalThis.crypto?.randomUUID === "function" ? `s${globalThis.crypto.randomUUID()}` : `s${Date.now().toString(36)}`);
|
|
3801
|
+
this.#token = options.token !== void 0 && options.token.length > 0 ? options.token : void 0;
|
|
3502
3802
|
this.#start = performance.now();
|
|
3503
3803
|
this.#registry = createCommandRegistry();
|
|
3504
|
-
const url = options.url ?? `ws://localhost:${String(IRIS_DEFAULT_PORT)}${IRIS_WS_PATH}`;
|
|
3505
3804
|
this.#transport = new Transport({
|
|
3506
3805
|
url,
|
|
3507
3806
|
hello: () => this.#hello(),
|
|
@@ -3618,6 +3917,7 @@ var Iris = class {
|
|
|
3618
3917
|
url: location.href,
|
|
3619
3918
|
title: document.title,
|
|
3620
3919
|
adapters: adapterNames(),
|
|
3920
|
+
...this.#token === void 0 ? {} : { token: this.#token },
|
|
3621
3921
|
hasCapabilities: hasCapabilities()
|
|
3622
3922
|
};
|
|
3623
3923
|
}
|