@poe-platform/safe-bash 0.1.714 → 0.1.716

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -295,6 +295,13 @@ Portable browser profiles restore blank tabs by default, preserving storage,
295
295
  settings, tab count, and selection. Only pass `tabRestoration: 'navigate'` to
296
296
  `restoreBrowserProfile` when replaying saved URLs is authorized; action URLs can
297
297
  repeat effects. Use `recovery: true` to also suppress configuration and provider scripts.
298
+ Owner-bound hosts can enable `namedSessionAttachment: true` on
299
+ `createPlaywrightCli({ adapter, persistence, ... })` to support `attach NAME`.
300
+ It selects an existing live session or restores its committed resumable profile
301
+ for subsequent invocations with the same `PLAYWRIGHT_CLI_SESSION` default,
302
+ including an authenticated agent ID different from the target name. Explicit
303
+ `-s=NAME` overrides selection; `detach` retains the browser, while `close` retires it. See the
304
+ [host capability contract](src/contracts/playwright-sessions.md#persistence).
298
305
 
299
306
  For Cloudflare Workers, start with the exported `cloudflareWorkerLimits` profile
300
307
  and configure command-family buffers at no more than 8 MiB. Create a separate
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/playwright/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAyD,KAAK,2BAA2B,EAAE,MAAM,2BAA2B,CAAC;AACpI,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAMpE,MAAM,MAAM,oBAAoB,GAAG,2BAA2B,GAAG;IAAE,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AAiBhG,2EAA2E;AAC3E,wBAAgB,mBAAmB,CAAC,OAAO,GAAE,oBAAyB;;;;;;;uBAiGsqhB,CAAC;;EAH5uhB;AAED,cAAc,2BAA2B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/playwright/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAyD,KAAK,2BAA2B,EAAE,MAAM,2BAA2B,CAAC;AACpI,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAMpE,MAAM,MAAM,oBAAoB,GAAG,2BAA2B,GAAG;IAAE,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AAiBhG,2EAA2E;AAC3E,wBAAgB,mBAAmB,CAAC,OAAO,GAAE,oBAAyB;;;;;;;uBAiGw1iB,CAAC;;EAH95iB;AAED,cAAc,2BAA2B,CAAC"}
@@ -8,6 +8,8 @@ export interface PlaywrightControllerOptions {
8
8
  readonly adapter?: PlaywrightAdapter;
9
9
  readonly abilities?: PlaywrightAbilities;
10
10
  readonly persistence?: PlaywrightSessionPersistence;
11
+ /** Opt in only when this controller and persistence are bound to one authenticated owner. */
12
+ readonly namedSessionAttachment?: boolean;
11
13
  readonly limits?: {
12
14
  readonly maxSessions?: number;
13
15
  readonly actionTimeoutMs?: number;
@@ -1 +1 @@
1
- {"version":3,"file":"controller.d.ts","sourceRoot":"","sources":["../../src/playwright/controller.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"controller.d.ts","sourceRoot":"","sources":["../../src/playwright/controller.ts"],"names":[],"mappings":"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"}
@@ -30,8 +30,10 @@ export function createPlaywrightController(options = {}) {
30
30
  throw new TypeError('Invalid Playwright configuration');
31
31
  if (options.adapter !== undefined && (!options.adapter || typeof options.adapter.acquire !== 'function'))
32
32
  throw new TypeError('An injected Playwright adapter is required');
33
- if (Object.keys(options).some(key => !['adapter', 'abilities', 'limits', 'billing', 'persistence'].includes(key)))
33
+ if (Object.keys(options).some(key => !['adapter', 'abilities', 'limits', 'billing', 'persistence', 'namedSessionAttachment'].includes(key)))
34
34
  throw new TypeError('Unsupported Playwright configuration');
35
+ if (options.namedSessionAttachment !== undefined && typeof options.namedSessionAttachment !== 'boolean')
36
+ throw new TypeError('Invalid named session attachment capability');
35
37
  if (options.persistence && ['restore', 'checkpoint', 'delete'].some(key => typeof Reflect.get(options.persistence, key) !== 'function'))
36
38
  throw new TypeError('Invalid Playwright persistence');
37
39
  if (options.persistence?.close !== undefined && typeof options.persistence.close !== 'function')
@@ -60,8 +62,14 @@ export function createPlaywrightController(options = {}) {
60
62
  if (!Number.isSafeInteger(value) || value < 1)
61
63
  throw new RangeError('Invalid Playwright limit');
62
64
  const sessions = new Map();
65
+ const pendingRestores = new Set();
66
+ const occupiedSessions = (except) => new Set([
67
+ ...[...sessions.values()].filter(session => session.state !== 'closed').map(session => session.name),
68
+ ...pendingRestores,
69
+ ].filter(name => name !== except)).size;
63
70
  const outcomes = new Map();
64
71
  const explicitlyClosed = new Set();
72
+ let attachedSession;
65
73
  let suppressUnknownRestores = false;
66
74
  const tails = new Map();
67
75
  const work = new Set();
@@ -371,7 +379,7 @@ export function createPlaywrightController(options = {}) {
371
379
  if (previous?.releasing)
372
380
  await previous.releasing;
373
381
  check();
374
- if ([...sessions.values()].filter(session => session.state !== 'closed').length >= maxSessions)
382
+ if (occupiedSessions(name) >= maxSessions)
375
383
  throw new PlaywrightResourceLimitError('Playwright session capacity exceeded');
376
384
  const epoch = Array.from(crypto.getRandomValues(new Uint32Array(4)), value => String(value).padStart(10, '0')).join('');
377
385
  const session = { name, generation: ++generation, state: 'acquiring', cleanups: new Set(),
@@ -387,6 +395,9 @@ export function createPlaywrightController(options = {}) {
387
395
  void release(session).catch(() => { }); };
388
396
  try {
389
397
  const restored = await acquire.call(request, { signal });
398
+ if ([...sessions.values()].some(other => other !== session && other.state !== 'closed' && other.lease
399
+ && (other.lease === restored.lease || other.lease.context === restored.lease?.context)))
400
+ throw new TypeError('Restored Playwright lease is already owned by another session');
390
401
  session.lease = restored.lease;
391
402
  if (restored.recovery === 'saved-storage')
392
403
  session.recovery = 'saved-storage';
@@ -449,6 +460,50 @@ export function createPlaywrightController(options = {}) {
449
460
  work.delete(operation);
450
461
  }
451
462
  };
463
+ // Called only while holding this name's queue. Persistence returns a fresh
464
+ // owned lease from its last committed resumable profile.
465
+ const restorePersistedSession = async (name, signal) => {
466
+ signal.throwIfAborted();
467
+ if (!options.persistence || explicitlyClosed.has(name) || suppressUnknownRestores && !sessions.has(name))
468
+ return;
469
+ const current = sessions.get(name);
470
+ if (current && current.state !== 'closed')
471
+ return;
472
+ if (current?.releasing)
473
+ await current.releasing;
474
+ if (occupiedSessions(name) >= maxSessions)
475
+ throw new PlaywrightResourceLimitError('Playwright session capacity exceeded');
476
+ pendingRestores.add(name);
477
+ try {
478
+ const restored = await options.persistence.restore({ name, signal });
479
+ if (!restored)
480
+ return;
481
+ let transferred = false;
482
+ try {
483
+ await restoreSession({ name, signal,
484
+ ...(restored.recovery === undefined ? {} : { recovery: restored.recovery }),
485
+ ...(restored.expiresAt === undefined ? {} : { expiresAt: restored.expiresAt }),
486
+ ...(restored.idleTimeoutMs === undefined ? {} : { idleTimeoutMs: restored.idleTimeoutMs }),
487
+ ...(restored.contextOptions === undefined ? {} : { contextOptions: restored.contextOptions }),
488
+ ...(restored.configuration === undefined ? {} : { configuration: restored.configuration }),
489
+ acquire: async () => { transferred = true; return restored; } }, true);
490
+ }
491
+ catch (error) {
492
+ if (!transferred) {
493
+ try {
494
+ await restored.lease.release();
495
+ }
496
+ catch (cleanupError) {
497
+ throw new AggregateError([error, cleanupError], 'Playwright restoration and cleanup failed');
498
+ }
499
+ }
500
+ throw error;
501
+ }
502
+ }
503
+ finally {
504
+ pendingRestores.delete(name);
505
+ }
506
+ };
452
507
  const run = async (invocation) => {
453
508
  const commandBudget = createPlaywrightCommandBudget(maxCommandBytes);
454
509
  let reportedError = false;
@@ -463,6 +518,9 @@ export function createPlaywrightController(options = {}) {
463
518
  if (lifetime.signal.aborted)
464
519
  throw new Error('Playwright controller is disposed');
465
520
  const parsed = parseInvocation(invocation, abilities, options.adapter);
521
+ const defaultSelection = invocation.env.PLAYWRIGHT_CLI_SESSION ?? 'default';
522
+ if ('session' in parsed && options.namedSessionAttachment && parsed.command !== 'attach' && !parsed.explicitSession && attachedSession?.selection === defaultSelection)
523
+ parsed.session = attachedSession.name;
466
524
  if (invocation.operationId !== undefined)
467
525
  validatePlaywrightSessionName(invocation.operationId);
468
526
  const operationId = invocation.operationId ?? (options.persistence?.recordOperation ? crypto.randomUUID() : undefined);
@@ -851,7 +909,7 @@ export function createPlaywrightController(options = {}) {
851
909
  const execute = async () => {
852
910
  check();
853
911
  if (parsed.command === 'help') {
854
- const help = formatPlaywrightHelp(parsed.topic, abilities);
912
+ const help = formatPlaywrightHelp(parsed.topic, abilities, options.namedSessionAttachment);
855
913
  await write(parsed.json ? JSON.stringify({ help: help.trimEnd() }, null, 2) + '\n' : help);
856
914
  check();
857
915
  return;
@@ -868,9 +926,49 @@ export function createPlaywrightController(options = {}) {
868
926
  throw new Error('only one of [name], --cdp, --endpoint, or --extension can be specified');
869
927
  if (!targets.length || parsed.options.extension === true)
870
928
  throw new Error('no target specified for attach command; use one of [name], --cdp, --endpoint, or --extension to specify the target to attach to.');
871
- throw new Error('An authenticated browser attachment broker is unavailable in this host');
929
+ if (!options.namedSessionAttachment)
930
+ throw new Error('An authenticated browser attachment broker is unavailable in this host');
931
+ if (Object.keys(parsed.options).length)
932
+ throw new Error('Only owner-scoped named session attachment is supported; attachment options are unavailable');
933
+ const name = parsed.args[0];
934
+ validatePlaywrightSessionName(name);
935
+ if (parsed.explicitSession && parsed.session !== name)
936
+ throw new Error('Attachment session must match the owned target name');
937
+ await enqueue(name, async () => {
938
+ check();
939
+ await retireExpired();
940
+ if (explicitlyClosed.has(name))
941
+ throw new Error(`Session closed: ${name}; reopen explicitly`);
942
+ const wasLive = sessions.get(name)?.state === 'open';
943
+ if (!wasLive && options.persistence?.list && !(await savedSessions(local.signal)).some(saved => saved.name === name))
944
+ throw new Error(`No resumable owned session: ${name}`);
945
+ await restorePersistedSession(name, local.signal);
946
+ const session = sessions.get(name);
947
+ if (!session || session.state !== 'open')
948
+ throw new Error(`No resumable owned session: ${name}`);
949
+ if (!wasLive)
950
+ active = session;
951
+ checkSession(session);
952
+ // Selection owns no lease and does not reset or checkpoint live state.
953
+ await write(parsed.json ? JSON.stringify({ session: name, status: 'attached' }, null, 2) + '\n' : `Browser '${name}' attached.\n`);
954
+ checkSession(session);
955
+ attachedSession = { name, selection: defaultSelection };
956
+ retained = true;
957
+ });
958
+ return;
872
959
  }
873
960
  if (parsed.command === 'detach' && !ability.execute) {
961
+ if (options.namedSessionAttachment) {
962
+ await enqueue(parsed.session, async () => {
963
+ check();
964
+ const attached = attachedSession?.name === parsed.session;
965
+ await write(parsed.json ? JSON.stringify({ session: parsed.session, status: attached ? 'detached' : 'not-attached' }, null, 2) + '\n'
966
+ : `Browser '${parsed.session}' is ${attached ? 'detached' : 'not attached'}.\n`);
967
+ if (attachedSession?.name === parsed.session)
968
+ attachedSession = undefined;
969
+ });
970
+ return;
971
+ }
874
972
  const known = sessions.has(parsed.session) || (await savedSessions(local.signal)).some(session => session.name === parsed.session);
875
973
  if (known)
876
974
  throw new Error(parsed.json ? `session '${parsed.session}' was not attached; use close to stop it.` : `session '${parsed.session}' was not attached; use \`playwright-cli${parsed.session === 'default' ? '' : ` -s=${parsed.session}`} close\` to stop it.`);
@@ -912,6 +1010,7 @@ export function createPlaywrightController(options = {}) {
912
1010
  if ((parsed.command === 'close-all' || parsed.command === 'kill-all') && !ability.execute) {
913
1011
  const saved = await savedSessions(local.signal);
914
1012
  suppressUnknownRestores = true;
1013
+ attachedSession = undefined;
915
1014
  const closed = [...new Set([...saved.map(session => session.name), ...[...sessions.values()].filter(session => session.state === 'open').map(session => session.name)])];
916
1015
  if (parsed.command === 'kill-all')
917
1016
  for (const session of sessions.values())
@@ -919,6 +1018,8 @@ export function createPlaywrightController(options = {}) {
919
1018
  const results = await Promise.allSettled([...new Set([...sessions.keys(), ...tails.keys()])].map(name => enqueue(name, async () => {
920
1019
  check();
921
1020
  explicitlyClosed.add(name);
1021
+ if (attachedSession?.name === name)
1022
+ attachedSession = undefined;
922
1023
  const session = sessions.get(name);
923
1024
  if (session)
924
1025
  await retireForCommand(session, parsed.command === 'kill-all');
@@ -952,33 +1053,8 @@ export function createPlaywrightController(options = {}) {
952
1053
  let commandFailure;
953
1054
  await (async () => {
954
1055
  check();
955
- if (options.persistence && ability.scope === 'session' && !['open', 'close', 'close-all', 'list', 'delete-data'].includes(parsed.command)
956
- && !explicitlyClosed.has(parsed.session) && (!suppressUnknownRestores || sessions.has(parsed.session))
957
- && (!sessions.has(parsed.session) || sessions.get(parsed.session).state === 'closed')) {
958
- const restored = await options.persistence.restore({ name: parsed.session, signal: local.signal });
959
- if (restored) {
960
- let transferred = false;
961
- try {
962
- await restoreSession({ name: parsed.session, signal: local.signal,
963
- ...(restored.recovery === undefined ? {} : { recovery: restored.recovery }),
964
- ...(restored.expiresAt === undefined ? {} : { expiresAt: restored.expiresAt }),
965
- ...(restored.idleTimeoutMs === undefined ? {} : { idleTimeoutMs: restored.idleTimeoutMs }),
966
- ...(restored.contextOptions === undefined ? {} : { contextOptions: restored.contextOptions }),
967
- ...(restored.configuration === undefined ? {} : { configuration: restored.configuration }),
968
- acquire: async () => { transferred = true; return restored; } }, true);
969
- }
970
- catch (error) {
971
- if (!transferred) {
972
- try {
973
- await restored.lease.release();
974
- }
975
- catch (cleanupError) {
976
- throw new AggregateError([error, cleanupError], 'Playwright restoration and cleanup failed');
977
- }
978
- }
979
- throw error;
980
- }
981
- }
1056
+ if (ability.scope === 'session' && !['open', 'close', 'close-all', 'list', 'delete-data'].includes(parsed.command)) {
1057
+ await restorePersistedSession(parsed.session, local.signal);
982
1058
  check();
983
1059
  }
984
1060
  const current = sessions.get(parsed.session);
@@ -1108,6 +1184,8 @@ export function createPlaywrightController(options = {}) {
1108
1184
  const session = sessions.get(parsed.session);
1109
1185
  const wasOpen = session?.state === 'open' || (await savedSessions(local.signal)).some(saved => saved.name === parsed.session);
1110
1186
  explicitlyClosed.add(parsed.session);
1187
+ if (attachedSession?.name === parsed.session)
1188
+ attachedSession = undefined;
1111
1189
  const errors = [];
1112
1190
  try {
1113
1191
  if (session)
@@ -1134,6 +1212,8 @@ export function createPlaywrightController(options = {}) {
1134
1212
  if (parsed.command === 'delete-data') {
1135
1213
  const session = sessions.get(parsed.session);
1136
1214
  explicitlyClosed.add(parsed.session);
1215
+ if (attachedSession?.name === parsed.session)
1216
+ attachedSession = undefined;
1137
1217
  const errors = [];
1138
1218
  try {
1139
1219
  if (session && session.state !== 'closed')
@@ -1172,7 +1252,7 @@ export function createPlaywrightController(options = {}) {
1172
1252
  check();
1173
1253
  // Preflight and reservation are synchronous: another session cannot
1174
1254
  // claim this slot during retirement's asynchronous boundary.
1175
- const occupied = [...sessions.values()].filter(s => s.state !== 'closed').length;
1255
+ const occupied = occupiedSessions();
1176
1256
  if (occupied >= maxSessions)
1177
1257
  throw new PlaywrightResourceLimitError('Playwright session capacity exceeded');
1178
1258
  active = { name: parsed.session, generation: ++generation, state: 'acquiring', snapshot: createSnapshotEngine({ maxSnapshotBytes, maxSnapshotRefs }, () => `e${++refSequence}`), cleanups: new Set(), idleTimeoutMs: openOptions.idleTimeoutMs, contextOptions: openOptions.contextOptions };