@syrin/iris 0.5.0 → 0.8.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/dist/cli.js +6568 -3488
- package/dist/index.d.ts +3 -0
- package/dist/index.js +1720 -342
- package/dist/next.js +5 -1
- package/dist/server.d.ts +9 -0
- package/dist/server.js +5862 -3584
- package/dist/test.js +5573 -3398
- package/dist/vite.d.ts +43 -0
- package/dist/vite.js +148 -0
- package/package.json +15 -10
package/dist/index.js
CHANGED
|
@@ -1,8 +1,4 @@
|
|
|
1
|
-
// ../protocol/dist/constants.js
|
|
2
|
-
var IRIS_DEFAULT_PORT = 4400;
|
|
3
|
-
var IRIS_WS_PATH = "/iris";
|
|
4
|
-
var IRIS_PROTOCOL_VERSION = 1;
|
|
5
|
-
var UpdateCheckIntervalMs = 24 * 60 * 60 * 1e3;
|
|
1
|
+
// ../protocol/dist/flow-constants.js
|
|
6
2
|
var RunKind = {
|
|
7
3
|
FLOW_REPLAY: "flow_replay",
|
|
8
4
|
// auto-recorded by iris_flow_replay
|
|
@@ -21,8 +17,10 @@ var AnchorKind = {
|
|
|
21
17
|
// { kind:'testid', value }
|
|
22
18
|
ROLE: "role",
|
|
23
19
|
// { kind:'role', role, name? } — best-effort fallback
|
|
24
|
-
SIGNAL: "signal"
|
|
20
|
+
SIGNAL: "signal",
|
|
25
21
|
// { kind:'signal', name } — wait/assert anchors
|
|
22
|
+
COMPONENT: "component"
|
|
23
|
+
// { kind:'component', component?, source?, role?, name? } — auto-anchor (no testid)
|
|
26
24
|
};
|
|
27
25
|
var DEGRADED_ANCHOR_ROLE = "unresolved";
|
|
28
26
|
var AnnotationKind = {
|
|
@@ -30,10 +28,14 @@ var AnnotationKind = {
|
|
|
30
28
|
// → step.expect.signal (invariant)
|
|
31
29
|
ASSERT_VISIBLE: "assert-visible",
|
|
32
30
|
// → step.expect.element (invariant)
|
|
31
|
+
ASSERT_STATE: "assert-state",
|
|
32
|
+
// → step.expect.state (store-truth invariant on the last step)
|
|
33
33
|
MARK_DYNAMIC: "mark-dynamic",
|
|
34
34
|
// → flow.dynamic[] (don't assert words/content)
|
|
35
|
-
SUCCESS_STATE: "success-state"
|
|
35
|
+
SUCCESS_STATE: "success-state",
|
|
36
36
|
// → flow.success (golden end condition)
|
|
37
|
+
INTENT: "intent"
|
|
38
|
+
// → flow.intent (the business goal this flow exists to verify)
|
|
37
39
|
};
|
|
38
40
|
var RecorderPhase = {
|
|
39
41
|
IDLE: "idle",
|
|
@@ -43,6 +45,45 @@ var RecorderPhase = {
|
|
|
43
45
|
ANNOTATING: "annotating"
|
|
44
46
|
// recording paused, awaiting an annotation target/kind
|
|
45
47
|
};
|
|
48
|
+
|
|
49
|
+
// ../protocol/dist/constants.js
|
|
50
|
+
var IRIS_DEFAULT_PORT = 4400;
|
|
51
|
+
var IRIS_WS_PATH = "/iris";
|
|
52
|
+
var IRIS_PROTOCOL_VERSION = 1;
|
|
53
|
+
var TRANSPORT_LIMITS = {
|
|
54
|
+
MAX_MESSAGE_BYTES: 1024 * 1024,
|
|
55
|
+
MAX_MESSAGES_PER_SECOND: 1e3,
|
|
56
|
+
MAX_SESSIONS: 32,
|
|
57
|
+
MAX_PENDING_CONNECTIONS: 16,
|
|
58
|
+
HELLO_TIMEOUT_MS: 5e3,
|
|
59
|
+
MAX_BUFFER_BYTES: 8 * 1024 * 1024,
|
|
60
|
+
MAX_SESSION_ID_LENGTH: 128,
|
|
61
|
+
MAX_URL_LENGTH: 4096,
|
|
62
|
+
MAX_TITLE_LENGTH: 512,
|
|
63
|
+
MAX_ADAPTERS: 32,
|
|
64
|
+
MAX_ADAPTER_NAME_LENGTH: 128,
|
|
65
|
+
MAX_TOKEN_LENGTH: 512,
|
|
66
|
+
MAX_COMMAND_ID_LENGTH: 128,
|
|
67
|
+
MAX_COMMAND_NAME_LENGTH: 128,
|
|
68
|
+
MAX_REF_LENGTH: 128,
|
|
69
|
+
MAX_ERROR_LENGTH: 4096,
|
|
70
|
+
MAX_SERIALIZE_DEPTH: 8,
|
|
71
|
+
MAX_COLLECTION_ITEMS: 200,
|
|
72
|
+
MAX_OBJECT_KEYS: 200,
|
|
73
|
+
MAX_STRING_LENGTH: 64 * 1024,
|
|
74
|
+
/** Human review marks: the note the human types when flagging a mistake on the page. */
|
|
75
|
+
MAX_MARK_NOTE_LENGTH: 2e3,
|
|
76
|
+
/** Human review marks: the legible element label that pins the mark (e.g. "Submit button"). */
|
|
77
|
+
MAX_MARK_LABEL_LENGTH: 256
|
|
78
|
+
};
|
|
79
|
+
var REDACTED_VALUE = "[REDACTED]";
|
|
80
|
+
var DANGEROUS_ACTION_CONFIRM_ARG = "confirmDangerous";
|
|
81
|
+
var UpdateCheckIntervalMs = 24 * 60 * 60 * 1e3;
|
|
82
|
+
var RING_BUFFER_DEFAULTS = {
|
|
83
|
+
MAX_EVENTS: 2e3,
|
|
84
|
+
MAX_AGE_MS: 6e4,
|
|
85
|
+
MAX_BYTES: TRANSPORT_LIMITS.MAX_BUFFER_BYTES
|
|
86
|
+
};
|
|
46
87
|
var EventType = {
|
|
47
88
|
DOM_ADDED: "dom.added",
|
|
48
89
|
DOM_REMOVED: "dom.removed",
|
|
@@ -73,19 +114,37 @@ var EventType = {
|
|
|
73
114
|
* Live-control: browser → bridge. A human acted on the presenter panel.
|
|
74
115
|
* `data: { kind: HumanControlKind; text?: string }`. Rides the existing EventMessage.
|
|
75
116
|
*/
|
|
76
|
-
HUMAN_CONTROL: "human.control"
|
|
117
|
+
HUMAN_CONTROL: "human.control",
|
|
118
|
+
/**
|
|
119
|
+
* Human review: browser → bridge. A human pinned a mistake to an element on the running page
|
|
120
|
+
* (the "annotate the bug where you see it" loop). `data` narrows to HumanMarkDataSchema — a note
|
|
121
|
+
* plus a re-resolvable element anchor (and its source file:line when the framework stamped one) so
|
|
122
|
+
* the agent that drains the mark knows exactly which element and which source to fix.
|
|
123
|
+
*/
|
|
124
|
+
HUMAN_MARK: "human.mark"
|
|
77
125
|
};
|
|
78
126
|
var SessionState = {
|
|
79
127
|
ACTIVE: "active",
|
|
80
128
|
PAUSED: "paused",
|
|
81
129
|
ENDED: "ended"
|
|
82
130
|
};
|
|
131
|
+
function isSessionState(value) {
|
|
132
|
+
return value === SessionState.ACTIVE || value === SessionState.PAUSED || value === SessionState.ENDED;
|
|
133
|
+
}
|
|
83
134
|
var SESSION_AUTO = "auto";
|
|
84
135
|
var HumanControlKind = {
|
|
85
136
|
PAUSE: "pause",
|
|
86
137
|
RESUME: "resume",
|
|
87
138
|
END: "end",
|
|
88
|
-
MESSAGE: "message"
|
|
139
|
+
MESSAGE: "message",
|
|
140
|
+
/** Human clicked ▶ on a saved flow in the panel — replay it (no agent). `text` carries the name. */
|
|
141
|
+
REPLAY: "replay"
|
|
142
|
+
};
|
|
143
|
+
var MarkAnchorStrategy = {
|
|
144
|
+
TESTID: "testid",
|
|
145
|
+
COMPONENT: "component",
|
|
146
|
+
ROLE: "role",
|
|
147
|
+
POSITION: "position"
|
|
89
148
|
};
|
|
90
149
|
var SESSION_HEALTH = {
|
|
91
150
|
HEARTBEAT_MS: 5e3,
|
|
@@ -93,7 +152,11 @@ var SESSION_HEALTH = {
|
|
|
93
152
|
STALE_THRESHOLD_MS: 12e3
|
|
94
153
|
};
|
|
95
154
|
var SESSION_LIFECYCLE = {
|
|
96
|
-
/**
|
|
155
|
+
/**
|
|
156
|
+
* Default agent-idle window before the panel hands back to the human as WAITING. The agent signals
|
|
157
|
+
* this IMMEDIATELY via iris_yield; this reaper is only the slow backstop for a forgotten yield, so
|
|
158
|
+
* it's deliberately long (a short window would auto-end a session mid slow-step). iris_session-tunable.
|
|
159
|
+
*/
|
|
97
160
|
IDLE_END_MS: 3e5,
|
|
98
161
|
/** Floor for a tuned idle window (so an agent can't disable the safety net). */
|
|
99
162
|
IDLE_END_MIN_MS: 5e3,
|
|
@@ -162,7 +225,10 @@ var QueryBy = {
|
|
|
162
225
|
LABEL: "label",
|
|
163
226
|
PLACEHOLDER: "placeholder",
|
|
164
227
|
TESTID: "testid",
|
|
165
|
-
ALT: "alt"
|
|
228
|
+
ALT: "alt",
|
|
229
|
+
/** Resolve by component identity / source location (auto-anchors — addresses any element with
|
|
230
|
+
* no hand-added testid). Pair with ElementQuery.component and/or .source. */
|
|
231
|
+
COMPONENT: "component"
|
|
166
232
|
};
|
|
167
233
|
var IrisCommand = {
|
|
168
234
|
SNAPSHOT: "snapshot",
|
|
@@ -188,7 +254,9 @@ var IrisCommand = {
|
|
|
188
254
|
/** Navigate the page to a new URL. `args: { url: string }`. */
|
|
189
255
|
NAVIGATE: "navigate",
|
|
190
256
|
/** Reload the page. `args: { hard?: boolean }` — hard clears the cache via location replace trick. */
|
|
191
|
-
REFRESH: "refresh"
|
|
257
|
+
REFRESH: "refresh",
|
|
258
|
+
/** Bridge → browser: the saved flows the human can replay from the panel. `args: { flows: [{name}] }`. */
|
|
259
|
+
FLOWS: "flows"
|
|
192
260
|
};
|
|
193
261
|
var PresenterMode = {
|
|
194
262
|
IDLE: "idle",
|
|
@@ -207,136 +275,369 @@ var MessageKind = {
|
|
|
207
275
|
EVENT: "event"
|
|
208
276
|
};
|
|
209
277
|
|
|
210
|
-
// ../protocol/dist/
|
|
278
|
+
// ../protocol/dist/notices.js
|
|
279
|
+
var PresenterTone = {
|
|
280
|
+
CALM: "calm",
|
|
281
|
+
WAITING: "waiting",
|
|
282
|
+
ASK: "ask",
|
|
283
|
+
WARN: "warn"
|
|
284
|
+
};
|
|
285
|
+
function isPresenterTone(value) {
|
|
286
|
+
return value === PresenterTone.CALM || value === PresenterTone.WAITING || value === PresenterTone.ASK || value === PresenterTone.WARN;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// ../protocol/dist/messages.js
|
|
211
290
|
import { z } from "zod";
|
|
212
|
-
var
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
291
|
+
var sessionIdSchema = z.string().min(1).max(TRANSPORT_LIMITS.MAX_SESSION_ID_LENGTH);
|
|
292
|
+
var refSchema = z.string().max(TRANSPORT_LIMITS.MAX_REF_LENGTH);
|
|
293
|
+
var HumanControlDataSchema = z.object({
|
|
294
|
+
kind: z.nativeEnum(HumanControlKind),
|
|
295
|
+
text: z.string().optional()
|
|
296
|
+
});
|
|
297
|
+
var HumanMarkDataSchema = z.object({
|
|
298
|
+
note: z.string().min(1).max(TRANSPORT_LIMITS.MAX_MARK_NOTE_LENGTH),
|
|
299
|
+
anchor: z.string().max(TRANSPORT_LIMITS.MAX_REF_LENGTH),
|
|
300
|
+
strategy: z.nativeEnum(MarkAnchorStrategy),
|
|
301
|
+
/** Human-legible element label (role + accessible name / text), to show the agent what was flagged. */
|
|
302
|
+
label: z.string().max(TRANSPORT_LIMITS.MAX_MARK_LABEL_LENGTH).optional(),
|
|
303
|
+
/** Source file:line stamped by the framework's compiler/plugin, when available. */
|
|
304
|
+
source: z.object({
|
|
305
|
+
file: z.string().max(TRANSPORT_LIMITS.MAX_URL_LENGTH),
|
|
306
|
+
line: z.number().int().min(0)
|
|
307
|
+
}).optional(),
|
|
308
|
+
/** Route/URL the mark was made on, so the agent can reproduce the context. */
|
|
309
|
+
route: z.string().max(TRANSPORT_LIMITS.MAX_URL_LENGTH).optional()
|
|
310
|
+
});
|
|
311
|
+
var IrisEventSchema = z.object({
|
|
312
|
+
t: z.number(),
|
|
313
|
+
type: z.nativeEnum(EventType),
|
|
314
|
+
sessionId: sessionIdSchema,
|
|
315
|
+
/** Stable element reference this event concerns, when applicable (e.g. "e7"). */
|
|
316
|
+
ref: refSchema.optional(),
|
|
317
|
+
/** Event-type-specific payload. Kept open here; refined per observer at the edges. */
|
|
318
|
+
data: z.record(z.unknown()).default({})
|
|
319
|
+
});
|
|
320
|
+
var HelloMessageSchema = z.object({
|
|
321
|
+
kind: z.literal(MessageKind.HELLO),
|
|
322
|
+
protocolVersion: z.literal(IRIS_PROTOCOL_VERSION),
|
|
323
|
+
sessionId: sessionIdSchema,
|
|
324
|
+
url: z.string().max(TRANSPORT_LIMITS.MAX_URL_LENGTH),
|
|
325
|
+
title: z.string().max(TRANSPORT_LIMITS.MAX_TITLE_LENGTH),
|
|
326
|
+
adapters: z.array(z.string().max(TRANSPORT_LIMITS.MAX_ADAPTER_NAME_LENGTH)).max(TRANSPORT_LIMITS.MAX_ADAPTERS),
|
|
327
|
+
/** Optional browser/bridge pairing token. Required when the bridge configures one. */
|
|
328
|
+
token: z.string().max(TRANSPORT_LIMITS.MAX_TOKEN_LENGTH).optional(),
|
|
329
|
+
/** Whether the app has advertised a capability registry (iris.describe). */
|
|
330
|
+
hasCapabilities: z.boolean().optional()
|
|
331
|
+
});
|
|
332
|
+
var CommandMessageSchema = z.object({
|
|
333
|
+
kind: z.literal(MessageKind.COMMAND),
|
|
334
|
+
id: z.string().min(1).max(TRANSPORT_LIMITS.MAX_COMMAND_ID_LENGTH),
|
|
335
|
+
sessionId: sessionIdSchema.optional(),
|
|
336
|
+
name: z.string().min(1).max(TRANSPORT_LIMITS.MAX_COMMAND_NAME_LENGTH),
|
|
337
|
+
args: z.record(z.unknown()).default({})
|
|
338
|
+
});
|
|
339
|
+
var CommandResultSchema = z.object({
|
|
340
|
+
kind: z.literal(MessageKind.COMMAND_RESULT),
|
|
341
|
+
id: z.string().min(1).max(TRANSPORT_LIMITS.MAX_COMMAND_ID_LENGTH),
|
|
342
|
+
ok: z.boolean(),
|
|
343
|
+
result: z.unknown().optional(),
|
|
344
|
+
error: z.string().max(TRANSPORT_LIMITS.MAX_ERROR_LENGTH).optional()
|
|
345
|
+
});
|
|
346
|
+
var EventMessageSchema = z.object({
|
|
347
|
+
kind: z.literal(MessageKind.EVENT),
|
|
348
|
+
event: IrisEventSchema
|
|
349
|
+
});
|
|
350
|
+
var IrisMessageSchema = z.discriminatedUnion("kind", [
|
|
351
|
+
HelloMessageSchema,
|
|
352
|
+
CommandMessageSchema,
|
|
353
|
+
CommandResultSchema,
|
|
354
|
+
EventMessageSchema
|
|
355
|
+
]);
|
|
356
|
+
|
|
357
|
+
// ../protocol/dist/security.js
|
|
358
|
+
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;
|
|
359
|
+
function isLoopbackHostname(hostname) {
|
|
360
|
+
const normalized = hostname.toLowerCase().replace(/^\[|\]$/g, "");
|
|
361
|
+
if (normalized === "localhost" || normalized === "::1" || normalized === "0:0:0:0:0:0:0:1") {
|
|
362
|
+
return true;
|
|
363
|
+
}
|
|
364
|
+
const octets = normalized.split(".");
|
|
365
|
+
return octets.length === 4 && octets[0] === "127" && octets.every((octet) => {
|
|
366
|
+
if (!/^\d{1,3}$/.test(octet))
|
|
367
|
+
return false;
|
|
368
|
+
const value = Number(octet);
|
|
369
|
+
return value >= 0 && value <= 255;
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
function isDangerousActionText(text) {
|
|
373
|
+
return DANGEROUS_ACTION.test(text.replace(/[_-]+/g, " "));
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// ../protocol/dist/state-select.js
|
|
377
|
+
function keysOf(value) {
|
|
378
|
+
if (Array.isArray(value))
|
|
379
|
+
return value.map((_, i) => String(i));
|
|
380
|
+
if (typeof value === "object" && value !== null)
|
|
381
|
+
return Object.keys(value);
|
|
382
|
+
return [];
|
|
383
|
+
}
|
|
384
|
+
function selectPath(root, path) {
|
|
385
|
+
const segments = path.split(".").filter((s) => s.length > 0);
|
|
386
|
+
let current = root;
|
|
387
|
+
for (const segment of segments) {
|
|
388
|
+
if (Array.isArray(current)) {
|
|
389
|
+
const index = Number(segment);
|
|
390
|
+
if (!Number.isInteger(index) || index < 0 || index >= current.length) {
|
|
391
|
+
return { found: false, value: null, availableKeys: keysOf(current) };
|
|
392
|
+
}
|
|
393
|
+
current = current[index];
|
|
394
|
+
continue;
|
|
395
|
+
}
|
|
396
|
+
if (typeof current === "object" && current !== null && segment in current) {
|
|
397
|
+
current = current[segment];
|
|
398
|
+
continue;
|
|
399
|
+
}
|
|
400
|
+
return { found: false, value: null, availableKeys: keysOf(current) };
|
|
401
|
+
}
|
|
402
|
+
return { found: true, value: current };
|
|
403
|
+
}
|
|
404
|
+
function capDepth(value, maxDepth) {
|
|
405
|
+
if (maxDepth < 0)
|
|
406
|
+
return value;
|
|
407
|
+
if (Array.isArray(value)) {
|
|
408
|
+
if (maxDepth === 0)
|
|
409
|
+
return `[Array(${String(value.length)})]`;
|
|
410
|
+
return value.map((v) => capDepth(v, maxDepth - 1));
|
|
411
|
+
}
|
|
412
|
+
if (typeof value === "object" && value !== null) {
|
|
413
|
+
const keys = Object.keys(value);
|
|
414
|
+
if (maxDepth === 0)
|
|
415
|
+
return `{\u2026${String(keys.length)} keys}`;
|
|
416
|
+
const out = {};
|
|
417
|
+
for (const key of keys)
|
|
418
|
+
out[key] = capDepth(value[key], maxDepth - 1);
|
|
419
|
+
return out;
|
|
420
|
+
}
|
|
421
|
+
return value;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// ../protocol/dist/types.js
|
|
425
|
+
import { z as z2 } from "zod";
|
|
426
|
+
var ElementQuerySchema = z2.object({
|
|
427
|
+
by: z2.nativeEnum(QueryBy).optional(),
|
|
428
|
+
value: z2.string().optional(),
|
|
429
|
+
role: z2.string().optional(),
|
|
430
|
+
name: z2.string().optional(),
|
|
431
|
+
text: z2.string().optional(),
|
|
432
|
+
label: z2.string().optional(),
|
|
433
|
+
placeholder: z2.string().optional(),
|
|
434
|
+
testid: z2.string().optional(),
|
|
435
|
+
alt: z2.string().optional(),
|
|
436
|
+
/** Component display name (auto-anchor resolution). The nearest enclosing component of the target. */
|
|
437
|
+
component: z2.string().optional(),
|
|
438
|
+
/** Source location of the target element (auto-anchor resolution) — the precise, granular match. */
|
|
439
|
+
source: z2.object({ file: z2.string(), line: z2.number(), column: z2.number().optional() }).optional(),
|
|
222
440
|
/** CSS selector or ref to scope the search. */
|
|
223
|
-
scope:
|
|
441
|
+
scope: z2.string().optional()
|
|
224
442
|
});
|
|
225
|
-
var CapabilityFlowSchema =
|
|
226
|
-
name:
|
|
227
|
-
steps:
|
|
443
|
+
var CapabilityFlowSchema = z2.object({
|
|
444
|
+
name: z2.string(),
|
|
445
|
+
steps: z2.array(z2.string())
|
|
228
446
|
});
|
|
229
|
-
var CapabilitiesSchema =
|
|
230
|
-
testids:
|
|
231
|
-
signals:
|
|
232
|
-
stores:
|
|
233
|
-
flows:
|
|
447
|
+
var CapabilitiesSchema = z2.object({
|
|
448
|
+
testids: z2.array(z2.string()),
|
|
449
|
+
signals: z2.array(z2.string()),
|
|
450
|
+
stores: z2.array(z2.string()),
|
|
451
|
+
flows: z2.array(CapabilityFlowSchema)
|
|
234
452
|
});
|
|
235
|
-
var ContractFileSchema =
|
|
236
|
-
version:
|
|
237
|
-
generatedAt:
|
|
453
|
+
var ContractFileSchema = z2.object({
|
|
454
|
+
version: z2.number(),
|
|
455
|
+
generatedAt: z2.number(),
|
|
238
456
|
capabilities: CapabilitiesSchema
|
|
239
457
|
});
|
|
240
|
-
var RunEvidenceSchema =
|
|
241
|
-
consoleErrors:
|
|
242
|
-
networkErrors:
|
|
243
|
-
driftSteps:
|
|
458
|
+
var RunEvidenceSchema = z2.object({
|
|
459
|
+
consoleErrors: z2.number().optional(),
|
|
460
|
+
networkErrors: z2.number().optional(),
|
|
461
|
+
driftSteps: z2.number().optional()
|
|
244
462
|
});
|
|
245
|
-
var RunRecordSchema =
|
|
246
|
-
kind:
|
|
247
|
-
name:
|
|
248
|
-
status:
|
|
249
|
-
at:
|
|
250
|
-
summary:
|
|
463
|
+
var RunRecordSchema = z2.object({
|
|
464
|
+
kind: z2.nativeEnum(RunKind),
|
|
465
|
+
name: z2.string(),
|
|
466
|
+
status: z2.nativeEnum(RunStatus),
|
|
467
|
+
at: z2.number(),
|
|
468
|
+
summary: z2.string().optional(),
|
|
251
469
|
evidence: RunEvidenceSchema.optional(),
|
|
252
|
-
durationMs:
|
|
470
|
+
durationMs: z2.number().optional()
|
|
253
471
|
});
|
|
254
|
-
var ProjectLearnedSchema =
|
|
255
|
-
flows:
|
|
256
|
-
routes:
|
|
472
|
+
var ProjectLearnedSchema = z2.object({
|
|
473
|
+
flows: z2.array(z2.string()).optional(),
|
|
474
|
+
routes: z2.array(z2.string()).optional()
|
|
257
475
|
});
|
|
258
|
-
var ProjectFileSchema =
|
|
259
|
-
version:
|
|
476
|
+
var ProjectFileSchema = z2.object({
|
|
477
|
+
version: z2.number(),
|
|
260
478
|
learned: ProjectLearnedSchema.optional(),
|
|
261
|
-
runs:
|
|
479
|
+
runs: z2.array(RunRecordSchema)
|
|
262
480
|
});
|
|
263
|
-
var FlowAnchorSchema =
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
kind:
|
|
267
|
-
role:
|
|
268
|
-
name:
|
|
481
|
+
var FlowAnchorSchema = z2.discriminatedUnion("kind", [
|
|
482
|
+
z2.object({ kind: z2.literal(AnchorKind.TESTID), value: z2.string().min(1) }),
|
|
483
|
+
z2.object({
|
|
484
|
+
kind: z2.literal(AnchorKind.ROLE),
|
|
485
|
+
role: z2.string().min(1),
|
|
486
|
+
name: z2.string().optional()
|
|
269
487
|
}),
|
|
270
|
-
|
|
488
|
+
z2.object({ kind: z2.literal(AnchorKind.SIGNAL), name: z2.string().min(1) }),
|
|
489
|
+
// Auto-anchor: re-find an element by component identity / source location when it has no testid.
|
|
490
|
+
// component or source carries the durable signal; role/name are disambiguating extras.
|
|
491
|
+
z2.object({
|
|
492
|
+
kind: z2.literal(AnchorKind.COMPONENT),
|
|
493
|
+
component: z2.string().optional(),
|
|
494
|
+
source: z2.object({ file: z2.string(), line: z2.number(), column: z2.number().optional() }).optional(),
|
|
495
|
+
role: z2.string().optional(),
|
|
496
|
+
name: z2.string().optional()
|
|
497
|
+
})
|
|
271
498
|
]);
|
|
272
|
-
var FlowExpectSchema =
|
|
273
|
-
signal:
|
|
499
|
+
var FlowExpectSchema = z2.object({
|
|
500
|
+
signal: z2.string().optional(),
|
|
274
501
|
/**
|
|
275
502
|
* Optional payload shape an `assert-signal` annotation requires the signal
|
|
276
503
|
* to match (the predicate DSL's signal.dataMatches). Additive/optional — a flow file with a
|
|
277
504
|
* bare `signal` still parses, and the on-disk version stays FLOW_FILE_VERSION 1.
|
|
278
505
|
*/
|
|
279
|
-
signalData:
|
|
280
|
-
net:
|
|
281
|
-
method:
|
|
282
|
-
urlContains:
|
|
283
|
-
status:
|
|
506
|
+
signalData: z2.record(z2.unknown()).optional(),
|
|
507
|
+
net: z2.object({
|
|
508
|
+
method: z2.string().optional(),
|
|
509
|
+
urlContains: z2.string().optional(),
|
|
510
|
+
status: z2.number().optional(),
|
|
511
|
+
/**
|
|
512
|
+
* Exact number of matching requests since the action — turns presence into a cardinality
|
|
513
|
+
* assertion. Catches the double-submit / useEffect-double-fire / retry-storm regression class:
|
|
514
|
+
* the request fired (presence passes) but fired the WRONG number of times. Omit = presence (≥1).
|
|
515
|
+
*/
|
|
516
|
+
count: z2.number().int().nonnegative().optional()
|
|
517
|
+
}).optional(),
|
|
518
|
+
/**
|
|
519
|
+
* Console golden end-condition: assert the action logged (or, with absent:true, did NOT log) a
|
|
520
|
+
* console message at `level` (default 'error'). `absent:true` is the common case — "the action
|
|
521
|
+
* completed with a clean console" — catching the regression where an action throws a caught error
|
|
522
|
+
* / logs an uncaught rejection while the UI still renders fine (a presence check passes it).
|
|
523
|
+
*/
|
|
524
|
+
console: z2.object({
|
|
525
|
+
level: z2.string().optional(),
|
|
526
|
+
absent: z2.boolean().optional()
|
|
527
|
+
}).optional(),
|
|
528
|
+
element: z2.object({
|
|
529
|
+
testid: z2.string().optional(),
|
|
530
|
+
role: z2.string().optional(),
|
|
531
|
+
name: z2.string().optional()
|
|
284
532
|
}).optional(),
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
533
|
+
/**
|
|
534
|
+
* Assert a registered store's value — the source of truth no DOM/network read can reach. Compiles
|
|
535
|
+
* to the predicate engine's `state` predicate. Additive/optional — a flow without it still parses
|
|
536
|
+
* and the on-disk version stays FLOW_FILE_VERSION 1. `equals` accepts a literal, omitted = presence,
|
|
537
|
+
* or a `{ $gte | $contains | $length }` operator pattern.
|
|
538
|
+
*/
|
|
539
|
+
state: z2.object({
|
|
540
|
+
store: z2.string().optional(),
|
|
541
|
+
path: z2.string(),
|
|
542
|
+
equals: z2.unknown().optional(),
|
|
543
|
+
/**
|
|
544
|
+
* Treat this as an INVARIANT that must still hold AFTER the action settles, rather than a
|
|
545
|
+
* condition to wait for. Set it for a blast-radius check ("this unrelated path must NOT have
|
|
546
|
+
* moved") — without it a wait-until-true read passes before an over-reaching side-effect lands.
|
|
547
|
+
*/
|
|
548
|
+
hold: z2.boolean().optional()
|
|
289
549
|
}).optional()
|
|
290
550
|
});
|
|
291
|
-
var baseFlowStep =
|
|
292
|
-
tool:
|
|
551
|
+
var baseFlowStep = z2.object({
|
|
552
|
+
tool: z2.string(),
|
|
293
553
|
anchor: FlowAnchorSchema,
|
|
294
|
-
action:
|
|
295
|
-
args:
|
|
554
|
+
action: z2.nativeEnum(ActionType).optional(),
|
|
555
|
+
args: z2.record(z2.unknown()).optional(),
|
|
296
556
|
expect: FlowExpectSchema.optional(),
|
|
297
|
-
degraded:
|
|
557
|
+
degraded: z2.boolean().optional()
|
|
298
558
|
});
|
|
299
559
|
var FlowStepSchema = baseFlowStep.extend({
|
|
300
|
-
steps:
|
|
560
|
+
steps: z2.lazy(() => z2.array(FlowStepSchema).optional())
|
|
301
561
|
});
|
|
302
|
-
var FlowFileSchema =
|
|
303
|
-
version:
|
|
304
|
-
name:
|
|
562
|
+
var FlowFileSchema = z2.object({
|
|
563
|
+
version: z2.literal(FLOW_FILE_VERSION),
|
|
564
|
+
name: z2.string(),
|
|
565
|
+
/**
|
|
566
|
+
* The business goal this flow exists to verify, one line (e.g. "ship a deploy to production").
|
|
567
|
+
* Optional + back-compat (a flow without it still parses). Set via an `intent` annotation. The
|
|
568
|
+
* point of "intent + outcome oracle": a flow that declares an intent should also assert an
|
|
569
|
+
* observable business OUTCOME (a consequence success-state), or it claims to verify a goal it
|
|
570
|
+
* cannot actually check — flow-classify flags that gap.
|
|
571
|
+
*/
|
|
572
|
+
intent: z2.string().optional(),
|
|
305
573
|
// FUTURE: fixtures/preconditions — schema slot reserved, unpopulated this cut. The recorder
|
|
306
574
|
// never writes it and no fixture runner exists.
|
|
307
|
-
fixture:
|
|
575
|
+
fixture: z2.string().optional(),
|
|
308
576
|
/** From the injected clock (ms) — deterministic in tests, byte-stable on disk. */
|
|
309
|
-
createdAt:
|
|
310
|
-
steps:
|
|
577
|
+
createdAt: z2.number(),
|
|
578
|
+
steps: z2.array(FlowStepSchema),
|
|
311
579
|
success: FlowExpectSchema.optional(),
|
|
312
580
|
/**
|
|
313
581
|
* Anchors whose CONTENT must not be asserted (e.g. LLM output). Replay asserts
|
|
314
582
|
* presence, not words. Compiled from a `mark-dynamic` annotation.
|
|
315
583
|
*/
|
|
316
|
-
dynamic:
|
|
584
|
+
dynamic: z2.array(FlowAnchorSchema).optional()
|
|
317
585
|
});
|
|
318
|
-
var RecordedFlowSchema =
|
|
319
|
-
name:
|
|
586
|
+
var RecordedFlowSchema = z2.object({
|
|
587
|
+
name: z2.string(),
|
|
320
588
|
flow: FlowFileSchema
|
|
321
589
|
});
|
|
322
|
-
var AnnotationSchema =
|
|
323
|
-
|
|
324
|
-
kind:
|
|
325
|
-
name:
|
|
326
|
-
dataMatches:
|
|
590
|
+
var AnnotationSchema = z2.discriminatedUnion("kind", [
|
|
591
|
+
z2.object({
|
|
592
|
+
kind: z2.literal(AnnotationKind.ASSERT_SIGNAL),
|
|
593
|
+
name: z2.string().min(1),
|
|
594
|
+
dataMatches: z2.record(z2.unknown()).optional()
|
|
327
595
|
}),
|
|
328
|
-
|
|
329
|
-
kind:
|
|
330
|
-
testid:
|
|
596
|
+
z2.object({
|
|
597
|
+
kind: z2.literal(AnnotationKind.ASSERT_VISIBLE),
|
|
598
|
+
testid: z2.string().min(1)
|
|
331
599
|
}),
|
|
332
|
-
|
|
333
|
-
kind:
|
|
334
|
-
|
|
600
|
+
z2.object({
|
|
601
|
+
kind: z2.literal(AnnotationKind.ASSERT_STATE),
|
|
602
|
+
statePath: z2.string().min(1),
|
|
603
|
+
store: z2.string().min(1).optional(),
|
|
604
|
+
equals: z2.unknown().optional()
|
|
335
605
|
}),
|
|
336
|
-
|
|
337
|
-
kind:
|
|
338
|
-
|
|
339
|
-
|
|
606
|
+
z2.object({
|
|
607
|
+
kind: z2.literal(AnnotationKind.MARK_DYNAMIC),
|
|
608
|
+
testid: z2.string().min(1)
|
|
609
|
+
}),
|
|
610
|
+
z2.object({
|
|
611
|
+
kind: z2.literal(AnnotationKind.SUCCESS_STATE),
|
|
612
|
+
signal: z2.string().min(1).optional(),
|
|
613
|
+
testid: z2.string().min(1).optional(),
|
|
614
|
+
// A store-truth golden end-condition: the flow succeeds when this store path holds (e.g. the
|
|
615
|
+
// created deployment actually reached status 'live' in the store, not just on screen).
|
|
616
|
+
statePath: z2.string().min(1).optional(),
|
|
617
|
+
store: z2.string().min(1).optional(),
|
|
618
|
+
equals: z2.unknown().optional(),
|
|
619
|
+
// Treat the statePath as an INVARIANT that must hold AFTER settle (a blast-radius "this unrelated
|
|
620
|
+
// path must not have moved" check), not a condition to wait for.
|
|
621
|
+
hold: z2.boolean().optional(),
|
|
622
|
+
// A network-cardinality golden end-condition: the flow succeeds only when EXACTLY `count` matching
|
|
623
|
+
// requests fired (omit count = presence). Catches the double-submit / retry-storm regression class.
|
|
624
|
+
net: z2.object({
|
|
625
|
+
method: z2.string().min(1).optional(),
|
|
626
|
+
urlContains: z2.string().min(1).optional(),
|
|
627
|
+
status: z2.number().optional(),
|
|
628
|
+
count: z2.number().int().nonnegative().optional()
|
|
629
|
+
}).optional(),
|
|
630
|
+
// A console golden end-condition: with absent:true, "the action completed with a clean console"
|
|
631
|
+
// (no message at `level`, default 'error') — catches an action that logs a caught error / rejection
|
|
632
|
+
// while the UI still renders fine.
|
|
633
|
+
console: z2.object({
|
|
634
|
+
level: z2.string().min(1).optional(),
|
|
635
|
+
absent: z2.boolean().optional()
|
|
636
|
+
}).optional()
|
|
637
|
+
}),
|
|
638
|
+
z2.object({
|
|
639
|
+
kind: z2.literal(AnnotationKind.INTENT),
|
|
640
|
+
text: z2.string().min(1)
|
|
340
641
|
})
|
|
341
642
|
]);
|
|
342
643
|
|
|
@@ -371,6 +672,96 @@ var RefRegistry = class {
|
|
|
371
672
|
};
|
|
372
673
|
var refs = new RefRegistry();
|
|
373
674
|
|
|
675
|
+
// ../browser/dist/security/serialization.js
|
|
676
|
+
var TRUNCATED_VALUE = "[TRUNCATED]";
|
|
677
|
+
var UNSERIALIZABLE_VALUE = "[UNSERIALIZABLE]";
|
|
678
|
+
var OMIT_VALUE = /* @__PURE__ */ Symbol("omit");
|
|
679
|
+
var MAX_KEY_LENGTH = 256;
|
|
680
|
+
var MAX_TOTAL_CHARACTERS = Math.floor(TRANSPORT_LIMITS.MAX_MESSAGE_BYTES / 8);
|
|
681
|
+
var MAX_TOTAL_NODES = TRANSPORT_LIMITS.MAX_COLLECTION_ITEMS * 5;
|
|
682
|
+
var SENSITIVE_KEY = /password|passwd|passcode|secret|(?:(?:access|refresh|auth|bearer|api|id|session|csrf|client)[-_]?tokens?|(?:^|[-_])tokens?(?=$|[-_]))|authorization|api[-_]?key|access[-_]?key|private[-_]?key|client[-_]?secret|credit[-_]?card|card[-_]?number|cvv|cvc|ssn/i;
|
|
683
|
+
function isSensitiveKey(key) {
|
|
684
|
+
return SENSITIVE_KEY.test(key);
|
|
685
|
+
}
|
|
686
|
+
function boundedString(value, state, max) {
|
|
687
|
+
const allowed = Math.max(0, Math.min(max, state.remainingCharacters));
|
|
688
|
+
if (value.length <= allowed) {
|
|
689
|
+
state.remainingCharacters -= value.length;
|
|
690
|
+
return value;
|
|
691
|
+
}
|
|
692
|
+
const truncated = allowed <= TRUNCATED_VALUE.length ? TRUNCATED_VALUE.slice(0, allowed) : `${value.slice(0, allowed - TRUNCATED_VALUE.length)}${TRUNCATED_VALUE}`;
|
|
693
|
+
state.remainingCharacters -= truncated.length;
|
|
694
|
+
return truncated;
|
|
695
|
+
}
|
|
696
|
+
function sanitize(value, state, depth, key) {
|
|
697
|
+
if (key !== void 0 && isSensitiveKey(key))
|
|
698
|
+
return REDACTED_VALUE;
|
|
699
|
+
if (depth > TRANSPORT_LIMITS.MAX_SERIALIZE_DEPTH || state.nodes >= MAX_TOTAL_NODES) {
|
|
700
|
+
return TRUNCATED_VALUE;
|
|
701
|
+
}
|
|
702
|
+
state.nodes += 1;
|
|
703
|
+
if (value === null || typeof value === "boolean")
|
|
704
|
+
return value;
|
|
705
|
+
if (typeof value === "string") {
|
|
706
|
+
return boundedString(value, state, key?.toLowerCase() === "error" ? TRANSPORT_LIMITS.MAX_ERROR_LENGTH : TRANSPORT_LIMITS.MAX_STRING_LENGTH);
|
|
707
|
+
}
|
|
708
|
+
if (typeof value === "number")
|
|
709
|
+
return Number.isFinite(value) ? value : null;
|
|
710
|
+
if (typeof value === "bigint")
|
|
711
|
+
return value.toString();
|
|
712
|
+
if (typeof value === "undefined" || typeof value === "function" || typeof value === "symbol") {
|
|
713
|
+
return OMIT_VALUE;
|
|
714
|
+
}
|
|
715
|
+
if (value instanceof Date)
|
|
716
|
+
return value.toISOString();
|
|
717
|
+
if (value instanceof Error) {
|
|
718
|
+
return {
|
|
719
|
+
name: boundedString(value.name, state, 256),
|
|
720
|
+
message: boundedString(value.message, state, TRANSPORT_LIMITS.MAX_ERROR_LENGTH)
|
|
721
|
+
};
|
|
722
|
+
}
|
|
723
|
+
if (state.seen.has(value))
|
|
724
|
+
return "[CIRCULAR]";
|
|
725
|
+
state.seen.add(value);
|
|
726
|
+
try {
|
|
727
|
+
if (Array.isArray(value)) {
|
|
728
|
+
return value.slice(0, TRANSPORT_LIMITS.MAX_COLLECTION_ITEMS).map((item) => {
|
|
729
|
+
const sanitized = sanitize(item, state, depth + 1);
|
|
730
|
+
return sanitized === OMIT_VALUE ? null : sanitized;
|
|
731
|
+
});
|
|
732
|
+
}
|
|
733
|
+
const out = /* @__PURE__ */ Object.create(null);
|
|
734
|
+
for (const rawKey of Object.keys(value).slice(0, TRANSPORT_LIMITS.MAX_OBJECT_KEYS)) {
|
|
735
|
+
const safeKey = boundedString(rawKey, state, MAX_KEY_LENGTH);
|
|
736
|
+
try {
|
|
737
|
+
const sanitized = sanitize(value[rawKey], state, depth + 1, rawKey);
|
|
738
|
+
if (sanitized !== OMIT_VALUE)
|
|
739
|
+
out[safeKey] = sanitized;
|
|
740
|
+
} catch {
|
|
741
|
+
out[safeKey] = UNSERIALIZABLE_VALUE;
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
return out;
|
|
745
|
+
} finally {
|
|
746
|
+
state.seen.delete(value);
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
function sanitizeForTransport(value) {
|
|
750
|
+
const sanitized = sanitize(value, {
|
|
751
|
+
seen: /* @__PURE__ */ new WeakSet(),
|
|
752
|
+
remainingCharacters: MAX_TOTAL_CHARACTERS,
|
|
753
|
+
nodes: 0
|
|
754
|
+
}, 0);
|
|
755
|
+
return sanitized === OMIT_VALUE ? null : sanitized;
|
|
756
|
+
}
|
|
757
|
+
function safeStringify(value) {
|
|
758
|
+
try {
|
|
759
|
+
return JSON.stringify(sanitizeForTransport(value));
|
|
760
|
+
} catch {
|
|
761
|
+
return JSON.stringify(UNSERIALIZABLE_VALUE);
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
|
|
374
765
|
// ../browser/dist/dom/a11y.js
|
|
375
766
|
var NAME_FROM_CONTENT = /* @__PURE__ */ new Set([
|
|
376
767
|
"button",
|
|
@@ -537,6 +928,17 @@ function getStates(el) {
|
|
|
537
928
|
}
|
|
538
929
|
function getValue(el) {
|
|
539
930
|
if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement || el instanceof HTMLSelectElement) {
|
|
931
|
+
const autocomplete = el.getAttribute("autocomplete") ?? "";
|
|
932
|
+
const identifiers = [
|
|
933
|
+
el.getAttribute("name") ?? "",
|
|
934
|
+
el.id,
|
|
935
|
+
el.getAttribute("data-testid") ?? "",
|
|
936
|
+
el.getAttribute("aria-label") ?? ""
|
|
937
|
+
];
|
|
938
|
+
const sensitiveAutocomplete = /current-password|new-password|cc-number|cc-csc|one-time-code/i.test(autocomplete);
|
|
939
|
+
if (el instanceof HTMLInputElement && el.type.toLowerCase() === "password" || sensitiveAutocomplete || identifiers.some(isSensitiveKey)) {
|
|
940
|
+
return REDACTED_VALUE;
|
|
941
|
+
}
|
|
540
942
|
return el.value;
|
|
541
943
|
}
|
|
542
944
|
const valueNow = el.getAttribute("aria-valuenow");
|
|
@@ -598,8 +1000,8 @@ function isIrisOverlay(el) {
|
|
|
598
1000
|
return el.closest(IRIS_OVERLAY) !== null;
|
|
599
1001
|
}
|
|
600
1002
|
function isIgnored(el) {
|
|
601
|
-
const
|
|
602
|
-
return el.closest(
|
|
1003
|
+
const sel2 = extraIgnore.length > 0 ? `${IRIS_OVERLAY},${DEV_OVERLAYS},${extraIgnore}` : `${IRIS_OVERLAY},${DEV_OVERLAYS}`;
|
|
1004
|
+
return el.closest(sel2) !== null;
|
|
603
1005
|
}
|
|
604
1006
|
|
|
605
1007
|
// ../browser/dist/dom/snapshot.js
|
|
@@ -619,6 +1021,16 @@ var INTERACTIVE = /* @__PURE__ */ new Set([
|
|
|
619
1021
|
"option"
|
|
620
1022
|
]);
|
|
621
1023
|
var SKIP_TAGS = /* @__PURE__ */ new Set(["script", "style", "noscript", "template", "head", "meta", "link"]);
|
|
1024
|
+
var TEXT_MAX = 80;
|
|
1025
|
+
function directText(el) {
|
|
1026
|
+
let out = "";
|
|
1027
|
+
for (const node of el.childNodes) {
|
|
1028
|
+
if (node.nodeType === 3)
|
|
1029
|
+
out += node.textContent ?? "";
|
|
1030
|
+
}
|
|
1031
|
+
const collapsed = out.replace(/\s+/g, " ").trim();
|
|
1032
|
+
return collapsed.length > TEXT_MAX ? `${collapsed.slice(0, TEXT_MAX)}\u2026` : collapsed;
|
|
1033
|
+
}
|
|
622
1034
|
function skip(el) {
|
|
623
1035
|
if (SKIP_TAGS.has(el.tagName.toLowerCase()))
|
|
624
1036
|
return true;
|
|
@@ -640,13 +1052,29 @@ function stateSuffix(el) {
|
|
|
640
1052
|
const states = getStates(el).filter((s) => s === ElementState.DISABLED || s === ElementState.CHECKED || s === ElementState.EXPANDED || s === ElementState.FOCUSED);
|
|
641
1053
|
return states.length > 0 ? ` [${states.join(",")}]` : "";
|
|
642
1054
|
}
|
|
643
|
-
function formatLine(el, depth, role, name) {
|
|
1055
|
+
function formatLine(el, depth, role, name, layout) {
|
|
644
1056
|
const indent = " ".repeat(depth);
|
|
645
1057
|
const value = getValue(el);
|
|
646
1058
|
const namePart = name.length > 0 ? ` "${name}"` : "";
|
|
647
1059
|
const refPart = INTERACTIVE.has(role) || name.length > 0 ? ` (ref=${refs.refFor(el)})` : "";
|
|
648
1060
|
const valuePart = value !== void 0 && value.length > 0 ? ` [value="${value}"]` : "";
|
|
649
|
-
|
|
1061
|
+
const layoutPart = layout.length > 0 ? ` [${layout}]` : "";
|
|
1062
|
+
return `${indent}- ${role}${namePart}${refPart}${valuePart}${layoutPart}${stateSuffix(el)}`;
|
|
1063
|
+
}
|
|
1064
|
+
function formatTextLine(depth, text) {
|
|
1065
|
+
return `${" ".repeat(depth)}- text "${text}"`;
|
|
1066
|
+
}
|
|
1067
|
+
function layoutSignature(el) {
|
|
1068
|
+
const view = el.ownerDocument.defaultView;
|
|
1069
|
+
if (view === null)
|
|
1070
|
+
return "";
|
|
1071
|
+
const style = view.getComputedStyle(el);
|
|
1072
|
+
const display = style.display;
|
|
1073
|
+
if (display === "grid" || display === "inline-grid") {
|
|
1074
|
+
const cols = style.gridTemplateColumns;
|
|
1075
|
+
return cols !== "" && cols !== "none" ? `grid-cols:${cols}` : "grid";
|
|
1076
|
+
}
|
|
1077
|
+
return "";
|
|
650
1078
|
}
|
|
651
1079
|
function walk(parent, depth, ctx) {
|
|
652
1080
|
if (depth > ctx.maxDepth)
|
|
@@ -661,11 +1089,14 @@ function walk(parent, depth, ctx) {
|
|
|
661
1089
|
const role = getRole(child);
|
|
662
1090
|
const name = getAccessibleName(child);
|
|
663
1091
|
const interactive = INTERACTIVE.has(role);
|
|
664
|
-
const
|
|
665
|
-
const
|
|
1092
|
+
const lean = ctx.mode === SnapshotMode.INTERACTIVE;
|
|
1093
|
+
const text = !lean && role === "generic" && name.length === 0 ? directText(child) : "";
|
|
1094
|
+
const layout = lean ? "" : layoutSignature(child);
|
|
1095
|
+
const meaningful = interactive || role !== "generic" || name.length > 0 || text.length > 0 || layout.length > 0;
|
|
1096
|
+
const include = lean ? interactive : meaningful;
|
|
666
1097
|
if (include) {
|
|
667
1098
|
ctx.nodes += 1;
|
|
668
|
-
ctx.lines.push(formatLine(child, depth, role, name));
|
|
1099
|
+
ctx.lines.push(text.length > 0 && name.length === 0 && layout.length === 0 ? formatTextLine(depth, text) : formatLine(child, depth, role, name, layout));
|
|
669
1100
|
walk(child, depth + 1, ctx);
|
|
670
1101
|
} else {
|
|
671
1102
|
walk(child, depth, ctx);
|
|
@@ -756,9 +1187,50 @@ function hasCapabilities() {
|
|
|
756
1187
|
return capabilities.testids.length > 0 || capabilities.signals.length > 0 || capabilities.stores.length > 0 || capabilities.flows.length > 0;
|
|
757
1188
|
}
|
|
758
1189
|
|
|
1190
|
+
// ../browser/dist/registry/adapters.js
|
|
1191
|
+
var globalStore2 = globalThis;
|
|
1192
|
+
var adapters = globalStore2.__irisAdapters ??= [];
|
|
1193
|
+
function registerAdapter(adapter) {
|
|
1194
|
+
if (!adapters.some((a) => a.name === adapter.name))
|
|
1195
|
+
adapters.push(adapter);
|
|
1196
|
+
}
|
|
1197
|
+
function identifyComponent(el) {
|
|
1198
|
+
for (const adapter of adapters) {
|
|
1199
|
+
const info = adapter.identify(el);
|
|
1200
|
+
if (info !== null)
|
|
1201
|
+
return info;
|
|
1202
|
+
}
|
|
1203
|
+
return null;
|
|
1204
|
+
}
|
|
1205
|
+
function readComponentState(el) {
|
|
1206
|
+
for (const adapter of adapters) {
|
|
1207
|
+
if (adapter.readState === void 0)
|
|
1208
|
+
continue;
|
|
1209
|
+
const state = adapter.readState(el);
|
|
1210
|
+
if (state !== void 0)
|
|
1211
|
+
return state;
|
|
1212
|
+
}
|
|
1213
|
+
return void 0;
|
|
1214
|
+
}
|
|
1215
|
+
function elementHasHoverHandlers(el) {
|
|
1216
|
+
for (const adapter of adapters) {
|
|
1217
|
+
if (adapter.hasHoverHandlers === void 0)
|
|
1218
|
+
continue;
|
|
1219
|
+
if (adapter.hasHoverHandlers(el))
|
|
1220
|
+
return true;
|
|
1221
|
+
}
|
|
1222
|
+
return false;
|
|
1223
|
+
}
|
|
1224
|
+
function adapterNames() {
|
|
1225
|
+
return adapters.map((a) => a.name);
|
|
1226
|
+
}
|
|
1227
|
+
|
|
759
1228
|
// ../browser/dist/dom/query.js
|
|
760
1229
|
var TESTID_ATTR = "data-testid";
|
|
1230
|
+
var SOURCE_ATTR = "data-iris-source";
|
|
761
1231
|
var MAX_PRESENT_TESTIDS = 12;
|
|
1232
|
+
var MAX_COMPONENT_CANDIDATES = 2e3;
|
|
1233
|
+
var COMPONENT_CANDIDATE_SELECTOR = "[data-iris-source], [data-testid], button, a, input, select, textarea, [role]";
|
|
762
1234
|
function resolveContainer(scope) {
|
|
763
1235
|
const body = document.body;
|
|
764
1236
|
if (scope === void 0)
|
|
@@ -774,6 +1246,38 @@ function resolveContainer(scope) {
|
|
|
774
1246
|
}
|
|
775
1247
|
return body;
|
|
776
1248
|
}
|
|
1249
|
+
function findBySource(container, source) {
|
|
1250
|
+
const prefix = `${source.file}:${source.line}:`.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
1251
|
+
try {
|
|
1252
|
+
return Array.from(container.querySelectorAll(`[${SOURCE_ATTR}^="${prefix}"]`));
|
|
1253
|
+
} catch {
|
|
1254
|
+
return [];
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
function findByComponentName(container, component) {
|
|
1258
|
+
const out = [];
|
|
1259
|
+
let scanned = 0;
|
|
1260
|
+
for (const el of Array.from(container.querySelectorAll(COMPONENT_CANDIDATE_SELECTOR))) {
|
|
1261
|
+
if (scanned >= MAX_COMPONENT_CANDIDATES)
|
|
1262
|
+
break;
|
|
1263
|
+
scanned += 1;
|
|
1264
|
+
const info = identifyComponent(el);
|
|
1265
|
+
if (info !== null && info.componentStack[0] === component)
|
|
1266
|
+
out.push(el);
|
|
1267
|
+
}
|
|
1268
|
+
return out;
|
|
1269
|
+
}
|
|
1270
|
+
function findByComponent(container, query) {
|
|
1271
|
+
if (query.source !== void 0) {
|
|
1272
|
+
const bySource = findBySource(container, query.source);
|
|
1273
|
+
if (bySource.length > 0)
|
|
1274
|
+
return bySource;
|
|
1275
|
+
}
|
|
1276
|
+
if (query.component !== void 0 && query.component.length > 0) {
|
|
1277
|
+
return findByComponentName(container, query.component);
|
|
1278
|
+
}
|
|
1279
|
+
return [];
|
|
1280
|
+
}
|
|
777
1281
|
function findCandidates(query) {
|
|
778
1282
|
const container = resolveContainer(query.scope);
|
|
779
1283
|
const by = query.by;
|
|
@@ -792,10 +1296,15 @@ function findCandidates(query) {
|
|
|
792
1296
|
return queryAllByTestId(container, value, { exact: true });
|
|
793
1297
|
case QueryBy.ALT:
|
|
794
1298
|
return queryAllByAltText(container, value, { exact: false });
|
|
1299
|
+
case QueryBy.COMPONENT:
|
|
1300
|
+
return findByComponent(container, { ...query, component: query.component ?? value });
|
|
795
1301
|
default:
|
|
796
1302
|
return [];
|
|
797
1303
|
}
|
|
798
1304
|
}
|
|
1305
|
+
if (query.component !== void 0 || query.source !== void 0) {
|
|
1306
|
+
return findByComponent(container, query);
|
|
1307
|
+
}
|
|
799
1308
|
if (query.role !== void 0) {
|
|
800
1309
|
const options = query.name !== void 0 ? { hidden: true, name: query.name } : { hidden: true };
|
|
801
1310
|
return queryAllByRole(container, query.role, options);
|
|
@@ -909,44 +1418,6 @@ function runQuery(query) {
|
|
|
909
1418
|
return { elements: result2.elements };
|
|
910
1419
|
}
|
|
911
1420
|
|
|
912
|
-
// ../browser/dist/registry/adapters.js
|
|
913
|
-
var globalStore2 = globalThis;
|
|
914
|
-
var adapters = globalStore2.__irisAdapters ??= [];
|
|
915
|
-
function registerAdapter(adapter) {
|
|
916
|
-
if (!adapters.some((a) => a.name === adapter.name))
|
|
917
|
-
adapters.push(adapter);
|
|
918
|
-
}
|
|
919
|
-
function identifyComponent(el) {
|
|
920
|
-
for (const adapter of adapters) {
|
|
921
|
-
const info = adapter.identify(el);
|
|
922
|
-
if (info !== null)
|
|
923
|
-
return info;
|
|
924
|
-
}
|
|
925
|
-
return null;
|
|
926
|
-
}
|
|
927
|
-
function readComponentState(el) {
|
|
928
|
-
for (const adapter of adapters) {
|
|
929
|
-
if (adapter.readState === void 0)
|
|
930
|
-
continue;
|
|
931
|
-
const state = adapter.readState(el);
|
|
932
|
-
if (state !== void 0)
|
|
933
|
-
return state;
|
|
934
|
-
}
|
|
935
|
-
return void 0;
|
|
936
|
-
}
|
|
937
|
-
function elementHasHoverHandlers(el) {
|
|
938
|
-
for (const adapter of adapters) {
|
|
939
|
-
if (adapter.hasHoverHandlers === void 0)
|
|
940
|
-
continue;
|
|
941
|
-
if (adapter.hasHoverHandlers(el))
|
|
942
|
-
return true;
|
|
943
|
-
}
|
|
944
|
-
return false;
|
|
945
|
-
}
|
|
946
|
-
function adapterNames() {
|
|
947
|
-
return adapters.map((a) => a.name);
|
|
948
|
-
}
|
|
949
|
-
|
|
950
1421
|
// ../browser/dist/timers/native-timers.js
|
|
951
1422
|
var g = globalThis;
|
|
952
1423
|
var realSetTimeout = typeof g.setTimeout === "function" ? g.setTimeout.bind(g) : null;
|
|
@@ -1021,8 +1492,20 @@ function requireElement(ref) {
|
|
|
1021
1492
|
throw new Error(`ref '${ref}' is not an HTMLElement`);
|
|
1022
1493
|
return el;
|
|
1023
1494
|
}
|
|
1024
|
-
|
|
1025
|
-
const testid =
|
|
1495
|
+
function anchorOf(el) {
|
|
1496
|
+
const testid = el.getAttribute("data-testid") ?? void 0;
|
|
1497
|
+
if (testid !== void 0)
|
|
1498
|
+
return { testid };
|
|
1499
|
+
const info = identifyComponent(el);
|
|
1500
|
+
const out = {};
|
|
1501
|
+
const component = info?.componentStack[0];
|
|
1502
|
+
if (component !== void 0)
|
|
1503
|
+
out.component = component;
|
|
1504
|
+
if (info?.source !== void 0)
|
|
1505
|
+
out.source = info.source;
|
|
1506
|
+
return out;
|
|
1507
|
+
}
|
|
1508
|
+
var result = (ref, action, effect, settled, settleReason, anchor, warning) => {
|
|
1026
1509
|
const base = {
|
|
1027
1510
|
ok: true,
|
|
1028
1511
|
ref,
|
|
@@ -1032,8 +1515,14 @@ var result = (ref, action, effect, settled, settleReason, warning) => {
|
|
|
1032
1515
|
settleReason,
|
|
1033
1516
|
effect
|
|
1034
1517
|
};
|
|
1035
|
-
if (testid !== void 0)
|
|
1036
|
-
base.testid = testid;
|
|
1518
|
+
if (anchor.testid !== void 0) {
|
|
1519
|
+
base.testid = anchor.testid;
|
|
1520
|
+
} else {
|
|
1521
|
+
if (anchor.component !== void 0)
|
|
1522
|
+
base.component = anchor.component;
|
|
1523
|
+
if (anchor.source !== void 0)
|
|
1524
|
+
base.source = anchor.source;
|
|
1525
|
+
}
|
|
1037
1526
|
if (warning !== void 0)
|
|
1038
1527
|
base.warning = warning;
|
|
1039
1528
|
return base;
|
|
@@ -1041,6 +1530,30 @@ var result = (ref, action, effect, settled, settleReason, warning) => {
|
|
|
1041
1530
|
var FILL_LIKE = /* @__PURE__ */ new Set([ActionType.FILL, ActionType.TYPE, ActionType.CLEAR]);
|
|
1042
1531
|
var isFillLike = (action) => FILL_LIKE.has(action);
|
|
1043
1532
|
var CLICK_LIKE = /* @__PURE__ */ new Set([ActionType.CLICK, ActionType.DBLCLICK]);
|
|
1533
|
+
function dangerousActionContext(el) {
|
|
1534
|
+
const form = el.closest("form");
|
|
1535
|
+
return [
|
|
1536
|
+
getAccessibleName(el),
|
|
1537
|
+
el.textContent ?? "",
|
|
1538
|
+
el.getAttribute("value") ?? "",
|
|
1539
|
+
el.getAttribute("title") ?? "",
|
|
1540
|
+
el.getAttribute("aria-label") ?? "",
|
|
1541
|
+
el.getAttribute("href") ?? "",
|
|
1542
|
+
form?.getAttribute("action") ?? "",
|
|
1543
|
+
form?.textContent ?? ""
|
|
1544
|
+
].join(" ");
|
|
1545
|
+
}
|
|
1546
|
+
function requiresDangerousConfirmation(text) {
|
|
1547
|
+
return isDangerousActionText(text);
|
|
1548
|
+
}
|
|
1549
|
+
function assertActionAllowed(el, action, args) {
|
|
1550
|
+
const canTrigger = action === ActionType.CLICK || action === ActionType.DBLCLICK || action === ActionType.DRAG || action === ActionType.SUBMIT || action === ActionType.PRESS && asString(args["key"], "Enter") === "Enter";
|
|
1551
|
+
const dragTarget = action === ActionType.DRAG ? refs.resolve(asString(args["toRef"])) : null;
|
|
1552
|
+
const context = dragTarget instanceof HTMLElement ? `${dangerousActionContext(el)} ${dangerousActionContext(dragTarget)}` : dangerousActionContext(el);
|
|
1553
|
+
if (canTrigger && requiresDangerousConfirmation(context) && args[DANGEROUS_ACTION_CONFIRM_ARG] !== true) {
|
|
1554
|
+
throw new Error(`potentially destructive action blocked; retry with args.${DANGEROUS_ACTION_CONFIRM_ARG}=true`);
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1044
1557
|
var NO_GEOMETRY = { occluded: false, occludedBy: null, scrolledIntoView: false };
|
|
1045
1558
|
function fireClickSequence(el) {
|
|
1046
1559
|
const doc = el.ownerDocument;
|
|
@@ -1207,6 +1720,8 @@ async function dispatchFor(el, action, args) {
|
|
|
1207
1720
|
}
|
|
1208
1721
|
async function executeAction(ref, action, args = {}) {
|
|
1209
1722
|
const el = requireElement(ref);
|
|
1723
|
+
assertActionAllowed(el, action, args);
|
|
1724
|
+
const anchor = anchorOf(el);
|
|
1210
1725
|
const visible = isVisible(el);
|
|
1211
1726
|
const enabled = enabledOf(el);
|
|
1212
1727
|
const prevFocus = activeRef(el);
|
|
@@ -1249,7 +1764,7 @@ async function executeAction(ref, action, args = {}) {
|
|
|
1249
1764
|
scrolledIntoView: geometry.scrolledIntoView
|
|
1250
1765
|
};
|
|
1251
1766
|
const warning = geometry.occluded ? ActionWarning.CLICK_OCCLUDED : action === ActionType.HOVER && elementHasHoverHandlers(el) ? ActionWarning.HOVER_NATIVE_ENTER_LEAVE : void 0;
|
|
1252
|
-
return result(ref, action, effect, settled, settleReason, warning);
|
|
1767
|
+
return result(ref, action, effect, settled, settleReason, anchor, warning);
|
|
1253
1768
|
}
|
|
1254
1769
|
var sleep = (ms) => new Promise((r) => nativeSetTimeout(r, ms));
|
|
1255
1770
|
function firePointer(el, type, relatedTarget = null) {
|
|
@@ -1313,7 +1828,10 @@ async function dragElement(source, target, data) {
|
|
|
1313
1828
|
}
|
|
1314
1829
|
return dropPrevented;
|
|
1315
1830
|
}
|
|
1316
|
-
async function dispatchWebMcp(tool, params) {
|
|
1831
|
+
async function dispatchWebMcp(tool, params, confirmDangerous = false) {
|
|
1832
|
+
if (requiresDangerousConfirmation(tool) && !confirmDangerous) {
|
|
1833
|
+
throw new Error(`potentially destructive WebMCP tool blocked; retry with ${DANGEROUS_ACTION_CONFIRM_ARG}=true`);
|
|
1834
|
+
}
|
|
1317
1835
|
const mc = navigator.modelContext;
|
|
1318
1836
|
if (mc === void 0 || typeof mc.callTool !== "function") {
|
|
1319
1837
|
throw new Error("WebMCP (navigator.modelContext) not available on this page");
|
|
@@ -1342,6 +1860,77 @@ async function executeSequence(steps) {
|
|
|
1342
1860
|
return { ok: true, count: steps.length, effects, steps: stepResults };
|
|
1343
1861
|
}
|
|
1344
1862
|
|
|
1863
|
+
// ../browser/dist/dom/theme.js
|
|
1864
|
+
var cached = null;
|
|
1865
|
+
function toRgb(value) {
|
|
1866
|
+
if (value.length === 0)
|
|
1867
|
+
return null;
|
|
1868
|
+
const probe = document.createElement("span");
|
|
1869
|
+
probe.style.color = "";
|
|
1870
|
+
probe.style.color = value;
|
|
1871
|
+
if (probe.style.color === "")
|
|
1872
|
+
return null;
|
|
1873
|
+
probe.style.position = "absolute";
|
|
1874
|
+
probe.style.pointerEvents = "none";
|
|
1875
|
+
document.body.appendChild(probe);
|
|
1876
|
+
const rgb = getComputedStyle(probe).color;
|
|
1877
|
+
probe.remove();
|
|
1878
|
+
return rgb;
|
|
1879
|
+
}
|
|
1880
|
+
function collectTokens() {
|
|
1881
|
+
const out = {};
|
|
1882
|
+
for (const sheet of Array.from(document.styleSheets)) {
|
|
1883
|
+
let rules = null;
|
|
1884
|
+
try {
|
|
1885
|
+
rules = sheet.cssRules;
|
|
1886
|
+
} catch {
|
|
1887
|
+
continue;
|
|
1888
|
+
}
|
|
1889
|
+
if (rules === null)
|
|
1890
|
+
continue;
|
|
1891
|
+
for (const rule of Array.from(rules)) {
|
|
1892
|
+
if (!(rule instanceof CSSStyleRule))
|
|
1893
|
+
continue;
|
|
1894
|
+
if (!/(^|,)\s*(:root|html)\b/.test(rule.selectorText))
|
|
1895
|
+
continue;
|
|
1896
|
+
for (const prop of Array.from(rule.style)) {
|
|
1897
|
+
if (prop.startsWith("--"))
|
|
1898
|
+
out[prop] = rule.style.getPropertyValue(prop).trim();
|
|
1899
|
+
}
|
|
1900
|
+
}
|
|
1901
|
+
}
|
|
1902
|
+
return out;
|
|
1903
|
+
}
|
|
1904
|
+
function palette() {
|
|
1905
|
+
if (cached !== null)
|
|
1906
|
+
return cached;
|
|
1907
|
+
const byColor = /* @__PURE__ */ new Map();
|
|
1908
|
+
for (const [name, value] of Object.entries(collectTokens())) {
|
|
1909
|
+
const rgb = toRgb(value);
|
|
1910
|
+
if (rgb !== null && !byColor.has(rgb))
|
|
1911
|
+
byColor.set(rgb, name);
|
|
1912
|
+
}
|
|
1913
|
+
cached = { byColor };
|
|
1914
|
+
return cached;
|
|
1915
|
+
}
|
|
1916
|
+
function isTransparent(rgb) {
|
|
1917
|
+
return rgb === "rgba(0, 0, 0, 0)" || rgb === "transparent";
|
|
1918
|
+
}
|
|
1919
|
+
function themeReport(cs) {
|
|
1920
|
+
const p = palette();
|
|
1921
|
+
const colorToken = p.byColor.get(cs.color) ?? null;
|
|
1922
|
+
const backgroundToken = p.byColor.get(cs.backgroundColor) ?? null;
|
|
1923
|
+
const colorOff = !isTransparent(cs.color) && colorToken === null;
|
|
1924
|
+
const bgOff = !isTransparent(cs.backgroundColor) && backgroundToken === null;
|
|
1925
|
+
return {
|
|
1926
|
+
colorToken,
|
|
1927
|
+
backgroundToken,
|
|
1928
|
+
// Only meaningful when a palette exists; an app with no tokens can't violate one.
|
|
1929
|
+
offTheme: p.byColor.size > 0 && (colorOff || bgOff),
|
|
1930
|
+
tokenCount: p.byColor.size
|
|
1931
|
+
};
|
|
1932
|
+
}
|
|
1933
|
+
|
|
1345
1934
|
// ../browser/dist/registry/stores.js
|
|
1346
1935
|
var globalStore3 = globalThis;
|
|
1347
1936
|
var stores = globalStore3.__irisStores ??= /* @__PURE__ */ new Map();
|
|
@@ -1360,7 +1949,7 @@ function readStores(only) {
|
|
|
1360
1949
|
if (only !== void 0 && name !== only)
|
|
1361
1950
|
continue;
|
|
1362
1951
|
try {
|
|
1363
|
-
out[name] = getter();
|
|
1952
|
+
out[name] = sanitizeForTransport(getter());
|
|
1364
1953
|
} catch (error) {
|
|
1365
1954
|
out[name] = { __error: error instanceof Error ? error.message : String(error) };
|
|
1366
1955
|
}
|
|
@@ -1480,6 +2069,9 @@ function scrollContainer(ref, dy, fraction) {
|
|
|
1480
2069
|
function str(value) {
|
|
1481
2070
|
return typeof value === "string" ? value : void 0;
|
|
1482
2071
|
}
|
|
2072
|
+
function num(value) {
|
|
2073
|
+
return typeof value === "number" ? value : void 0;
|
|
2074
|
+
}
|
|
1483
2075
|
function record(value) {
|
|
1484
2076
|
return typeof value === "object" && value !== null ? value : {};
|
|
1485
2077
|
}
|
|
@@ -1505,15 +2097,39 @@ function inspect(ref) {
|
|
|
1505
2097
|
const component = identifyComponent(el);
|
|
1506
2098
|
const view = el.ownerDocument.defaultView;
|
|
1507
2099
|
const cs = view !== null ? view.getComputedStyle(el) : null;
|
|
1508
|
-
const styles = cs !== null ? {
|
|
2100
|
+
const styles = cs !== null ? {
|
|
2101
|
+
color: cs.color,
|
|
2102
|
+
backgroundColor: cs.backgroundColor,
|
|
2103
|
+
opacity: cs.opacity,
|
|
2104
|
+
cursor: cs.cursor,
|
|
2105
|
+
display: cs.display,
|
|
2106
|
+
visibility: cs.visibility
|
|
2107
|
+
} : null;
|
|
1509
2108
|
return {
|
|
1510
2109
|
...describe(el),
|
|
1511
2110
|
tag: el.tagName.toLowerCase(),
|
|
2111
|
+
href: el.getAttribute("href") ?? void 0,
|
|
2112
|
+
formAction: el instanceof HTMLButtonElement || el instanceof HTMLInputElement ? el.form?.getAttribute("action") ?? void 0 : void 0,
|
|
2113
|
+
formText: el instanceof HTMLButtonElement || el instanceof HTMLInputElement ? el.form?.textContent ?? void 0 : void 0,
|
|
1512
2114
|
box: { x: rect.x, y: rect.y, width: rect.width, height: rect.height },
|
|
2115
|
+
// True when another element sits over this one's center point — the click would hit the overlay,
|
|
2116
|
+
// not this control (a z-index/overlay bug the DOM tree cannot show).
|
|
2117
|
+
occluded: isOccluded(el, rect),
|
|
1513
2118
|
styles,
|
|
2119
|
+
// Theme compliance vs the app's design tokens (off-theme colors a DOM tool can't judge).
|
|
2120
|
+
theme: cs !== null ? themeReport(cs) : null,
|
|
1514
2121
|
component
|
|
1515
2122
|
};
|
|
1516
2123
|
}
|
|
2124
|
+
function isOccluded(el, rect) {
|
|
2125
|
+
if (rect.width === 0 || rect.height === 0)
|
|
2126
|
+
return false;
|
|
2127
|
+
const doc = el.ownerDocument;
|
|
2128
|
+
if (typeof doc.elementFromPoint !== "function")
|
|
2129
|
+
return false;
|
|
2130
|
+
const top = doc.elementFromPoint(rect.x + rect.width / 2, rect.y + rect.height / 2);
|
|
2131
|
+
return top !== null && top !== el && !el.contains(top);
|
|
2132
|
+
}
|
|
1517
2133
|
function isComponentStateResult(value) {
|
|
1518
2134
|
return typeof value === "object" && value !== null && "ok" in value && typeof value.ok === "boolean";
|
|
1519
2135
|
}
|
|
@@ -1521,11 +2137,25 @@ var COMPONENT_UNAVAILABLE = {
|
|
|
1521
2137
|
ok: false,
|
|
1522
2138
|
reason: ComponentStateReason.UNAVAILABLE
|
|
1523
2139
|
};
|
|
1524
|
-
function readState(ref, store) {
|
|
2140
|
+
function readState(ref, store, path, depth) {
|
|
1525
2141
|
const stores2 = readStores(store);
|
|
2142
|
+
const names = storeNames();
|
|
2143
|
+
if (path !== void 0 || depth !== void 0) {
|
|
2144
|
+
const base = store !== void 0 ? stores2[store] : { stores: stores2, storeNames: names };
|
|
2145
|
+
const selection = path !== void 0 ? selectPath(base, path) : { found: true, value: base };
|
|
2146
|
+
const value = selection.found && depth !== void 0 ? capDepth(selection.value, depth) : selection.value;
|
|
2147
|
+
return {
|
|
2148
|
+
store,
|
|
2149
|
+
path,
|
|
2150
|
+
found: selection.found,
|
|
2151
|
+
value,
|
|
2152
|
+
..."availableKeys" in selection ? { availableKeys: selection.availableKeys } : {},
|
|
2153
|
+
storeNames: names
|
|
2154
|
+
};
|
|
2155
|
+
}
|
|
1526
2156
|
const result2 = {
|
|
1527
2157
|
stores: stores2,
|
|
1528
|
-
storeNames:
|
|
2158
|
+
storeNames: names
|
|
1529
2159
|
};
|
|
1530
2160
|
if (ref !== void 0 && ref.length > 0) {
|
|
1531
2161
|
const el = refs.resolve(ref);
|
|
@@ -1553,6 +2183,16 @@ function listAnimations() {
|
|
|
1553
2183
|
});
|
|
1554
2184
|
return { animations };
|
|
1555
2185
|
}
|
|
2186
|
+
function resolveNavigationUrl(rawUrl, baseUrl) {
|
|
2187
|
+
if (rawUrl.length === 0 || rawUrl.length > TRANSPORT_LIMITS.MAX_URL_LENGTH)
|
|
2188
|
+
return null;
|
|
2189
|
+
try {
|
|
2190
|
+
const url = new URL(rawUrl, baseUrl);
|
|
2191
|
+
return url.protocol === "http:" || url.protocol === "https:" ? url.toString() : null;
|
|
2192
|
+
} catch {
|
|
2193
|
+
return null;
|
|
2194
|
+
}
|
|
2195
|
+
}
|
|
1556
2196
|
function createCommandRegistry() {
|
|
1557
2197
|
const reg = /* @__PURE__ */ new Map();
|
|
1558
2198
|
reg.set(IrisCommand.SNAPSHOT, (args) => buildSnapshot({
|
|
@@ -1565,7 +2205,7 @@ function createCommandRegistry() {
|
|
|
1565
2205
|
const action = str(args["action"]) ?? "";
|
|
1566
2206
|
if (action === ActionType.WEBMCP) {
|
|
1567
2207
|
const inner = record(args["args"]);
|
|
1568
|
-
return dispatchWebMcp(str(inner["tool"]) ?? "", record(inner["params"]));
|
|
2208
|
+
return dispatchWebMcp(str(inner["tool"]) ?? "", record(inner["params"]), inner[DANGEROUS_ACTION_CONFIRM_ARG] === true);
|
|
1569
2209
|
}
|
|
1570
2210
|
return executeAction(str(args["ref"]) ?? "", action, record(args["args"]));
|
|
1571
2211
|
});
|
|
@@ -1584,7 +2224,7 @@ function createCommandRegistry() {
|
|
|
1584
2224
|
}
|
|
1585
2225
|
return { frozen: isClockFrozen() };
|
|
1586
2226
|
});
|
|
1587
|
-
reg.set(IrisCommand.STATE_READ, (args) => readState(str(args["ref"]), str(args["store"])));
|
|
2227
|
+
reg.set(IrisCommand.STATE_READ, (args) => readState(str(args["ref"]), str(args["store"]), str(args["path"]), num(args["depth"])));
|
|
1588
2228
|
reg.set(IrisCommand.CAPABILITIES, () => getCapabilities());
|
|
1589
2229
|
reg.set(IrisCommand.SCROLL, (args) => {
|
|
1590
2230
|
const dy = args["dy"];
|
|
@@ -1592,9 +2232,12 @@ function createCommandRegistry() {
|
|
|
1592
2232
|
return scrollContainer(str(args["ref"]), typeof dy === "number" ? dy : void 0, typeof fraction === "number" ? fraction : void 0);
|
|
1593
2233
|
});
|
|
1594
2234
|
reg.set(IrisCommand.NAVIGATE, (args) => {
|
|
1595
|
-
const
|
|
1596
|
-
if (
|
|
2235
|
+
const rawUrl = str(args["url"]);
|
|
2236
|
+
if (rawUrl === void 0 || rawUrl.length === 0)
|
|
1597
2237
|
return { ok: false, reason: "url required" };
|
|
2238
|
+
const url = resolveNavigationUrl(rawUrl, window.location.href);
|
|
2239
|
+
if (url === null)
|
|
2240
|
+
return { ok: false, reason: "only http(s) navigation is allowed" };
|
|
1598
2241
|
window.location.assign(url);
|
|
1599
2242
|
return { ok: true, url };
|
|
1600
2243
|
});
|
|
@@ -1687,12 +2330,23 @@ var Transport = class {
|
|
|
1687
2330
|
} catch {
|
|
1688
2331
|
return;
|
|
1689
2332
|
}
|
|
1690
|
-
const
|
|
1691
|
-
if (
|
|
2333
|
+
const result2 = CommandMessageSchema.safeParse(parsed);
|
|
2334
|
+
if (!result2.success)
|
|
1692
2335
|
return;
|
|
1693
|
-
const command =
|
|
1694
|
-
const
|
|
1695
|
-
|
|
2336
|
+
const command = result2.data;
|
|
2337
|
+
const currentSessionId = this.#deps.hello().sessionId;
|
|
2338
|
+
if (command.sessionId !== void 0 && command.sessionId !== currentSessionId)
|
|
2339
|
+
return;
|
|
2340
|
+
let outcome;
|
|
2341
|
+
try {
|
|
2342
|
+
outcome = await this.#deps.handleCommand(command);
|
|
2343
|
+
} catch (error) {
|
|
2344
|
+
outcome = {
|
|
2345
|
+
ok: false,
|
|
2346
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2347
|
+
};
|
|
2348
|
+
}
|
|
2349
|
+
this.#sendRaw(safeStringify({
|
|
1696
2350
|
kind: MessageKind.COMMAND_RESULT,
|
|
1697
2351
|
id: command.id,
|
|
1698
2352
|
ok: outcome.ok,
|
|
@@ -1701,7 +2355,7 @@ var Transport = class {
|
|
|
1701
2355
|
}));
|
|
1702
2356
|
}
|
|
1703
2357
|
sendEvent(event) {
|
|
1704
|
-
this.#sendRaw(
|
|
2358
|
+
this.#sendRaw(safeStringify({ kind: MessageKind.EVENT, event }));
|
|
1705
2359
|
}
|
|
1706
2360
|
#sendRaw(text) {
|
|
1707
2361
|
if (this.#ws !== void 0 && this.#ws.readyState === WebSocket.OPEN) {
|
|
@@ -1820,14 +2474,20 @@ function methodOf(input, init) {
|
|
|
1820
2474
|
return "GET";
|
|
1821
2475
|
}
|
|
1822
2476
|
function installNetwork(emit) {
|
|
1823
|
-
const origFetch = window.fetch
|
|
2477
|
+
const origFetch = window.fetch;
|
|
2478
|
+
const callFetch = origFetch.bind(window);
|
|
2479
|
+
let seq2 = 0;
|
|
2480
|
+
const nextId = () => `n${++seq2}`;
|
|
1824
2481
|
window.fetch = async (input, init) => {
|
|
2482
|
+
const id = nextId();
|
|
1825
2483
|
const start = performance.now();
|
|
1826
2484
|
const method = methodOf(input, init);
|
|
1827
2485
|
const url = urlOf(input);
|
|
2486
|
+
emit(EventType.NET_PENDING, { id, method, url, initiator: "fetch" });
|
|
1828
2487
|
try {
|
|
1829
|
-
const res = await
|
|
2488
|
+
const res = await callFetch(input, init);
|
|
1830
2489
|
emit(EventType.NET_REQUEST, {
|
|
2490
|
+
id,
|
|
1831
2491
|
method,
|
|
1832
2492
|
url,
|
|
1833
2493
|
status: res.status,
|
|
@@ -1838,6 +2498,7 @@ function installNetwork(emit) {
|
|
|
1838
2498
|
return res;
|
|
1839
2499
|
} catch (error) {
|
|
1840
2500
|
emit(EventType.NET_REQUEST, {
|
|
2501
|
+
id,
|
|
1841
2502
|
method,
|
|
1842
2503
|
url,
|
|
1843
2504
|
status: 0,
|
|
@@ -1855,15 +2516,17 @@ function installNetwork(emit) {
|
|
|
1855
2516
|
const origSend = proto.send;
|
|
1856
2517
|
const callOpen = origOpen;
|
|
1857
2518
|
proto.open = function(method, url, ...rest) {
|
|
1858
|
-
meta.set(this, { method: method.toUpperCase(), url: String(url), start: 0 });
|
|
2519
|
+
meta.set(this, { id: nextId(), method: method.toUpperCase(), url: String(url), start: 0 });
|
|
1859
2520
|
callOpen.call(this, method, url, ...rest);
|
|
1860
2521
|
};
|
|
1861
2522
|
proto.send = function(body) {
|
|
1862
2523
|
const m = meta.get(this);
|
|
1863
2524
|
if (m !== void 0) {
|
|
1864
2525
|
m.start = performance.now();
|
|
2526
|
+
emit(EventType.NET_PENDING, { id: m.id, method: m.method, url: m.url, initiator: "xhr" });
|
|
1865
2527
|
this.addEventListener("loadend", () => {
|
|
1866
2528
|
emit(EventType.NET_REQUEST, {
|
|
2529
|
+
id: m.id,
|
|
1867
2530
|
method: m.method,
|
|
1868
2531
|
url: m.url,
|
|
1869
2532
|
status: this.status,
|
|
@@ -1892,8 +2555,10 @@ function snapshotLocation() {
|
|
|
1892
2555
|
};
|
|
1893
2556
|
}
|
|
1894
2557
|
function installRoute(emit) {
|
|
1895
|
-
const origPush = history.pushState
|
|
1896
|
-
const origReplace = history.replaceState
|
|
2558
|
+
const origPush = history.pushState;
|
|
2559
|
+
const origReplace = history.replaceState;
|
|
2560
|
+
const callPush = origPush.bind(history);
|
|
2561
|
+
const callReplace = origReplace.bind(history);
|
|
1897
2562
|
const fire = (from) => {
|
|
1898
2563
|
const to = snapshotLocation();
|
|
1899
2564
|
if (to.href === from)
|
|
@@ -1908,12 +2573,12 @@ function installRoute(emit) {
|
|
|
1908
2573
|
};
|
|
1909
2574
|
history.pushState = (data, unused, url) => {
|
|
1910
2575
|
const from = location.href;
|
|
1911
|
-
|
|
2576
|
+
callPush(data, unused, url ?? null);
|
|
1912
2577
|
fire(from);
|
|
1913
2578
|
};
|
|
1914
2579
|
history.replaceState = (data, unused, url) => {
|
|
1915
2580
|
const from = location.href;
|
|
1916
|
-
|
|
2581
|
+
callReplace(data, unused, url ?? null);
|
|
1917
2582
|
fire(from);
|
|
1918
2583
|
};
|
|
1919
2584
|
let lastHref = location.href;
|
|
@@ -1943,22 +2608,19 @@ function stringifyArgs(args) {
|
|
|
1943
2608
|
return a;
|
|
1944
2609
|
if (a instanceof Error)
|
|
1945
2610
|
return a.message;
|
|
1946
|
-
|
|
1947
|
-
return JSON.stringify(a);
|
|
1948
|
-
} catch {
|
|
1949
|
-
return String(a);
|
|
1950
|
-
}
|
|
2611
|
+
return safeStringify(a);
|
|
1951
2612
|
}).join(" ");
|
|
1952
2613
|
}
|
|
1953
2614
|
function installConsole(emit) {
|
|
1954
2615
|
const methods = ["log", "warn", "error"];
|
|
1955
2616
|
const originals2 = /* @__PURE__ */ new Map();
|
|
1956
2617
|
for (const method of methods) {
|
|
1957
|
-
const original = console[method]
|
|
2618
|
+
const original = console[method];
|
|
1958
2619
|
originals2.set(method, original);
|
|
2620
|
+
const callOriginal = original.bind(console);
|
|
1959
2621
|
console[method] = (...args) => {
|
|
1960
2622
|
emit(METHOD_EVENT[method], { message: stringifyArgs(args) });
|
|
1961
|
-
|
|
2623
|
+
callOriginal(...args);
|
|
1962
2624
|
};
|
|
1963
2625
|
}
|
|
1964
2626
|
const onError = (event) => {
|
|
@@ -2238,6 +2900,7 @@ function appendLogRow(container, kind, text, ts, logMax) {
|
|
|
2238
2900
|
|
|
2239
2901
|
// ../browser/dist/presenter/presenter-controls.js
|
|
2240
2902
|
var DATA_IRIS_STATE = "data-iris-state";
|
|
2903
|
+
var DATA_IRIS_TONE = "data-iris-tone";
|
|
2241
2904
|
var DATA_ON = "data-on";
|
|
2242
2905
|
var GLOW_OFF = "0";
|
|
2243
2906
|
var CONTROL_LABEL = {
|
|
@@ -2251,6 +2914,7 @@ var PAUSED_BADGE_TEXT = "PAUSED";
|
|
|
2251
2914
|
var ENDED_BANNER_TEXT = "Session ended";
|
|
2252
2915
|
var COPY_LABEL = "Copy run";
|
|
2253
2916
|
var EXPORT_LABEL = "Export";
|
|
2917
|
+
var FLOWS_LABEL = "Replay a flow";
|
|
2254
2918
|
var COPIED_TEXT = "Copied \u2713";
|
|
2255
2919
|
var RUN_FILENAME = "iris-run.json";
|
|
2256
2920
|
var ENDED_FADE_MS = 4e3;
|
|
@@ -2268,11 +2932,12 @@ var CONTROLS_CSS = `
|
|
|
2268
2932
|
color:var(--iris-accent);border:1px solid var(--iris-accent);background:var(--iris-accent-soft);padding:2px 8px;border-radius:999px;}
|
|
2269
2933
|
[data-iris-overlay][data-iris-state="paused"] [data-iris-badge]{display:inline-flex;}
|
|
2270
2934
|
[data-iris-hud] [data-iris-foot]{flex:none;padding:10px 12px 12px;border-top:1px solid var(--iris-line2);background:rgba(0,0,0,.16);}
|
|
2271
|
-
[data-iris-hud] .iris-composer{display:flex;align-items:
|
|
2935
|
+
[data-iris-hud] .iris-composer{display:flex;align-items:flex-end;gap:6px;background:rgba(255,255,255,.05);
|
|
2272
2936
|
border:1px solid var(--iris-line);border-radius:14px;padding:5px 6px 5px 14px;transition:border-color .15s,box-shadow .15s;}
|
|
2273
2937
|
[data-iris-hud] .iris-composer:focus-within{border-color:var(--iris-accent);box-shadow:0 0 0 3px var(--iris-accent-soft);}
|
|
2274
|
-
[data-iris-hud] .iris-msg{flex:1;min-width:0;pointer-events:auto;background:transparent;border:none;outline:none;
|
|
2275
|
-
color:var(--iris-fg);font-family:var(--iris-font);font-size:13px;height:
|
|
2938
|
+
[data-iris-hud] .iris-msg{flex:1;min-width:0;pointer-events:auto;background:transparent;border:none;outline:none;resize:none;
|
|
2939
|
+
color:var(--iris-fg);font-family:var(--iris-font);font-size:13px;line-height:18px;height:18px;min-height:18px;max-height:96px;
|
|
2940
|
+
padding:5px 0;overflow-y:auto;}
|
|
2276
2941
|
[data-iris-hud] .iris-msg::placeholder{color:var(--iris-faint);}
|
|
2277
2942
|
[data-iris-hud] .iris-msg:disabled{opacity:.5;}
|
|
2278
2943
|
[data-iris-hud] .iris-send{flex:none;width:30px;height:30px;padding:0;border-radius:10px;border:none;cursor:pointer;pointer-events:auto;
|
|
@@ -2293,11 +2958,40 @@ var CONTROLS_CSS = `
|
|
|
2293
2958
|
box-shadow:inset 0 0 0 3px rgba(246,180,76,.9),inset 0 0 30px 6px rgba(246,180,76,.4);}
|
|
2294
2959
|
[data-iris-overlay][data-iris-state="ended"] [data-iris-glow][data-on="1"]{animation:none;
|
|
2295
2960
|
box-shadow:inset 0 0 0 2px rgba(61,215,166,.55);}
|
|
2961
|
+
/* Handoff tones tell the human the agent's mode at a glance. waiting = calm teal "your turn" (no
|
|
2962
|
+
alarm); ask = amber "answer me" with a pulse; warn = amber "agent crashed" with a pulse. Each leads
|
|
2963
|
+
the banner with an icon and overrides the calm ended-green accent. */
|
|
2964
|
+
[data-iris-overlay][data-iris-tone="waiting"] [data-iris-hud]{--iris-accent:#38bdf8;--iris-accent-soft:rgba(56,189,248,.16);}
|
|
2965
|
+
[data-iris-overlay][data-iris-tone="waiting"] [data-iris-banner]{font-weight:600;color:#7dd3fc;}
|
|
2966
|
+
[data-iris-overlay][data-iris-tone="waiting"] [data-iris-banner]::before{content:"\\270B ";}
|
|
2967
|
+
[data-iris-overlay][data-iris-tone="waiting"] [data-iris-glow][data-on="1"]{animation:none;
|
|
2968
|
+
box-shadow:inset 0 0 0 2px rgba(56,189,248,.5);}
|
|
2969
|
+
[data-iris-overlay][data-iris-tone="ask"] [data-iris-hud],
|
|
2970
|
+
[data-iris-overlay][data-iris-tone="warn"] [data-iris-hud]{--iris-accent:#fb923c;--iris-accent-soft:rgba(251,146,60,.18);}
|
|
2971
|
+
[data-iris-overlay][data-iris-tone="ask"] [data-iris-banner],
|
|
2972
|
+
[data-iris-overlay][data-iris-tone="warn"] [data-iris-banner]{font-weight:600;color:#fdba74;}
|
|
2973
|
+
[data-iris-overlay][data-iris-tone="ask"] [data-iris-banner]::before{content:"\\2753 ";}
|
|
2974
|
+
[data-iris-overlay][data-iris-tone="warn"] [data-iris-banner]::before{content:"\\26A0\\FE0F ";}
|
|
2975
|
+
[data-iris-overlay][data-iris-tone="ask"] [data-iris-glow][data-on="1"],
|
|
2976
|
+
[data-iris-overlay][data-iris-tone="warn"] [data-iris-glow][data-on="1"]{animation:iris-warn-pulse 1.5s ease-in-out infinite;
|
|
2977
|
+
box-shadow:inset 0 0 0 2px rgba(251,146,60,.7);}
|
|
2978
|
+
@keyframes iris-warn-pulse{0%,100%{box-shadow:inset 0 0 0 2px rgba(251,146,60,.32);}
|
|
2979
|
+
50%{box-shadow:inset 0 0 0 3px rgba(251,146,60,.85),inset 0 0 26px 5px rgba(251,146,60,.34);}}
|
|
2980
|
+
/* Replay-a-flow row: the human re-runs a saved flow with no agent. Hidden until flows are pushed. */
|
|
2981
|
+
[data-iris-hud] .iris-flows{display:none;flex-wrap:wrap;gap:6px;padding:9px 12px;border-top:1px solid var(--iris-line2);}
|
|
2982
|
+
[data-iris-hud] .iris-flows[data-has="1"]{display:flex;}
|
|
2983
|
+
[data-iris-hud] .iris-flows-cap{flex:0 0 100%;margin-bottom:1px;color:var(--iris-faint);font-size:9.5px;letter-spacing:.09em;text-transform:uppercase;}
|
|
2984
|
+
[data-iris-hud] .iris-flow{pointer-events:auto;cursor:pointer;display:inline-flex;align-items:center;gap:5px;height:24px;padding:0 10px;
|
|
2985
|
+
border-radius:7px;border:1px solid var(--iris-line);background:rgba(255,255,255,.04);color:var(--iris-muted);
|
|
2986
|
+
font-family:var(--iris-font);font-size:11px;font-weight:500;transition:background .15s,color .15s,border-color .15s,transform .1s;}
|
|
2987
|
+
[data-iris-hud] .iris-flow:hover{color:var(--iris-fg);background:var(--iris-accent-soft);border-color:var(--iris-accent);}
|
|
2988
|
+
[data-iris-hud] .iris-flow:active{transform:scale(.95);}
|
|
2296
2989
|
`;
|
|
2297
2990
|
var CONTROLS_HEAD_HTML = `<button type="button" data-iris-pause class="iris-ctl">${CONTROL_LABEL.PAUSE}</button><button type="button" data-iris-end class="iris-ctl">${CONTROL_LABEL.END}</button><span data-iris-badge class="iris-badge">${PAUSED_BADGE_TEXT}</span>`;
|
|
2298
2991
|
var CONTROLS_BANNER_HTML = `<div data-iris-banner class="iris-banner">${ENDED_BANNER_TEXT}</div>`;
|
|
2992
|
+
var CONTROLS_FLOWS_HTML = `<div data-iris-flows class="iris-flows"><span class="iris-flows-cap">${FLOWS_LABEL}</span></div>`;
|
|
2299
2993
|
var SEND_ICON = `<svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="22" y1="2" x2="11" y2="13"></line><polygon points="22 2 15 22 11 13 2 9 22 2"></polygon></svg>`;
|
|
2300
|
-
var CONTROLS_FOOT_HTML = `<div data-iris-foot><div class="iris-composer"><
|
|
2994
|
+
var CONTROLS_FOOT_HTML = `<div data-iris-foot><div class="iris-composer"><textarea data-iris-input class="iris-msg" rows="1" placeholder="${INPUT_PLACEHOLDER}"></textarea><button type="button" data-iris-send class="iris-send" aria-label="${CONTROL_LABEL.SEND}">${SEND_ICON}</button></div><div class="iris-export"><button type="button" data-iris-copy class="iris-ctl">${COPY_LABEL}</button><button type="button" data-iris-export class="iris-ctl">${EXPORT_LABEL}</button><span data-iris-export-msg class="iris-export-msg"></span></div></div>`;
|
|
2301
2995
|
function queryControlRefs(root) {
|
|
2302
2996
|
return {
|
|
2303
2997
|
pauseBtn: root.querySelector("[data-iris-pause]") ?? void 0,
|
|
@@ -2307,7 +3001,8 @@ function queryControlRefs(root) {
|
|
|
2307
3001
|
banner: root.querySelector("[data-iris-banner]") ?? void 0,
|
|
2308
3002
|
copyBtn: root.querySelector("[data-iris-copy]") ?? void 0,
|
|
2309
3003
|
exportBtn: root.querySelector("[data-iris-export]") ?? void 0,
|
|
2310
|
-
exportMsg: root.querySelector("[data-iris-export-msg]") ?? void 0
|
|
3004
|
+
exportMsg: root.querySelector("[data-iris-export-msg]") ?? void 0,
|
|
3005
|
+
flows: root.querySelector("[data-iris-flows]") ?? void 0
|
|
2311
3006
|
};
|
|
2312
3007
|
}
|
|
2313
3008
|
var ControlPanel = class {
|
|
@@ -2319,7 +3014,8 @@ var ControlPanel = class {
|
|
|
2319
3014
|
banner: void 0,
|
|
2320
3015
|
copyBtn: void 0,
|
|
2321
3016
|
exportBtn: void 0,
|
|
2322
|
-
exportMsg: void 0
|
|
3017
|
+
exportMsg: void 0,
|
|
3018
|
+
flows: void 0
|
|
2323
3019
|
};
|
|
2324
3020
|
#state = SessionState.ACTIVE;
|
|
2325
3021
|
#fadeTimer;
|
|
@@ -2341,8 +3037,20 @@ var ControlPanel = class {
|
|
|
2341
3037
|
this.#refs.endBtn?.addEventListener("click", () => this.#onEnd());
|
|
2342
3038
|
this.#refs.sendBtn?.addEventListener("click", () => this.#onSend());
|
|
2343
3039
|
this.#refs.input?.addEventListener("keydown", (e) => {
|
|
2344
|
-
if (e instanceof KeyboardEvent && e.key === "Enter")
|
|
3040
|
+
if (e instanceof KeyboardEvent && e.key === "Enter" && !e.shiftKey) {
|
|
3041
|
+
e.preventDefault();
|
|
2345
3042
|
this.#onSend();
|
|
3043
|
+
}
|
|
3044
|
+
});
|
|
3045
|
+
this.#refs.input?.addEventListener("input", () => this.#autosize());
|
|
3046
|
+
this.#refs.flows?.addEventListener("click", (e) => {
|
|
3047
|
+
const target = e.target;
|
|
3048
|
+
if (!(target instanceof HTMLElement))
|
|
3049
|
+
return;
|
|
3050
|
+
const name = target.closest("[data-iris-replay]")?.getAttribute("data-iris-replay");
|
|
3051
|
+
if (name !== null && name !== void 0 && name.length > 0) {
|
|
3052
|
+
this.#host.emit(HumanControlKind.REPLAY, name);
|
|
3053
|
+
}
|
|
2346
3054
|
});
|
|
2347
3055
|
this.#refs.copyBtn?.addEventListener("click", () => this.#onCopy());
|
|
2348
3056
|
this.#refs.exportBtn?.addEventListener("click", () => this.#onExport());
|
|
@@ -2403,15 +3111,49 @@ var ControlPanel = class {
|
|
|
2403
3111
|
this.#host.logHuman(text);
|
|
2404
3112
|
if (this.#refs.input !== void 0)
|
|
2405
3113
|
this.#refs.input.value = "";
|
|
3114
|
+
this.#autosize();
|
|
3115
|
+
}
|
|
3116
|
+
/** Grow the composer to fit its content (up to the CSS max-height), then shrink back — soothing,
|
|
3117
|
+
* no scrollbar until it's genuinely long. Driven on input and after a send clears the field. */
|
|
3118
|
+
#autosize() {
|
|
3119
|
+
const el = this.#refs.input;
|
|
3120
|
+
if (el === void 0)
|
|
3121
|
+
return;
|
|
3122
|
+
el.style.height = "auto";
|
|
3123
|
+
el.style.height = `${String(Math.min(el.scrollHeight, 96))}px`;
|
|
3124
|
+
}
|
|
3125
|
+
/** Render the replayable-flow chips from the server push. Each ▶ click re-runs that flow, no agent.
|
|
3126
|
+
* Takes the raw wire value and narrows it here (the panel is the consumer of this push). */
|
|
3127
|
+
setFlows(flows) {
|
|
3128
|
+
const el = this.#refs.flows;
|
|
3129
|
+
if (el === void 0)
|
|
3130
|
+
return;
|
|
3131
|
+
const list = Array.isArray(flows) ? flows : [];
|
|
3132
|
+
const names = list.map((f) => typeof f === "object" && f !== null ? f["name"] : f).filter((n) => typeof n === "string" && n.length > 0);
|
|
3133
|
+
el.querySelectorAll("[data-iris-replay]").forEach((b) => b.remove());
|
|
3134
|
+
for (const name of names) {
|
|
3135
|
+
const btn = el.ownerDocument.createElement("button");
|
|
3136
|
+
btn.type = "button";
|
|
3137
|
+
btn.className = "iris-flow";
|
|
3138
|
+
btn.setAttribute("data-iris-replay", name);
|
|
3139
|
+
btn.textContent = `\u25B6 ${name}`;
|
|
3140
|
+
el.appendChild(btn);
|
|
3141
|
+
}
|
|
3142
|
+
el.setAttribute("data-has", names.length > 0 ? "1" : "0");
|
|
2406
3143
|
}
|
|
2407
3144
|
/**
|
|
2408
3145
|
* Drive the panel's visual state. Idempotent; NEVER emits a control — the shared path for both the
|
|
2409
3146
|
* optimistic local click and the authoritative server PRESENTER echo. Only the ended-border fade
|
|
2410
3147
|
* touches a clock, via the injected native timer.
|
|
2411
3148
|
*/
|
|
2412
|
-
setState(state, text) {
|
|
3149
|
+
setState(state, text, tone) {
|
|
2413
3150
|
this.#state = state;
|
|
2414
3151
|
this.#root?.setAttribute(DATA_IRIS_STATE, state);
|
|
3152
|
+
const handoff = tone !== void 0 && tone !== PresenterTone.CALM;
|
|
3153
|
+
if (handoff)
|
|
3154
|
+
this.#root?.setAttribute(DATA_IRIS_TONE, tone);
|
|
3155
|
+
else
|
|
3156
|
+
this.#root?.removeAttribute(DATA_IRIS_TONE);
|
|
2415
3157
|
if (this.#fadeTimer !== void 0) {
|
|
2416
3158
|
nativeClearTimeout(this.#fadeTimer);
|
|
2417
3159
|
this.#fadeTimer = void 0;
|
|
@@ -2429,8 +3171,8 @@ var ControlPanel = class {
|
|
|
2429
3171
|
if (refs2.input !== void 0)
|
|
2430
3172
|
refs2.input.disabled = ended;
|
|
2431
3173
|
if (refs2.banner !== void 0) {
|
|
2432
|
-
const summary = text !== void 0 && text.trim().length > 0 ?
|
|
2433
|
-
refs2.banner.textContent = `${ENDED_BANNER_TEXT}${summary}`;
|
|
3174
|
+
const summary = text !== void 0 && text.trim().length > 0 ? text.trim() : "";
|
|
3175
|
+
refs2.banner.textContent = handoff && summary.length > 0 ? summary : `${ENDED_BANNER_TEXT}${summary.length > 0 ? ` \xB7 ${summary}` : ""}`;
|
|
2434
3176
|
}
|
|
2435
3177
|
if (ended) {
|
|
2436
3178
|
const glow = this.#glow;
|
|
@@ -2441,8 +3183,8 @@ var ControlPanel = class {
|
|
|
2441
3183
|
}
|
|
2442
3184
|
};
|
|
2443
3185
|
|
|
2444
|
-
// ../browser/dist/presenter/presenter.js
|
|
2445
|
-
var
|
|
3186
|
+
// ../browser/dist/presenter/presenter-styles.js
|
|
3187
|
+
var PRESENTER_CSS = `
|
|
2446
3188
|
@import url("https://fonts.googleapis.com/css2?family=IBM+Plex+Serif:wght@400;500&family=Inter:wght@400;450;500;600&display=swap");
|
|
2447
3189
|
[data-iris-glow]{position:fixed;inset:0;pointer-events:none;z-index:2147483600;opacity:0;
|
|
2448
3190
|
transition:opacity .25s ease;box-shadow:inset 0 0 0 3px rgba(99,102,241,.9),inset 0 0 28px 6px rgba(99,102,241,.45);}
|
|
@@ -2514,6 +3256,14 @@ var CSS = `
|
|
|
2514
3256
|
[data-iris-hud] [data-iris-min-btn]:hover{color:var(--iris-fg);background:rgba(255,255,255,.08);}
|
|
2515
3257
|
[data-iris-hud] [data-iris-min-btn]:active{transform:scale(.94);}
|
|
2516
3258
|
[data-iris-hud] .iris-pass{color:var(--iris-ok);}[data-iris-hud] .iris-fail{color:var(--iris-bad);}
|
|
3259
|
+
[data-iris-hud] .iris-tally{flex:none;display:inline-flex;align-items:center;gap:8px;font-size:11.5px;
|
|
3260
|
+
font-weight:600;font-variant-numeric:tabular-nums;letter-spacing:.01em;}
|
|
3261
|
+
[data-iris-hud] .iris-tally[hidden]{display:none;}
|
|
3262
|
+
[data-iris-hud] .iris-tally .iris-t-pass{color:var(--iris-ok);}
|
|
3263
|
+
[data-iris-hud] .iris-tally .iris-t-fail{color:var(--iris-bad);}
|
|
3264
|
+
[data-iris-hud] .iris-tally [data-z="1"]{opacity:.4;}
|
|
3265
|
+
@keyframes iris-tally-pop{0%{transform:scale(1)}38%{transform:scale(1.3)}100%{transform:scale(1)}}
|
|
3266
|
+
[data-iris-hud] .iris-tally [data-bump="1"]{display:inline-block;animation:iris-tally-pop .36s cubic-bezier(.16,1,.3,1);}
|
|
2517
3267
|
[data-iris-hud] .iris-chip{display:none;flex:none;font-size:9px;font-weight:600;letter-spacing:.08em;
|
|
2518
3268
|
padding:2px 7px;border-radius:6px;vertical-align:middle;}
|
|
2519
3269
|
[data-iris-hud] .iris-chip[data-mode="reading"]{display:inline-block;color:var(--iris-read);
|
|
@@ -2544,6 +3294,8 @@ var CSS = `
|
|
|
2544
3294
|
[data-iris-overlay][data-iris-throttled="1"] [data-iris-hud]{--iris-accent:#fbbf24;--iris-accent-soft:rgba(251,191,36,.16);}
|
|
2545
3295
|
${LOG_CSS}
|
|
2546
3296
|
${CONTROLS_CSS}`;
|
|
3297
|
+
|
|
3298
|
+
// ../browser/dist/presenter/presenter-config.js
|
|
2547
3299
|
var BorderMode = { SESSION: "session", BUSY: "busy" };
|
|
2548
3300
|
var DEFAULT_BORDER_MODE = BorderMode.SESSION;
|
|
2549
3301
|
var DATA_BUSY = "data-busy";
|
|
@@ -2566,6 +3318,204 @@ var GLOW_OFF2 = "0";
|
|
|
2566
3318
|
var DATA_ON2 = "data-on";
|
|
2567
3319
|
var MIN_ATTR = "data-iris-min";
|
|
2568
3320
|
var THROTTLED_ATTR = "data-iris-throttled";
|
|
3321
|
+
|
|
3322
|
+
// ../browser/dist/presenter/presenter-run-state.js
|
|
3323
|
+
function buildRunState(input) {
|
|
3324
|
+
const start = input.startMs ?? input.now;
|
|
3325
|
+
const counts = { reads: 0, acts: 0, narrations: 0, human: 0, passes: 0, fails: 0 };
|
|
3326
|
+
for (const e of input.runLog) {
|
|
3327
|
+
if (e.kind === LOG_KIND.READ)
|
|
3328
|
+
counts.reads += 1;
|
|
3329
|
+
else if (e.kind === LOG_KIND.ACT)
|
|
3330
|
+
counts.acts += 1;
|
|
3331
|
+
else if (e.kind === LOG_KIND.NARRATION)
|
|
3332
|
+
counts.narrations += 1;
|
|
3333
|
+
else if (e.kind === LOG_KIND.HUMAN)
|
|
3334
|
+
counts.human += 1;
|
|
3335
|
+
if (e.result === LOG_RESULT.PASS)
|
|
3336
|
+
counts.passes += 1;
|
|
3337
|
+
else if (e.result === LOG_RESULT.FAIL)
|
|
3338
|
+
counts.fails += 1;
|
|
3339
|
+
}
|
|
3340
|
+
return {
|
|
3341
|
+
session: input.sessionId,
|
|
3342
|
+
url: typeof location === "undefined" ? "" : location.href,
|
|
3343
|
+
state: input.state,
|
|
3344
|
+
startedMs: start,
|
|
3345
|
+
durationMs: Math.max(0, (input.endMs ?? input.now) - start),
|
|
3346
|
+
counts,
|
|
3347
|
+
capabilities: getCapabilities(),
|
|
3348
|
+
log: input.runLog.map((e) => ({ ...e }))
|
|
3349
|
+
};
|
|
3350
|
+
}
|
|
3351
|
+
|
|
3352
|
+
// ../browser/dist/presenter/presenter-effects.js
|
|
3353
|
+
function moveCursor(cursor, x, y) {
|
|
3354
|
+
if (cursor === void 0)
|
|
3355
|
+
return;
|
|
3356
|
+
cursor.setAttribute("data-on", "1");
|
|
3357
|
+
cursor.style.transform = `translate(${String(x)}px, ${String(y)}px)`;
|
|
3358
|
+
}
|
|
3359
|
+
function ringAround(ring, rect) {
|
|
3360
|
+
if (ring === void 0)
|
|
3361
|
+
return;
|
|
3362
|
+
ring.style.left = `${String(rect.left - 4)}px`;
|
|
3363
|
+
ring.style.top = `${String(rect.top - 4)}px`;
|
|
3364
|
+
ring.style.width = `${String(rect.width + 8)}px`;
|
|
3365
|
+
ring.style.height = `${String(rect.height + 8)}px`;
|
|
3366
|
+
ring.setAttribute("data-on", "1");
|
|
3367
|
+
nativeSetTimeout(() => ring.setAttribute("data-on", "0"), 700);
|
|
3368
|
+
}
|
|
3369
|
+
function spawnRipple(root, x, y) {
|
|
3370
|
+
if (root === void 0)
|
|
3371
|
+
return;
|
|
3372
|
+
const r = document.createElement("div");
|
|
3373
|
+
r.setAttribute("data-iris-ripple", "");
|
|
3374
|
+
r.style.left = `${String(x)}px`;
|
|
3375
|
+
r.style.top = `${String(y)}px`;
|
|
3376
|
+
root.appendChild(r);
|
|
3377
|
+
nativeSetTimeout(() => r.remove(), 520);
|
|
3378
|
+
}
|
|
3379
|
+
function pace(ms) {
|
|
3380
|
+
return new Promise((res) => nativeSetTimeout(res, ms));
|
|
3381
|
+
}
|
|
3382
|
+
|
|
3383
|
+
// ../browser/dist/presenter/presenter-glow.js
|
|
3384
|
+
var GlowController = class {
|
|
3385
|
+
#phase = GlowPhase.IDLE;
|
|
3386
|
+
#lastActivityMs = 0;
|
|
3387
|
+
#idleCheckTimer;
|
|
3388
|
+
#fadeTimer;
|
|
3389
|
+
#glow;
|
|
3390
|
+
#cursor;
|
|
3391
|
+
#now;
|
|
3392
|
+
#idleAfterMs;
|
|
3393
|
+
#glowFadeMs;
|
|
3394
|
+
#borderMode;
|
|
3395
|
+
#setMode;
|
|
3396
|
+
constructor(deps) {
|
|
3397
|
+
this.#now = deps.now;
|
|
3398
|
+
this.#idleAfterMs = deps.idleAfterMs;
|
|
3399
|
+
this.#glowFadeMs = deps.glowFadeMs;
|
|
3400
|
+
this.#borderMode = deps.borderMode;
|
|
3401
|
+
this.#setMode = deps.setMode;
|
|
3402
|
+
}
|
|
3403
|
+
/** Wire the glow + cursor elements after the Presenter mounts the DOM. */
|
|
3404
|
+
setElements(glow, cursor) {
|
|
3405
|
+
this.#glow = glow;
|
|
3406
|
+
this.#cursor = cursor;
|
|
3407
|
+
}
|
|
3408
|
+
/** Current glow phase (test/diagnostic accessor). */
|
|
3409
|
+
phase() {
|
|
3410
|
+
return this.#phase;
|
|
3411
|
+
}
|
|
3412
|
+
/** Last activity timestamp — read by the Presenter's liveness heartbeat. */
|
|
3413
|
+
lastActivityMs() {
|
|
3414
|
+
return this.#lastActivityMs;
|
|
3415
|
+
}
|
|
3416
|
+
/** Set the activity baseline WITHOUT entering busy (sessionStart / revive). */
|
|
3417
|
+
resetActivity(ms) {
|
|
3418
|
+
this.#lastActivityMs = ms;
|
|
3419
|
+
}
|
|
3420
|
+
/**
|
|
3421
|
+
* Record agent activity. Idempotent while busy — only the first activity from idle/fading flips the
|
|
3422
|
+
* glow on (no strobe). `ms` lets log() read the clock exactly once per row.
|
|
3423
|
+
*/
|
|
3424
|
+
markActivity(ms = this.#now()) {
|
|
3425
|
+
this.#lastActivityMs = ms;
|
|
3426
|
+
if (this.#phase === GlowPhase.IDLE || this.#phase === GlowPhase.FADING)
|
|
3427
|
+
this.#enterBusy();
|
|
3428
|
+
this.#armIdleCheck();
|
|
3429
|
+
}
|
|
3430
|
+
/** Re-arm the quiet-window idle check (kept for iris.ts's finally block). */
|
|
3431
|
+
scheduleIdle() {
|
|
3432
|
+
this.#armIdleCheck();
|
|
3433
|
+
}
|
|
3434
|
+
/** Clear both timers (called from Presenter.destroy). */
|
|
3435
|
+
teardown() {
|
|
3436
|
+
if (this.#idleCheckTimer !== void 0)
|
|
3437
|
+
nativeClearTimeout(this.#idleCheckTimer);
|
|
3438
|
+
if (this.#fadeTimer !== void 0)
|
|
3439
|
+
nativeClearTimeout(this.#fadeTimer);
|
|
3440
|
+
this.#idleCheckTimer = void 0;
|
|
3441
|
+
this.#fadeTimer = void 0;
|
|
3442
|
+
}
|
|
3443
|
+
#enterBusy() {
|
|
3444
|
+
if (this.#fadeTimer !== void 0) {
|
|
3445
|
+
nativeClearTimeout(this.#fadeTimer);
|
|
3446
|
+
this.#fadeTimer = void 0;
|
|
3447
|
+
}
|
|
3448
|
+
this.#phase = GlowPhase.BUSY;
|
|
3449
|
+
if (this.#borderMode === BorderMode.SESSION) {
|
|
3450
|
+
this.#glow?.setAttribute(DATA_BUSY, BUSY_ON);
|
|
3451
|
+
} else {
|
|
3452
|
+
this.#glow?.setAttribute(DATA_ON2, GLOW_ON);
|
|
3453
|
+
}
|
|
3454
|
+
this.#cursor?.setAttribute(DATA_ON2, GLOW_ON);
|
|
3455
|
+
}
|
|
3456
|
+
#armIdleCheck() {
|
|
3457
|
+
if (this.#idleCheckTimer !== void 0)
|
|
3458
|
+
nativeClearTimeout(this.#idleCheckTimer);
|
|
3459
|
+
this.#idleCheckTimer = nativeSetTimeout(() => this.#checkIdle(), this.#idleAfterMs);
|
|
3460
|
+
}
|
|
3461
|
+
#checkIdle() {
|
|
3462
|
+
this.#idleCheckTimer = void 0;
|
|
3463
|
+
if (this.#phase !== GlowPhase.BUSY)
|
|
3464
|
+
return;
|
|
3465
|
+
const quietFor = this.#now() - this.#lastActivityMs;
|
|
3466
|
+
if (quietFor < this.#idleAfterMs) {
|
|
3467
|
+
this.#idleCheckTimer = nativeSetTimeout(() => this.#checkIdle(), this.#idleAfterMs - quietFor);
|
|
3468
|
+
return;
|
|
3469
|
+
}
|
|
3470
|
+
this.#beginFade();
|
|
3471
|
+
}
|
|
3472
|
+
#beginFade() {
|
|
3473
|
+
this.#phase = GlowPhase.FADING;
|
|
3474
|
+
if (this.#borderMode === BorderMode.SESSION) {
|
|
3475
|
+
this.#glow?.setAttribute(DATA_BUSY, BUSY_OFF);
|
|
3476
|
+
} else {
|
|
3477
|
+
this.#glow?.setAttribute(DATA_ON2, GLOW_OFF2);
|
|
3478
|
+
}
|
|
3479
|
+
this.#cursor?.setAttribute(DATA_ON2, GLOW_OFF2);
|
|
3480
|
+
this.#setMode(PresenterMode.IDLE);
|
|
3481
|
+
this.#fadeTimer = nativeSetTimeout(() => {
|
|
3482
|
+
this.#fadeTimer = void 0;
|
|
3483
|
+
if (this.#phase === GlowPhase.FADING)
|
|
3484
|
+
this.#phase = GlowPhase.IDLE;
|
|
3485
|
+
}, this.#glowFadeMs);
|
|
3486
|
+
}
|
|
3487
|
+
};
|
|
3488
|
+
|
|
3489
|
+
// ../browser/dist/presenter/presenter-tally.js
|
|
3490
|
+
function countVerdicts(runLog) {
|
|
3491
|
+
let passes = 0;
|
|
3492
|
+
let fails = 0;
|
|
3493
|
+
for (const e of runLog) {
|
|
3494
|
+
if (e.result === LOG_RESULT.PASS)
|
|
3495
|
+
passes += 1;
|
|
3496
|
+
else if (e.result === LOG_RESULT.FAIL)
|
|
3497
|
+
fails += 1;
|
|
3498
|
+
}
|
|
3499
|
+
return { passes, fails };
|
|
3500
|
+
}
|
|
3501
|
+
function renderTally(el, runLog, prev) {
|
|
3502
|
+
const next = countVerdicts(runLog);
|
|
3503
|
+
if (el === void 0)
|
|
3504
|
+
return next;
|
|
3505
|
+
if (next.passes === 0 && next.fails === 0) {
|
|
3506
|
+
el.setAttribute("hidden", "");
|
|
3507
|
+
return next;
|
|
3508
|
+
}
|
|
3509
|
+
const bumpPass = next.passes > prev.passes ? ' data-bump="1"' : "";
|
|
3510
|
+
const bumpFail = next.fails > prev.fails ? ' data-bump="1"' : "";
|
|
3511
|
+
const dimP = next.passes === 0 ? ' data-z="1"' : "";
|
|
3512
|
+
const dimF = next.fails === 0 ? ' data-z="1"' : "";
|
|
3513
|
+
el.removeAttribute("hidden");
|
|
3514
|
+
el.innerHTML = `<span class="iris-t-pass"${dimP}${bumpPass}>\u2713 ${String(next.passes)}</span><span class="iris-t-fail"${dimF}${bumpFail}>\u2717 ${String(next.fails)}</span>`;
|
|
3515
|
+
return next;
|
|
3516
|
+
}
|
|
3517
|
+
|
|
3518
|
+
// ../browser/dist/presenter/presenter.js
|
|
2569
3519
|
var Presenter = class {
|
|
2570
3520
|
#paceMs;
|
|
2571
3521
|
#root;
|
|
@@ -2575,18 +3525,17 @@ var Presenter = class {
|
|
|
2575
3525
|
#hud;
|
|
2576
3526
|
#actLine;
|
|
2577
3527
|
#chip;
|
|
3528
|
+
/** Live verdict tally (✓N ✗M) in the header — the running testing score the human watches. */
|
|
3529
|
+
#tally;
|
|
3530
|
+
#tallied = { passes: 0, fails: 0 };
|
|
2578
3531
|
#liveLine;
|
|
2579
3532
|
#mode = PresenterMode.IDLE;
|
|
2580
3533
|
#now;
|
|
2581
|
-
#idleAfterMs;
|
|
2582
|
-
#glowFadeMs;
|
|
2583
3534
|
#heartbeatMs;
|
|
2584
3535
|
#idleNoticeMs;
|
|
2585
3536
|
#borderMode;
|
|
2586
|
-
|
|
2587
|
-
#
|
|
2588
|
-
#idleCheckTimer;
|
|
2589
|
-
#fadeTimer;
|
|
3537
|
+
/** The glow / activity state machine (border shimmer + cursor visibility from activity timing). */
|
|
3538
|
+
#glowCtl;
|
|
2590
3539
|
/** Liveness: the most recent action text + a 1s ticker that ages it into an "idle · {dur}" clock. */
|
|
2591
3540
|
#lastActionText = "";
|
|
2592
3541
|
#heartbeatTimer;
|
|
@@ -2609,13 +3558,18 @@ var Presenter = class {
|
|
|
2609
3558
|
constructor(options = {}) {
|
|
2610
3559
|
this.#paceMs = options.paceMs ?? DEFAULT_PACE;
|
|
2611
3560
|
this.#now = options.now ?? nativeNow;
|
|
2612
|
-
this.#idleAfterMs = options.idleAfterMs ?? IDLE_AFTER_MS;
|
|
2613
|
-
this.#glowFadeMs = options.glowFadeMs ?? GLOW_FADE_MS;
|
|
2614
3561
|
this.#heartbeatMs = options.heartbeatMs ?? HEARTBEAT_MS;
|
|
2615
3562
|
this.#idleNoticeMs = options.idleNoticeMs ?? IDLE_NOTICE_MS;
|
|
2616
3563
|
this.#idleEndMs = options.idleEndMs ?? IDLE_END_MS;
|
|
2617
3564
|
this.#sessionId = options.sessionId ?? "";
|
|
2618
3565
|
this.#borderMode = options.border ?? DEFAULT_BORDER_MODE;
|
|
3566
|
+
this.#glowCtl = new GlowController({
|
|
3567
|
+
now: this.#now,
|
|
3568
|
+
idleAfterMs: options.idleAfterMs ?? IDLE_AFTER_MS,
|
|
3569
|
+
glowFadeMs: options.glowFadeMs ?? GLOW_FADE_MS,
|
|
3570
|
+
borderMode: this.#borderMode,
|
|
3571
|
+
setMode: (mode) => this.setMode(mode)
|
|
3572
|
+
});
|
|
2619
3573
|
this.#logMax = clampLogMax(options.logMax);
|
|
2620
3574
|
this.#onControl = options.onControl;
|
|
2621
3575
|
this.#panel = new ControlPanel({
|
|
@@ -2640,8 +3594,21 @@ var Presenter = class {
|
|
|
2640
3594
|
return this.#sessionActive;
|
|
2641
3595
|
}
|
|
2642
3596
|
/** Drive the panel's live-control visual state (server-push / agent path; never emits). */
|
|
2643
|
-
setState(state, text) {
|
|
2644
|
-
this.#panel.setState(state, text);
|
|
3597
|
+
setState(state, text, tone) {
|
|
3598
|
+
this.#panel.setState(state, text, tone);
|
|
3599
|
+
}
|
|
3600
|
+
/** Apply a bridge→browser presenter push: PRESENTER (state echo) or FLOWS (replay list, the human's
|
|
3601
|
+
* no-agent replay surface). Owns the wire parsing so the SDK dispatcher stays a thin router;
|
|
3602
|
+
* setState-only so an echo can't re-emit. */
|
|
3603
|
+
handlePush(command) {
|
|
3604
|
+
const a = command.args;
|
|
3605
|
+
if (command.name === IrisCommand.FLOWS)
|
|
3606
|
+
return void this.#panel.setFlows(a["flows"]);
|
|
3607
|
+
const state = a["state"];
|
|
3608
|
+
const tone = a["tone"];
|
|
3609
|
+
const text = typeof a["text"] === "string" && a["text"].length > 0 ? a["text"] : void 0;
|
|
3610
|
+
if (isSessionState(state))
|
|
3611
|
+
this.setState(state, text, isPresenterTone(tone) ? tone : void 0);
|
|
2645
3612
|
}
|
|
2646
3613
|
/** Current cap on accumulated log rows. */
|
|
2647
3614
|
get logMax() {
|
|
@@ -2656,7 +3623,7 @@ var Presenter = class {
|
|
|
2656
3623
|
return;
|
|
2657
3624
|
const style = document.createElement("style");
|
|
2658
3625
|
style.setAttribute("data-iris-overlay", "");
|
|
2659
|
-
style.textContent =
|
|
3626
|
+
style.textContent = PRESENTER_CSS;
|
|
2660
3627
|
document.head.appendChild(style);
|
|
2661
3628
|
const root = document.createElement("div");
|
|
2662
3629
|
root.setAttribute("data-iris-overlay", "");
|
|
@@ -2665,10 +3632,11 @@ var Presenter = class {
|
|
|
2665
3632
|
<div data-iris-cursor></div>
|
|
2666
3633
|
<div data-iris-ring></div>
|
|
2667
3634
|
<div data-iris-hud>
|
|
2668
|
-
<div class="iris-hud-head"><span class="iris-dot"></span><span class="iris-brand">iris</span><span class="iris-chip" data-iris-chip></span><span class="iris-live"></span><span class="iris-head-sp"></span><button type="button" data-iris-min-btn title="Minimise" aria-label="Minimise the panel">\u2304</button>${CONTROLS_HEAD_HTML}<span class="iris-maxhint" aria-hidden="true">\u2303</span></div>
|
|
3635
|
+
<div class="iris-hud-head"><span class="iris-dot"></span><span class="iris-brand">iris</span><span class="iris-chip" data-iris-chip></span><span class="iris-tally" data-iris-tally hidden></span><span class="iris-live"></span><span class="iris-head-sp"></span><button type="button" data-iris-min-btn title="Minimise" aria-label="Minimise the panel">\u2304</button>${CONTROLS_HEAD_HTML}<span class="iris-maxhint" aria-hidden="true">\u2303</span></div>
|
|
2669
3636
|
<div class="iris-act-strip"><span class="iris-act">idle</span></div>
|
|
2670
3637
|
${CONTROLS_BANNER_HTML}
|
|
2671
3638
|
<div ${DATA_IRIS_LOG}></div>
|
|
3639
|
+
${CONTROLS_FLOWS_HTML}
|
|
2672
3640
|
${CONTROLS_FOOT_HTML}
|
|
2673
3641
|
</div>`;
|
|
2674
3642
|
document.body.appendChild(root);
|
|
@@ -2680,6 +3648,7 @@ var Presenter = class {
|
|
|
2680
3648
|
this.#actLine = root.querySelector(".iris-act") ?? void 0;
|
|
2681
3649
|
this.#log = root.querySelector(`[${DATA_IRIS_LOG}]`) ?? void 0;
|
|
2682
3650
|
this.#chip = root.querySelector("[data-iris-chip]") ?? void 0;
|
|
3651
|
+
this.#tally = root.querySelector("[data-iris-tally]") ?? void 0;
|
|
2683
3652
|
this.#liveLine = root.querySelector(".iris-live") ?? void 0;
|
|
2684
3653
|
const setMin = (on) => root.setAttribute(MIN_ATTR, on ? "1" : "0");
|
|
2685
3654
|
root.querySelector("[data-iris-min-btn]")?.addEventListener("click", (e) => {
|
|
@@ -2690,20 +3659,16 @@ var Presenter = class {
|
|
|
2690
3659
|
if (root.getAttribute(MIN_ATTR) === "1")
|
|
2691
3660
|
setMin(false);
|
|
2692
3661
|
});
|
|
3662
|
+
this.#glowCtl.setElements(this.#glow, this.#cursor);
|
|
2693
3663
|
this.#panel.mount(root, this.#glow);
|
|
2694
3664
|
this.setMode(this.#mode);
|
|
2695
3665
|
}
|
|
2696
3666
|
destroy() {
|
|
2697
|
-
|
|
2698
|
-
nativeClearTimeout(this.#idleCheckTimer);
|
|
2699
|
-
if (this.#fadeTimer !== void 0)
|
|
2700
|
-
nativeClearTimeout(this.#fadeTimer);
|
|
3667
|
+
this.#glowCtl.teardown();
|
|
2701
3668
|
if (this.#heartbeatTimer !== void 0)
|
|
2702
3669
|
nativeClearTimeout(this.#heartbeatTimer);
|
|
2703
3670
|
this.#heartbeatTimer = void 0;
|
|
2704
3671
|
this.#panel.teardown();
|
|
2705
|
-
this.#idleCheckTimer = void 0;
|
|
2706
|
-
this.#fadeTimer = void 0;
|
|
2707
3672
|
this.#sessionActive = false;
|
|
2708
3673
|
this.#logBaseMs = void 0;
|
|
2709
3674
|
this.#log = void 0;
|
|
@@ -2726,7 +3691,7 @@ var Presenter = class {
|
|
|
2726
3691
|
this.#startMs ??= this.#now();
|
|
2727
3692
|
this.#endMs = void 0;
|
|
2728
3693
|
this.#showSession();
|
|
2729
|
-
this.#
|
|
3694
|
+
this.#glowCtl.resetActivity(this.#now());
|
|
2730
3695
|
this.#startHeartbeat();
|
|
2731
3696
|
}
|
|
2732
3697
|
/** Turn the base border (session mode) + the HUD/log on — the visible "session is live" state. */
|
|
@@ -2740,7 +3705,7 @@ var Presenter = class {
|
|
|
2740
3705
|
this.#panel.setState(SessionState.ACTIVE);
|
|
2741
3706
|
this.#endMs = void 0;
|
|
2742
3707
|
this.#showSession();
|
|
2743
|
-
this.#
|
|
3708
|
+
this.#glowCtl.resetActivity(this.#now());
|
|
2744
3709
|
this.#startHeartbeat();
|
|
2745
3710
|
}
|
|
2746
3711
|
/**
|
|
@@ -2767,23 +3732,15 @@ var Presenter = class {
|
|
|
2767
3732
|
* just refresh the last-activity timestamp and re-arm the idle check.
|
|
2768
3733
|
*/
|
|
2769
3734
|
markActivity() {
|
|
2770
|
-
this.#
|
|
2771
|
-
}
|
|
2772
|
-
/** markActivity with a caller-supplied timestamp so log() reads the clock exactly once per row. */
|
|
2773
|
-
#markActivityAt(ms) {
|
|
2774
|
-
this.#lastActivityMs = ms;
|
|
2775
|
-
if (this.#phase === GlowPhase.IDLE || this.#phase === GlowPhase.FADING) {
|
|
2776
|
-
this.#enterBusy();
|
|
2777
|
-
}
|
|
2778
|
-
this.#armIdleCheck();
|
|
3735
|
+
this.#glowCtl.markActivity();
|
|
2779
3736
|
}
|
|
2780
3737
|
/** Re-arm the quiet-window idle check (kept for iris.ts's finally block). */
|
|
2781
3738
|
scheduleIdle() {
|
|
2782
|
-
this.#
|
|
3739
|
+
this.#glowCtl.scheduleIdle();
|
|
2783
3740
|
}
|
|
2784
3741
|
/** Test/diagnostic accessor for the current glow phase. */
|
|
2785
3742
|
glowPhase() {
|
|
2786
|
-
return this.#phase;
|
|
3743
|
+
return this.#glowCtl.phase();
|
|
2787
3744
|
}
|
|
2788
3745
|
/** Current intent (reading vs acting), exposed for tests + the watcher. */
|
|
2789
3746
|
get mode() {
|
|
@@ -2803,50 +3760,6 @@ var Presenter = class {
|
|
|
2803
3760
|
if (mode === PresenterMode.READING)
|
|
2804
3761
|
this.#cursor?.setAttribute(DATA_ON2, GLOW_OFF2);
|
|
2805
3762
|
}
|
|
2806
|
-
#enterBusy() {
|
|
2807
|
-
if (this.#fadeTimer !== void 0) {
|
|
2808
|
-
nativeClearTimeout(this.#fadeTimer);
|
|
2809
|
-
this.#fadeTimer = void 0;
|
|
2810
|
-
}
|
|
2811
|
-
this.#phase = GlowPhase.BUSY;
|
|
2812
|
-
if (this.#borderMode === BorderMode.SESSION) {
|
|
2813
|
-
this.#glow?.setAttribute(DATA_BUSY, BUSY_ON);
|
|
2814
|
-
} else {
|
|
2815
|
-
this.#glow?.setAttribute(DATA_ON2, GLOW_ON);
|
|
2816
|
-
}
|
|
2817
|
-
this.#cursor?.setAttribute(DATA_ON2, GLOW_ON);
|
|
2818
|
-
}
|
|
2819
|
-
#armIdleCheck() {
|
|
2820
|
-
if (this.#idleCheckTimer !== void 0)
|
|
2821
|
-
nativeClearTimeout(this.#idleCheckTimer);
|
|
2822
|
-
this.#idleCheckTimer = nativeSetTimeout(() => this.#checkIdle(), this.#idleAfterMs);
|
|
2823
|
-
}
|
|
2824
|
-
#checkIdle() {
|
|
2825
|
-
this.#idleCheckTimer = void 0;
|
|
2826
|
-
if (this.#phase !== GlowPhase.BUSY)
|
|
2827
|
-
return;
|
|
2828
|
-
const quietFor = this.#now() - this.#lastActivityMs;
|
|
2829
|
-
if (quietFor < this.#idleAfterMs) {
|
|
2830
|
-
this.#idleCheckTimer = nativeSetTimeout(() => this.#checkIdle(), this.#idleAfterMs - quietFor);
|
|
2831
|
-
return;
|
|
2832
|
-
}
|
|
2833
|
-
this.#beginFade();
|
|
2834
|
-
}
|
|
2835
|
-
#beginFade() {
|
|
2836
|
-
this.#phase = GlowPhase.FADING;
|
|
2837
|
-
if (this.#borderMode === BorderMode.SESSION) {
|
|
2838
|
-
this.#glow?.setAttribute(DATA_BUSY, BUSY_OFF);
|
|
2839
|
-
} else {
|
|
2840
|
-
this.#glow?.setAttribute(DATA_ON2, GLOW_OFF2);
|
|
2841
|
-
}
|
|
2842
|
-
this.#cursor?.setAttribute(DATA_ON2, GLOW_OFF2);
|
|
2843
|
-
this.setMode(PresenterMode.IDLE);
|
|
2844
|
-
this.#fadeTimer = nativeSetTimeout(() => {
|
|
2845
|
-
this.#fadeTimer = void 0;
|
|
2846
|
-
if (this.#phase === GlowPhase.FADING)
|
|
2847
|
-
this.#phase = GlowPhase.IDLE;
|
|
2848
|
-
}, this.#glowFadeMs);
|
|
2849
|
-
}
|
|
2850
3763
|
status(text) {
|
|
2851
3764
|
this.markActivity();
|
|
2852
3765
|
this.#lastActionText = text;
|
|
@@ -2873,7 +3786,7 @@ var Presenter = class {
|
|
|
2873
3786
|
return;
|
|
2874
3787
|
if (this.state === SessionState.ENDED)
|
|
2875
3788
|
return;
|
|
2876
|
-
const idleMs = this.#now() - this.#lastActivityMs;
|
|
3789
|
+
const idleMs = this.#now() - this.#glowCtl.lastActivityMs();
|
|
2877
3790
|
if (idleMs >= this.#idleEndMs) {
|
|
2878
3791
|
this.#endIdle(idleMs);
|
|
2879
3792
|
return;
|
|
@@ -2904,33 +3817,14 @@ var Presenter = class {
|
|
|
2904
3817
|
* (The full network/console ring-buffer lives server-side; this is the in-page run summary.)
|
|
2905
3818
|
*/
|
|
2906
3819
|
runState() {
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
const counts = { reads: 0, acts: 0, narrations: 0, human: 0, passes: 0, fails: 0 };
|
|
2910
|
-
for (const e of this.#runLog) {
|
|
2911
|
-
if (e.kind === LOG_KIND.READ)
|
|
2912
|
-
counts.reads += 1;
|
|
2913
|
-
else if (e.kind === LOG_KIND.ACT)
|
|
2914
|
-
counts.acts += 1;
|
|
2915
|
-
else if (e.kind === LOG_KIND.NARRATION)
|
|
2916
|
-
counts.narrations += 1;
|
|
2917
|
-
else if (e.kind === LOG_KIND.HUMAN)
|
|
2918
|
-
counts.human += 1;
|
|
2919
|
-
if (e.result === LOG_RESULT.PASS)
|
|
2920
|
-
counts.passes += 1;
|
|
2921
|
-
else if (e.result === LOG_RESULT.FAIL)
|
|
2922
|
-
counts.fails += 1;
|
|
2923
|
-
}
|
|
2924
|
-
return {
|
|
2925
|
-
session: this.#sessionId,
|
|
2926
|
-
url: typeof location === "undefined" ? "" : location.href,
|
|
3820
|
+
return buildRunState({
|
|
3821
|
+
sessionId: this.#sessionId,
|
|
2927
3822
|
state: this.state,
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
};
|
|
3823
|
+
startMs: this.#startMs,
|
|
3824
|
+
endMs: this.#endMs,
|
|
3825
|
+
now: this.#now(),
|
|
3826
|
+
runLog: this.#runLog
|
|
3827
|
+
});
|
|
2934
3828
|
}
|
|
2935
3829
|
/**
|
|
2936
3830
|
* Append an activity-log row. Accumulates (never overwrites): each call adds a timestamped row
|
|
@@ -2939,7 +3833,7 @@ var Presenter = class {
|
|
|
2939
3833
|
*/
|
|
2940
3834
|
log(kind, text, result2) {
|
|
2941
3835
|
const ms = this.#now();
|
|
2942
|
-
this.#
|
|
3836
|
+
this.#glowCtl.markActivity(ms);
|
|
2943
3837
|
if (this.#log === void 0)
|
|
2944
3838
|
return void 0;
|
|
2945
3839
|
const trimmed = text.trim();
|
|
@@ -2956,13 +3850,19 @@ var Presenter = class {
|
|
|
2956
3850
|
handle.result(result2);
|
|
2957
3851
|
if (this.#liveLine !== void 0)
|
|
2958
3852
|
this.#liveLine.textContent = trimmed;
|
|
3853
|
+
this.#renderTally();
|
|
2959
3854
|
return {
|
|
2960
3855
|
result: (r) => {
|
|
2961
3856
|
handle.result(r);
|
|
2962
3857
|
entry.result = r;
|
|
3858
|
+
this.#renderTally();
|
|
2963
3859
|
}
|
|
2964
3860
|
};
|
|
2965
3861
|
}
|
|
3862
|
+
/** Repaint the header verdict tally from the run log; the side that grew gets a one-shot pop. */
|
|
3863
|
+
#renderTally() {
|
|
3864
|
+
this.#tallied = renderTally(this.#tally, this.#runLog, this.#tallied);
|
|
3865
|
+
}
|
|
2966
3866
|
/** Back-compat: narration appends to the live log (append-only, never overwrites). */
|
|
2967
3867
|
narrate(text, level = "info") {
|
|
2968
3868
|
const line = level === "info" ? text : `[${level}] ${text}`;
|
|
@@ -2994,46 +3894,17 @@ var Presenter = class {
|
|
|
2994
3894
|
const el = refs.resolve(refId);
|
|
2995
3895
|
this.status(`${actionVerb(action)} ${label}`);
|
|
2996
3896
|
if (!(el instanceof HTMLElement)) {
|
|
2997
|
-
await this.#
|
|
3897
|
+
await pace(this.#paceMs);
|
|
2998
3898
|
return;
|
|
2999
3899
|
}
|
|
3000
3900
|
const rect = el.getBoundingClientRect();
|
|
3001
3901
|
const cx = rect.left + rect.width / 2;
|
|
3002
3902
|
const cy = rect.top + rect.height / 2;
|
|
3003
|
-
this.#
|
|
3004
|
-
this.#
|
|
3005
|
-
await this.#
|
|
3903
|
+
moveCursor(this.#cursor, cx, cy);
|
|
3904
|
+
ringAround(this.#ring, rect);
|
|
3905
|
+
await pace(this.#paceMs);
|
|
3006
3906
|
if (action === "click" || action === "dblclick" || action === "submit")
|
|
3007
|
-
this.#
|
|
3008
|
-
}
|
|
3009
|
-
#moveCursor(x, y) {
|
|
3010
|
-
if (this.#cursor === void 0)
|
|
3011
|
-
return;
|
|
3012
|
-
this.#cursor.setAttribute("data-on", "1");
|
|
3013
|
-
this.#cursor.style.transform = `translate(${String(x)}px, ${String(y)}px)`;
|
|
3014
|
-
}
|
|
3015
|
-
#ringAround(rect) {
|
|
3016
|
-
if (this.#ring === void 0)
|
|
3017
|
-
return;
|
|
3018
|
-
this.#ring.style.left = `${String(rect.left - 4)}px`;
|
|
3019
|
-
this.#ring.style.top = `${String(rect.top - 4)}px`;
|
|
3020
|
-
this.#ring.style.width = `${String(rect.width + 8)}px`;
|
|
3021
|
-
this.#ring.style.height = `${String(rect.height + 8)}px`;
|
|
3022
|
-
this.#ring.setAttribute("data-on", "1");
|
|
3023
|
-
nativeSetTimeout(() => this.#ring?.setAttribute("data-on", "0"), 700);
|
|
3024
|
-
}
|
|
3025
|
-
#ripple(x, y) {
|
|
3026
|
-
if (this.#root === void 0)
|
|
3027
|
-
return;
|
|
3028
|
-
const r = document.createElement("div");
|
|
3029
|
-
r.setAttribute("data-iris-ripple", "");
|
|
3030
|
-
r.style.left = `${String(x)}px`;
|
|
3031
|
-
r.style.top = `${String(y)}px`;
|
|
3032
|
-
this.#root.appendChild(r);
|
|
3033
|
-
nativeSetTimeout(() => r.remove(), 520);
|
|
3034
|
-
}
|
|
3035
|
-
#pause() {
|
|
3036
|
-
return new Promise((res) => nativeSetTimeout(res, this.#paceMs));
|
|
3907
|
+
spawnRipple(this.#root, cx, cy);
|
|
3037
3908
|
}
|
|
3038
3909
|
};
|
|
3039
3910
|
|
|
@@ -3092,8 +3963,10 @@ var BUTTON_LABEL = {
|
|
|
3092
3963
|
var ANNOTATION_LABEL = {
|
|
3093
3964
|
[AnnotationKind.ASSERT_SIGNAL]: "assert signal",
|
|
3094
3965
|
[AnnotationKind.ASSERT_VISIBLE]: "assert visible",
|
|
3966
|
+
[AnnotationKind.ASSERT_STATE]: "assert state",
|
|
3095
3967
|
[AnnotationKind.MARK_DYNAMIC]: "mark dynamic",
|
|
3096
|
-
[AnnotationKind.SUCCESS_STATE]: "success state"
|
|
3968
|
+
[AnnotationKind.SUCCESS_STATE]: "success state",
|
|
3969
|
+
[AnnotationKind.INTENT]: "intent"
|
|
3097
3970
|
};
|
|
3098
3971
|
var NEEDS_SIGNAL = /* @__PURE__ */ new Set([
|
|
3099
3972
|
AnnotationKind.ASSERT_SIGNAL,
|
|
@@ -3412,6 +4285,8 @@ var Recorder = class {
|
|
|
3412
4285
|
return;
|
|
3413
4286
|
menu.textContent = "";
|
|
3414
4287
|
for (const kind of Object.values(AnnotationKind)) {
|
|
4288
|
+
if (kind === AnnotationKind.INTENT)
|
|
4289
|
+
continue;
|
|
3415
4290
|
const item = document.createElement("button");
|
|
3416
4291
|
item.setAttribute("data-iris-annkind", kind);
|
|
3417
4292
|
item.textContent = ANNOTATION_LABEL[kind];
|
|
@@ -3462,7 +4337,445 @@ function installRecorder(deps) {
|
|
|
3462
4337
|
return new Recorder(deps);
|
|
3463
4338
|
}
|
|
3464
4339
|
|
|
4340
|
+
// ../browser/dist/dom/auto-anchor.js
|
|
4341
|
+
var AnchorStrategy = {
|
|
4342
|
+
TESTID: "testid",
|
|
4343
|
+
COMPONENT: "component",
|
|
4344
|
+
ROLE: "role",
|
|
4345
|
+
POSITION: "position"
|
|
4346
|
+
};
|
|
4347
|
+
function nonEmpty(s) {
|
|
4348
|
+
return typeof s === "string" && s.length > 0;
|
|
4349
|
+
}
|
|
4350
|
+
function sourceTag(source) {
|
|
4351
|
+
const base = source.file.split("/").pop() ?? source.file;
|
|
4352
|
+
return `${base}:${source.line}`;
|
|
4353
|
+
}
|
|
4354
|
+
function synthesizeAnchor(input) {
|
|
4355
|
+
if (nonEmpty(input.testid)) {
|
|
4356
|
+
return { strategy: AnchorStrategy.TESTID, value: input.testid, stable: true };
|
|
4357
|
+
}
|
|
4358
|
+
if (nonEmpty(input.component) && input.source !== void 0) {
|
|
4359
|
+
return {
|
|
4360
|
+
strategy: AnchorStrategy.COMPONENT,
|
|
4361
|
+
value: `${input.component}@${sourceTag(input.source)}`,
|
|
4362
|
+
stable: true
|
|
4363
|
+
};
|
|
4364
|
+
}
|
|
4365
|
+
if (nonEmpty(input.component) && (nonEmpty(input.role) || nonEmpty(input.name))) {
|
|
4366
|
+
const qualifier = nonEmpty(input.name) ? input.name : input.role;
|
|
4367
|
+
return {
|
|
4368
|
+
strategy: AnchorStrategy.COMPONENT,
|
|
4369
|
+
value: `${input.component}[${qualifier ?? ""}]`,
|
|
4370
|
+
stable: true
|
|
4371
|
+
};
|
|
4372
|
+
}
|
|
4373
|
+
if (nonEmpty(input.role) && nonEmpty(input.name)) {
|
|
4374
|
+
return { strategy: AnchorStrategy.ROLE, value: `${input.role}:${input.name}`, stable: false };
|
|
4375
|
+
}
|
|
4376
|
+
if (nonEmpty(input.role)) {
|
|
4377
|
+
const suffix = input.nth !== void 0 ? `#${input.nth}` : "";
|
|
4378
|
+
return { strategy: AnchorStrategy.ROLE, value: `${input.role}${suffix}`, stable: false };
|
|
4379
|
+
}
|
|
4380
|
+
return { strategy: AnchorStrategy.POSITION, value: `el#${input.nth ?? 0}`, stable: false };
|
|
4381
|
+
}
|
|
4382
|
+
|
|
4383
|
+
// ../browser/dist/review/mark-anchor.js
|
|
4384
|
+
var TESTID_ATTR3 = "data-testid";
|
|
4385
|
+
var SOURCE_ATTR2 = "data-iris-source";
|
|
4386
|
+
var STRATEGY = {
|
|
4387
|
+
[AnchorStrategy.TESTID]: MarkAnchorStrategy.TESTID,
|
|
4388
|
+
[AnchorStrategy.COMPONENT]: MarkAnchorStrategy.COMPONENT,
|
|
4389
|
+
[AnchorStrategy.ROLE]: MarkAnchorStrategy.ROLE,
|
|
4390
|
+
[AnchorStrategy.POSITION]: MarkAnchorStrategy.POSITION
|
|
4391
|
+
};
|
|
4392
|
+
function parseSourceAttr(value) {
|
|
4393
|
+
if (value === null)
|
|
4394
|
+
return void 0;
|
|
4395
|
+
const m = /^(.*):(\d+):(\d+)$/.exec(value);
|
|
4396
|
+
if (m === null)
|
|
4397
|
+
return void 0;
|
|
4398
|
+
const file = m[1];
|
|
4399
|
+
const line = Number(m[2]);
|
|
4400
|
+
if (file === void 0 || file.length === 0 || !Number.isFinite(line))
|
|
4401
|
+
return void 0;
|
|
4402
|
+
return { file, line };
|
|
4403
|
+
}
|
|
4404
|
+
function sourceFor(el, adapterSource) {
|
|
4405
|
+
if (adapterSource !== void 0)
|
|
4406
|
+
return { file: adapterSource.file, line: adapterSource.line };
|
|
4407
|
+
const host = el.closest(`[${SOURCE_ATTR2}]`);
|
|
4408
|
+
return host !== null ? parseSourceAttr(host.getAttribute(SOURCE_ATTR2)) : void 0;
|
|
4409
|
+
}
|
|
4410
|
+
function labelFor(el, role, name) {
|
|
4411
|
+
if (name.length > 0)
|
|
4412
|
+
return role.length > 0 ? `${role} "${name}"` : `"${name}"`;
|
|
4413
|
+
return role.length > 0 ? role : el.tagName.toLowerCase();
|
|
4414
|
+
}
|
|
4415
|
+
function resolveMarkAnchor(el) {
|
|
4416
|
+
const testid = el.getAttribute(TESTID_ATTR3) ?? void 0;
|
|
4417
|
+
const info = identifyComponent(el);
|
|
4418
|
+
const component = info?.componentStack[0];
|
|
4419
|
+
const source = sourceFor(el, info?.source);
|
|
4420
|
+
const role = getRole(el);
|
|
4421
|
+
const name = getAccessibleName(el);
|
|
4422
|
+
const input = {};
|
|
4423
|
+
if (testid !== void 0)
|
|
4424
|
+
input.testid = testid;
|
|
4425
|
+
if (component !== void 0)
|
|
4426
|
+
input.component = component;
|
|
4427
|
+
if (source !== void 0)
|
|
4428
|
+
input.source = source;
|
|
4429
|
+
if (role.length > 0)
|
|
4430
|
+
input.role = role;
|
|
4431
|
+
if (name.length > 0)
|
|
4432
|
+
input.name = name;
|
|
4433
|
+
const synthesized = synthesizeAnchor(input);
|
|
4434
|
+
const out = {
|
|
4435
|
+
anchor: synthesized.value,
|
|
4436
|
+
strategy: STRATEGY[synthesized.strategy],
|
|
4437
|
+
label: labelFor(el, role, name)
|
|
4438
|
+
};
|
|
4439
|
+
if (source !== void 0)
|
|
4440
|
+
out.source = source;
|
|
4441
|
+
return out;
|
|
4442
|
+
}
|
|
4443
|
+
|
|
4444
|
+
// ../browser/dist/review/annotator.js
|
|
4445
|
+
var HIGHLIGHT_REST_MS = 130;
|
|
4446
|
+
var MARK_ATTR = "data-iris-mark";
|
|
4447
|
+
var ACTIVE_ATTR = "data-iris-mark-active";
|
|
4448
|
+
var Z = 2147483640;
|
|
4449
|
+
var sel = (role) => `[${MARK_ATTR}="${role}"]`;
|
|
4450
|
+
var CSS = `
|
|
4451
|
+
${sel("fab")}{position:fixed;left:18px;bottom:18px;z-index:${String(Z + 2)};
|
|
4452
|
+
font:500 13px/1 "Inter",system-ui,sans-serif;display:inline-flex;align-items:center;gap:7px;
|
|
4453
|
+
padding:9px 13px;border-radius:11px;cursor:pointer;color:#e9ebf2;
|
|
4454
|
+
background:linear-gradient(180deg,rgba(19,22,32,.92),rgba(13,15,22,.92));
|
|
4455
|
+
border:1px solid rgba(255,255,255,.12);box-shadow:0 10px 30px -10px rgba(0,0,0,.6);
|
|
4456
|
+
-webkit-backdrop-filter:blur(20px);backdrop-filter:blur(20px);transition:transform .12s,border-color .15s;}
|
|
4457
|
+
${sel("fab")}:hover{transform:translateY(-1px);border-color:rgba(124,131,255,.55);}
|
|
4458
|
+
${sel("fab")}[data-on="1"]{color:#fff;border-color:#7c83ff;background:linear-gradient(180deg,#6366f1,#4f46e5);}
|
|
4459
|
+
${sel("dot")}{width:8px;height:8px;border-radius:50%;background:#ff7a7a;flex:none;}
|
|
4460
|
+
${sel("fab")}[data-on="1"] ${sel("dot")}{background:#fff;}
|
|
4461
|
+
html[${ACTIVE_ATTR}] *{cursor:crosshair !important;}
|
|
4462
|
+
/* The Flag button + its popover are interactive \u2014 keep the pointer cursor over them, not crosshair. */
|
|
4463
|
+
html[${ACTIVE_ATTR}] ${sel("fab")},html[${ACTIVE_ATTR}] ${sel("fab")} *,html[${ACTIVE_ATTR}] ${sel("pop")},html[${ACTIVE_ATTR}] ${sel("pop")} *{cursor:pointer !important;}
|
|
4464
|
+
/* The outline glides to the rested element with an ease (soothing), and fades rather than snapping. */
|
|
4465
|
+
${sel("hi")}{position:fixed;z-index:${String(Z + 1)};pointer-events:none;opacity:0;box-sizing:border-box;
|
|
4466
|
+
border:2px solid #7c83ff;border-radius:6px;background:rgba(124,131,255,.12);box-shadow:0 0 0 2px rgba(124,131,255,.22);
|
|
4467
|
+
transition:left .22s cubic-bezier(.22,1,.36,1),top .22s cubic-bezier(.22,1,.36,1),width .22s cubic-bezier(.22,1,.36,1),height .22s cubic-bezier(.22,1,.36,1),opacity .18s ease;}
|
|
4468
|
+
${sel("hi")}[data-on="1"]{opacity:1;}
|
|
4469
|
+
${sel("hilabel")}{position:absolute;top:-21px;left:-2px;background:#6366f1;color:#fff;
|
|
4470
|
+
font:600 10.5px/1 "Inter",system-ui,sans-serif;padding:3px 6px;border-radius:5px;white-space:nowrap;
|
|
4471
|
+
max-width:300px;overflow:hidden;text-overflow:ellipsis;}
|
|
4472
|
+
${sel("pin")}{position:fixed;z-index:${String(Z + 1)};width:22px;height:22px;margin:-11px 0 0 -11px;
|
|
4473
|
+
border-radius:50% 50% 50% 2px;background:#ff5d5d;border:2px solid #fff;box-shadow:0 4px 12px -2px rgba(0,0,0,.5);
|
|
4474
|
+
color:#fff;font:700 11px/18px "Inter",system-ui,sans-serif;text-align:center;pointer-events:none;}
|
|
4475
|
+
${sel("pop")}{position:fixed;z-index:${String(Z + 3)};width:280px;box-sizing:border-box;
|
|
4476
|
+
background:linear-gradient(180deg,rgba(19,22,32,.96),rgba(13,15,22,.96));border:1px solid rgba(255,255,255,.14);
|
|
4477
|
+
border-radius:14px;padding:12px;box-shadow:0 24px 60px -16px rgba(0,0,0,.7);
|
|
4478
|
+
-webkit-backdrop-filter:blur(24px);backdrop-filter:blur(24px);font:13px/1.5 "Inter",system-ui,sans-serif;color:#e9ebf2;}
|
|
4479
|
+
${sel("pop")} .iris-mark-where{color:#9aa0b2;font-size:11.5px;margin-bottom:7px;
|
|
4480
|
+
white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
|
|
4481
|
+
${sel("pop")} textarea{width:100%;box-sizing:border-box;min-height:62px;resize:vertical;
|
|
4482
|
+
background:rgba(0,0,0,.3);border:1px solid rgba(255,255,255,.12);border-radius:9px;color:#e9ebf2;
|
|
4483
|
+
font:13px/1.45 "Inter",system-ui,sans-serif;padding:8px;outline:none;}
|
|
4484
|
+
${sel("pop")} textarea:focus{border-color:#7c83ff;}
|
|
4485
|
+
${sel("pop")} .iris-mark-row{display:flex;gap:8px;align-items:center;margin-top:9px;}
|
|
4486
|
+
${sel("pop")} .iris-mark-hint{margin-right:auto;color:#6a7186;font-size:10.5px;letter-spacing:.02em;}
|
|
4487
|
+
${sel("pop")} button{font:600 12px/1 "Inter",system-ui,sans-serif;padding:8px 12px;border-radius:9px;cursor:pointer;border:1px solid rgba(255,255,255,.12);background:rgba(255,255,255,.05);color:#cdd2e2;}
|
|
4488
|
+
${sel("pop")} button[data-send]{background:#6366f1;border-color:#7c83ff;color:#fff;}
|
|
4489
|
+
${sel("pop")} button[data-send]:disabled{opacity:.5;cursor:default;}`;
|
|
4490
|
+
var Annotator = class {
|
|
4491
|
+
#emit;
|
|
4492
|
+
#now;
|
|
4493
|
+
#onMark;
|
|
4494
|
+
#root;
|
|
4495
|
+
#fab;
|
|
4496
|
+
#pop;
|
|
4497
|
+
/** Hover outline box that shows WHICH element a click would flag (agentation-style). */
|
|
4498
|
+
#hi;
|
|
4499
|
+
#hiLabel;
|
|
4500
|
+
/** Debounce timer — boxes the element only once the cursor rests, so a fast sweep doesn't flicker. */
|
|
4501
|
+
#hiTimer;
|
|
4502
|
+
#active = false;
|
|
4503
|
+
#markCount = 0;
|
|
4504
|
+
#onClick;
|
|
4505
|
+
#onKeydown;
|
|
4506
|
+
#onMove;
|
|
4507
|
+
constructor(deps) {
|
|
4508
|
+
this.#emit = deps.emit;
|
|
4509
|
+
this.#now = deps.now;
|
|
4510
|
+
this.#onMark = deps.onMark;
|
|
4511
|
+
}
|
|
4512
|
+
/** Whether annotate mode is currently capturing clicks. */
|
|
4513
|
+
get active() {
|
|
4514
|
+
return this.#active;
|
|
4515
|
+
}
|
|
4516
|
+
/** Number of marks sent this session (drives the pin numbering). */
|
|
4517
|
+
get markCount() {
|
|
4518
|
+
return this.#markCount;
|
|
4519
|
+
}
|
|
4520
|
+
mount() {
|
|
4521
|
+
if (this.#root !== void 0 || typeof document === "undefined")
|
|
4522
|
+
return;
|
|
4523
|
+
const style = document.createElement("style");
|
|
4524
|
+
style.setAttribute(MARK_ATTR, "style");
|
|
4525
|
+
style.textContent = CSS;
|
|
4526
|
+
document.head.appendChild(style);
|
|
4527
|
+
const root = document.createElement("div");
|
|
4528
|
+
root.setAttribute(MARK_ATTR, "root");
|
|
4529
|
+
root.innerHTML = `<button type="button" ${MARK_ATTR}="fab" aria-label="Flag a bug for the agent">
|
|
4530
|
+
<span ${MARK_ATTR}="dot"></span><span>Flag a bug</span></button>
|
|
4531
|
+
<div ${MARK_ATTR}="hi"><span ${MARK_ATTR}="hilabel"></span></div>`;
|
|
4532
|
+
document.body.appendChild(root);
|
|
4533
|
+
this.#root = root;
|
|
4534
|
+
this.#fab = root.querySelector(sel("fab")) ?? void 0;
|
|
4535
|
+
this.#hi = root.querySelector(sel("hi")) ?? void 0;
|
|
4536
|
+
this.#hiLabel = root.querySelector(sel("hilabel")) ?? void 0;
|
|
4537
|
+
this.#fab?.addEventListener("click", (e) => {
|
|
4538
|
+
e.stopPropagation();
|
|
4539
|
+
this.toggle();
|
|
4540
|
+
});
|
|
4541
|
+
this.#onClick = (ev) => this.#handleClick(ev);
|
|
4542
|
+
document.addEventListener("click", this.#onClick, { capture: true });
|
|
4543
|
+
this.#onMove = (ev) => this.#scheduleMove(ev);
|
|
4544
|
+
document.addEventListener("mousemove", this.#onMove, { passive: true, capture: true });
|
|
4545
|
+
this.#onKeydown = (ev) => {
|
|
4546
|
+
if (ev.key !== "Escape" || !this.#active)
|
|
4547
|
+
return;
|
|
4548
|
+
if (this.#pop !== void 0)
|
|
4549
|
+
this.#closePopover();
|
|
4550
|
+
else
|
|
4551
|
+
this.toggle(false);
|
|
4552
|
+
};
|
|
4553
|
+
document.addEventListener("keydown", this.#onKeydown);
|
|
4554
|
+
}
|
|
4555
|
+
destroy() {
|
|
4556
|
+
if (this.#onClick !== void 0) {
|
|
4557
|
+
document.removeEventListener("click", this.#onClick, { capture: true });
|
|
4558
|
+
this.#onClick = void 0;
|
|
4559
|
+
}
|
|
4560
|
+
if (this.#onKeydown !== void 0) {
|
|
4561
|
+
document.removeEventListener("keydown", this.#onKeydown);
|
|
4562
|
+
this.#onKeydown = void 0;
|
|
4563
|
+
}
|
|
4564
|
+
if (this.#onMove !== void 0) {
|
|
4565
|
+
document.removeEventListener("mousemove", this.#onMove, { capture: true });
|
|
4566
|
+
this.#onMove = void 0;
|
|
4567
|
+
}
|
|
4568
|
+
if (this.#hiTimer !== void 0) {
|
|
4569
|
+
nativeClearTimeout(this.#hiTimer);
|
|
4570
|
+
this.#hiTimer = void 0;
|
|
4571
|
+
}
|
|
4572
|
+
this.#closePopover();
|
|
4573
|
+
document.documentElement.removeAttribute(ACTIVE_ATTR);
|
|
4574
|
+
this.#root?.remove();
|
|
4575
|
+
document.querySelectorAll(`style[${MARK_ATTR}="style"]`).forEach((s) => s.remove());
|
|
4576
|
+
this.#root = void 0;
|
|
4577
|
+
this.#fab = void 0;
|
|
4578
|
+
this.#active = false;
|
|
4579
|
+
}
|
|
4580
|
+
/** Turn annotate mode on/off. With no argument, flips the current state. */
|
|
4581
|
+
toggle(on) {
|
|
4582
|
+
this.#active = on ?? !this.#active;
|
|
4583
|
+
this.#fab?.setAttribute("data-on", this.#active ? "1" : "0");
|
|
4584
|
+
if (this.#active)
|
|
4585
|
+
document.documentElement.setAttribute(ACTIVE_ATTR, "1");
|
|
4586
|
+
else {
|
|
4587
|
+
document.documentElement.removeAttribute(ACTIVE_ATTR);
|
|
4588
|
+
this.#hideHighlight();
|
|
4589
|
+
this.#closePopover();
|
|
4590
|
+
}
|
|
4591
|
+
}
|
|
4592
|
+
#handleClick(ev) {
|
|
4593
|
+
if (!this.#active)
|
|
4594
|
+
return;
|
|
4595
|
+
const target = ev.target;
|
|
4596
|
+
if (!(target instanceof Element))
|
|
4597
|
+
return;
|
|
4598
|
+
if (target.closest(`[${MARK_ATTR}]`) !== null)
|
|
4599
|
+
return;
|
|
4600
|
+
ev.preventDefault();
|
|
4601
|
+
ev.stopPropagation();
|
|
4602
|
+
this.#openPopover(target, ev.clientX, ev.clientY);
|
|
4603
|
+
}
|
|
4604
|
+
/**
|
|
4605
|
+
* Debounce the outline: while the cursor is moving the human is still travelling, so we wait for a
|
|
4606
|
+
* brief rest before boxing the element under it (the box then eases into place via CSS). A pending
|
|
4607
|
+
* timer is replaced on every move, so only the resting position ever paints.
|
|
4608
|
+
*/
|
|
4609
|
+
#scheduleMove(ev) {
|
|
4610
|
+
if (this.#hiTimer !== void 0)
|
|
4611
|
+
nativeClearTimeout(this.#hiTimer);
|
|
4612
|
+
this.#hiTimer = nativeSetTimeout(() => {
|
|
4613
|
+
this.#hiTimer = void 0;
|
|
4614
|
+
this.#handleMove(ev);
|
|
4615
|
+
}, HIGHLIGHT_REST_MS);
|
|
4616
|
+
}
|
|
4617
|
+
/** Box the element under the cursor (when active, no popover open) so you see what a click flags. */
|
|
4618
|
+
#handleMove(ev) {
|
|
4619
|
+
if (this.#hi === void 0)
|
|
4620
|
+
return;
|
|
4621
|
+
const target = ev.target;
|
|
4622
|
+
const skip2 = !this.#active || this.#pop !== void 0 || !(target instanceof Element) || target.closest(`[${MARK_ATTR}]`) !== null;
|
|
4623
|
+
if (skip2) {
|
|
4624
|
+
this.#hi.setAttribute("data-on", "0");
|
|
4625
|
+
return;
|
|
4626
|
+
}
|
|
4627
|
+
const rect = target.getBoundingClientRect();
|
|
4628
|
+
if (rect.width === 0 && rect.height === 0) {
|
|
4629
|
+
this.#hi.setAttribute("data-on", "0");
|
|
4630
|
+
return;
|
|
4631
|
+
}
|
|
4632
|
+
this.#hi.style.left = `${String(rect.left)}px`;
|
|
4633
|
+
this.#hi.style.top = `${String(rect.top)}px`;
|
|
4634
|
+
this.#hi.style.width = `${String(rect.width)}px`;
|
|
4635
|
+
this.#hi.style.height = `${String(rect.height)}px`;
|
|
4636
|
+
this.#hi.setAttribute("data-on", "1");
|
|
4637
|
+
if (this.#hiLabel !== void 0)
|
|
4638
|
+
this.#hiLabel.textContent = describeEl(target);
|
|
4639
|
+
}
|
|
4640
|
+
/** Hide the hover outline (annotate mode off, or a popover took over). Cancels any pending box. */
|
|
4641
|
+
#hideHighlight() {
|
|
4642
|
+
if (this.#hiTimer !== void 0) {
|
|
4643
|
+
nativeClearTimeout(this.#hiTimer);
|
|
4644
|
+
this.#hiTimer = void 0;
|
|
4645
|
+
}
|
|
4646
|
+
this.#hi?.setAttribute("data-on", "0");
|
|
4647
|
+
}
|
|
4648
|
+
#openPopover(el, x, y) {
|
|
4649
|
+
this.#closePopover();
|
|
4650
|
+
this.#hideHighlight();
|
|
4651
|
+
const resolved = resolveMarkAnchor(el);
|
|
4652
|
+
const pop = document.createElement("div");
|
|
4653
|
+
pop.setAttribute(MARK_ATTR, "pop");
|
|
4654
|
+
const where = resolved.source !== void 0 ? `${resolved.label} \xB7 ${resolved.source.file}:${String(resolved.source.line)}` : resolved.label;
|
|
4655
|
+
pop.innerHTML = `<div class="iris-mark-where"></div>
|
|
4656
|
+
<textarea placeholder="What's wrong here? The agent will read this and fix it."></textarea>
|
|
4657
|
+
<div class="iris-mark-row"><span class="iris-mark-hint">\u2318\u21B5 send \xB7 esc cancel</span><button type="button" data-cancel>Cancel</button>
|
|
4658
|
+
<button type="button" data-send disabled>Send to agent</button></div>`;
|
|
4659
|
+
const whereEl = pop.querySelector(".iris-mark-where");
|
|
4660
|
+
if (whereEl !== null)
|
|
4661
|
+
whereEl.textContent = where;
|
|
4662
|
+
const left = Math.min(x, window.innerWidth - 296);
|
|
4663
|
+
const top = Math.min(y + 12, window.innerHeight - 170);
|
|
4664
|
+
pop.style.left = `${String(Math.max(8, left))}px`;
|
|
4665
|
+
pop.style.top = `${String(Math.max(8, top))}px`;
|
|
4666
|
+
document.body.appendChild(pop);
|
|
4667
|
+
this.#pop = pop;
|
|
4668
|
+
const textarea = pop.querySelector("textarea");
|
|
4669
|
+
const send = pop.querySelector("button[data-send]");
|
|
4670
|
+
const submit = () => {
|
|
4671
|
+
const note = textarea?.value.trim() ?? "";
|
|
4672
|
+
if (note.length === 0)
|
|
4673
|
+
return;
|
|
4674
|
+
this.#sendMark(note, resolved, x, y);
|
|
4675
|
+
this.#closePopover();
|
|
4676
|
+
};
|
|
4677
|
+
textarea?.addEventListener("input", () => {
|
|
4678
|
+
if (send !== null)
|
|
4679
|
+
send.disabled = textarea.value.trim().length === 0;
|
|
4680
|
+
});
|
|
4681
|
+
textarea?.addEventListener("keydown", (e) => {
|
|
4682
|
+
if (e.key === "Escape") {
|
|
4683
|
+
e.preventDefault();
|
|
4684
|
+
this.#closePopover();
|
|
4685
|
+
} else if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
|
|
4686
|
+
e.preventDefault();
|
|
4687
|
+
submit();
|
|
4688
|
+
}
|
|
4689
|
+
});
|
|
4690
|
+
textarea?.focus();
|
|
4691
|
+
pop.querySelector("button[data-cancel]")?.addEventListener("click", () => this.#closePopover());
|
|
4692
|
+
send?.addEventListener("click", submit);
|
|
4693
|
+
}
|
|
4694
|
+
#sendMark(note, resolved, x, y) {
|
|
4695
|
+
const data = {
|
|
4696
|
+
note,
|
|
4697
|
+
anchor: resolved.anchor,
|
|
4698
|
+
strategy: resolved.strategy,
|
|
4699
|
+
label: resolved.label,
|
|
4700
|
+
route: typeof location === "undefined" ? "" : location.pathname + location.search
|
|
4701
|
+
};
|
|
4702
|
+
if (resolved.source !== void 0)
|
|
4703
|
+
data["source"] = resolved.source;
|
|
4704
|
+
this.#emit(EventType.HUMAN_MARK, data);
|
|
4705
|
+
this.#onMark?.(note, resolved.label);
|
|
4706
|
+
this.#markCount += 1;
|
|
4707
|
+
this.#dropPin(x, y, this.#markCount);
|
|
4708
|
+
}
|
|
4709
|
+
#dropPin(x, y, n) {
|
|
4710
|
+
if (this.#root === void 0)
|
|
4711
|
+
return;
|
|
4712
|
+
const pin = document.createElement("div");
|
|
4713
|
+
pin.setAttribute(MARK_ATTR, "pin");
|
|
4714
|
+
pin.style.left = `${String(x)}px`;
|
|
4715
|
+
pin.style.top = `${String(y)}px`;
|
|
4716
|
+
pin.textContent = String(n);
|
|
4717
|
+
this.#root.appendChild(pin);
|
|
4718
|
+
const ref = pin;
|
|
4719
|
+
this.#now();
|
|
4720
|
+
window.setTimeout(() => ref.remove(), 2600);
|
|
4721
|
+
}
|
|
4722
|
+
#closePopover() {
|
|
4723
|
+
this.#pop?.remove();
|
|
4724
|
+
this.#pop = void 0;
|
|
4725
|
+
}
|
|
4726
|
+
};
|
|
4727
|
+
function describeEl(el) {
|
|
4728
|
+
const testid = el.getAttribute("data-testid");
|
|
4729
|
+
if (testid !== null && testid.length > 0)
|
|
4730
|
+
return testid;
|
|
4731
|
+
const tag = el.tagName.toLowerCase();
|
|
4732
|
+
const aria = el.getAttribute("aria-label");
|
|
4733
|
+
if (aria !== null && aria.length > 0)
|
|
4734
|
+
return `${tag} "${aria}"`;
|
|
4735
|
+
const text = (el.textContent ?? "").trim().replace(/\s+/g, " ").slice(0, 40);
|
|
4736
|
+
return text.length > 0 ? `${tag} "${text}"` : tag;
|
|
4737
|
+
}
|
|
4738
|
+
function installAnnotator(deps) {
|
|
4739
|
+
const annotator = new Annotator(deps);
|
|
4740
|
+
annotator.mount();
|
|
4741
|
+
return annotator;
|
|
4742
|
+
}
|
|
4743
|
+
|
|
3465
4744
|
// ../browser/dist/iris.js
|
|
4745
|
+
function connectionPolicy(pageHostname, bridgeUrl, allowNonLocalhost, token) {
|
|
4746
|
+
let bridge;
|
|
4747
|
+
try {
|
|
4748
|
+
bridge = new URL(bridgeUrl);
|
|
4749
|
+
} catch {
|
|
4750
|
+
return { allowed: false, reason: "invalid Iris bridge URL" };
|
|
4751
|
+
}
|
|
4752
|
+
if (bridge.protocol !== "ws:" && bridge.protocol !== "wss:") {
|
|
4753
|
+
return { allowed: false, reason: "Iris bridge URL must use ws:// or wss://" };
|
|
4754
|
+
}
|
|
4755
|
+
if ((token?.length ?? 0) > TRANSPORT_LIMITS.MAX_TOKEN_LENGTH) {
|
|
4756
|
+
return {
|
|
4757
|
+
allowed: false,
|
|
4758
|
+
reason: `Iris pairing token exceeds ${String(TRANSPORT_LIMITS.MAX_TOKEN_LENGTH)} characters`
|
|
4759
|
+
};
|
|
4760
|
+
}
|
|
4761
|
+
const remoteBridge = !isLoopbackHostname(bridge.hostname);
|
|
4762
|
+
if (remoteBridge && bridge.protocol !== "wss:") {
|
|
4763
|
+
return { allowed: false, reason: "a non-local Iris bridge must use wss://" };
|
|
4764
|
+
}
|
|
4765
|
+
const remote = !isLoopbackHostname(pageHostname) || remoteBridge;
|
|
4766
|
+
if (!remote)
|
|
4767
|
+
return { allowed: true };
|
|
4768
|
+
if (!allowNonLocalhost) {
|
|
4769
|
+
return {
|
|
4770
|
+
allowed: false,
|
|
4771
|
+
reason: "Iris is disabled outside localhost unless allowNonLocalhost is explicitly enabled"
|
|
4772
|
+
};
|
|
4773
|
+
}
|
|
4774
|
+
if (token === void 0 || token.length === 0) {
|
|
4775
|
+
return { allowed: false, reason: "a pairing token is required outside localhost" };
|
|
4776
|
+
}
|
|
4777
|
+
return { allowed: true };
|
|
4778
|
+
}
|
|
3466
4779
|
function str2(value, fallback = "") {
|
|
3467
4780
|
return typeof value === "string" ? value : fallback;
|
|
3468
4781
|
}
|
|
@@ -3470,9 +4783,6 @@ var BRIDGE_LOST_SUMMARY = "Session ended \u2014 lost connection to Iris (the age
|
|
|
3470
4783
|
function resolveSessionLabel(option, gen) {
|
|
3471
4784
|
return option === void 0 || option === SESSION_AUTO ? gen() : option;
|
|
3472
4785
|
}
|
|
3473
|
-
function isSessionState(value) {
|
|
3474
|
-
return value === SessionState.ACTIVE || value === SessionState.PAUSED || value === SessionState.ENDED;
|
|
3475
|
-
}
|
|
3476
4786
|
function refLabel(refId) {
|
|
3477
4787
|
const el = refs.resolve(refId);
|
|
3478
4788
|
if (!(el instanceof Element))
|
|
@@ -3490,7 +4800,9 @@ var Iris = class {
|
|
|
3490
4800
|
#overlay;
|
|
3491
4801
|
#presenter;
|
|
3492
4802
|
#recorder;
|
|
4803
|
+
#annotator;
|
|
3493
4804
|
#eventCount = 0;
|
|
4805
|
+
#token;
|
|
3494
4806
|
/** Act-row log handle for the in-flight act/act_sequence, so its outcome stamps the right row. */
|
|
3495
4807
|
#actHandle;
|
|
3496
4808
|
connect(options = {}) {
|
|
@@ -3498,10 +4810,16 @@ var Iris = class {
|
|
|
3498
4810
|
return;
|
|
3499
4811
|
if (typeof window === "undefined" || typeof document === "undefined")
|
|
3500
4812
|
return;
|
|
3501
|
-
|
|
4813
|
+
const url = options.url ?? `ws://localhost:${String(IRIS_DEFAULT_PORT)}${IRIS_WS_PATH}`;
|
|
4814
|
+
const policy = connectionPolicy(window.location.hostname, url, options.allowNonLocalhost === true, options.token);
|
|
4815
|
+
if (!policy.allowed) {
|
|
4816
|
+
globalThis.console.warn(`[Iris] ${policy.reason ?? "connection blocked"}`);
|
|
4817
|
+
return;
|
|
4818
|
+
}
|
|
4819
|
+
this.#session = resolveSessionLabel(options.session, () => typeof globalThis.crypto?.randomUUID === "function" ? `s${globalThis.crypto.randomUUID()}` : `s${Date.now().toString(36)}`);
|
|
4820
|
+
this.#token = options.token !== void 0 && options.token.length > 0 ? options.token : void 0;
|
|
3502
4821
|
this.#start = performance.now();
|
|
3503
4822
|
this.#registry = createCommandRegistry();
|
|
3504
|
-
const url = options.url ?? `ws://localhost:${String(IRIS_DEFAULT_PORT)}${IRIS_WS_PATH}`;
|
|
3505
4823
|
this.#transport = new Transport({
|
|
3506
4824
|
url,
|
|
3507
4825
|
hello: () => this.#hello(),
|
|
@@ -3557,6 +4875,16 @@ var Iris = class {
|
|
|
3557
4875
|
this.#recorder = installRecorder({ emit, now: () => Date.now() });
|
|
3558
4876
|
this.#recorder.mount();
|
|
3559
4877
|
}
|
|
4878
|
+
if (options.annotate ?? options.present === true) {
|
|
4879
|
+
const presenter = this.#presenter;
|
|
4880
|
+
this.#annotator = new Annotator({
|
|
4881
|
+
emit,
|
|
4882
|
+
now: () => Date.now(),
|
|
4883
|
+
// Echo the flag into the live panel so the human watches their bug report land in the log.
|
|
4884
|
+
onMark: (note, label) => presenter?.log(LOG_KIND.HUMAN, `\u{1F6A9} ${label}: ${note}`)
|
|
4885
|
+
});
|
|
4886
|
+
this.#annotator.mount();
|
|
4887
|
+
}
|
|
3560
4888
|
this.#transport.connect();
|
|
3561
4889
|
this.#connected = true;
|
|
3562
4890
|
}
|
|
@@ -3595,6 +4923,8 @@ var Iris = class {
|
|
|
3595
4923
|
this.#presenter = void 0;
|
|
3596
4924
|
this.#recorder?.destroy();
|
|
3597
4925
|
this.#recorder = void 0;
|
|
4926
|
+
this.#annotator?.destroy();
|
|
4927
|
+
this.#annotator = void 0;
|
|
3598
4928
|
resetClock();
|
|
3599
4929
|
this.#connected = false;
|
|
3600
4930
|
}
|
|
@@ -3618,6 +4948,7 @@ var Iris = class {
|
|
|
3618
4948
|
url: location.href,
|
|
3619
4949
|
title: document.title,
|
|
3620
4950
|
adapters: adapterNames(),
|
|
4951
|
+
...this.#token === void 0 ? {} : { token: this.#token },
|
|
3621
4952
|
hasCapabilities: hasCapabilities()
|
|
3622
4953
|
};
|
|
3623
4954
|
}
|
|
@@ -3633,11 +4964,8 @@ var Iris = class {
|
|
|
3633
4964
|
this.#presenter?.setIdleEndMs(idleEndMs);
|
|
3634
4965
|
return { ok: true, result: { applied: this.#presenter !== void 0, idleEndMs } };
|
|
3635
4966
|
}
|
|
3636
|
-
if (command.name === IrisCommand.PRESENTER) {
|
|
3637
|
-
|
|
3638
|
-
if (isSessionState(state)) {
|
|
3639
|
-
this.#presenter?.setState(state, str2(command.args["text"]) || void 0);
|
|
3640
|
-
}
|
|
4967
|
+
if (command.name === IrisCommand.PRESENTER || command.name === IrisCommand.FLOWS) {
|
|
4968
|
+
this.#presenter?.handlePush(command);
|
|
3641
4969
|
return { ok: true, result: { applied: this.#presenter !== void 0 } };
|
|
3642
4970
|
}
|
|
3643
4971
|
const handler = this.#registry.get(command.name);
|
|
@@ -3780,6 +5108,52 @@ function registerIrisDomain(domain) {
|
|
|
3780
5108
|
var globalStore4 = globalThis;
|
|
3781
5109
|
var iris = globalStore4.__irisInstance ??= new Iris();
|
|
3782
5110
|
|
|
5111
|
+
// ../react/dist/render-meter.js
|
|
5112
|
+
var HOOK_KEY = "__REACT_DEVTOOLS_GLOBAL_HOOK__";
|
|
5113
|
+
var RENDER_STORE = "__iris_renders";
|
|
5114
|
+
var commits = 0;
|
|
5115
|
+
var installed2 = false;
|
|
5116
|
+
function noop() {
|
|
5117
|
+
}
|
|
5118
|
+
function getRenderStats() {
|
|
5119
|
+
return { commits };
|
|
5120
|
+
}
|
|
5121
|
+
function installRenderMeter() {
|
|
5122
|
+
if (installed2)
|
|
5123
|
+
return;
|
|
5124
|
+
installed2 = true;
|
|
5125
|
+
try {
|
|
5126
|
+
const root = globalThis;
|
|
5127
|
+
const existing = root[HOOK_KEY];
|
|
5128
|
+
if (existing === void 0) {
|
|
5129
|
+
root[HOOK_KEY] = {
|
|
5130
|
+
supportsFiber: true,
|
|
5131
|
+
renderers: /* @__PURE__ */ new Map(),
|
|
5132
|
+
inject: () => 1,
|
|
5133
|
+
onScheduleFiberRoot: noop,
|
|
5134
|
+
onCommitFiberRoot: () => {
|
|
5135
|
+
commits += 1;
|
|
5136
|
+
},
|
|
5137
|
+
onPostCommitFiberRoot: noop,
|
|
5138
|
+
onCommitFiberUnmount: noop
|
|
5139
|
+
};
|
|
5140
|
+
} else {
|
|
5141
|
+
const original = typeof existing.onCommitFiberRoot === "function" ? existing.onCommitFiberRoot.bind(existing) : void 0;
|
|
5142
|
+
existing.onCommitFiberRoot = (...args) => {
|
|
5143
|
+
commits += 1;
|
|
5144
|
+
if (original !== void 0) {
|
|
5145
|
+
try {
|
|
5146
|
+
original(...args);
|
|
5147
|
+
} catch {
|
|
5148
|
+
}
|
|
5149
|
+
}
|
|
5150
|
+
};
|
|
5151
|
+
}
|
|
5152
|
+
registerStore(RENDER_STORE, () => getRenderStats());
|
|
5153
|
+
} catch {
|
|
5154
|
+
}
|
|
5155
|
+
}
|
|
5156
|
+
|
|
3783
5157
|
// ../react/dist/index.js
|
|
3784
5158
|
var FIBER_PREFIXES = ["__reactFiber$", "__reactInternalInstance$"];
|
|
3785
5159
|
var MAX_DEPTH = 200;
|
|
@@ -3943,14 +5317,15 @@ function hasHoverHandlers(el) {
|
|
|
3943
5317
|
const p = props;
|
|
3944
5318
|
return HOVER_HANDLER_KEYS.some((k) => typeof p[k] === "function");
|
|
3945
5319
|
}
|
|
3946
|
-
var
|
|
5320
|
+
var installed3 = false;
|
|
3947
5321
|
function install() {
|
|
3948
|
-
if (
|
|
5322
|
+
if (installed3)
|
|
3949
5323
|
return;
|
|
3950
|
-
|
|
5324
|
+
installed3 = true;
|
|
3951
5325
|
registerAdapter({ name: "react", identify, readState: readState2, hasHoverHandlers });
|
|
3952
5326
|
}
|
|
3953
5327
|
export {
|
|
5328
|
+
Annotator,
|
|
3954
5329
|
Iris,
|
|
3955
5330
|
RefRegistry,
|
|
3956
5331
|
SESSION_AUTO,
|
|
@@ -3971,6 +5346,8 @@ export {
|
|
|
3971
5346
|
identify,
|
|
3972
5347
|
identifyComponent,
|
|
3973
5348
|
install,
|
|
5349
|
+
installAnnotator,
|
|
5350
|
+
installRenderMeter,
|
|
3974
5351
|
iris,
|
|
3975
5352
|
isVisible,
|
|
3976
5353
|
matchQuery,
|
|
@@ -3982,6 +5359,7 @@ export {
|
|
|
3982
5359
|
registerCapabilities,
|
|
3983
5360
|
registerIrisDomain,
|
|
3984
5361
|
registerStore,
|
|
5362
|
+
resolveMarkAnchor,
|
|
3985
5363
|
runQuery,
|
|
3986
5364
|
setIgnoreSelectors,
|
|
3987
5365
|
storeNames,
|