teamshare-bridge 0.26.19 → 0.26.21
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/bridge/daemon.js +60 -0
- package/dist/bridge/daemon.js.map +1 -1
- package/dist/bridge/local-server.js +29 -11
- package/dist/bridge/local-server.js.map +1 -1
- package/dist/bridge/session-relay.d.ts +14 -0
- package/dist/bridge/session-relay.js +53 -2
- package/dist/bridge/session-relay.js.map +1 -1
- package/dist/cli/commands/research.d.ts +7 -3
- package/dist/cli/commands/research.js +67 -15
- package/dist/cli/commands/research.js.map +1 -1
- package/dist/cli/commands/usage.js +85 -32
- package/dist/cli/commands/usage.js.map +1 -1
- package/dist/cli/index.js +1 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/lib/brief.js +3 -2
- package/dist/lib/brief.js.map +1 -1
- package/dist/lib/browser/batch.d.ts +23 -0
- package/dist/lib/browser/batch.js +33 -0
- package/dist/lib/browser/batch.js.map +1 -1
- package/dist/lib/browser/manager.d.ts +1 -0
- package/dist/lib/browser/manager.js +1 -0
- package/dist/lib/browser/manager.js.map +1 -1
- package/dist/lib/browser/network-log.d.ts +28 -3
- package/dist/lib/browser/network-log.js +74 -2
- package/dist/lib/browser/network-log.js.map +1 -1
- package/dist/lib/browser/profile-sweep.d.ts +15 -0
- package/dist/lib/browser/profile-sweep.js +69 -0
- package/dist/lib/browser/profile-sweep.js.map +1 -0
- package/dist/lib/browser/screen-change.d.ts +5 -0
- package/dist/lib/browser/screen-change.js +22 -1
- package/dist/lib/browser/screen-change.js.map +1 -1
- package/dist/lib/browser/session-pool.d.ts +40 -0
- package/dist/lib/browser/session-pool.js +62 -1
- package/dist/lib/browser/session-pool.js.map +1 -1
- package/dist/lib/browser/session.d.ts +51 -8
- package/dist/lib/browser/session.js +144 -15
- package/dist/lib/browser/session.js.map +1 -1
- package/dist/lib/llm/usage-reader.d.ts +91 -4
- package/dist/lib/llm/usage-reader.js +750 -111
- package/dist/lib/llm/usage-reader.js.map +1 -1
- package/dist/lib/llm/usage.d.ts +9 -9
- package/dist/lib/llm/usage.js +9 -8
- package/dist/lib/llm/usage.js.map +1 -1
- package/dist/lib/orchestrator/handlers/browser.js +342 -56
- package/dist/lib/orchestrator/handlers/browser.js.map +1 -1
- package/package.json +1 -1
|
@@ -36,6 +36,26 @@ async function screenOf(session) {
|
|
|
36
36
|
return null;
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* Pre-action settle context (2026-11): the current URL + timestamp, captured
|
|
41
|
+
* BEFORE a navigation-capable action runs. The post-action settle compares
|
|
42
|
+
* against these so an SPA route change or full load is visible. Best-effort
|
|
43
|
+
* and cheap (one pageInfo); a failure just means `fromUrl` is absent.
|
|
44
|
+
*/
|
|
45
|
+
async function settleContext(session) {
|
|
46
|
+
const since = Date.now();
|
|
47
|
+
if (!session?.pageInfo)
|
|
48
|
+
return { since };
|
|
49
|
+
try {
|
|
50
|
+
const info = await session.pageInfo();
|
|
51
|
+
if (info?.url)
|
|
52
|
+
return { since, fromUrl: info.url };
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
/* best-effort */
|
|
56
|
+
}
|
|
57
|
+
return { since };
|
|
58
|
+
}
|
|
39
59
|
/**
|
|
40
60
|
* Emit an action result, attaching an inline screenshot when the session is in
|
|
41
61
|
* QA vision mode (2026-10) AND a bounded per-action network delta (2026-10).
|
|
@@ -52,8 +72,34 @@ async function screenOf(session) {
|
|
|
52
72
|
* payloads get one too: the toast or validation error that explains a failure
|
|
53
73
|
* is exactly what the agent needs to see. The capture is BEST-EFFORT - a
|
|
54
74
|
* screenshot problem must never turn a successful action into a failed one.
|
|
75
|
+
*
|
|
76
|
+
* 2026-11: in qaVision mode the screen is read AFTER a settle
|
|
77
|
+
* (`waitForScreenSettle`): the observer's mutation score, SPA route changes
|
|
78
|
+
* and full navigations are awaited first, so the screen + screenshot show the
|
|
79
|
+
* page the action produced instead of the mid-transition DOM. Pass
|
|
80
|
+
* `settle: false` to skip (e.g. a deliberate double-click sequence).
|
|
55
81
|
*/
|
|
56
|
-
async function emitWithVision(emit, session, cmdId, payload) {
|
|
82
|
+
async function emitWithVision(emit, session, cmdId, payload, opts) {
|
|
83
|
+
const wantSettle = session?.connected === true &&
|
|
84
|
+
session?.qaVision === true &&
|
|
85
|
+
opts?.settle !== false &&
|
|
86
|
+
typeof session?.waitForScreenSettle === 'function';
|
|
87
|
+
const settled = wantSettle && session
|
|
88
|
+
? await session.waitForScreenSettle({
|
|
89
|
+
since: opts?.since ?? Date.now(),
|
|
90
|
+
fromUrl: opts?.fromUrl,
|
|
91
|
+
}).catch(() => null)
|
|
92
|
+
: null;
|
|
93
|
+
if (settled && opts?.rescreen) {
|
|
94
|
+
try {
|
|
95
|
+
const fresh = await opts.rescreen();
|
|
96
|
+
if (fresh)
|
|
97
|
+
payload.screen = fresh;
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
/* keep the pre-settle screen */
|
|
101
|
+
}
|
|
102
|
+
}
|
|
57
103
|
if (session?.connected && session.qaVision === true) {
|
|
58
104
|
try {
|
|
59
105
|
const shot = await session.screenshot('jpeg', 62);
|
|
@@ -66,6 +112,9 @@ async function emitWithVision(emit, session, cmdId, payload) {
|
|
|
66
112
|
/* vision is best-effort */
|
|
67
113
|
}
|
|
68
114
|
}
|
|
115
|
+
if (settled && (settled.changed || settled.navigated)) {
|
|
116
|
+
payload.change = settled;
|
|
117
|
+
}
|
|
69
118
|
if (session?.connected && typeof session.networkDelta === 'function') {
|
|
70
119
|
try {
|
|
71
120
|
const delta = await session.networkDelta(Date.now() - 8000, 10);
|
|
@@ -79,14 +128,30 @@ async function emitWithVision(emit, session, cmdId, payload) {
|
|
|
79
128
|
emit('orchestrator:browser-result', cmdId, payload);
|
|
80
129
|
}
|
|
81
130
|
/**
|
|
82
|
-
*
|
|
131
|
+
* Sessions an identity opened, most recent last (2026-10, multi-window 2026-11).
|
|
83
132
|
*
|
|
84
133
|
* An agent that opened a NAMED session and then calls browser_action without a
|
|
85
|
-
* sessionId
|
|
86
|
-
*
|
|
134
|
+
* sessionId keeps driving that same browser. With TWO or more live windows and
|
|
135
|
+
* no sessionId the command is AMBIGUOUS and refused — acting on "whichever was
|
|
136
|
+
* touched last" would drive the wrong Chrome. Pruned when sessions close.
|
|
87
137
|
*/
|
|
88
|
-
const
|
|
89
|
-
|
|
138
|
+
const sessionsByAgent = new Map();
|
|
139
|
+
function rememberSession(identityKey, sessionId) {
|
|
140
|
+
const list = sessionsByAgent.get(identityKey) ?? [];
|
|
141
|
+
const next = [...list.filter((id) => id !== sessionId), sessionId];
|
|
142
|
+
sessionsByAgent.set(identityKey, next.slice(-20));
|
|
143
|
+
}
|
|
144
|
+
function forgetSession(identityKey, sessionId) {
|
|
145
|
+
const list = sessionsByAgent.get(identityKey);
|
|
146
|
+
if (!list)
|
|
147
|
+
return;
|
|
148
|
+
const next = list.filter((id) => id !== sessionId);
|
|
149
|
+
if (next.length)
|
|
150
|
+
sessionsByAgent.set(identityKey, next);
|
|
151
|
+
else
|
|
152
|
+
sessionsByAgent.delete(identityKey);
|
|
153
|
+
}
|
|
154
|
+
/** The identity key used by `sessionsByAgent` and the fallback session. */
|
|
90
155
|
function identityKeyOf(agentUserId) {
|
|
91
156
|
const normalized = (0, session_pool_1.normalizeSessionKey)(agentUserId);
|
|
92
157
|
return normalized ? `agent:${normalized}` : 'shared';
|
|
@@ -109,19 +174,49 @@ async function handleBrowserCommand(data, emit) {
|
|
|
109
174
|
});
|
|
110
175
|
return;
|
|
111
176
|
}
|
|
112
|
-
// SESSION PER IDENTITY (2026-10, multi-
|
|
113
|
-
// made every agent on this PC share one Chrome. The key is
|
|
114
|
-
// sessionId, else the caller's
|
|
115
|
-
//
|
|
177
|
+
// SESSION PER IDENTITY (2026-10, multi-window 2026-11): the old fixed
|
|
178
|
+
// 'default' made every agent on this PC share one Chrome. The key is the
|
|
179
|
+
// explicit sessionId, else the caller's remembered windows. With two or more
|
|
180
|
+
// live windows and no sessionId the command is AMBIGUOUS and refused.
|
|
116
181
|
const identityKey = identityKeyOf(agentUserId);
|
|
117
182
|
const explicitSession = (0, session_pool_1.normalizeSessionKey)(data.sessionId);
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
|
|
183
|
+
const manager = (0, manager_1.getBrowserManager)();
|
|
184
|
+
const liveWindows = manager.list().map((id) => {
|
|
185
|
+
const s = manager.get(id);
|
|
186
|
+
return { id, actor: s?.actor ?? null };
|
|
187
|
+
});
|
|
188
|
+
const remembered = (sessionsByAgent.get(identityKey) ?? []).filter((id) => manager.get(id));
|
|
189
|
+
let sessionId;
|
|
190
|
+
if (explicitSession) {
|
|
191
|
+
sessionId = explicitSession;
|
|
192
|
+
}
|
|
193
|
+
else if (action === 'connect') {
|
|
194
|
+
sessionId = (0, session_pool_1.deriveSessionKey)(null, agentUserId);
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
const resolved = (0, session_pool_1.resolveSessionForCommand)({
|
|
198
|
+
explicit: null,
|
|
199
|
+
remembered,
|
|
200
|
+
derived: (0, session_pool_1.deriveSessionKey)(null, agentUserId),
|
|
201
|
+
live: liveWindows,
|
|
202
|
+
});
|
|
203
|
+
if ('ambiguous' in resolved) {
|
|
204
|
+
const list = resolved.ambiguous
|
|
205
|
+
.map((w) => `${w.id}${w.actor ? ` (${w.actor})` : ''}`)
|
|
206
|
+
.join(', ');
|
|
207
|
+
emit('orchestrator:browser-result', String(data.cmdId ?? ''), {
|
|
208
|
+
success: false,
|
|
209
|
+
error: `BROWSER_SESSION_AMBIGUOUS: this agent has ${resolved.ambiguous.length} browser windows open ` +
|
|
210
|
+
`(${list}). Pass sessionId on every browser_action — each test account has its own window.`,
|
|
211
|
+
sessions: resolved.ambiguous,
|
|
212
|
+
});
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
sessionId = resolved.sessionId;
|
|
216
|
+
}
|
|
121
217
|
if (explicitSession || action === 'connect') {
|
|
122
|
-
|
|
218
|
+
rememberSession(identityKey, sessionId);
|
|
123
219
|
}
|
|
124
|
-
const manager = (0, manager_1.getBrowserManager)();
|
|
125
220
|
// Any command counts as activity, so the idle-close grace restarts.
|
|
126
221
|
manager.touch(sessionId);
|
|
127
222
|
log.info(`Browser command: ${action} (session: ${sessionId})`);
|
|
@@ -167,6 +262,29 @@ async function handleBrowserCommand(data, emit) {
|
|
|
167
262
|
// OPT-IN QA vision mode (default false, 2026-10): every mutating action
|
|
168
263
|
// returns a screenshot. Only the QA brief turns it on.
|
|
169
264
|
const qaVision = data.qaVision === true;
|
|
265
|
+
// One window per identity (2026-11): the caller names the account this
|
|
266
|
+
// window belongs to, and isolated forces a per-session profile so a QA
|
|
267
|
+
// window never inherits the human's shared debug profile.
|
|
268
|
+
const callActor = typeof data.actor === 'string' && data.actor.trim()
|
|
269
|
+
? data.actor.trim().slice(0, 80)
|
|
270
|
+
: null;
|
|
271
|
+
const isolated = data.isolated === true;
|
|
272
|
+
// A tagged window never changes hands.
|
|
273
|
+
const tagged = manager.get(sessionId);
|
|
274
|
+
if (tagged?.actor && callActor) {
|
|
275
|
+
const gate = (0, session_pool_1.assertActorForLogin)({
|
|
276
|
+
sessionActor: tagged.actor,
|
|
277
|
+
callActor,
|
|
278
|
+
});
|
|
279
|
+
if (gate) {
|
|
280
|
+
emit('orchestrator:browser-result', data.cmdId, {
|
|
281
|
+
success: false,
|
|
282
|
+
error: gate.message,
|
|
283
|
+
code: gate.code,
|
|
284
|
+
});
|
|
285
|
+
break;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
170
288
|
// REUSE only a LIVE session. A disconnected one may have lost its port
|
|
171
289
|
// and its profile to a concurrent browser while it was down, so it is
|
|
172
290
|
// re-allocated below instead of silently reconnecting onto them.
|
|
@@ -174,6 +292,7 @@ async function handleBrowserCommand(data, emit) {
|
|
|
174
292
|
if (existing?.connected) {
|
|
175
293
|
existing.setAllowCredentials(allowCredentials);
|
|
176
294
|
existing.setQaVision(qaVision);
|
|
295
|
+
existing.setActor(callActor);
|
|
177
296
|
// reset:true drops a crashed run's leftovers (open modal, corrupted
|
|
178
297
|
// field values, stale validation errors) before the new run starts.
|
|
179
298
|
// A resumed run omits it and keeps its page.
|
|
@@ -193,6 +312,7 @@ async function handleBrowserCommand(data, emit) {
|
|
|
193
312
|
alreadyConnected: true,
|
|
194
313
|
sessionId,
|
|
195
314
|
port: existing.port,
|
|
315
|
+
...(existing.actor ? { actor: existing.actor } : {}),
|
|
196
316
|
qaVision: existing.qaVision,
|
|
197
317
|
...pageState,
|
|
198
318
|
...(screen ? { screen } : {}),
|
|
@@ -218,6 +338,7 @@ async function handleBrowserCommand(data, emit) {
|
|
|
218
338
|
sessionKey: sessionId,
|
|
219
339
|
usedDirs: manager.usedProfileDirs(sessionId),
|
|
220
340
|
...(namedProfileDir ? { namedDir: namedProfileDir } : {}),
|
|
341
|
+
...(isolated ? { forceIsolated: true } : {}),
|
|
221
342
|
});
|
|
222
343
|
if (profile.conflict) {
|
|
223
344
|
emit('orchestrator:browser-result', data.cmdId, {
|
|
@@ -236,11 +357,13 @@ async function handleBrowserCommand(data, emit) {
|
|
|
236
357
|
profileDir: profile.dir,
|
|
237
358
|
allowCredentials,
|
|
238
359
|
qaVision,
|
|
360
|
+
...(callActor ? { actor: callActor } : {}),
|
|
239
361
|
});
|
|
240
362
|
// A reused session keeps its flag from a previous connect; honour the
|
|
241
363
|
// explicit value on every connect so the caller stays in control.
|
|
242
364
|
session.setAllowCredentials(allowCredentials);
|
|
243
365
|
session.setQaVision(qaVision);
|
|
366
|
+
session.setActor(callActor);
|
|
244
367
|
const info = await session.connect(port);
|
|
245
368
|
const actualPort = session.port;
|
|
246
369
|
log.info(`Browser session "${sessionId}" on port ${actualPort} ` +
|
|
@@ -254,6 +377,7 @@ async function handleBrowserCommand(data, emit) {
|
|
|
254
377
|
sessionId,
|
|
255
378
|
port: actualPort,
|
|
256
379
|
profile: profile.shared ? 'shared' : 'isolated',
|
|
380
|
+
...(callActor ? { actor: callActor } : {}),
|
|
257
381
|
qaVision: session.qaVision,
|
|
258
382
|
...pageState,
|
|
259
383
|
...(screen ? { screen } : {}),
|
|
@@ -265,7 +389,8 @@ async function handleBrowserCommand(data, emit) {
|
|
|
265
389
|
if (!session?.connected) {
|
|
266
390
|
emit('orchestrator:browser-result', data.cmdId, {
|
|
267
391
|
success: false,
|
|
268
|
-
error:
|
|
392
|
+
error: `No active browser session "${sessionId}". ` +
|
|
393
|
+
`Call browser_open({ sessionId: "${sessionId}" }) first.`,
|
|
269
394
|
});
|
|
270
395
|
return;
|
|
271
396
|
}
|
|
@@ -284,7 +409,8 @@ async function handleBrowserCommand(data, emit) {
|
|
|
284
409
|
if (!session?.connected) {
|
|
285
410
|
emit('orchestrator:browser-result', data.cmdId, {
|
|
286
411
|
success: false,
|
|
287
|
-
error:
|
|
412
|
+
error: `No active browser session "${sessionId}". ` +
|
|
413
|
+
`Call browser_open({ sessionId: "${sessionId}" }) first.`,
|
|
288
414
|
});
|
|
289
415
|
return;
|
|
290
416
|
}
|
|
@@ -296,12 +422,24 @@ async function handleBrowserCommand(data, emit) {
|
|
|
296
422
|
});
|
|
297
423
|
return;
|
|
298
424
|
}
|
|
425
|
+
// Capture the pre-navigation URL + time BEFORE navigating: the settle
|
|
426
|
+
// compares against these, so it can see an SPA route change and a full
|
|
427
|
+
// load alike.
|
|
428
|
+
const navFrom = await session
|
|
429
|
+
.pageInfo()
|
|
430
|
+
.then((info) => info.url ?? null)
|
|
431
|
+
.catch(() => null);
|
|
432
|
+
const navSince = Date.now();
|
|
299
433
|
const info = await session.navigate(url);
|
|
300
434
|
const screen = await screenOf(session);
|
|
301
435
|
await emitWithVision(emit, session, data.cmdId, {
|
|
302
436
|
success: true,
|
|
303
437
|
...info,
|
|
304
438
|
...(screen ? { screen } : {}),
|
|
439
|
+
}, {
|
|
440
|
+
since: navSince,
|
|
441
|
+
...(navFrom ? { fromUrl: navFrom } : {}),
|
|
442
|
+
rescreen: () => screenOf(session),
|
|
305
443
|
});
|
|
306
444
|
break;
|
|
307
445
|
}
|
|
@@ -310,7 +448,8 @@ async function handleBrowserCommand(data, emit) {
|
|
|
310
448
|
if (!session?.connected) {
|
|
311
449
|
emit('orchestrator:browser-result', data.cmdId, {
|
|
312
450
|
success: false,
|
|
313
|
-
error:
|
|
451
|
+
error: `No active browser session "${sessionId}". ` +
|
|
452
|
+
`Call browser_open({ sessionId: "${sessionId}" }) first.`,
|
|
314
453
|
});
|
|
315
454
|
return;
|
|
316
455
|
}
|
|
@@ -393,6 +532,7 @@ async function handleBrowserCommand(data, emit) {
|
|
|
393
532
|
}
|
|
394
533
|
clicked = { x, y };
|
|
395
534
|
}
|
|
535
|
+
const settleFrom = await settleContext(session);
|
|
396
536
|
const screen = await screenOf(session);
|
|
397
537
|
await emitWithVision(emit, session, data.cmdId, {
|
|
398
538
|
success: true,
|
|
@@ -401,6 +541,9 @@ async function handleBrowserCommand(data, emit) {
|
|
|
401
541
|
x: clicked.x,
|
|
402
542
|
y: clicked.y,
|
|
403
543
|
...(screen ? { screen } : {}),
|
|
544
|
+
}, {
|
|
545
|
+
...settleFrom,
|
|
546
|
+
rescreen: () => screenOf(session),
|
|
404
547
|
});
|
|
405
548
|
break;
|
|
406
549
|
}
|
|
@@ -409,7 +552,8 @@ async function handleBrowserCommand(data, emit) {
|
|
|
409
552
|
if (!session?.connected) {
|
|
410
553
|
emit('orchestrator:browser-result', data.cmdId, {
|
|
411
554
|
success: false,
|
|
412
|
-
error:
|
|
555
|
+
error: `No active browser session "${sessionId}". ` +
|
|
556
|
+
`Call browser_open({ sessionId: "${sessionId}" }) first.`,
|
|
413
557
|
});
|
|
414
558
|
return;
|
|
415
559
|
}
|
|
@@ -448,6 +592,7 @@ async function handleBrowserCommand(data, emit) {
|
|
|
448
592
|
const observed = await session.type(text, {
|
|
449
593
|
clearFirst: data.clearFirst === true,
|
|
450
594
|
});
|
|
595
|
+
const settleFrom = await settleContext(session);
|
|
451
596
|
const screen = await screenOf(session);
|
|
452
597
|
await emitWithVision(emit, session, data.cmdId, {
|
|
453
598
|
success: true,
|
|
@@ -455,6 +600,9 @@ async function handleBrowserCommand(data, emit) {
|
|
|
455
600
|
length: text.length,
|
|
456
601
|
observed,
|
|
457
602
|
...(screen ? { screen } : {}),
|
|
603
|
+
}, {
|
|
604
|
+
...settleFrom,
|
|
605
|
+
rescreen: () => screenOf(session),
|
|
458
606
|
});
|
|
459
607
|
}
|
|
460
608
|
catch (err) {
|
|
@@ -472,10 +620,30 @@ async function handleBrowserCommand(data, emit) {
|
|
|
472
620
|
if (!session?.connected) {
|
|
473
621
|
emit('orchestrator:browser-result', data.cmdId, {
|
|
474
622
|
success: false,
|
|
475
|
-
error:
|
|
623
|
+
error: `No active browser session "${sessionId}". ` +
|
|
624
|
+
`Call browser_open({ sessionId: "${sessionId}" }) first.`,
|
|
625
|
+
});
|
|
626
|
+
return;
|
|
627
|
+
}
|
|
628
|
+
// One window per identity (2026-11): a tagged window only signs in
|
|
629
|
+
// its own account. Never sign in as B in A's window.
|
|
630
|
+
const callActor = typeof data.actor === 'string' && data.actor.trim()
|
|
631
|
+
? data.actor.trim()
|
|
632
|
+
: null;
|
|
633
|
+
const gate = (0, session_pool_1.assertActorForLogin)({
|
|
634
|
+
sessionActor: session.actor,
|
|
635
|
+
callActor,
|
|
636
|
+
});
|
|
637
|
+
if (gate) {
|
|
638
|
+
emit('orchestrator:browser-result', data.cmdId, {
|
|
639
|
+
success: false,
|
|
640
|
+
error: gate.message,
|
|
641
|
+
code: gate.code,
|
|
476
642
|
});
|
|
477
643
|
return;
|
|
478
644
|
}
|
|
645
|
+
if (callActor)
|
|
646
|
+
session.setActor(callActor);
|
|
479
647
|
if (data.allowCredentials === true)
|
|
480
648
|
session.setAllowCredentials(true);
|
|
481
649
|
// Password is optional: an `email_otp` / OTP-only step sends the email
|
|
@@ -495,11 +663,15 @@ async function handleBrowserCommand(data, emit) {
|
|
|
495
663
|
: {}),
|
|
496
664
|
});
|
|
497
665
|
// Never echo the password: the payload carries booleans + an error code.
|
|
666
|
+
const settleFrom = await settleContext(session);
|
|
498
667
|
const screen = await screenOf(session);
|
|
499
668
|
await emitWithVision(emit, session, data.cmdId, {
|
|
500
669
|
success: true,
|
|
501
670
|
...result,
|
|
502
671
|
...(screen ? { screen } : {}),
|
|
672
|
+
}, {
|
|
673
|
+
...settleFrom,
|
|
674
|
+
rescreen: () => screenOf(session),
|
|
503
675
|
});
|
|
504
676
|
break;
|
|
505
677
|
}
|
|
@@ -508,7 +680,8 @@ async function handleBrowserCommand(data, emit) {
|
|
|
508
680
|
if (!session?.connected) {
|
|
509
681
|
emit('orchestrator:browser-result', data.cmdId, {
|
|
510
682
|
success: false,
|
|
511
|
-
error:
|
|
683
|
+
error: `No active browser session "${sessionId}". ` +
|
|
684
|
+
`Call browser_open({ sessionId: "${sessionId}" }) first.`,
|
|
512
685
|
});
|
|
513
686
|
return;
|
|
514
687
|
}
|
|
@@ -534,12 +707,21 @@ async function handleBrowserCommand(data, emit) {
|
|
|
534
707
|
: {}),
|
|
535
708
|
...(data.clearFirst === false ? {} : {}),
|
|
536
709
|
});
|
|
710
|
+
// An OTP submit navigates (2026-11): settle before reading, like
|
|
711
|
+
// every other navigation-capable action, or the screen shows the
|
|
712
|
+
// pre-submit form while the app is already on the next page.
|
|
713
|
+
const settleFrom = await settleContext(session);
|
|
537
714
|
const screen = await screenOf(session);
|
|
538
715
|
await emitWithVision(emit, session, data.cmdId, {
|
|
539
716
|
success: result.ok,
|
|
540
|
-
...(result.ok
|
|
717
|
+
...(result.ok
|
|
718
|
+
? {}
|
|
719
|
+
: { error: result.error ?? 'OTP_FILL_FAILED' }),
|
|
541
720
|
...result,
|
|
542
721
|
...(screen ? { screen } : {}),
|
|
722
|
+
}, {
|
|
723
|
+
...settleFrom,
|
|
724
|
+
rescreen: () => screenOf(session),
|
|
543
725
|
});
|
|
544
726
|
}
|
|
545
727
|
catch (err) {
|
|
@@ -557,17 +739,22 @@ async function handleBrowserCommand(data, emit) {
|
|
|
557
739
|
if (!session?.connected) {
|
|
558
740
|
emit('orchestrator:browser-result', data.cmdId, {
|
|
559
741
|
success: false,
|
|
560
|
-
error:
|
|
742
|
+
error: `No active browser session "${sessionId}". ` +
|
|
743
|
+
`Call browser_open({ sessionId: "${sessionId}" }) first.`,
|
|
561
744
|
});
|
|
562
745
|
return;
|
|
563
746
|
}
|
|
564
747
|
const key = String(data.key ?? '');
|
|
748
|
+
const settleFrom = await settleContext(session);
|
|
565
749
|
await session.press(key);
|
|
566
750
|
const screen = await screenOf(session);
|
|
567
751
|
await emitWithVision(emit, session, data.cmdId, {
|
|
568
752
|
success: true,
|
|
569
753
|
pressed: key,
|
|
570
754
|
...(screen ? { screen } : {}),
|
|
755
|
+
}, {
|
|
756
|
+
...settleFrom,
|
|
757
|
+
rescreen: () => screenOf(session),
|
|
571
758
|
});
|
|
572
759
|
break;
|
|
573
760
|
}
|
|
@@ -579,7 +766,8 @@ async function handleBrowserCommand(data, emit) {
|
|
|
579
766
|
if (!session?.connected) {
|
|
580
767
|
emit('orchestrator:browser-result', data.cmdId, {
|
|
581
768
|
success: false,
|
|
582
|
-
error:
|
|
769
|
+
error: `No active browser session "${sessionId}". ` +
|
|
770
|
+
`Call browser_open({ sessionId: "${sessionId}" }) first.`,
|
|
583
771
|
});
|
|
584
772
|
return;
|
|
585
773
|
}
|
|
@@ -633,7 +821,8 @@ async function handleBrowserCommand(data, emit) {
|
|
|
633
821
|
if (!session?.connected) {
|
|
634
822
|
emit('orchestrator:browser-result', data.cmdId, {
|
|
635
823
|
success: false,
|
|
636
|
-
error:
|
|
824
|
+
error: `No active browser session "${sessionId}". ` +
|
|
825
|
+
`Call browser_open({ sessionId: "${sessionId}" }) first.`,
|
|
637
826
|
});
|
|
638
827
|
return;
|
|
639
828
|
}
|
|
@@ -689,7 +878,8 @@ async function handleBrowserCommand(data, emit) {
|
|
|
689
878
|
if (!session?.connected) {
|
|
690
879
|
emit('orchestrator:browser-result', data.cmdId, {
|
|
691
880
|
success: false,
|
|
692
|
-
error:
|
|
881
|
+
error: `No active browser session "${sessionId}". ` +
|
|
882
|
+
`Call browser_open({ sessionId: "${sessionId}" }) first.`,
|
|
693
883
|
});
|
|
694
884
|
return;
|
|
695
885
|
}
|
|
@@ -704,16 +894,19 @@ async function handleBrowserCommand(data, emit) {
|
|
|
704
894
|
}
|
|
705
895
|
// 2026-10: the interactive-element ref list (`e1 … eN`), including
|
|
706
896
|
// portalled menus/popovers/dialogs. Actions accept `ref`.
|
|
897
|
+
// 2026-11: `filter` narrows by substring over role + name + tag
|
|
898
|
+
// (`getRefs {filter:"button"}`); numbering is always full-scan.
|
|
707
899
|
case 'getRefs': {
|
|
708
900
|
const session = manager.get(sessionId);
|
|
709
901
|
if (!session?.connected) {
|
|
710
902
|
emit('orchestrator:browser-result', data.cmdId, {
|
|
711
903
|
success: false,
|
|
712
|
-
error:
|
|
904
|
+
error: `No active browser session "${sessionId}". ` +
|
|
905
|
+
`Call browser_open({ sessionId: "${sessionId}" }) first.`,
|
|
713
906
|
});
|
|
714
907
|
return;
|
|
715
908
|
}
|
|
716
|
-
const refs = await session.getRefs();
|
|
909
|
+
const refs = await session.getRefs(typeof data.filter === 'string' ? data.filter : undefined);
|
|
717
910
|
let oneTimeCode;
|
|
718
911
|
try {
|
|
719
912
|
const snap = await session.snapshot({ refs: false });
|
|
@@ -741,7 +934,8 @@ async function handleBrowserCommand(data, emit) {
|
|
|
741
934
|
if (!session?.connected) {
|
|
742
935
|
emit('orchestrator:browser-result', data.cmdId, {
|
|
743
936
|
success: false,
|
|
744
|
-
error:
|
|
937
|
+
error: `No active browser session "${sessionId}". ` +
|
|
938
|
+
`Call browser_open({ sessionId: "${sessionId}" }) first.`,
|
|
745
939
|
});
|
|
746
940
|
return;
|
|
747
941
|
}
|
|
@@ -760,7 +954,8 @@ async function handleBrowserCommand(data, emit) {
|
|
|
760
954
|
if (!session?.connected) {
|
|
761
955
|
emit('orchestrator:browser-result', data.cmdId, {
|
|
762
956
|
success: false,
|
|
763
|
-
error:
|
|
957
|
+
error: `No active browser session "${sessionId}". ` +
|
|
958
|
+
`Call browser_open({ sessionId: "${sessionId}" }) first.`,
|
|
764
959
|
});
|
|
765
960
|
return;
|
|
766
961
|
}
|
|
@@ -773,12 +968,65 @@ async function handleBrowserCommand(data, emit) {
|
|
|
773
968
|
});
|
|
774
969
|
break;
|
|
775
970
|
}
|
|
971
|
+
// 2026-11: set the viewport before a design-verification screenshot.
|
|
972
|
+
// The design's viewport IS the contract; the backend clamps to
|
|
973
|
+
// 320–8192 and the read-back reports what the page actually uses.
|
|
974
|
+
case 'setViewport': {
|
|
975
|
+
const session = manager.get(sessionId);
|
|
976
|
+
if (!session?.connected) {
|
|
977
|
+
emit('orchestrator:browser-result', data.cmdId, {
|
|
978
|
+
success: false,
|
|
979
|
+
error: `No active browser session "${sessionId}". ` +
|
|
980
|
+
`Call browser_open({ sessionId: "${sessionId}" }) first.`,
|
|
981
|
+
});
|
|
982
|
+
return;
|
|
983
|
+
}
|
|
984
|
+
const width = typeof data.viewportWidth === 'number'
|
|
985
|
+
? data.viewportWidth
|
|
986
|
+
: typeof data.width === 'number'
|
|
987
|
+
? data.width
|
|
988
|
+
: NaN;
|
|
989
|
+
const height = typeof data.viewportHeight === 'number'
|
|
990
|
+
? data.viewportHeight
|
|
991
|
+
: typeof data.height === 'number'
|
|
992
|
+
? data.height
|
|
993
|
+
: NaN;
|
|
994
|
+
if (!Number.isFinite(width) || !Number.isFinite(height)) {
|
|
995
|
+
emit('orchestrator:browser-result', data.cmdId, {
|
|
996
|
+
success: false,
|
|
997
|
+
error: 'setViewport needs viewportWidth + viewportHeight (320-8192).',
|
|
998
|
+
});
|
|
999
|
+
return;
|
|
1000
|
+
}
|
|
1001
|
+
try {
|
|
1002
|
+
const applied = await session.setViewport(width, height, typeof data.deviceScaleFactor === 'number'
|
|
1003
|
+
? data.deviceScaleFactor
|
|
1004
|
+
: 1);
|
|
1005
|
+
const screen = await screenOf(session);
|
|
1006
|
+
await emitWithVision(emit, session, data.cmdId, {
|
|
1007
|
+
success: true,
|
|
1008
|
+
viewport: true,
|
|
1009
|
+
...applied,
|
|
1010
|
+
...(screen ? { screen } : {}),
|
|
1011
|
+
});
|
|
1012
|
+
}
|
|
1013
|
+
catch (err) {
|
|
1014
|
+
const screen = await screenOf(session);
|
|
1015
|
+
await emitWithVision(emit, session, data.cmdId, {
|
|
1016
|
+
success: false,
|
|
1017
|
+
error: err instanceof Error ? err.message : String(err),
|
|
1018
|
+
...(screen ? { screen } : {}),
|
|
1019
|
+
});
|
|
1020
|
+
}
|
|
1021
|
+
break;
|
|
1022
|
+
}
|
|
776
1023
|
case 'evaluate': {
|
|
777
1024
|
const session = manager.get(sessionId);
|
|
778
1025
|
if (!session?.connected) {
|
|
779
1026
|
emit('orchestrator:browser-result', data.cmdId, {
|
|
780
1027
|
success: false,
|
|
781
|
-
error:
|
|
1028
|
+
error: `No active browser session "${sessionId}". ` +
|
|
1029
|
+
`Call browser_open({ sessionId: "${sessionId}" }) first.`,
|
|
782
1030
|
});
|
|
783
1031
|
return;
|
|
784
1032
|
}
|
|
@@ -795,7 +1043,8 @@ async function handleBrowserCommand(data, emit) {
|
|
|
795
1043
|
if (!session?.connected) {
|
|
796
1044
|
emit('orchestrator:browser-result', data.cmdId, {
|
|
797
1045
|
success: false,
|
|
798
|
-
error:
|
|
1046
|
+
error: `No active browser session "${sessionId}". ` +
|
|
1047
|
+
`Call browser_open({ sessionId: "${sessionId}" }) first.`,
|
|
799
1048
|
});
|
|
800
1049
|
return;
|
|
801
1050
|
}
|
|
@@ -812,7 +1061,8 @@ async function handleBrowserCommand(data, emit) {
|
|
|
812
1061
|
if (!session?.connected) {
|
|
813
1062
|
emit('orchestrator:browser-result', data.cmdId, {
|
|
814
1063
|
success: false,
|
|
815
|
-
error:
|
|
1064
|
+
error: `No active browser session "${sessionId}". ` +
|
|
1065
|
+
`Call browser_open({ sessionId: "${sessionId}" }) first.`,
|
|
816
1066
|
});
|
|
817
1067
|
return;
|
|
818
1068
|
}
|
|
@@ -831,7 +1081,8 @@ async function handleBrowserCommand(data, emit) {
|
|
|
831
1081
|
if (!session?.connected) {
|
|
832
1082
|
emit('orchestrator:browser-result', data.cmdId, {
|
|
833
1083
|
success: false,
|
|
834
|
-
error:
|
|
1084
|
+
error: `No active browser session "${sessionId}". ` +
|
|
1085
|
+
`Call browser_open({ sessionId: "${sessionId}" }) first.`,
|
|
835
1086
|
});
|
|
836
1087
|
return;
|
|
837
1088
|
}
|
|
@@ -871,7 +1122,8 @@ async function handleBrowserCommand(data, emit) {
|
|
|
871
1122
|
if (!session?.connected) {
|
|
872
1123
|
emit('orchestrator:browser-result', data.cmdId, {
|
|
873
1124
|
success: false,
|
|
874
|
-
error:
|
|
1125
|
+
error: `No active browser session "${sessionId}". ` +
|
|
1126
|
+
`Call browser_open({ sessionId: "${sessionId}" }) first.`,
|
|
875
1127
|
});
|
|
876
1128
|
return;
|
|
877
1129
|
}
|
|
@@ -929,7 +1181,8 @@ async function handleBrowserCommand(data, emit) {
|
|
|
929
1181
|
if (!session?.connected) {
|
|
930
1182
|
emit('orchestrator:browser-result', data.cmdId, {
|
|
931
1183
|
success: false,
|
|
932
|
-
error:
|
|
1184
|
+
error: `No active browser session "${sessionId}". ` +
|
|
1185
|
+
`Call browser_open({ sessionId: "${sessionId}" }) first.`,
|
|
933
1186
|
});
|
|
934
1187
|
return;
|
|
935
1188
|
}
|
|
@@ -951,7 +1204,8 @@ async function handleBrowserCommand(data, emit) {
|
|
|
951
1204
|
if (!session?.connected) {
|
|
952
1205
|
emit('orchestrator:browser-result', data.cmdId, {
|
|
953
1206
|
success: false,
|
|
954
|
-
error:
|
|
1207
|
+
error: `No active browser session "${sessionId}". ` +
|
|
1208
|
+
`Call browser_open({ sessionId: "${sessionId}" }) first.`,
|
|
955
1209
|
});
|
|
956
1210
|
return;
|
|
957
1211
|
}
|
|
@@ -961,11 +1215,15 @@ async function handleBrowserCommand(data, emit) {
|
|
|
961
1215
|
? { promptText: data.promptText }
|
|
962
1216
|
: {}),
|
|
963
1217
|
});
|
|
1218
|
+
const settleFrom = await settleContext(session);
|
|
964
1219
|
const screen = await screenOf(session);
|
|
965
1220
|
await emitWithVision(emit, session, data.cmdId, {
|
|
966
1221
|
success: result.ok,
|
|
967
1222
|
...result,
|
|
968
1223
|
...(screen ? { screen } : {}),
|
|
1224
|
+
}, {
|
|
1225
|
+
...settleFrom,
|
|
1226
|
+
rescreen: () => screenOf(session),
|
|
969
1227
|
});
|
|
970
1228
|
break;
|
|
971
1229
|
}
|
|
@@ -978,7 +1236,8 @@ async function handleBrowserCommand(data, emit) {
|
|
|
978
1236
|
if (!session?.connected) {
|
|
979
1237
|
emit('orchestrator:browser-result', data.cmdId, {
|
|
980
1238
|
success: false,
|
|
981
|
-
error:
|
|
1239
|
+
error: `No active browser session "${sessionId}". ` +
|
|
1240
|
+
`Call browser_open({ sessionId: "${sessionId}" }) first.`,
|
|
982
1241
|
});
|
|
983
1242
|
return;
|
|
984
1243
|
}
|
|
@@ -1003,7 +1262,8 @@ async function handleBrowserCommand(data, emit) {
|
|
|
1003
1262
|
if (!session?.connected) {
|
|
1004
1263
|
emit('orchestrator:browser-result', data.cmdId, {
|
|
1005
1264
|
success: false,
|
|
1006
|
-
error:
|
|
1265
|
+
error: `No active browser session "${sessionId}". ` +
|
|
1266
|
+
`Call browser_open({ sessionId: "${sessionId}" }) first.`,
|
|
1007
1267
|
});
|
|
1008
1268
|
return;
|
|
1009
1269
|
}
|
|
@@ -1028,7 +1288,8 @@ async function handleBrowserCommand(data, emit) {
|
|
|
1028
1288
|
if (!session?.connected) {
|
|
1029
1289
|
emit('orchestrator:browser-result', data.cmdId, {
|
|
1030
1290
|
success: false,
|
|
1031
|
-
error:
|
|
1291
|
+
error: `No active browser session "${sessionId}". ` +
|
|
1292
|
+
`Call browser_open({ sessionId: "${sessionId}" }) first.`,
|
|
1032
1293
|
});
|
|
1033
1294
|
return;
|
|
1034
1295
|
}
|
|
@@ -1053,7 +1314,8 @@ async function handleBrowserCommand(data, emit) {
|
|
|
1053
1314
|
if (!session?.connected) {
|
|
1054
1315
|
emit('orchestrator:browser-result', data.cmdId, {
|
|
1055
1316
|
success: false,
|
|
1056
|
-
error:
|
|
1317
|
+
error: `No active browser session "${sessionId}". ` +
|
|
1318
|
+
`Call browser_open({ sessionId: "${sessionId}" }) first.`,
|
|
1057
1319
|
});
|
|
1058
1320
|
return;
|
|
1059
1321
|
}
|
|
@@ -1074,7 +1336,8 @@ async function handleBrowserCommand(data, emit) {
|
|
|
1074
1336
|
if (!session?.connected) {
|
|
1075
1337
|
emit('orchestrator:browser-result', data.cmdId, {
|
|
1076
1338
|
success: false,
|
|
1077
|
-
error:
|
|
1339
|
+
error: `No active browser session "${sessionId}". ` +
|
|
1340
|
+
`Call browser_open({ sessionId: "${sessionId}" }) first.`,
|
|
1078
1341
|
});
|
|
1079
1342
|
return;
|
|
1080
1343
|
}
|
|
@@ -1106,7 +1369,8 @@ async function handleBrowserCommand(data, emit) {
|
|
|
1106
1369
|
if (!session?.connected) {
|
|
1107
1370
|
emit('orchestrator:browser-result', data.cmdId, {
|
|
1108
1371
|
success: false,
|
|
1109
|
-
error:
|
|
1372
|
+
error: `No active browser session "${sessionId}". ` +
|
|
1373
|
+
`Call browser_open({ sessionId: "${sessionId}" }) first.`,
|
|
1110
1374
|
});
|
|
1111
1375
|
return;
|
|
1112
1376
|
}
|
|
@@ -1123,7 +1387,8 @@ async function handleBrowserCommand(data, emit) {
|
|
|
1123
1387
|
if (!session?.connected) {
|
|
1124
1388
|
emit('orchestrator:browser-result', data.cmdId, {
|
|
1125
1389
|
success: false,
|
|
1126
|
-
error:
|
|
1390
|
+
error: `No active browser session "${sessionId}". ` +
|
|
1391
|
+
`Call browser_open({ sessionId: "${sessionId}" }) first.`,
|
|
1127
1392
|
});
|
|
1128
1393
|
return;
|
|
1129
1394
|
}
|
|
@@ -1140,7 +1405,8 @@ async function handleBrowserCommand(data, emit) {
|
|
|
1140
1405
|
if (!session?.connected) {
|
|
1141
1406
|
emit('orchestrator:browser-result', data.cmdId, {
|
|
1142
1407
|
success: false,
|
|
1143
|
-
error:
|
|
1408
|
+
error: `No active browser session "${sessionId}". ` +
|
|
1409
|
+
`Call browser_open({ sessionId: "${sessionId}" }) first.`,
|
|
1144
1410
|
});
|
|
1145
1411
|
return;
|
|
1146
1412
|
}
|
|
@@ -1161,7 +1427,8 @@ async function handleBrowserCommand(data, emit) {
|
|
|
1161
1427
|
if (!session?.connected) {
|
|
1162
1428
|
emit('orchestrator:browser-result', data.cmdId, {
|
|
1163
1429
|
success: false,
|
|
1164
|
-
error:
|
|
1430
|
+
error: `No active browser session "${sessionId}". ` +
|
|
1431
|
+
`Call browser_open({ sessionId: "${sessionId}" }) first.`,
|
|
1165
1432
|
});
|
|
1166
1433
|
return;
|
|
1167
1434
|
}
|
|
@@ -1204,7 +1471,8 @@ async function handleBrowserCommand(data, emit) {
|
|
|
1204
1471
|
if (!session?.connected) {
|
|
1205
1472
|
emit('orchestrator:browser-result', data.cmdId, {
|
|
1206
1473
|
success: false,
|
|
1207
|
-
error:
|
|
1474
|
+
error: `No active browser session "${sessionId}". ` +
|
|
1475
|
+
`Call browser_open({ sessionId: "${sessionId}" }) first.`,
|
|
1208
1476
|
});
|
|
1209
1477
|
return;
|
|
1210
1478
|
}
|
|
@@ -1245,7 +1513,8 @@ async function handleBrowserCommand(data, emit) {
|
|
|
1245
1513
|
if (!session?.connected) {
|
|
1246
1514
|
emit('orchestrator:browser-result', data.cmdId, {
|
|
1247
1515
|
success: false,
|
|
1248
|
-
error:
|
|
1516
|
+
error: `No active browser session "${sessionId}". ` +
|
|
1517
|
+
`Call browser_open({ sessionId: "${sessionId}" }) first.`,
|
|
1249
1518
|
});
|
|
1250
1519
|
return;
|
|
1251
1520
|
}
|
|
@@ -1281,7 +1550,8 @@ async function handleBrowserCommand(data, emit) {
|
|
|
1281
1550
|
if (!session?.connected) {
|
|
1282
1551
|
emit('orchestrator:browser-result', data.cmdId, {
|
|
1283
1552
|
success: false,
|
|
1284
|
-
error:
|
|
1553
|
+
error: `No active browser session "${sessionId}". ` +
|
|
1554
|
+
`Call browser_open({ sessionId: "${sessionId}" }) first.`,
|
|
1285
1555
|
});
|
|
1286
1556
|
return;
|
|
1287
1557
|
}
|
|
@@ -1314,7 +1584,8 @@ async function handleBrowserCommand(data, emit) {
|
|
|
1314
1584
|
if (!session?.connected) {
|
|
1315
1585
|
emit('orchestrator:browser-result', data.cmdId, {
|
|
1316
1586
|
success: false,
|
|
1317
|
-
error:
|
|
1587
|
+
error: `No active browser session "${sessionId}". ` +
|
|
1588
|
+
`Call browser_open({ sessionId: "${sessionId}" }) first.`,
|
|
1318
1589
|
});
|
|
1319
1590
|
return;
|
|
1320
1591
|
}
|
|
@@ -1354,17 +1625,22 @@ async function handleBrowserCommand(data, emit) {
|
|
|
1354
1625
|
if (!session?.connected) {
|
|
1355
1626
|
emit('orchestrator:browser-result', data.cmdId, {
|
|
1356
1627
|
success: false,
|
|
1357
|
-
error:
|
|
1628
|
+
error: `No active browser session "${sessionId}". ` +
|
|
1629
|
+
`Call browser_open({ sessionId: "${sessionId}" }) first.`,
|
|
1358
1630
|
});
|
|
1359
1631
|
return;
|
|
1360
1632
|
}
|
|
1361
1633
|
const result = await session.submit(typeof data.selector === 'string' ? data.selector : undefined);
|
|
1634
|
+
const settleFrom = await settleContext(session);
|
|
1362
1635
|
await emitWithVision(emit, session, data.cmdId, {
|
|
1363
1636
|
success: result?.ok === true,
|
|
1364
1637
|
submitted: result?.ok === true,
|
|
1365
1638
|
...(result?.ok
|
|
1366
1639
|
? {}
|
|
1367
1640
|
: { error: result?.error ?? 'the form could not be submitted' }),
|
|
1641
|
+
}, {
|
|
1642
|
+
...settleFrom,
|
|
1643
|
+
rescreen: () => screenOf(session),
|
|
1368
1644
|
});
|
|
1369
1645
|
break;
|
|
1370
1646
|
}
|
|
@@ -1377,18 +1653,30 @@ async function handleBrowserCommand(data, emit) {
|
|
|
1377
1653
|
if (!session?.connected) {
|
|
1378
1654
|
emit('orchestrator:browser-result', data.cmdId, {
|
|
1379
1655
|
success: false,
|
|
1380
|
-
error:
|
|
1656
|
+
error: `No active browser session "${sessionId}". ` +
|
|
1657
|
+
`Call browser_open({ sessionId: "${sessionId}" }) first.`,
|
|
1381
1658
|
});
|
|
1382
1659
|
return;
|
|
1383
1660
|
}
|
|
1384
1661
|
const steps = data.actions;
|
|
1385
|
-
|
|
1662
|
+
// One window per identity (2026-11): the batch carries ONE session, so
|
|
1663
|
+
// a top-level `actor` applies to every step that does not name its own.
|
|
1664
|
+
// Without this, a batched login could not be attributed (or gated).
|
|
1665
|
+
const batchActor = typeof data.actor === 'string' && data.actor.trim()
|
|
1666
|
+
? data.actor.trim()
|
|
1667
|
+
: null;
|
|
1668
|
+
const resolvedSteps = (steps ?? []).map((step) => batchActor && !step.actor ? { ...step, actor: batchActor } : step);
|
|
1669
|
+
const settleFrom = await settleContext(session);
|
|
1670
|
+
const summary = await (0, batch_1.executeBatch)(session, resolvedSteps);
|
|
1386
1671
|
const screen = await screenOf(session);
|
|
1387
1672
|
await emitWithVision(emit, session, data.cmdId, {
|
|
1388
1673
|
success: true,
|
|
1389
1674
|
batch: true,
|
|
1390
1675
|
...summary,
|
|
1391
1676
|
...(screen ? { screen } : {}),
|
|
1677
|
+
}, {
|
|
1678
|
+
...settleFrom,
|
|
1679
|
+
rescreen: () => screenOf(session),
|
|
1392
1680
|
});
|
|
1393
1681
|
break;
|
|
1394
1682
|
}
|
|
@@ -1403,9 +1691,7 @@ async function handleBrowserCommand(data, emit) {
|
|
|
1403
1691
|
// never claims a termination that did not happen (the pre-check flag
|
|
1404
1692
|
// could say `true` while the kill silently failed).
|
|
1405
1693
|
const terminated = await manager.close(sessionId);
|
|
1406
|
-
|
|
1407
|
-
lastSessionByAgent.delete(identityKey);
|
|
1408
|
-
}
|
|
1694
|
+
forgetSession(identityKey, sessionId);
|
|
1409
1695
|
emit('orchestrator:browser-result', data.cmdId, {
|
|
1410
1696
|
success: true,
|
|
1411
1697
|
closed: true,
|