@tomflow/proflow-execution-browser-extension 0.1.36 → 0.1.38

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.
@@ -4,6 +4,248 @@ var __export = (target, all) => {
4
4
  __defProp(target, name, { get: all[name], enumerable: true });
5
5
  };
6
6
 
7
+ // packages/execution-browser-extension/src/carrier-attention.ts
8
+ function createCarrierAttentionRegistry(idFactory = () => crypto.randomUUID()) {
9
+ const byTab = /* @__PURE__ */ new Map();
10
+ return Object.freeze({
11
+ derive(input) {
12
+ const current = byTab.get(input.tabId);
13
+ if (current?.contentInstanceId === input.contentInstanceId && current.url === input.url && current.permissionFingerprint === input.permissionFingerprint) {
14
+ const refreshed = { ...current, ...input };
15
+ byTab.set(input.tabId, refreshed);
16
+ return refreshed;
17
+ }
18
+ const occurrenceRef = idFactory();
19
+ const attention = {
20
+ ...input,
21
+ occurrenceRef,
22
+ attentionRef: `carrier-attention:${input.tabId}:${occurrenceRef}`
23
+ };
24
+ byTab.set(input.tabId, attention);
25
+ return attention;
26
+ },
27
+ find(attentionRef) {
28
+ for (const attention of byTab.values())
29
+ if (attention.attentionRef === attentionRef) return attention;
30
+ return null;
31
+ },
32
+ current(tabId) {
33
+ return byTab.get(tabId) ?? null;
34
+ },
35
+ values() {
36
+ return [...byTab.values()];
37
+ },
38
+ delete(attentionRef) {
39
+ for (const [tabId, attention] of byTab)
40
+ if (attention.attentionRef === attentionRef) return byTab.delete(tabId);
41
+ return false;
42
+ },
43
+ removeTab(tabId) {
44
+ return byTab.delete(tabId);
45
+ }
46
+ });
47
+ }
48
+
49
+ // packages/execution-browser-extension/src/carrier-continuation-control.ts
50
+ function parseDenials(value) {
51
+ if (value === void 0) return [];
52
+ if (!Array.isArray(value) || value.length > 128) return null;
53
+ const parsed = [];
54
+ for (const item of value) {
55
+ if (typeof item !== "object" || item === null || Array.isArray(item))
56
+ return null;
57
+ const attentionRef = Reflect.get(item, "attentionRef");
58
+ const tabId = Reflect.get(item, "tabId");
59
+ const taskId = Reflect.get(item, "taskId");
60
+ const roleRef = Reflect.get(item, "roleRef");
61
+ const workerRef = Reflect.get(item, "workerRef");
62
+ const url2 = Reflect.get(item, "url");
63
+ const contentInstanceId = Reflect.get(item, "contentInstanceId");
64
+ const permissionFingerprint = Reflect.get(item, "permissionFingerprint");
65
+ if (typeof attentionRef !== "string" || !Number.isInteger(tabId) || taskId !== null && typeof taskId !== "string" || roleRef !== null && typeof roleRef !== "string" || workerRef !== null && typeof workerRef !== "string" || typeof url2 !== "string" || typeof contentInstanceId !== "string" || typeof permissionFingerprint !== "string")
66
+ return null;
67
+ parsed.push({
68
+ attentionRef,
69
+ tabId,
70
+ taskId,
71
+ roleRef,
72
+ workerRef,
73
+ url: url2,
74
+ contentInstanceId,
75
+ permissionFingerprint
76
+ });
77
+ }
78
+ return parsed;
79
+ }
80
+ function createCarrierContinuationControl(initial) {
81
+ const byTab = /* @__PURE__ */ new Map();
82
+ const load = (value) => {
83
+ const denials = parseDenials(value);
84
+ byTab.clear();
85
+ if (!denials) return false;
86
+ for (const denial of denials) byTab.set(denial.tabId, denial);
87
+ return true;
88
+ };
89
+ if (initial !== void 0) load(initial);
90
+ return Object.freeze({
91
+ beginDenied(denial) {
92
+ byTab.set(denial.tabId, { ...denial });
93
+ },
94
+ cancelDenied(attentionRef) {
95
+ for (const [tabId, denial] of byTab)
96
+ if (denial.attentionRef === attentionRef) return byTab.delete(tabId);
97
+ return false;
98
+ },
99
+ hasMatchingPermissionDenial(context) {
100
+ const denial = byTab.get(context.tabId);
101
+ return denial?.contentInstanceId === context.contentInstanceId && denial.url === context.url && denial.permissionFingerprint === context.permissionFingerprint && denial.taskId === context.taskId && denial.roleRef === context.roleRef && denial.workerRef === context.workerRef;
102
+ },
103
+ hasMatchingDispatchDenial(context) {
104
+ if (context.conversationLocator === null) return false;
105
+ for (const denial of byTab.values())
106
+ if (denial.taskId === context.taskId && denial.roleRef === context.roleRef && denial.workerRef === context.workerRef && denial.url === context.conversationLocator)
107
+ return true;
108
+ return false;
109
+ },
110
+ consumeRecovery(previous, current) {
111
+ const denial = byTab.get(current.tabId);
112
+ if (!denial) return null;
113
+ if (denial.url !== current.url || denial.contentInstanceId !== current.contentInstanceId) {
114
+ byTab.delete(current.tabId);
115
+ return null;
116
+ }
117
+ if (current.pageState !== "IDLE") return null;
118
+ if (previous && (previous.tabId !== denial.tabId || previous.url !== denial.url || previous.contentInstanceId !== denial.contentInstanceId || previous.pageState !== "BLOCKED" || previous.blockerFacts?.fingerprint !== denial.permissionFingerprint)) {
119
+ byTab.delete(current.tabId);
120
+ return null;
121
+ }
122
+ byTab.delete(current.tabId);
123
+ return { ...denial };
124
+ },
125
+ suppressRecovery(previous, current) {
126
+ return this.consumeRecovery(previous, current) !== null;
127
+ },
128
+ snapshot() {
129
+ return [...byTab.values()].map((denial) => ({ ...denial }));
130
+ },
131
+ load
132
+ });
133
+ }
134
+
135
+ // packages/execution-browser-extension/src/carrier-permission-attempt.ts
136
+ function parsedAttempts(value) {
137
+ const result = /* @__PURE__ */ new Map();
138
+ if (value === void 0) return { attempts: result, valid: true };
139
+ if (!Array.isArray(value)) return { attempts: result, valid: false };
140
+ let valid = value.length <= 256;
141
+ for (const candidate of value.slice(0, 256)) {
142
+ if (typeof candidate !== "object" || candidate === null || Array.isArray(candidate)) {
143
+ valid = false;
144
+ continue;
145
+ }
146
+ const tabId = Reflect.get(candidate, "tabId");
147
+ const key = Reflect.get(candidate, "key");
148
+ if (!Number.isInteger(tabId) || tabId < 0 || typeof key !== "string" || key.length === 0 || key.length > 1e3) {
149
+ valid = false;
150
+ continue;
151
+ }
152
+ if (result.has(tabId)) valid = false;
153
+ result.set(tabId, key);
154
+ }
155
+ return { attempts: result, valid };
156
+ }
157
+ function createCarrierPermissionAttemptRegistry(initial) {
158
+ const attempts = parsedAttempts(initial).attempts;
159
+ return Object.freeze({
160
+ has(tabId, key) {
161
+ return attempts.get(tabId) === key;
162
+ },
163
+ begin(tabId, key) {
164
+ attempts.set(tabId, key);
165
+ },
166
+ release(tabId, key) {
167
+ if (attempts.get(tabId) !== key) return false;
168
+ return attempts.delete(tabId);
169
+ },
170
+ observe(tabId, currentKey) {
171
+ const attempted = attempts.get(tabId);
172
+ if (attempted === void 0 || attempted === currentKey) return false;
173
+ return attempts.delete(tabId);
174
+ },
175
+ load(value) {
176
+ const parsed = parsedAttempts(value);
177
+ attempts.clear();
178
+ for (const [tabId, key] of parsed.attempts) attempts.set(tabId, key);
179
+ return parsed.valid;
180
+ },
181
+ snapshot() {
182
+ return [...attempts].map(([tabId, key]) => ({ tabId, key }));
183
+ }
184
+ });
185
+ }
186
+
187
+ // packages/execution-browser-extension/src/carrier-permission-lifecycle.ts
188
+ async function resolveRoutineCarrierPermission(input) {
189
+ const denied = () => input.humanDenied?.() ? { status: "HUMAN_REQUIRED", reason: "HUMAN_DENIED" } : null;
190
+ const maxClassifications = Math.max(1, input.maxClassifications ?? 40);
191
+ let decision = null;
192
+ for (let attempt = 0; attempt < maxClassifications; attempt += 1) {
193
+ const beforeClassification = denied();
194
+ if (beforeClassification) return beforeClassification;
195
+ try {
196
+ decision = await input.port.classify();
197
+ } catch {
198
+ return {
199
+ status: "HUMAN_REQUIRED",
200
+ reason: "PERMISSION_CLASSIFICATION_FAILED"
201
+ };
202
+ }
203
+ const afterClassification = denied();
204
+ if (afterClassification) return afterClassification;
205
+ if (decision.decision !== "DEFER") break;
206
+ if (!await input.port.revalidate()) return { status: "STALE" };
207
+ if (attempt === maxClassifications - 1)
208
+ return {
209
+ status: "HUMAN_REQUIRED",
210
+ reason: "PERMISSION_CONTEXT_DEFER_TIMEOUT"
211
+ };
212
+ await input.port.waitBeforeReclassify?.();
213
+ }
214
+ if (!decision)
215
+ return {
216
+ status: "HUMAN_REQUIRED",
217
+ reason: "PERMISSION_CLASSIFICATION_FAILED"
218
+ };
219
+ if (decision.decision !== "AUTO_ALLOW")
220
+ return { status: "HUMAN_REQUIRED", reason: decision.reason };
221
+ if (!input.facts.actions.includes("allowAlways"))
222
+ return {
223
+ status: "HUMAN_REQUIRED",
224
+ reason: "AUTO_ALLOW_ACTION_UNAVAILABLE"
225
+ };
226
+ if (input.autoAlreadyAttempted)
227
+ return {
228
+ status: "HUMAN_REQUIRED",
229
+ reason: "AUTO_ALLOW_REALITY_UNCONFIRMED"
230
+ };
231
+ if (!await input.port.revalidate()) return { status: "STALE" };
232
+ const beforeAction = denied();
233
+ if (beforeAction) return beforeAction;
234
+ try {
235
+ await input.port.act("allowAlways");
236
+ } catch {
237
+ return { status: "HUMAN_REQUIRED", reason: "AUTO_ALLOW_FAILED" };
238
+ }
239
+ return await input.port.released() ? { status: "RELEASED", action: "allowAlways" } : { status: "HUMAN_REQUIRED", reason: "AUTO_ALLOW_REALITY_UNCONFIRMED" };
240
+ }
241
+ async function resolveHumanCarrierPermission(input) {
242
+ if (!await input.revalidate()) throw new Error("STALE_PERMISSION");
243
+ await input.act(input.action);
244
+ if (!await input.released())
245
+ throw new Error("PERMISSION_ACTION_REALITY_UNCONFIRMED");
246
+ return "RELEASED";
247
+ }
248
+
7
249
  // node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/classic/external.js
8
250
  var external_exports = {};
9
251
  __export(external_exports, {
@@ -13544,6 +13786,13 @@ function createCollaborationCarrierApplication(options) {
13544
13786
  return Object.freeze({ deliverMessage, recoverPending });
13545
13787
  }
13546
13788
 
13789
+ // packages/execution-browser-extension/src/recovery-trigger.ts
13790
+ function shouldTriggerObserverRecovery(previous, current) {
13791
+ if (current.pageState !== "IDLE") return false;
13792
+ if (!previous) return true;
13793
+ return previous.pageState !== "IDLE" || previous.url !== current.url || previous.contentInstanceId !== current.contentInstanceId || previous.activityKind !== current.activityKind;
13794
+ }
13795
+
13547
13796
  // packages/execution-browser-extension/src/system-observer.ts
13548
13797
  var SYSTEM_OBSERVER_VIEWS = [
13549
13798
  "task",
@@ -13866,33 +14115,61 @@ function createTaskObserver(options) {
13866
14115
  return Object.freeze({ advance, drive });
13867
14116
  }
13868
14117
 
13869
- // packages/execution-browser-extension/src/recovery-trigger.ts
13870
- function shouldTriggerObserverRecovery(previous, current) {
13871
- if (current.pageState !== "IDLE") return false;
13872
- if (!previous) return true;
13873
- return previous.pageState !== "IDLE" || previous.url !== current.url || previous.contentInstanceId !== current.contentInstanceId || previous.activityKind !== current.activityKind;
13874
- }
13875
-
13876
14118
  // packages/execution-browser-extension/extension/background.ts
13877
14119
  var extensionInstanceId = `extension:${crypto.randomUUID()}`;
13878
14120
  var sessions = /* @__PURE__ */ new Map();
14121
+ var permissionHandling = /* @__PURE__ */ new Map();
14122
+ var permissionAutoAttempts = createCarrierPermissionAttemptRegistry();
14123
+ var carrierAttentions = createCarrierAttentionRegistry();
14124
+ var carrierContinuationControl = createCarrierContinuationControl();
13879
14125
  var BROWSER_CARRIER_KEEPALIVE_KEY = "proflowBrowserSnapshot";
13880
14126
  var BROWSER_CARRIER_KEEPALIVE_MS = 2e4;
13881
14127
  var BROWSER_BRIDGE_FETCH_TIMEOUT_MS = 5e3;
14128
+ var snapshotPersistence = Promise.resolve();
14129
+ var transientPermissionAttemptsRestore = null;
13882
14130
  var sleep = (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds));
13883
- async function persistSnapshot() {
13884
- await chrome.storage.session.set({
14131
+ function persistSnapshot() {
14132
+ const value = {
13885
14133
  proflowBrowserSnapshot: {
13886
14134
  extensionInstanceId,
13887
14135
  observedAt: (/* @__PURE__ */ new Date()).toISOString(),
13888
14136
  sessions: [...sessions.values()],
14137
+ permissionAutoAttempts: permissionAutoAttempts.snapshot(),
14138
+ carrierContinuationDenials: carrierContinuationControl.snapshot(),
13889
14139
  recoveryScan: "BOUNDED_ON_START"
13890
14140
  }
13891
- });
14141
+ };
14142
+ snapshotPersistence = snapshotPersistence.catch(() => void 0).then(() => chrome.storage.session.set(value));
14143
+ return snapshotPersistence;
13892
14144
  }
13893
14145
  function isRecord(value) {
13894
14146
  return typeof value === "object" && value !== null && !Array.isArray(value);
13895
14147
  }
14148
+ function restoreTransientPermissionAttempts() {
14149
+ if (transientPermissionAttemptsRestore)
14150
+ return transientPermissionAttemptsRestore;
14151
+ transientPermissionAttemptsRestore = (async () => {
14152
+ try {
14153
+ const stored = await chrome.storage.session.get(
14154
+ BROWSER_CARRIER_KEEPALIVE_KEY
14155
+ );
14156
+ const snapshot = stored[BROWSER_CARRIER_KEEPALIVE_KEY];
14157
+ const validSnapshot = isRecord(snapshot);
14158
+ const attemptsLoaded = permissionAutoAttempts.load(
14159
+ validSnapshot ? snapshot.permissionAutoAttempts : void 0
14160
+ );
14161
+ const denialsLoaded = carrierContinuationControl.load(
14162
+ validSnapshot ? snapshot.carrierContinuationDenials : void 0
14163
+ );
14164
+ return attemptsLoaded && denialsLoaded;
14165
+ } catch {
14166
+ permissionAutoAttempts.load(void 0);
14167
+ carrierContinuationControl.load(void 0);
14168
+ return false;
14169
+ }
14170
+ })();
14171
+ return transientPermissionAttemptsRestore;
14172
+ }
13896
14173
  function parseConfig(value) {
13897
14174
  if (!isRecord(value)) return null;
13898
14175
  const endpoint = value.endpoint;
@@ -14104,6 +14381,8 @@ var taskObserver = createTaskObserver({
14104
14381
  },
14105
14382
  carrier: {
14106
14383
  async requestWake(input) {
14384
+ if (carrierContinuationControl.hasMatchingDispatchDenial(input))
14385
+ throw new Error("CARRIER_CONTINUATION_HUMAN_DENIED");
14107
14386
  return invokeObserverApplication("task.wake", input);
14108
14387
  }
14109
14388
  }
@@ -14197,8 +14476,14 @@ async function persistSystemObserverState(result) {
14197
14476
  }
14198
14477
  var observerRecoveryInFlight = null;
14199
14478
  var observerRecoveryRetryCount = 0;
14479
+ var nextRecoverySuppressions = [];
14480
+ function suppressNextObserverRecovery(denials) {
14481
+ nextRecoverySuppressions = denials;
14482
+ }
14200
14483
  function runObserverRecovery() {
14201
14484
  if (observerRecoveryInFlight) return observerRecoveryInFlight;
14485
+ const suppressedContinuations = nextRecoverySuppressions;
14486
+ nextRecoverySuppressions = [];
14202
14487
  observerRecoveryInFlight = (async () => {
14203
14488
  let recoveryNeedsRetry = false;
14204
14489
  await collaborationCarrier.recoverPending(50).catch(() => void 0);
@@ -14211,6 +14496,10 @@ function runObserverRecovery() {
14211
14496
  if (!isRecord(candidate) || typeof candidate.signalRef !== "string" || typeof candidate.executionRef !== "string" || typeof candidate.taskId !== "string" || typeof candidate.workerRef !== "string")
14212
14497
  continue;
14213
14498
  try {
14499
+ if (suppressedContinuations.some(
14500
+ (denial) => denial.taskId === candidate.taskId && denial.workerRef === candidate.workerRef
14501
+ ))
14502
+ continue;
14214
14503
  const decision = candidate.kind === "RECOVERY_RESUME" ? await taskObserver.drive(candidate.taskId, {
14215
14504
  trigger: "RECOVERY_RESUME",
14216
14505
  ref: candidate.executionRef,
@@ -14249,6 +14538,10 @@ function runObserverRecovery() {
14249
14538
  }).catch(() => {
14250
14539
  recoveryNeedsRetry = true;
14251
14540
  });
14541
+ if (suppressedContinuations.some(
14542
+ (denial) => denial.taskId === candidate.taskId
14543
+ ))
14544
+ continue;
14252
14545
  await taskObserver.drive(candidate.taskId).catch(() => {
14253
14546
  recoveryNeedsRetry = true;
14254
14547
  });
@@ -14302,6 +14595,340 @@ async function contentCommand(tabId, command) {
14302
14595
  }
14303
14596
  return response.value;
14304
14597
  }
14598
+ function carrierIdentity(url2) {
14599
+ try {
14600
+ const parsed = new URL(url2);
14601
+ const segments = parsed.pathname.split("/").filter(Boolean);
14602
+ if (parsed.protocol !== "https:" || parsed.hostname !== "chatgpt.com" || segments[0] !== "g" || !segments[1]?.startsWith("g-"))
14603
+ return null;
14604
+ return {
14605
+ roleRef: segments[1],
14606
+ workerRef: segments[2] === "c" && segments[3] ? segments[3] : null
14607
+ };
14608
+ } catch {
14609
+ return null;
14610
+ }
14611
+ }
14612
+ function permissionAttemptKey(observed) {
14613
+ return observed.blockerFacts ? `${observed.url}:${observed.blockerFacts.fingerprint}` : null;
14614
+ }
14615
+ function setCarrierAttention(observed, reason) {
14616
+ const facts = observed.blockerFacts;
14617
+ if (!facts) return;
14618
+ const identity = carrierIdentity(observed.url);
14619
+ carrierAttentions.derive({
14620
+ tabId: observed.tabId,
14621
+ contentInstanceId: observed.contentInstanceId,
14622
+ url: observed.url,
14623
+ permissionFingerprint: facts.fingerprint,
14624
+ taskId: facts.taskId,
14625
+ roleRef: identity?.roleRef ?? null,
14626
+ workerRef: identity?.workerRef ?? null,
14627
+ targetHost: facts.targetHost,
14628
+ operationId: facts.operationId,
14629
+ reason,
14630
+ actions: facts.actions.filter(
14631
+ (action) => action === "allowOnce" || action === "deny"
14632
+ ),
14633
+ observedAt: (/* @__PURE__ */ new Date()).toISOString()
14634
+ });
14635
+ void publishCarrierAttentions();
14636
+ }
14637
+ function carrierAttentionViews() {
14638
+ return carrierAttentions.values().map(
14639
+ ({
14640
+ attentionRef,
14641
+ occurrenceRef,
14642
+ taskId,
14643
+ roleRef,
14644
+ workerRef,
14645
+ targetHost,
14646
+ operationId,
14647
+ reason,
14648
+ actions,
14649
+ observedAt
14650
+ }) => ({
14651
+ attentionRef,
14652
+ occurrenceRef,
14653
+ taskId,
14654
+ roleRef,
14655
+ workerRef,
14656
+ targetHost,
14657
+ operationId,
14658
+ reason,
14659
+ actions,
14660
+ observedAt
14661
+ })
14662
+ );
14663
+ }
14664
+ async function publishCarrierAttentions() {
14665
+ const config2 = await bridgeConfig().catch(() => null);
14666
+ if (!config2) return;
14667
+ const query = `?extensionInstanceId=${encodeURIComponent(extensionInstanceId)}`;
14668
+ const response = await bridgeFetch(config2, `/v1/carrier/attentions${query}`, {
14669
+ method: "POST",
14670
+ body: JSON.stringify({ carrierAttentions: carrierAttentionViews() })
14671
+ });
14672
+ if (!response.ok) throw new Error("CARRIER_ATTENTION_PUBLISH_REJECTED");
14673
+ }
14674
+ function rawPermissionFingerprint(value) {
14675
+ if (!isRecord(value) || !isRecord(value.blockerFacts)) return null;
14676
+ return typeof value.blockerFacts.fingerprint === "string" ? value.blockerFacts.fingerprint : null;
14677
+ }
14678
+ function parsePermissionFacts(value) {
14679
+ if (!isRecord(value) || value.kind !== "ACTION_PERMISSION") return void 0;
14680
+ const actions = value.actions;
14681
+ if (value.targetHost !== null && typeof value.targetHost !== "string" || typeof value.operationId !== "string" || value.taskId !== null && typeof value.taskId !== "string" || !Array.isArray(actions) || actions.some(
14682
+ (action) => action !== "allowAlways" && action !== "allowOnce" && action !== "deny"
14683
+ ) || typeof value.fingerprint !== "string")
14684
+ return void 0;
14685
+ return {
14686
+ kind: "ACTION_PERMISSION",
14687
+ targetHost: value.targetHost,
14688
+ operationId: value.operationId,
14689
+ taskId: value.taskId,
14690
+ actions: [...actions],
14691
+ fingerprint: value.fingerprint
14692
+ };
14693
+ }
14694
+ function parseSnapshotObservation(value, tab) {
14695
+ if (!isRecord(value) || tab.id === void 0 || tab.windowId === void 0)
14696
+ return null;
14697
+ const pageState = value.pageState;
14698
+ const activityKind = value.activityKind;
14699
+ if (typeof value.url !== "string" || typeof value.contentInstanceId !== "string" || pageState !== "IDLE" && pageState !== "BUSY" && pageState !== "BLOCKED" && pageState !== "UNKNOWN" || activityKind !== null && activityKind !== "GENERATING" && activityKind !== "ACTION_PERMISSION" && activityKind !== "ACTION_RUNNING" && activityKind !== "WAITING_HUMAN" && activityKind !== "WAITING_PEER" && activityKind !== "RECOVERING" || typeof value.observedAt !== "string")
14700
+ return null;
14701
+ const blockerFacts = parsePermissionFacts(value.blockerFacts);
14702
+ if (activityKind === "ACTION_PERMISSION" && blockerFacts === void 0)
14703
+ return null;
14704
+ return {
14705
+ tabId: tab.id,
14706
+ windowId: tab.windowId,
14707
+ url: value.url,
14708
+ contentInstanceId: value.contentInstanceId,
14709
+ pageState,
14710
+ activityKind,
14711
+ ...blockerFacts ? { blockerFacts } : {},
14712
+ observedAt: value.observedAt
14713
+ };
14714
+ }
14715
+ async function waitForPermissionReleased(tabId, fingerprint) {
14716
+ for (let attempt = 0; attempt < 40; attempt += 1) {
14717
+ const current = sessions.get(tabId);
14718
+ if (current && current.blockerFacts?.fingerprint !== fingerprint)
14719
+ return true;
14720
+ try {
14721
+ const value = await contentCommand(tabId, { operation: "observe" });
14722
+ if (rawPermissionFingerprint(value) !== fingerprint) return true;
14723
+ } catch {
14724
+ }
14725
+ await sleep(250);
14726
+ }
14727
+ return false;
14728
+ }
14729
+ async function handleActionPermission(observed) {
14730
+ const facts = observed.blockerFacts;
14731
+ const key = permissionAttemptKey(observed);
14732
+ if (observed.pageState !== "BLOCKED" || observed.activityKind !== "ACTION_PERMISSION" || !facts || !key)
14733
+ return;
14734
+ if (permissionHandling.get(observed.tabId) === key) return;
14735
+ const existing = carrierAttentions.current(observed.tabId);
14736
+ if (existing?.contentInstanceId === observed.contentInstanceId && existing.permissionFingerprint === facts.fingerprint)
14737
+ return;
14738
+ permissionHandling.set(observed.tabId, key);
14739
+ try {
14740
+ const identity = carrierIdentity(observed.url);
14741
+ const humanDenied = () => carrierContinuationControl.hasMatchingPermissionDenial({
14742
+ tabId: observed.tabId,
14743
+ contentInstanceId: observed.contentInstanceId,
14744
+ url: observed.url,
14745
+ permissionFingerprint: facts.fingerprint,
14746
+ taskId: facts.taskId,
14747
+ roleRef: identity?.roleRef ?? null,
14748
+ workerRef: identity?.workerRef ?? null
14749
+ });
14750
+ if (humanDenied()) {
14751
+ setCarrierAttention(observed, "HUMAN_DENIED");
14752
+ return;
14753
+ }
14754
+ if (!await restoreTransientPermissionAttempts()) {
14755
+ setCarrierAttention(observed, "PERMISSION_ATTEMPT_STATE_UNAVAILABLE");
14756
+ return;
14757
+ }
14758
+ if (!identity || !facts.taskId) {
14759
+ setCarrierAttention(observed, "PERMISSION_CONTEXT_INCOMPLETE");
14760
+ return;
14761
+ }
14762
+ const result = await resolveRoutineCarrierPermission({
14763
+ facts,
14764
+ autoAlreadyAttempted: permissionAutoAttempts.has(observed.tabId, key),
14765
+ humanDenied,
14766
+ port: {
14767
+ async classify() {
14768
+ const value = await invokeObserverApplication(
14769
+ "browser.permission.classify",
14770
+ {
14771
+ taskId: facts.taskId,
14772
+ roleRef: identity.roleRef,
14773
+ ...identity.workerRef ? { workerRef: identity.workerRef } : {},
14774
+ conversationLocator: observed.url,
14775
+ targetHost: facts.targetHost,
14776
+ operationId: facts.operationId
14777
+ }
14778
+ );
14779
+ if (!isRecord(value) || value.decision !== "AUTO_ALLOW" && value.decision !== "DEFER" && value.decision !== "HUMAN_REQUIRED" || typeof value.reason !== "string")
14780
+ return {
14781
+ decision: "HUMAN_REQUIRED",
14782
+ reason: "PERMISSION_CLASSIFICATION_INVALID"
14783
+ };
14784
+ return {
14785
+ decision: value.decision,
14786
+ reason: value.reason
14787
+ };
14788
+ },
14789
+ revalidate() {
14790
+ const current = observationFor(observed.tabId);
14791
+ return current.contentInstanceId === observed.contentInstanceId && current.url === observed.url && current.blockerFacts?.fingerprint === facts.fingerprint;
14792
+ },
14793
+ waitBeforeReclassify: () => sleep(250),
14794
+ async act(action) {
14795
+ permissionAutoAttempts.begin(observed.tabId, key);
14796
+ await persistSnapshot();
14797
+ await contentCommand(observed.tabId, {
14798
+ operation: "permissionAction",
14799
+ permissionFingerprint: facts.fingerprint,
14800
+ permissionAction: action
14801
+ });
14802
+ },
14803
+ released: () => waitForPermissionReleased(observed.tabId, facts.fingerprint)
14804
+ }
14805
+ });
14806
+ if (result.status === "RELEASED") {
14807
+ permissionAutoAttempts.release(observed.tabId, key);
14808
+ carrierAttentions.removeTab(observed.tabId);
14809
+ void publishCarrierAttentions();
14810
+ await persistSnapshot();
14811
+ return;
14812
+ }
14813
+ if (result.status === "HUMAN_REQUIRED")
14814
+ setCarrierAttention(observed, result.reason);
14815
+ } catch {
14816
+ setCarrierAttention(observed, "AUTO_ALLOW_FAILED");
14817
+ } finally {
14818
+ if (permissionHandling.get(observed.tabId) === key)
14819
+ permissionHandling.delete(observed.tabId);
14820
+ }
14821
+ }
14822
+ var carrierBlockerStrategies = /* @__PURE__ */ new Map([["ACTION_PERMISSION", handleActionPermission]]);
14823
+ function handleCarrierBlocker(observed) {
14824
+ if (observed.pageState !== "BLOCKED" || observed.activityKind === null)
14825
+ return;
14826
+ const strategy = carrierBlockerStrategies.get(observed.activityKind);
14827
+ if (strategy) void strategy(observed);
14828
+ }
14829
+ function processContentObservation(observed, triggerRecovery) {
14830
+ const previous = sessions.get(observed.tabId);
14831
+ sessions.set(observed.tabId, observed);
14832
+ permissionAutoAttempts.observe(
14833
+ observed.tabId,
14834
+ permissionAttemptKey(observed)
14835
+ );
14836
+ const attention = carrierAttentions.current(observed.tabId);
14837
+ if (attention && (observed.contentInstanceId !== attention.contentInstanceId || observed.url !== attention.url || observed.blockerFacts?.fingerprint !== attention.permissionFingerprint)) {
14838
+ carrierAttentions.removeTab(observed.tabId);
14839
+ void publishCarrierAttentions();
14840
+ }
14841
+ handleCarrierBlocker(observed);
14842
+ const shouldRecover = triggerRecovery && shouldTriggerObserverRecovery(previous, observed);
14843
+ const suppressed = shouldRecover && carrierContinuationControl.suppressRecovery(previous, observed);
14844
+ void persistSnapshot();
14845
+ if (shouldRecover && !suppressed) void runObserverRecovery();
14846
+ }
14847
+ async function rebuildCarrierAttentionsFromTabs() {
14848
+ const consumedDenials = [];
14849
+ const tabs = await chrome.tabs.query({ url: "https://chatgpt.com/g/*" });
14850
+ for (const tab of tabs) {
14851
+ if (tab.id === void 0 || tab.windowId === void 0) continue;
14852
+ try {
14853
+ const response = await chrome.tabs.sendMessage(tab.id, {
14854
+ type: "PROFLOW_PAGE_SNAPSHOT_REQUEST"
14855
+ });
14856
+ if (!isRecord(response) || response.ok !== true) continue;
14857
+ const observed = parseSnapshotObservation(response.value, tab);
14858
+ if (observed) {
14859
+ processContentObservation(observed, false);
14860
+ const consumed = carrierContinuationControl.consumeRecovery(
14861
+ void 0,
14862
+ observed
14863
+ );
14864
+ if (consumed) consumedDenials.push(consumed);
14865
+ }
14866
+ } catch {
14867
+ }
14868
+ }
14869
+ await persistSnapshot();
14870
+ await publishCarrierAttentions().catch(() => void 0);
14871
+ return [
14872
+ ...consumedDenials,
14873
+ ...carrierContinuationControl.snapshot().filter(
14874
+ (denial) => !consumedDenials.some(
14875
+ (consumed) => consumed.attentionRef === denial.attentionRef
14876
+ )
14877
+ )
14878
+ ];
14879
+ }
14880
+ async function decideCarrierAttention(attentionRef, action) {
14881
+ const attention = carrierAttentions.find(attentionRef);
14882
+ if (!attention?.actions.includes(action))
14883
+ throw new Error("CARRIER_ATTENTION_ACTION_DENIED");
14884
+ await resolveHumanCarrierPermission({
14885
+ action,
14886
+ revalidate() {
14887
+ const current = observationFor(attention.tabId);
14888
+ return current.contentInstanceId === attention.contentInstanceId && current.url === attention.url && current.blockerFacts?.fingerprint === attention.permissionFingerprint;
14889
+ },
14890
+ async act(semanticAction) {
14891
+ if (semanticAction === "deny") {
14892
+ carrierContinuationControl.beginDenied({
14893
+ attentionRef: attention.attentionRef,
14894
+ tabId: attention.tabId,
14895
+ taskId: attention.taskId,
14896
+ roleRef: attention.roleRef,
14897
+ workerRef: attention.workerRef,
14898
+ url: attention.url,
14899
+ contentInstanceId: attention.contentInstanceId,
14900
+ permissionFingerprint: attention.permissionFingerprint
14901
+ });
14902
+ await persistSnapshot();
14903
+ }
14904
+ try {
14905
+ await contentCommand(attention.tabId, {
14906
+ operation: "permissionAction",
14907
+ permissionFingerprint: attention.permissionFingerprint,
14908
+ permissionAction: semanticAction
14909
+ });
14910
+ } catch (error46) {
14911
+ if (semanticAction === "deny") {
14912
+ carrierContinuationControl.cancelDenied(attention.attentionRef);
14913
+ await persistSnapshot();
14914
+ }
14915
+ throw error46;
14916
+ }
14917
+ },
14918
+ released: () => waitForPermissionReleased(
14919
+ attention.tabId,
14920
+ attention.permissionFingerprint
14921
+ )
14922
+ });
14923
+ permissionAutoAttempts.release(
14924
+ attention.tabId,
14925
+ `${attention.url}:${attention.permissionFingerprint}`
14926
+ );
14927
+ carrierAttentions.delete(attention.attentionRef);
14928
+ void publishCarrierAttentions();
14929
+ await persistSnapshot();
14930
+ return { attentionRef, action, status: "APPLIED" };
14931
+ }
14305
14932
  async function waitForSubmittedMessage(tabId, fingerprint) {
14306
14933
  for (let attempt = 0; attempt < 60; attempt += 1) {
14307
14934
  try {
@@ -14327,6 +14954,14 @@ function text(value, name) {
14327
14954
  return value;
14328
14955
  }
14329
14956
  async function executeCommand(command) {
14957
+ if (command.type === "CARRIER_ATTENTION_ACTION") {
14958
+ if (command.action !== "allowOnce" && command.action !== "deny")
14959
+ throw new Error("ATTENTION_ACTION_INVALID");
14960
+ return decideCarrierAttention(
14961
+ text(command.attentionRef, "ATTENTION_REF"),
14962
+ command.action
14963
+ );
14964
+ }
14330
14965
  if (command.type === "LIST_TABS") {
14331
14966
  const tabs = await chrome.tabs.query({ url: "https://chatgpt.com/g/*" });
14332
14967
  return tabs.map((tab) => tab.id).filter((tabId2) => tabId2 !== void 0).map((tabId2) => sessions.get(tabId2)).filter((value) => value !== void 0);
@@ -14520,6 +15155,7 @@ async function runBridgeLoop() {
14520
15155
  })
14521
15156
  });
14522
15157
  if (!hello.ok) throw new Error("BRIDGE_HELLO_REJECTED");
15158
+ await publishCarrierAttentions();
14523
15159
  let lastHeartbeatAt = Date.now();
14524
15160
  let lastExtensionKeepaliveAt = Date.now();
14525
15161
  while (true) {
@@ -14709,16 +15345,12 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
14709
15345
  return true;
14710
15346
  }
14711
15347
  if (message.type === "PROFLOW_CONTENT_OBSERVATION" && message.observation && sender.tab?.id !== void 0 && sender.tab.windowId !== void 0) {
14712
- const previous = sessions.get(sender.tab.id);
14713
15348
  const observed = {
14714
15349
  ...message.observation,
14715
15350
  tabId: sender.tab.id,
14716
15351
  windowId: sender.tab.windowId
14717
15352
  };
14718
- sessions.set(sender.tab.id, observed);
14719
- void persistSnapshot();
14720
- if (shouldTriggerObserverRecovery(previous, observed))
14721
- void runObserverRecovery();
15353
+ processContentObservation(observed, true);
14722
15354
  sendResponse({ accepted: true, extensionInstanceId });
14723
15355
  return;
14724
15356
  }
@@ -14732,6 +15364,7 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
14732
15364
  extensionInstanceId,
14733
15365
  observedAt: (/* @__PURE__ */ new Date()).toISOString(),
14734
15366
  sessions: [...sessions.values()],
15367
+ carrierAttentions: carrierAttentionViews(),
14735
15368
  taskApplicationConfigured: application !== null,
14736
15369
  approvalApplicationConfigured: approval !== null,
14737
15370
  systemObserver: observerState ? {
@@ -14745,6 +15378,26 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
14745
15378
  );
14746
15379
  return true;
14747
15380
  }
15381
+ if (message.type === "PROFLOW_CARRIER_ATTENTION_ACTION") {
15382
+ if (!message.input) {
15383
+ sendResponse({ ok: false, error: "CARRIER_ATTENTION_MESSAGE_INVALID" });
15384
+ return;
15385
+ }
15386
+ const attentionRef = message.input.attentionRef;
15387
+ const action = message.input.action;
15388
+ if (typeof attentionRef !== "string" || action !== "allowOnce" && action !== "deny") {
15389
+ sendResponse({ ok: false, error: "CARRIER_ATTENTION_MESSAGE_INVALID" });
15390
+ return;
15391
+ }
15392
+ void decideCarrierAttention(attentionRef, action).then(
15393
+ (value) => sendResponse({ ok: true, value }),
15394
+ (error46) => sendResponse({
15395
+ ok: false,
15396
+ error: error46 instanceof Error ? error46.message : "CARRIER_ATTENTION_ACTION_FAILED"
15397
+ })
15398
+ );
15399
+ return true;
15400
+ }
14748
15401
  if (message.type === "PROFLOW_APPROVAL_APPLICATION") {
14749
15402
  if (typeof message.operation !== "string" || !message.input) {
14750
15403
  sendResponse({
@@ -14799,9 +15452,15 @@ async function openTaskPage() {
14799
15452
  const body = await response.json();
14800
15453
  if (response.ok && isRecord(body) && typeof body.url === "string" && body.url.startsWith(`${bridge.endpoint}/tasks/bootstrap/`)) {
14801
15454
  const webUrl = `${bridge.endpoint}/tasks`;
14802
- const [existing] = await chrome.tabs.query({ url: webUrl, currentWindow: true });
15455
+ const [existing] = await chrome.tabs.query({
15456
+ url: webUrl,
15457
+ currentWindow: true
15458
+ });
14803
15459
  if (existing?.id !== void 0) {
14804
- await chrome.tabs.update(existing.id, { url: body.url, active: true });
15460
+ await chrome.tabs.update(existing.id, {
15461
+ url: body.url,
15462
+ active: true
15463
+ });
14805
15464
  } else {
14806
15465
  await chrome.tabs.create({ url: body.url, active: true });
14807
15466
  }
@@ -14822,27 +15481,36 @@ async function openTaskPage() {
14822
15481
  await chrome.tabs.create({ url: fallbackUrl, active: true });
14823
15482
  }
14824
15483
  async function startBackgroundRuntime() {
15484
+ await restoreTransientPermissionAttempts();
14825
15485
  await bootstrapManagedRuntimeConfig();
14826
15486
  await persistSnapshot();
14827
15487
  void runBridgeLoop();
14828
15488
  void runProvisioningBridgeLoop();
15489
+ const suppressedContinuations = await rebuildCarrierAttentionsFromTabs();
15490
+ suppressNextObserverRecovery(suppressedContinuations);
14829
15491
  void runObserverRecovery();
14830
15492
  }
14831
15493
  chrome.action.onClicked.addListener(() => {
14832
15494
  void openTaskPage();
14833
15495
  });
14834
15496
  chrome.runtime.onInstalled.addListener(() => {
14835
- void bootstrapManagedRuntimeConfig().then(async () => {
15497
+ void restoreTransientPermissionAttempts().then(async () => {
15498
+ await bootstrapManagedRuntimeConfig();
14836
15499
  await persistSnapshot();
14837
15500
  void runBridgeLoop();
15501
+ const suppressedContinuations = await rebuildCarrierAttentionsFromTabs();
15502
+ suppressNextObserverRecovery(suppressedContinuations);
14838
15503
  void runObserverRecovery();
14839
15504
  });
14840
15505
  });
14841
15506
  chrome.runtime.onStartup.addListener(() => {
14842
15507
  sessions.clear();
14843
- void bootstrapManagedRuntimeConfig().then(async () => {
15508
+ void restoreTransientPermissionAttempts().then(async () => {
15509
+ await bootstrapManagedRuntimeConfig();
14844
15510
  await persistSnapshot();
14845
15511
  void runBridgeLoop();
15512
+ const suppressedContinuations = await rebuildCarrierAttentionsFromTabs();
15513
+ suppressNextObserverRecovery(suppressedContinuations);
14846
15514
  void runObserverRecovery();
14847
15515
  });
14848
15516
  });