@tomflow/proflow-execution-browser-extension 0.1.46 → 0.1.48

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.
@@ -14,11 +14,13 @@ export type RunningBridgeProbe = {
14
14
  identity?: {
15
15
  extensionId: string;
16
16
  extensionInstanceId: string;
17
+ moduleVersion: string;
17
18
  };
18
19
  };
19
20
  export declare function classifyBrowserLiveSetup(input: {
20
21
  baseSetupReady: boolean;
21
22
  evidenceInstanceId?: string;
23
+ expectedModuleVersion: string;
22
24
  bridgeProbe: RunningBridgeProbe;
23
25
  }): {
24
26
  readonly setupReady: boolean;
@@ -26,6 +28,9 @@ export declare function classifyBrowserLiveSetup(input: {
26
28
  } | {
27
29
  setupReady: boolean;
28
30
  issueCode: "EXTENSION_SESSION_OFFLINE";
31
+ } | {
32
+ setupReady: boolean;
33
+ issueCode: "EXTENSION_VERSION_MISMATCH";
29
34
  } | {
30
35
  setupReady: boolean;
31
36
  issueCode: "EXTENSION_SESSION_REVALIDATION_REQUIRED";
@@ -39,6 +44,7 @@ export declare function pairBrowserExtensionSetup(context: ModuleCommandContext,
39
44
  }): Promise<{
40
45
  extensionId: string;
41
46
  extensionInstanceId: string;
47
+ moduleVersion: string;
42
48
  }>;
43
49
  export declare const behaviorAdapter: {
44
50
  readonly install: (context: ModuleCommandContext) => Promise<{
@@ -47,7 +53,7 @@ export declare const behaviorAdapter: {
47
53
  ok: true;
48
54
  status: "SUCCEEDED";
49
55
  moduleRef: "execution-browser-extension";
50
- moduleVersion: "0.1.46";
56
+ moduleVersion: "0.1.48";
51
57
  data: {
52
58
  loadDir: string;
53
59
  };
@@ -60,7 +66,7 @@ export declare const behaviorAdapter: {
60
66
  readonly ok: true;
61
67
  readonly status: "SUCCEEDED";
62
68
  readonly moduleRef: "execution-browser-extension";
63
- readonly moduleVersion: "0.1.46";
69
+ readonly moduleVersion: "0.1.48";
64
70
  };
65
71
  observedEffects: never[];
66
72
  }>;
@@ -70,7 +76,7 @@ export declare const behaviorAdapter: {
70
76
  ok: true;
71
77
  status: "SUCCEEDED";
72
78
  moduleRef: "execution-browser-extension";
73
- moduleVersion: "0.1.46";
79
+ moduleVersion: "0.1.48";
74
80
  data: {
75
81
  setupStatus: "ACTION_REQUIRED" | "READY";
76
82
  runtimeStatus: "NOT_APPLICABLE";
@@ -82,7 +88,7 @@ export declare const behaviorAdapter: {
82
88
  nextCommand: string;
83
89
  } | {
84
90
  scope: "SETUP";
85
- code: "EXTENSION_SESSION_OFFLINE" | "EXTENSION_SESSION_REVALIDATION_REQUIRED" | undefined;
91
+ code: "EXTENSION_SESSION_OFFLINE" | "EXTENSION_SESSION_REVALIDATION_REQUIRED" | "EXTENSION_VERSION_MISMATCH" | undefined;
86
92
  message: string;
87
93
  relatedModuleRefs: string[];
88
94
  nextCommand: string;
@@ -95,7 +101,7 @@ export declare const behaviorAdapter: {
95
101
  result: {
96
102
  contract: "deployment.result.v1";
97
103
  moduleRef: "execution-browser-extension";
98
- moduleVersion: "0.1.46";
104
+ moduleVersion: "0.1.48";
99
105
  ok: false;
100
106
  status: "ACTION_REQUIRED";
101
107
  data: {
@@ -127,14 +133,14 @@ export declare const behaviorAdapter: {
127
133
  readonly ok: true;
128
134
  readonly status: "SUCCEEDED";
129
135
  readonly moduleRef: "execution-browser-extension";
130
- readonly moduleVersion: "0.1.46";
136
+ readonly moduleVersion: "0.1.48";
131
137
  };
132
138
  observedEffects: string[];
133
139
  } | {
134
140
  result: {
135
141
  contract: "deployment.result.v1";
136
142
  moduleRef: "execution-browser-extension";
137
- moduleVersion: "0.1.46";
143
+ moduleVersion: "0.1.48";
138
144
  ok: false;
139
145
  status: "FAILED";
140
146
  error: {
@@ -167,7 +173,7 @@ export declare const behaviorAdapter: {
167
173
  ok: true;
168
174
  status: "SUCCEEDED";
169
175
  moduleRef: "execution-browser-extension";
170
- moduleVersion: "0.1.46";
176
+ moduleVersion: "0.1.48";
171
177
  data: {
172
178
  docs: string;
173
179
  };
@@ -180,11 +186,11 @@ export declare const behaviorAdapter: {
180
186
  readonly ok: true;
181
187
  readonly status: "SUCCEEDED";
182
188
  readonly moduleRef: "execution-browser-extension";
183
- readonly moduleVersion: "0.1.46";
189
+ readonly moduleVersion: "0.1.48";
184
190
  } | {
185
191
  contract: "deployment.result.v1";
186
192
  moduleRef: "execution-browser-extension";
187
- moduleVersion: "0.1.46";
193
+ moduleVersion: "0.1.48";
188
194
  ok: false;
189
195
  status: "FAILED";
190
196
  error: {
@@ -201,7 +207,7 @@ export declare const behaviorAdapter: {
201
207
  readonly ok: true;
202
208
  readonly status: "SUCCEEDED";
203
209
  readonly moduleRef: "execution-browser-extension";
204
- readonly moduleVersion: "0.1.46";
210
+ readonly moduleVersion: "0.1.48";
205
211
  };
206
212
  observedEffects: never[];
207
213
  }>;
@@ -263,6 +263,8 @@ async function materializeExecutorConfig(context, setup, prepared) {
263
263
  });
264
264
  }
265
265
  async function persistPairedBrowserExtension(context, prepared, identity) {
266
+ if (identity.moduleVersion !== descriptor.moduleVersion)
267
+ throw new Error("EXTENSION_VERSION_MISMATCH");
266
268
  const setup = { extensionId: identity.extensionId };
267
269
  await writeFile(setupFile(context), `${JSON.stringify(setup, null, 2)}\n`, {
268
270
  mode: 0o600,
@@ -270,7 +272,7 @@ async function persistPairedBrowserExtension(context, prepared, identity) {
270
272
  await materializeExecutorConfig(context, setup, prepared);
271
273
  const evidence = {
272
274
  contract: "proflow.browser-extension-verification.v1",
273
- moduleVersion: descriptor.moduleVersion,
275
+ moduleVersion: identity.moduleVersion,
274
276
  loadDir: prepared.loadDir,
275
277
  extensionId: identity.extensionId,
276
278
  extensionInstanceId: identity.extensionInstanceId,
@@ -289,6 +291,11 @@ export function classifyBrowserLiveSetup(input) {
289
291
  setupReady: false,
290
292
  issueCode: "EXTENSION_SESSION_OFFLINE",
291
293
  };
294
+ if (input.bridgeProbe.identity.moduleVersion !== input.expectedModuleVersion)
295
+ return {
296
+ setupReady: false,
297
+ issueCode: "EXTENSION_VERSION_MISMATCH",
298
+ };
292
299
  if (input.bridgeProbe.identity.extensionInstanceId !== input.evidenceInstanceId)
293
300
  return {
294
301
  setupReady: false,
@@ -321,12 +328,15 @@ async function probeRunningBridgeSession(prepared, extensionId, timeoutMs) {
321
328
  if (Reflect.get(body, "online") !== true)
322
329
  return { kind: "PRESENT" };
323
330
  const extensionInstanceId = Reflect.get(body, "extensionInstanceId");
331
+ const moduleVersion = Reflect.get(body, "moduleVersion");
324
332
  if (typeof extensionInstanceId !== "string" ||
325
- extensionInstanceId.length === 0)
333
+ extensionInstanceId.length === 0 ||
334
+ typeof moduleVersion !== "string" ||
335
+ moduleVersion.length === 0)
326
336
  throw new Error("RUNNING_BRIDGE_STATUS_INVALID");
327
337
  return {
328
338
  kind: "PRESENT",
329
- identity: { extensionId, extensionInstanceId },
339
+ identity: { extensionId, extensionInstanceId, moduleVersion },
330
340
  };
331
341
  }
332
342
  catch (error) {
@@ -454,6 +464,7 @@ export const behaviorAdapter = {
454
464
  const live = classifyBrowserLiveSetup({
455
465
  baseSetupReady,
456
466
  ...(evidence ? { evidenceInstanceId: evidence.extensionInstanceId } : {}),
467
+ expectedModuleVersion: descriptor.moduleVersion,
457
468
  bridgeProbe,
458
469
  });
459
470
  const issue = !baseSetupReady
@@ -471,7 +482,9 @@ export const behaviorAdapter = {
471
482
  code: live.issueCode,
472
483
  message: live.issueCode === "EXTENSION_SESSION_OFFLINE"
473
484
  ? "Chrome 扩展已加载,但当前运行会话未在线"
474
- : "Chrome 扩展已重新启动,需要自动刷新当前运行会话证据",
485
+ : live.issueCode === "EXTENSION_VERSION_MISMATCH"
486
+ ? "Chrome 当前运行的扩展版本与 Workspace 物化版本不一致"
487
+ : "Chrome 扩展已重新启动,需要自动刷新当前运行会话证据",
475
488
  relatedModuleRefs: ["execution-runtime"],
476
489
  nextCommand: "platform setup",
477
490
  };
@@ -3,7 +3,7 @@ export declare const descriptor: {
3
3
  readonly contractVersion: "1.0.0";
4
4
  readonly moduleRef: "execution-browser-extension";
5
5
  readonly packageName: "@tomflow/proflow-execution-browser-extension";
6
- readonly moduleVersion: "0.1.46";
6
+ readonly moduleVersion: "0.1.48";
7
7
  readonly kind: "browser-extension";
8
8
  readonly templateVersion: "1.0.0";
9
9
  readonly platformCompatibility: ">=1.0.0 <2.0.0";
@@ -3,7 +3,7 @@ export const descriptor = {
3
3
  contractVersion: "1.0.0",
4
4
  moduleRef: "execution-browser-extension",
5
5
  packageName: "@tomflow/proflow-execution-browser-extension",
6
- moduleVersion: "0.1.46",
6
+ moduleVersion: "0.1.48",
7
7
  kind: "browser-extension",
8
8
  templateVersion: "1.0.0",
9
9
  platformCompatibility: ">=1.0.0 <2.0.0",
@@ -13844,6 +13844,19 @@ function createCollaborationCarrierApplication(options) {
13844
13844
  }
13845
13845
 
13846
13846
  // packages/execution-browser-extension/src/recovery-trigger.ts
13847
+ async function boundedRecoveryObservation(observe, timeoutMs) {
13848
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0)
13849
+ throw new TypeError("RECOVERY_OBSERVATION_TIMEOUT_INVALID");
13850
+ let timer;
13851
+ const timeout = new Promise((resolve) => {
13852
+ timer = setTimeout(() => resolve(null), timeoutMs);
13853
+ });
13854
+ try {
13855
+ return await Promise.race([observe().catch(() => null), timeout]);
13856
+ } finally {
13857
+ if (timer !== void 0) clearTimeout(timer);
13858
+ }
13859
+ }
13847
13860
  function shouldTriggerObserverRecovery(previous, current) {
13848
13861
  if (current.pageState !== "IDLE") return false;
13849
13862
  if (!previous) return true;
@@ -14193,6 +14206,7 @@ function createTaskObserver(options) {
14193
14206
 
14194
14207
  // packages/execution-browser-extension/extension/background.ts
14195
14208
  var extensionInstanceId = `extension:${crypto.randomUUID()}`;
14209
+ var extensionModuleVersion = chrome.runtime.getManifest().version;
14196
14210
  var sessions = /* @__PURE__ */ new Map();
14197
14211
  var browserOpenObservationGate = createBrowserOpenObservationGate();
14198
14212
  var permissionHandling = /* @__PURE__ */ new Map();
@@ -14202,6 +14216,7 @@ var carrierContinuationControl = createCarrierContinuationControl();
14202
14216
  var BROWSER_CARRIER_KEEPALIVE_KEY = "proflowBrowserSnapshot";
14203
14217
  var BROWSER_CARRIER_KEEPALIVE_MS = 2e4;
14204
14218
  var BROWSER_BRIDGE_FETCH_TIMEOUT_MS = 5e3;
14219
+ var BROWSER_RECOVERY_SNAPSHOT_TIMEOUT_MS = 1e3;
14205
14220
  var snapshotPersistence = Promise.resolve();
14206
14221
  var transientPermissionAttemptsRestore = null;
14207
14222
  var sleep = (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds));
@@ -14946,23 +14961,31 @@ function processContentObservation(observed, triggerRecovery) {
14946
14961
  async function rebuildCarrierAttentionsFromTabs() {
14947
14962
  const consumedDenials = [];
14948
14963
  const tabs = await chrome.tabs.query({ url: "https://chatgpt.com/g/*" });
14949
- for (const tab of tabs) {
14950
- if (tab.id === void 0 || tab.windowId === void 0) continue;
14951
- try {
14952
- const response = await chrome.tabs.sendMessage(tab.id, {
14953
- type: "PROFLOW_PAGE_SNAPSHOT_REQUEST"
14954
- });
14955
- if (!isRecord(response) || response.ok !== true) continue;
14956
- const observed = parseSnapshotObservation(response.value, tab);
14957
- if (observed) {
14958
- processContentObservation(observed, false);
14959
- const consumed = carrierContinuationControl.consumeRecovery(
14960
- void 0,
14961
- observed
14962
- );
14963
- if (consumed) consumedDenials.push(consumed);
14964
- }
14965
- } catch {
14964
+ const snapshots = await Promise.all(
14965
+ tabs.map(async (tab) => {
14966
+ const tabId = tab.id;
14967
+ if (tabId === void 0 || tab.windowId === void 0) return null;
14968
+ const response = await boundedRecoveryObservation(
14969
+ () => chrome.tabs.sendMessage(tabId, {
14970
+ type: "PROFLOW_PAGE_SNAPSHOT_REQUEST"
14971
+ }),
14972
+ BROWSER_RECOVERY_SNAPSHOT_TIMEOUT_MS
14973
+ );
14974
+ return { tab, response };
14975
+ })
14976
+ );
14977
+ for (const snapshot of snapshots) {
14978
+ if (!snapshot) continue;
14979
+ const { tab, response } = snapshot;
14980
+ if (!isRecord(response) || response.ok !== true) continue;
14981
+ const observed = parseSnapshotObservation(response.value, tab);
14982
+ if (observed) {
14983
+ processContentObservation(observed, false);
14984
+ const consumed = carrierContinuationControl.consumeRecovery(
14985
+ void 0,
14986
+ observed
14987
+ );
14988
+ if (consumed) consumedDenials.push(consumed);
14966
14989
  }
14967
14990
  }
14968
14991
  await persistSnapshot();
@@ -15266,7 +15289,8 @@ async function runBridgeLoop() {
15266
15289
  method: "POST",
15267
15290
  body: JSON.stringify({
15268
15291
  extensionId: chrome.runtime.id,
15269
- extensionInstanceId
15292
+ extensionInstanceId,
15293
+ moduleVersion: extensionModuleVersion
15270
15294
  })
15271
15295
  });
15272
15296
  if (!hello.ok) throw new Error("BRIDGE_HELLO_REJECTED");
@@ -217,6 +217,7 @@ export async function createBrowserRealityBridgeServer(options) {
217
217
  sessionOnline: sessionOnline(),
218
218
  commandConsumerReady: commandConsumerReady(),
219
219
  extensionInstanceId: session?.extensionInstanceId ?? null,
220
+ moduleVersion: session?.moduleVersion ?? null,
220
221
  queuedCommands: queue.length,
221
222
  pendingCommands: pending.size,
222
223
  lastCommandPollAt,
@@ -317,7 +318,9 @@ export async function createBrowserRealityBridgeServer(options) {
317
318
  stringField(body, "extensionId") !== options.extensionId)
318
319
  throw new BrowserRealityBridgeError("BRIDGE_AUTH_INVALID", "extension identity mismatch");
319
320
  const extensionInstanceId = stringField(body, "extensionInstanceId");
320
- if (session?.extensionInstanceId !== extensionInstanceId) {
321
+ const moduleVersion = stringField(body, "moduleVersion");
322
+ if (session?.extensionInstanceId !== extensionInstanceId ||
323
+ session?.moduleVersion !== moduleVersion) {
321
324
  lastCommandPollAt = null;
322
325
  lastCommandDeliveredAt = null;
323
326
  lastCommandResultAt = null;
@@ -325,6 +328,7 @@ export async function createBrowserRealityBridgeServer(options) {
325
328
  }
326
329
  session = {
327
330
  extensionInstanceId,
331
+ moduleVersion,
328
332
  lastHeartbeatAt: now().getTime(),
329
333
  };
330
334
  send(response, 200, { accepted: true });
@@ -336,6 +340,7 @@ export async function createBrowserRealityBridgeServer(options) {
336
340
  sessionOnline: sessionOnline(),
337
341
  commandConsumerReady: commandConsumerReady(),
338
342
  extensionInstanceId: session?.extensionInstanceId ?? null,
343
+ moduleVersion: session?.moduleVersion ?? null,
339
344
  });
340
345
  return;
341
346
  }
@@ -14,6 +14,7 @@ export type BrowserExtensionPair = (context: {
14
14
  }) => Promise<{
15
15
  extensionId: string;
16
16
  extensionInstanceId: string;
17
+ moduleVersion: string;
17
18
  }>;
18
19
  export declare function browserExtensionInstallInstruction(loadDir: string): string;
19
20
  export declare function browserExtensionSetupSuccessMessage(): string;
@@ -27,4 +28,5 @@ export declare function runInteractiveBrowserExtensionSetup(input: {
27
28
  }): Promise<{
28
29
  extensionId: string;
29
30
  extensionInstanceId: string;
31
+ moduleVersion: string;
30
32
  }>;
@@ -7,6 +7,7 @@ export interface BrowserExtensionPairingOptions {
7
7
  export type BrowserExtensionPairingResult = {
8
8
  extensionId: string;
9
9
  extensionInstanceId: string;
10
+ moduleVersion: string;
10
11
  };
11
12
  export declare function createBrowserExtensionPairingServer(options: BrowserExtensionPairingOptions): Promise<Readonly<{
12
13
  endpoint: string;
@@ -14,6 +15,7 @@ export declare function createBrowserExtensionPairingServer(options: BrowserExte
14
15
  paired: boolean;
15
16
  extensionId: string | null;
16
17
  extensionInstanceId: string | null;
18
+ moduleVersion: string | null;
17
19
  };
18
20
  waitForPairing(): Promise<BrowserExtensionPairingResult>;
19
21
  close(): Promise<void>;
@@ -93,12 +93,13 @@ export async function createBrowserExtensionPairingServer(options) {
93
93
  const body = await readJson(request);
94
94
  const extensionId = text(body.extensionId);
95
95
  const extensionInstanceId = text(body.extensionInstanceId);
96
+ const moduleVersion = text(body.moduleVersion);
96
97
  if (!originId || extensionId !== originId)
97
98
  throw new Error("PAIRING_AUTH_INVALID");
98
99
  if (identity && identity.extensionId !== extensionId) {
99
100
  throw new Error("PAIRING_AUTH_INVALID");
100
101
  }
101
- identity = { extensionId, extensionInstanceId };
102
+ identity = { extensionId, extensionInstanceId, moduleVersion };
102
103
  send(response, 200, { accepted: true });
103
104
  return;
104
105
  }
@@ -160,6 +161,7 @@ export async function createBrowserExtensionPairingServer(options) {
160
161
  paired: paired !== undefined,
161
162
  extensionId: identity?.extensionId ?? null,
162
163
  extensionInstanceId: identity?.extensionInstanceId ?? null,
164
+ moduleVersion: identity?.moduleVersion ?? null,
163
165
  };
164
166
  },
165
167
  waitForPairing() {
@@ -4,5 +4,6 @@ type RecoveryObservation = {
4
4
  pageState: "IDLE" | "BUSY" | "BLOCKED" | "UNKNOWN";
5
5
  activityKind: string | null;
6
6
  };
7
+ export declare function boundedRecoveryObservation<Value>(observe: () => Promise<Value>, timeoutMs: number): Promise<Value | null>;
7
8
  export declare function shouldTriggerObserverRecovery(previous: RecoveryObservation | undefined, current: RecoveryObservation): boolean;
8
9
  export {};
@@ -1,3 +1,18 @@
1
+ export async function boundedRecoveryObservation(observe, timeoutMs) {
2
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0)
3
+ throw new TypeError("RECOVERY_OBSERVATION_TIMEOUT_INVALID");
4
+ let timer;
5
+ const timeout = new Promise((resolve) => {
6
+ timer = setTimeout(() => resolve(null), timeoutMs);
7
+ });
8
+ try {
9
+ return await Promise.race([observe().catch(() => null), timeout]);
10
+ }
11
+ finally {
12
+ if (timer !== undefined)
13
+ clearTimeout(timer);
14
+ }
15
+ }
1
16
  export function shouldTriggerObserverRecovery(previous, current) {
2
17
  if (current.pageState !== "IDLE")
3
18
  return false;
@@ -18,7 +18,10 @@ import {
18
18
  resolveRoutineCarrierPermission,
19
19
  } from "../src/carrier-permission-lifecycle.js";
20
20
  import { createCollaborationCarrierApplication } from "../src/collaboration-carrier.js";
21
- import { shouldTriggerObserverRecovery } from "../src/recovery-trigger.js";
21
+ import {
22
+ boundedRecoveryObservation,
23
+ shouldTriggerObserverRecovery,
24
+ } from "../src/recovery-trigger.js";
22
25
  import {
23
26
  createSystemObserver,
24
27
  type SystemObserverReasonFailure,
@@ -129,6 +132,7 @@ type ChromeTab = {
129
132
  type ChromeRuntime = {
130
133
  runtime: {
131
134
  id: string;
135
+ getManifest(): { version: string };
132
136
  getURL(path: string): string;
133
137
  onMessage: {
134
138
  addListener(
@@ -183,6 +187,7 @@ type ChromeRuntime = {
183
187
  declare const chrome: ChromeRuntime;
184
188
 
185
189
  const extensionInstanceId = `extension:${crypto.randomUUID()}`;
190
+ const extensionModuleVersion = chrome.runtime.getManifest().version;
186
191
  const sessions = new Map<number, ContentObservation>();
187
192
  const browserOpenObservationGate = createBrowserOpenObservationGate();
188
193
  const permissionHandling = new Map<number, string>();
@@ -192,6 +197,7 @@ const carrierContinuationControl = createCarrierContinuationControl();
192
197
  const BROWSER_CARRIER_KEEPALIVE_KEY = "proflowBrowserSnapshot";
193
198
  const BROWSER_CARRIER_KEEPALIVE_MS = 20_000;
194
199
  const BROWSER_BRIDGE_FETCH_TIMEOUT_MS = 5_000;
200
+ const BROWSER_RECOVERY_SNAPSHOT_TIMEOUT_MS = 1_000;
195
201
  let snapshotPersistence = Promise.resolve();
196
202
  let transientPermissionAttemptsRestore: Promise<boolean> | null = null;
197
203
 
@@ -1235,24 +1241,32 @@ async function rebuildCarrierAttentionsFromTabs(): Promise<
1235
1241
  > {
1236
1242
  const consumedDenials: CarrierContinuationDenial[] = [];
1237
1243
  const tabs = await chrome.tabs.query({ url: "https://chatgpt.com/g/*" });
1238
- for (const tab of tabs) {
1239
- if (tab.id === undefined || tab.windowId === undefined) continue;
1240
- try {
1241
- const response = await chrome.tabs.sendMessage(tab.id, {
1242
- type: "PROFLOW_PAGE_SNAPSHOT_REQUEST",
1243
- });
1244
- if (!isRecord(response) || response.ok !== true) continue;
1245
- const observed = parseSnapshotObservation(response.value, tab);
1246
- if (observed) {
1247
- processContentObservation(observed, false);
1248
- const consumed = carrierContinuationControl.consumeRecovery(
1249
- undefined,
1250
- observed,
1251
- );
1252
- if (consumed) consumedDenials.push(consumed);
1253
- }
1254
- } catch {
1255
- // A tab without a live content receiver is not reconstructed from guesses.
1244
+ const snapshots = await Promise.all(
1245
+ tabs.map(async (tab) => {
1246
+ const tabId = tab.id;
1247
+ if (tabId === undefined || tab.windowId === undefined) return null;
1248
+ const response = await boundedRecoveryObservation(
1249
+ () =>
1250
+ chrome.tabs.sendMessage(tabId, {
1251
+ type: "PROFLOW_PAGE_SNAPSHOT_REQUEST",
1252
+ }),
1253
+ BROWSER_RECOVERY_SNAPSHOT_TIMEOUT_MS,
1254
+ );
1255
+ return { tab, response };
1256
+ }),
1257
+ );
1258
+ for (const snapshot of snapshots) {
1259
+ if (!snapshot) continue;
1260
+ const { tab, response } = snapshot;
1261
+ if (!isRecord(response) || response.ok !== true) continue;
1262
+ const observed = parseSnapshotObservation(response.value, tab);
1263
+ if (observed) {
1264
+ processContentObservation(observed, false);
1265
+ const consumed = carrierContinuationControl.consumeRecovery(
1266
+ undefined,
1267
+ observed,
1268
+ );
1269
+ if (consumed) consumedDenials.push(consumed);
1256
1270
  }
1257
1271
  }
1258
1272
  await persistSnapshot();
@@ -1625,6 +1639,7 @@ async function runBridgeLoop() {
1625
1639
  body: JSON.stringify({
1626
1640
  extensionId: chrome.runtime.id,
1627
1641
  extensionInstanceId,
1642
+ moduleVersion: extensionModuleVersion,
1628
1643
  }),
1629
1644
  });
1630
1645
  if (!hello.ok) throw new Error("BRIDGE_HELLO_REJECTED");
package/manifest.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "manifest_version": 3,
3
3
  "name": "ProFlow Execution Browser",
4
- "version": "0.1.46",
4
+ "version": "0.1.48",
5
5
  "permissions": [
6
6
  "tabs",
7
7
  "storage"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tomflow/proflow-execution-browser-extension",
3
- "version": "0.1.46",
3
+ "version": "0.1.48",
4
4
  "description": "Execution-owned MV3 Browser executor, evidence provider and browser application surface.",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -3,7 +3,7 @@
3
3
  "contractVersion": "1.0.0",
4
4
  "moduleRef": "execution-browser-extension",
5
5
  "packageName": "@tomflow/proflow-execution-browser-extension",
6
- "moduleVersion": "0.1.46",
6
+ "moduleVersion": "0.1.48",
7
7
  "kind": "browser-extension",
8
8
  "templateVersion": "1.0.0",
9
9
  "platformCompatibility": ">=1.0.0 <2.0.0",
package/CHANGELOG.md DELETED
@@ -1,43 +0,0 @@
1
- # @tomflow/proflow-execution-browser-extension
2
-
3
- ## 0.1.46
4
-
5
- ### Patch Changes
6
-
7
- - Allow the durable TASK_RESUMED observer signal to use the bounded worker.wake trigger contract while preserving arbitrary-trigger rejection and browser delivery verification.
8
-
9
- ## 0.1.45
10
-
11
- ### Patch Changes
12
-
13
- - Release the verified Real-3 B1 task recovery, exact execution identity, durable observer resume, bounded gateway error, and matching Agent Action contract fixes.
14
-
15
- ## 0.1.44
16
-
17
- ### Patch Changes
18
-
19
- - Prefer the canonical ChatGPT prompt editor over earlier fallback textareas so worker wake messages are submitted through the real composer.
20
-
21
- ## 0.1.43
22
-
23
- ### Patch Changes
24
-
25
- - Enrich Browser OBSERVE responses with Chrome-owned tab and window identity before returning the page observation.
26
-
27
- ## 0.1.42
28
-
29
- ### Patch Changes
30
-
31
- - Close Real-3 node generation identity fencing across Platform Host admission, durable Execution idempotency, and Observer/Approval resume provenance.
32
-
33
- ## 0.1.41
34
-
35
- ### Patch Changes
36
-
37
- - Resume the existing Task Observer after successful Tasks web mutations so READY nodes receive their normal NODE_READY or REOPEN wake without manual recovery.
38
-
39
- ## 0.1.40
40
-
41
- ### Patch Changes
42
-
43
- - Make the authenticated ProFlow Tasks web app self-contained so its published browser module no longer escapes the `/tasks` session surface through a runtime relative import.