@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.
- package/dist/deployment/adapter.d.ts +17 -11
- package/dist/deployment/adapter.js +17 -4
- package/dist/deployment/descriptor.d.ts +1 -1
- package/dist/deployment/descriptor.js +1 -1
- package/dist/extension/background.js +42 -18
- package/dist/src/bridge.js +6 -1
- package/dist/src/install-workflow.d.ts +2 -0
- package/dist/src/pairing.d.ts +2 -0
- package/dist/src/pairing.js +3 -1
- package/dist/src/recovery-trigger.d.ts +1 -0
- package/dist/src/recovery-trigger.js +15 -0
- package/extension/background.ts +34 -19
- package/manifest.json +1 -1
- package/package.json +1 -1
- package/proflow.module.json +1 -1
- package/CHANGELOG.md +0 -43
|
@@ -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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
189
|
+
readonly moduleVersion: "0.1.48";
|
|
184
190
|
} | {
|
|
185
191
|
contract: "deployment.result.v1";
|
|
186
192
|
moduleRef: "execution-browser-extension";
|
|
187
|
-
moduleVersion: "0.1.
|
|
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.
|
|
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:
|
|
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
|
-
:
|
|
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.
|
|
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.
|
|
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
|
-
|
|
14950
|
-
|
|
14951
|
-
|
|
14952
|
-
|
|
14953
|
-
|
|
14954
|
-
|
|
14955
|
-
|
|
14956
|
-
|
|
14957
|
-
|
|
14958
|
-
|
|
14959
|
-
|
|
14960
|
-
|
|
14961
|
-
|
|
14962
|
-
|
|
14963
|
-
|
|
14964
|
-
|
|
14965
|
-
|
|
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");
|
package/dist/src/bridge.js
CHANGED
|
@@ -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
|
-
|
|
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
|
}>;
|
package/dist/src/pairing.d.ts
CHANGED
|
@@ -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>;
|
package/dist/src/pairing.js
CHANGED
|
@@ -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;
|
package/extension/background.ts
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
}
|
|
1255
|
-
|
|
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
package/package.json
CHANGED
package/proflow.module.json
CHANGED
|
@@ -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.
|
|
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.
|