@syrin/iris 0.4.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 +3488 -1050
- package/dist/index.js +419 -114
- package/dist/next.js +5 -1
- package/dist/server.d.ts +19 -1
- package/dist/server.js +2406 -1005
- package/dist/test.js +2248 -1084
- 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,31 @@
|
|
|
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";
|
|
29
|
+
var UpdateCheckIntervalMs = 24 * 60 * 60 * 1e3;
|
|
5
30
|
var RunKind = {
|
|
6
31
|
FLOW_REPLAY: "flow_replay",
|
|
7
32
|
// auto-recorded by iris_flow_replay
|
|
@@ -42,6 +67,11 @@ var RecorderPhase = {
|
|
|
42
67
|
ANNOTATING: "annotating"
|
|
43
68
|
// recording paused, awaiting an annotation target/kind
|
|
44
69
|
};
|
|
70
|
+
var RING_BUFFER_DEFAULTS = {
|
|
71
|
+
MAX_EVENTS: 2e3,
|
|
72
|
+
MAX_AGE_MS: 6e4,
|
|
73
|
+
MAX_BYTES: TRANSPORT_LIMITS.MAX_BUFFER_BYTES
|
|
74
|
+
};
|
|
45
75
|
var EventType = {
|
|
46
76
|
DOM_ADDED: "dom.added",
|
|
47
77
|
DOM_REMOVED: "dom.removed",
|
|
@@ -206,136 +236,209 @@ var MessageKind = {
|
|
|
206
236
|
EVENT: "event"
|
|
207
237
|
};
|
|
208
238
|
|
|
209
|
-
// ../protocol/dist/
|
|
239
|
+
// ../protocol/dist/messages.js
|
|
210
240
|
import { z } from "zod";
|
|
211
|
-
var
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
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(),
|
|
221
324
|
/** CSS selector or ref to scope the search. */
|
|
222
|
-
scope:
|
|
325
|
+
scope: z2.string().optional()
|
|
223
326
|
});
|
|
224
|
-
var CapabilityFlowSchema =
|
|
225
|
-
name:
|
|
226
|
-
steps:
|
|
327
|
+
var CapabilityFlowSchema = z2.object({
|
|
328
|
+
name: z2.string(),
|
|
329
|
+
steps: z2.array(z2.string())
|
|
227
330
|
});
|
|
228
|
-
var CapabilitiesSchema =
|
|
229
|
-
testids:
|
|
230
|
-
signals:
|
|
231
|
-
stores:
|
|
232
|
-
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)
|
|
233
336
|
});
|
|
234
|
-
var ContractFileSchema =
|
|
235
|
-
version:
|
|
236
|
-
generatedAt:
|
|
337
|
+
var ContractFileSchema = z2.object({
|
|
338
|
+
version: z2.number(),
|
|
339
|
+
generatedAt: z2.number(),
|
|
237
340
|
capabilities: CapabilitiesSchema
|
|
238
341
|
});
|
|
239
|
-
var RunEvidenceSchema =
|
|
240
|
-
consoleErrors:
|
|
241
|
-
networkErrors:
|
|
242
|
-
driftSteps:
|
|
342
|
+
var RunEvidenceSchema = z2.object({
|
|
343
|
+
consoleErrors: z2.number().optional(),
|
|
344
|
+
networkErrors: z2.number().optional(),
|
|
345
|
+
driftSteps: z2.number().optional()
|
|
243
346
|
});
|
|
244
|
-
var RunRecordSchema =
|
|
245
|
-
kind:
|
|
246
|
-
name:
|
|
247
|
-
status:
|
|
248
|
-
at:
|
|
249
|
-
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(),
|
|
250
353
|
evidence: RunEvidenceSchema.optional(),
|
|
251
|
-
durationMs:
|
|
354
|
+
durationMs: z2.number().optional()
|
|
252
355
|
});
|
|
253
|
-
var ProjectLearnedSchema =
|
|
254
|
-
flows:
|
|
255
|
-
routes:
|
|
356
|
+
var ProjectLearnedSchema = z2.object({
|
|
357
|
+
flows: z2.array(z2.string()).optional(),
|
|
358
|
+
routes: z2.array(z2.string()).optional()
|
|
256
359
|
});
|
|
257
|
-
var ProjectFileSchema =
|
|
258
|
-
version:
|
|
360
|
+
var ProjectFileSchema = z2.object({
|
|
361
|
+
version: z2.number(),
|
|
259
362
|
learned: ProjectLearnedSchema.optional(),
|
|
260
|
-
runs:
|
|
363
|
+
runs: z2.array(RunRecordSchema)
|
|
261
364
|
});
|
|
262
|
-
var FlowAnchorSchema =
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
kind:
|
|
266
|
-
role:
|
|
267
|
-
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()
|
|
268
371
|
}),
|
|
269
|
-
|
|
372
|
+
z2.object({ kind: z2.literal(AnchorKind.SIGNAL), name: z2.string().min(1) })
|
|
270
373
|
]);
|
|
271
|
-
var FlowExpectSchema =
|
|
272
|
-
signal:
|
|
374
|
+
var FlowExpectSchema = z2.object({
|
|
375
|
+
signal: z2.string().optional(),
|
|
273
376
|
/**
|
|
274
377
|
* Optional payload shape an `assert-signal` annotation requires the signal
|
|
275
378
|
* to match (the predicate DSL's signal.dataMatches). Additive/optional — a flow file with a
|
|
276
379
|
* bare `signal` still parses, and the on-disk version stays FLOW_FILE_VERSION 1.
|
|
277
380
|
*/
|
|
278
|
-
signalData:
|
|
279
|
-
net:
|
|
280
|
-
method:
|
|
281
|
-
urlContains:
|
|
282
|
-
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()
|
|
283
386
|
}).optional(),
|
|
284
|
-
element:
|
|
285
|
-
testid:
|
|
286
|
-
role:
|
|
287
|
-
name:
|
|
387
|
+
element: z2.object({
|
|
388
|
+
testid: z2.string().optional(),
|
|
389
|
+
role: z2.string().optional(),
|
|
390
|
+
name: z2.string().optional()
|
|
288
391
|
}).optional()
|
|
289
392
|
});
|
|
290
|
-
var baseFlowStep =
|
|
291
|
-
tool:
|
|
393
|
+
var baseFlowStep = z2.object({
|
|
394
|
+
tool: z2.string(),
|
|
292
395
|
anchor: FlowAnchorSchema,
|
|
293
|
-
action:
|
|
294
|
-
args:
|
|
396
|
+
action: z2.nativeEnum(ActionType).optional(),
|
|
397
|
+
args: z2.record(z2.unknown()).optional(),
|
|
295
398
|
expect: FlowExpectSchema.optional(),
|
|
296
|
-
degraded:
|
|
399
|
+
degraded: z2.boolean().optional()
|
|
297
400
|
});
|
|
298
401
|
var FlowStepSchema = baseFlowStep.extend({
|
|
299
|
-
steps:
|
|
402
|
+
steps: z2.lazy(() => z2.array(FlowStepSchema).optional())
|
|
300
403
|
});
|
|
301
|
-
var FlowFileSchema =
|
|
302
|
-
version:
|
|
303
|
-
name:
|
|
404
|
+
var FlowFileSchema = z2.object({
|
|
405
|
+
version: z2.literal(FLOW_FILE_VERSION),
|
|
406
|
+
name: z2.string(),
|
|
304
407
|
// FUTURE: fixtures/preconditions — schema slot reserved, unpopulated this cut. The recorder
|
|
305
408
|
// never writes it and no fixture runner exists.
|
|
306
|
-
fixture:
|
|
409
|
+
fixture: z2.string().optional(),
|
|
307
410
|
/** From the injected clock (ms) — deterministic in tests, byte-stable on disk. */
|
|
308
|
-
createdAt:
|
|
309
|
-
steps:
|
|
411
|
+
createdAt: z2.number(),
|
|
412
|
+
steps: z2.array(FlowStepSchema),
|
|
310
413
|
success: FlowExpectSchema.optional(),
|
|
311
414
|
/**
|
|
312
415
|
* Anchors whose CONTENT must not be asserted (e.g. LLM output). Replay asserts
|
|
313
416
|
* presence, not words. Compiled from a `mark-dynamic` annotation.
|
|
314
417
|
*/
|
|
315
|
-
dynamic:
|
|
418
|
+
dynamic: z2.array(FlowAnchorSchema).optional()
|
|
316
419
|
});
|
|
317
|
-
var RecordedFlowSchema =
|
|
318
|
-
name:
|
|
420
|
+
var RecordedFlowSchema = z2.object({
|
|
421
|
+
name: z2.string(),
|
|
319
422
|
flow: FlowFileSchema
|
|
320
423
|
});
|
|
321
|
-
var AnnotationSchema =
|
|
322
|
-
|
|
323
|
-
kind:
|
|
324
|
-
name:
|
|
325
|
-
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()
|
|
326
429
|
}),
|
|
327
|
-
|
|
328
|
-
kind:
|
|
329
|
-
testid:
|
|
430
|
+
z2.object({
|
|
431
|
+
kind: z2.literal(AnnotationKind.ASSERT_VISIBLE),
|
|
432
|
+
testid: z2.string().min(1)
|
|
330
433
|
}),
|
|
331
|
-
|
|
332
|
-
kind:
|
|
333
|
-
testid:
|
|
434
|
+
z2.object({
|
|
435
|
+
kind: z2.literal(AnnotationKind.MARK_DYNAMIC),
|
|
436
|
+
testid: z2.string().min(1)
|
|
334
437
|
}),
|
|
335
|
-
|
|
336
|
-
kind:
|
|
337
|
-
signal:
|
|
338
|
-
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()
|
|
339
442
|
})
|
|
340
443
|
]);
|
|
341
444
|
|
|
@@ -370,6 +473,96 @@ var RefRegistry = class {
|
|
|
370
473
|
};
|
|
371
474
|
var refs = new RefRegistry();
|
|
372
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
|
+
|
|
373
566
|
// ../browser/dist/dom/a11y.js
|
|
374
567
|
var NAME_FROM_CONTENT = /* @__PURE__ */ new Set([
|
|
375
568
|
"button",
|
|
@@ -536,6 +729,17 @@ function getStates(el) {
|
|
|
536
729
|
}
|
|
537
730
|
function getValue(el) {
|
|
538
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
|
+
}
|
|
539
743
|
return el.value;
|
|
540
744
|
}
|
|
541
745
|
const valueNow = el.getAttribute("aria-valuenow");
|
|
@@ -1040,6 +1244,30 @@ var result = (ref, action, effect, settled, settleReason, warning) => {
|
|
|
1040
1244
|
var FILL_LIKE = /* @__PURE__ */ new Set([ActionType.FILL, ActionType.TYPE, ActionType.CLEAR]);
|
|
1041
1245
|
var isFillLike = (action) => FILL_LIKE.has(action);
|
|
1042
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
|
+
}
|
|
1043
1271
|
var NO_GEOMETRY = { occluded: false, occludedBy: null, scrolledIntoView: false };
|
|
1044
1272
|
function fireClickSequence(el) {
|
|
1045
1273
|
const doc = el.ownerDocument;
|
|
@@ -1206,6 +1434,7 @@ async function dispatchFor(el, action, args) {
|
|
|
1206
1434
|
}
|
|
1207
1435
|
async function executeAction(ref, action, args = {}) {
|
|
1208
1436
|
const el = requireElement(ref);
|
|
1437
|
+
assertActionAllowed(el, action, args);
|
|
1209
1438
|
const visible = isVisible(el);
|
|
1210
1439
|
const enabled = enabledOf(el);
|
|
1211
1440
|
const prevFocus = activeRef(el);
|
|
@@ -1312,7 +1541,10 @@ async function dragElement(source, target, data) {
|
|
|
1312
1541
|
}
|
|
1313
1542
|
return dropPrevented;
|
|
1314
1543
|
}
|
|
1315
|
-
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
|
+
}
|
|
1316
1548
|
const mc = navigator.modelContext;
|
|
1317
1549
|
if (mc === void 0 || typeof mc.callTool !== "function") {
|
|
1318
1550
|
throw new Error("WebMCP (navigator.modelContext) not available on this page");
|
|
@@ -1359,7 +1591,7 @@ function readStores(only) {
|
|
|
1359
1591
|
if (only !== void 0 && name !== only)
|
|
1360
1592
|
continue;
|
|
1361
1593
|
try {
|
|
1362
|
-
out[name] = getter();
|
|
1594
|
+
out[name] = sanitizeForTransport(getter());
|
|
1363
1595
|
} catch (error) {
|
|
1364
1596
|
out[name] = { __error: error instanceof Error ? error.message : String(error) };
|
|
1365
1597
|
}
|
|
@@ -1508,6 +1740,9 @@ function inspect(ref) {
|
|
|
1508
1740
|
return {
|
|
1509
1741
|
...describe(el),
|
|
1510
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,
|
|
1511
1746
|
box: { x: rect.x, y: rect.y, width: rect.width, height: rect.height },
|
|
1512
1747
|
styles,
|
|
1513
1748
|
component
|
|
@@ -1552,6 +1787,16 @@ function listAnimations() {
|
|
|
1552
1787
|
});
|
|
1553
1788
|
return { animations };
|
|
1554
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
|
+
}
|
|
1555
1800
|
function createCommandRegistry() {
|
|
1556
1801
|
const reg = /* @__PURE__ */ new Map();
|
|
1557
1802
|
reg.set(IrisCommand.SNAPSHOT, (args) => buildSnapshot({
|
|
@@ -1564,7 +1809,7 @@ function createCommandRegistry() {
|
|
|
1564
1809
|
const action = str(args["action"]) ?? "";
|
|
1565
1810
|
if (action === ActionType.WEBMCP) {
|
|
1566
1811
|
const inner = record(args["args"]);
|
|
1567
|
-
return dispatchWebMcp(str(inner["tool"]) ?? "", record(inner["params"]));
|
|
1812
|
+
return dispatchWebMcp(str(inner["tool"]) ?? "", record(inner["params"]), inner[DANGEROUS_ACTION_CONFIRM_ARG] === true);
|
|
1568
1813
|
}
|
|
1569
1814
|
return executeAction(str(args["ref"]) ?? "", action, record(args["args"]));
|
|
1570
1815
|
});
|
|
@@ -1591,9 +1836,12 @@ function createCommandRegistry() {
|
|
|
1591
1836
|
return scrollContainer(str(args["ref"]), typeof dy === "number" ? dy : void 0, typeof fraction === "number" ? fraction : void 0);
|
|
1592
1837
|
});
|
|
1593
1838
|
reg.set(IrisCommand.NAVIGATE, (args) => {
|
|
1594
|
-
const
|
|
1595
|
-
if (
|
|
1839
|
+
const rawUrl = str(args["url"]);
|
|
1840
|
+
if (rawUrl === void 0 || rawUrl.length === 0)
|
|
1596
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" };
|
|
1597
1845
|
window.location.assign(url);
|
|
1598
1846
|
return { ok: true, url };
|
|
1599
1847
|
});
|
|
@@ -1644,6 +1892,7 @@ var Transport = class {
|
|
|
1644
1892
|
for (const msg of this.#queue)
|
|
1645
1893
|
ws.send(msg);
|
|
1646
1894
|
this.#queue = [];
|
|
1895
|
+
this.#deps.onConnected?.();
|
|
1647
1896
|
};
|
|
1648
1897
|
ws.onmessage = (event) => {
|
|
1649
1898
|
const data = event.data;
|
|
@@ -1685,12 +1934,23 @@ var Transport = class {
|
|
|
1685
1934
|
} catch {
|
|
1686
1935
|
return;
|
|
1687
1936
|
}
|
|
1688
|
-
const
|
|
1689
|
-
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)
|
|
1690
1943
|
return;
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
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({
|
|
1694
1954
|
kind: MessageKind.COMMAND_RESULT,
|
|
1695
1955
|
id: command.id,
|
|
1696
1956
|
ok: outcome.ok,
|
|
@@ -1699,7 +1959,7 @@ var Transport = class {
|
|
|
1699
1959
|
}));
|
|
1700
1960
|
}
|
|
1701
1961
|
sendEvent(event) {
|
|
1702
|
-
this.#sendRaw(
|
|
1962
|
+
this.#sendRaw(safeStringify({ kind: MessageKind.EVENT, event }));
|
|
1703
1963
|
}
|
|
1704
1964
|
#sendRaw(text) {
|
|
1705
1965
|
if (this.#ws !== void 0 && this.#ws.readyState === WebSocket.OPEN) {
|
|
@@ -1818,13 +2078,14 @@ function methodOf(input, init) {
|
|
|
1818
2078
|
return "GET";
|
|
1819
2079
|
}
|
|
1820
2080
|
function installNetwork(emit) {
|
|
1821
|
-
const origFetch = window.fetch
|
|
2081
|
+
const origFetch = window.fetch;
|
|
2082
|
+
const callFetch = origFetch.bind(window);
|
|
1822
2083
|
window.fetch = async (input, init) => {
|
|
1823
2084
|
const start = performance.now();
|
|
1824
2085
|
const method = methodOf(input, init);
|
|
1825
2086
|
const url = urlOf(input);
|
|
1826
2087
|
try {
|
|
1827
|
-
const res = await
|
|
2088
|
+
const res = await callFetch(input, init);
|
|
1828
2089
|
emit(EventType.NET_REQUEST, {
|
|
1829
2090
|
method,
|
|
1830
2091
|
url,
|
|
@@ -1890,8 +2151,10 @@ function snapshotLocation() {
|
|
|
1890
2151
|
};
|
|
1891
2152
|
}
|
|
1892
2153
|
function installRoute(emit) {
|
|
1893
|
-
const origPush = history.pushState
|
|
1894
|
-
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);
|
|
1895
2158
|
const fire = (from) => {
|
|
1896
2159
|
const to = snapshotLocation();
|
|
1897
2160
|
if (to.href === from)
|
|
@@ -1906,12 +2169,12 @@ function installRoute(emit) {
|
|
|
1906
2169
|
};
|
|
1907
2170
|
history.pushState = (data, unused, url) => {
|
|
1908
2171
|
const from = location.href;
|
|
1909
|
-
|
|
2172
|
+
callPush(data, unused, url ?? null);
|
|
1910
2173
|
fire(from);
|
|
1911
2174
|
};
|
|
1912
2175
|
history.replaceState = (data, unused, url) => {
|
|
1913
2176
|
const from = location.href;
|
|
1914
|
-
|
|
2177
|
+
callReplace(data, unused, url ?? null);
|
|
1915
2178
|
fire(from);
|
|
1916
2179
|
};
|
|
1917
2180
|
let lastHref = location.href;
|
|
@@ -1941,22 +2204,19 @@ function stringifyArgs(args) {
|
|
|
1941
2204
|
return a;
|
|
1942
2205
|
if (a instanceof Error)
|
|
1943
2206
|
return a.message;
|
|
1944
|
-
|
|
1945
|
-
return JSON.stringify(a);
|
|
1946
|
-
} catch {
|
|
1947
|
-
return String(a);
|
|
1948
|
-
}
|
|
2207
|
+
return safeStringify(a);
|
|
1949
2208
|
}).join(" ");
|
|
1950
2209
|
}
|
|
1951
2210
|
function installConsole(emit) {
|
|
1952
2211
|
const methods = ["log", "warn", "error"];
|
|
1953
2212
|
const originals2 = /* @__PURE__ */ new Map();
|
|
1954
2213
|
for (const method of methods) {
|
|
1955
|
-
const original = console[method]
|
|
2214
|
+
const original = console[method];
|
|
1956
2215
|
originals2.set(method, original);
|
|
2216
|
+
const callOriginal = original.bind(console);
|
|
1957
2217
|
console[method] = (...args) => {
|
|
1958
2218
|
emit(METHOD_EVENT[method], { message: stringifyArgs(args) });
|
|
1959
|
-
|
|
2219
|
+
callOriginal(...args);
|
|
1960
2220
|
};
|
|
1961
2221
|
}
|
|
1962
2222
|
const onError = (event) => {
|
|
@@ -3461,6 +3721,40 @@ function installRecorder(deps) {
|
|
|
3461
3721
|
}
|
|
3462
3722
|
|
|
3463
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
|
+
}
|
|
3464
3758
|
function str2(value, fallback = "") {
|
|
3465
3759
|
return typeof value === "string" ? value : fallback;
|
|
3466
3760
|
}
|
|
@@ -3489,6 +3783,7 @@ var Iris = class {
|
|
|
3489
3783
|
#presenter;
|
|
3490
3784
|
#recorder;
|
|
3491
3785
|
#eventCount = 0;
|
|
3786
|
+
#token;
|
|
3492
3787
|
/** Act-row log handle for the in-flight act/act_sequence, so its outcome stamps the right row. */
|
|
3493
3788
|
#actHandle;
|
|
3494
3789
|
connect(options = {}) {
|
|
@@ -3496,14 +3791,23 @@ var Iris = class {
|
|
|
3496
3791
|
return;
|
|
3497
3792
|
if (typeof window === "undefined" || typeof document === "undefined")
|
|
3498
3793
|
return;
|
|
3499
|
-
|
|
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;
|
|
3500
3802
|
this.#start = performance.now();
|
|
3501
3803
|
this.#registry = createCommandRegistry();
|
|
3502
|
-
const url = options.url ?? `ws://localhost:${String(IRIS_DEFAULT_PORT)}${IRIS_WS_PATH}`;
|
|
3503
3804
|
this.#transport = new Transport({
|
|
3504
3805
|
url,
|
|
3505
3806
|
hello: () => this.#hello(),
|
|
3506
3807
|
handleCommand: (command) => this.#handleCommand(command),
|
|
3808
|
+
// Show the presenter HUD as soon as the agent bridge connects — the user immediately sees
|
|
3809
|
+
// the glow border and narration panel, even before the first tool call lands.
|
|
3810
|
+
onConnected: () => this.#presenter?.sessionStart(),
|
|
3507
3811
|
// Liveness fallback: if the bridge stays unreachable (the agent killed the server process),
|
|
3508
3812
|
// no server-pushed end can arrive — so end the run we're presenting ourselves. A returning
|
|
3509
3813
|
// agent revives it via the normal sessionStart() path on its next command.
|
|
@@ -3613,6 +3917,7 @@ var Iris = class {
|
|
|
3613
3917
|
url: location.href,
|
|
3614
3918
|
title: document.title,
|
|
3615
3919
|
adapters: adapterNames(),
|
|
3920
|
+
...this.#token === void 0 ? {} : { token: this.#token },
|
|
3616
3921
|
hasCapabilities: hasCapabilities()
|
|
3617
3922
|
};
|
|
3618
3923
|
}
|