@tomflow/proflow-execution-browser-extension 0.1.37 → 0.1.39

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.
@@ -1,22 +1,37 @@
1
+ import type { PermissionSemanticAction } from "../src/carrier-permission.js";
2
+ import {
3
+ observeChatGptPage,
4
+ performChatGptPermissionAction,
5
+ submitChatGptComposer,
6
+ writeChatGptInput,
7
+ } from "../src/chatgpt-runtime-adapter.js";
1
8
  import { containsSubmittedFingerprint } from "../src/submitted-message.js";
2
9
 
3
- type PageState = "IDLE" | "BUSY" | "BLOCKED" | "UNKNOWN";
4
10
  type ContentCommand = {
5
11
  type: "PROFLOW_PAGE_COMMAND";
6
12
  contentInstanceId: string;
7
13
  expectedUrl: string;
8
- operation: "observe" | "input" | "click" | "submit" | "verify";
14
+ operation:
15
+ | "observe"
16
+ | "input"
17
+ | "click"
18
+ | "submit"
19
+ | "verify"
20
+ | "permissionAction";
9
21
  selector?: string;
10
22
  value?: string;
11
23
  fingerprint?: string;
24
+ permissionFingerprint?: string;
25
+ permissionAction?: PermissionSemanticAction;
12
26
  };
27
+ type ContentSnapshotRequest = { type: "PROFLOW_PAGE_SNAPSHOT_REQUEST" };
13
28
  type ChromeContent = {
14
29
  runtime: {
15
30
  sendMessage(message: unknown): Promise<unknown>;
16
31
  onMessage: {
17
32
  addListener(
18
33
  listener: (
19
- message: ContentCommand,
34
+ message: ContentCommand | ContentSnapshotRequest,
20
35
  sender: unknown,
21
36
  sendResponse: (value: unknown) => void,
22
37
  ) => boolean | undefined,
@@ -28,22 +43,8 @@ declare const chrome: ChromeContent;
28
43
 
29
44
  const contentInstanceId = `content:${crypto.randomUUID()}`;
30
45
 
31
- function pageState(): { pageState: PageState; activityKind: string | null } {
32
- if (document.querySelector('[role="dialog"]'))
33
- return { pageState: "BLOCKED", activityKind: "ACTION_PERMISSION" };
34
- if (
35
- document.querySelector(
36
- '[data-testid="stop-button"], button[aria-label*="Stop"]',
37
- )
38
- )
39
- return { pageState: "BUSY", activityKind: "GENERATING" };
40
- if (
41
- document.querySelector(
42
- '#prompt-textarea, textarea, [contenteditable="true"]',
43
- )
44
- )
45
- return { pageState: "IDLE", activityKind: null };
46
- return { pageState: "UNKNOWN", activityKind: null };
46
+ function pageState(): ReturnType<typeof observeChatGptPage> {
47
+ return observeChatGptPage(document);
47
48
  }
48
49
 
49
50
  function observation() {
@@ -76,6 +77,7 @@ function hasFingerprint(fingerprint: string | undefined): boolean {
76
77
 
77
78
  chrome.runtime.onMessage.addListener((command, _sender, sendResponse) => {
78
79
  void (async () => {
80
+ if (command.type === "PROFLOW_PAGE_SNAPSHOT_REQUEST") return observation();
79
81
  if (
80
82
  command.type !== "PROFLOW_PAGE_COMMAND" ||
81
83
  command.contentInstanceId !== contentInstanceId ||
@@ -88,33 +90,32 @@ chrome.runtime.onMessage.addListener((command, _sender, sendResponse) => {
88
90
  ...observation(),
89
91
  verified: hasFingerprint(command.fingerprint),
90
92
  };
91
- if (pageState().pageState === "BLOCKED")
92
- throw new Error("PAGE_PERMISSION_REQUIRES_HUMAN");
93
+ if (command.operation === "permissionAction") {
94
+ if (!command.permissionFingerprint || !command.permissionAction)
95
+ throw new Error("PERMISSION_ACTION_INVALID");
96
+ performChatGptPermissionAction(
97
+ document,
98
+ command.permissionFingerprint,
99
+ command.permissionAction,
100
+ );
101
+ return observation();
102
+ }
103
+ if (pageState().pageState === "BLOCKED") throw new Error("PAGE_BLOCKED");
93
104
  if (command.operation === "click") {
94
105
  safeElement(command.selector).click();
95
106
  return observation();
96
107
  }
97
- const input = safeElement(command.selector ?? "#prompt-textarea");
98
108
  if (command.value === undefined || command.value.length > 4_096)
99
109
  throw new Error("INPUT_BUDGET_EXCEEDED");
100
- input.focus();
101
- if (
102
- input instanceof HTMLTextAreaElement ||
103
- input instanceof HTMLInputElement
104
- )
105
- input.value = command.value;
106
- else input.textContent = command.value;
107
- input.dispatchEvent(
108
- new InputEvent("input", {
109
- bubbles: true,
110
- inputType: "insertText",
111
- data: command.value,
112
- }),
110
+ if (command.operation === "submit") {
111
+ await submitChatGptComposer(document, command.value);
112
+ return observation();
113
+ }
114
+ writeChatGptInput(
115
+ document,
116
+ command.selector ?? "#prompt-textarea",
117
+ command.value,
113
118
  );
114
- if (command.operation === "submit")
115
- safeElement(
116
- 'button[data-testid="send-button"], button[aria-label*="Send"]',
117
- ).click();
118
119
  return observation();
119
120
  })().then(
120
121
  (value) => sendResponse({ ok: true, value }),
@@ -55,6 +55,12 @@
55
55
  <div id="nodes"></div>
56
56
  </section>
57
57
 
58
+ <section>
59
+ <h2>Carrier Attention</h2>
60
+ <p class="meta">Transient Browser reality requiring a human decision. This is not an Execution Approval fact; Allow uses one-time permission only.</p>
61
+ <div id="carrier-attentions" class="meta">No carrier attention.</div>
62
+ </section>
63
+
58
64
  <section>
59
65
  <h2>Execution Approval</h2>
60
66
  <p class="meta">Approval is an Execution-owned durable fact. This page only sends authenticated human decisions to the Execution Owner.</p>
@@ -1,12 +1,11 @@
1
- export {};
1
+ import { parseCarrierAttentionViews } from "../src/carrier-attention-view.js";
2
2
 
3
3
  type ChromePanel = {
4
4
  runtime: { sendMessage(message: unknown): Promise<unknown> };
5
5
  };
6
6
  declare const chrome: ChromePanel;
7
7
 
8
- const extensionRuntime =
9
- typeof chrome !== "undefined" && chrome.runtime ? chrome.runtime : null;
8
+ const extensionRuntime = typeof chrome === "undefined" ? null : chrome.runtime;
10
9
 
11
10
  type TaskSummary = {
12
11
  taskId: string;
@@ -65,6 +64,7 @@ const startButton = element<HTMLButtonElement>("#start-task");
65
64
  const ensureWorkersButton = element<HTMLButtonElement>("#ensure-workers");
66
65
  const newTaskForm = element<HTMLFormElement>("#new-task-form");
67
66
  const approvalsTarget = element<HTMLElement>("#approvals");
67
+ const carrierAttentionsTarget = element<HTMLElement>("#carrier-attentions");
68
68
  const systemAssessmentTarget = element<HTMLElement>("#system-assessment");
69
69
 
70
70
  let selected: TaskView | null = null;
@@ -101,7 +101,8 @@ async function taskApplication(
101
101
  operation: string,
102
102
  input: Record<string, unknown>,
103
103
  ): Promise<unknown> {
104
- if (!extensionRuntime) return webApplication("/tasks/api/task", operation, input);
104
+ if (!extensionRuntime)
105
+ return webApplication("/tasks/api/task", operation, input);
105
106
  const raw = await extensionRuntime.sendMessage({
106
107
  type: "PROFLOW_TASK_APPLICATION",
107
108
  operation,
@@ -138,6 +139,90 @@ async function approvalApplication(
138
139
  return response.value;
139
140
  }
140
141
 
142
+ async function carrierAttentionAction(
143
+ attentionRef: string,
144
+ action: "allowOnce" | "deny",
145
+ ): Promise<void> {
146
+ if (!extensionRuntime) {
147
+ const response = await fetch("/tasks/api/carrier-attention", {
148
+ method: "POST",
149
+ headers: { "content-type": "application/json" },
150
+ body: JSON.stringify({ attentionRef, action }),
151
+ });
152
+ const body = record(await response.json());
153
+ if (!response.ok || body.ok !== true)
154
+ throw new Error(
155
+ typeof body.error === "string"
156
+ ? body.error
157
+ : "CARRIER_ATTENTION_ACTION_FAILED",
158
+ );
159
+ return;
160
+ }
161
+ const raw = await extensionRuntime.sendMessage({
162
+ type: "PROFLOW_CARRIER_ATTENTION_ACTION",
163
+ input: { attentionRef, action },
164
+ });
165
+ const response = record(raw);
166
+ if (response.ok !== true)
167
+ throw new Error(
168
+ typeof response.error === "string"
169
+ ? response.error
170
+ : "CARRIER_ATTENTION_ACTION_FAILED",
171
+ );
172
+ }
173
+
174
+ function renderCarrierAttentions(snapshot: Record<string, unknown>): void {
175
+ const attentions = parseCarrierAttentionViews(snapshot.carrierAttentions);
176
+ carrierAttentionsTarget.replaceChildren();
177
+ if (attentions.length === 0) {
178
+ carrierAttentionsTarget.textContent = "No carrier attention.";
179
+ return;
180
+ }
181
+ for (const attention of attentions) {
182
+ const row = document.createElement("div");
183
+ row.className = "task";
184
+ const label = document.createElement("div");
185
+ label.textContent = `${attention.operationId} · ${attention.targetHost ?? "unknown target"}`;
186
+ const detail = document.createElement("div");
187
+ detail.className = "meta";
188
+ detail.textContent = [
189
+ attention.reason,
190
+ attention.taskId ? `task ${attention.taskId}` : "task unknown",
191
+ attention.roleRef ? `role ${attention.roleRef}` : "role unknown",
192
+ ].join(" · ");
193
+ row.append(label, detail);
194
+ if (attention.actions.includes("allowOnce")) {
195
+ const allow = document.createElement("button");
196
+ allow.type = "button";
197
+ allow.textContent = "Allow once";
198
+ allow.addEventListener(
199
+ "click",
200
+ () =>
201
+ void run(async () => {
202
+ await carrierAttentionAction(attention.attentionRef, "allowOnce");
203
+ await refreshBrowserStatus();
204
+ }),
205
+ );
206
+ row.append(allow);
207
+ }
208
+ if (attention.actions.includes("deny")) {
209
+ const deny = document.createElement("button");
210
+ deny.type = "button";
211
+ deny.textContent = "Deny";
212
+ deny.addEventListener(
213
+ "click",
214
+ () =>
215
+ void run(async () => {
216
+ await carrierAttentionAction(attention.attentionRef, "deny");
217
+ await refreshBrowserStatus();
218
+ }),
219
+ );
220
+ row.append(deny);
221
+ }
222
+ carrierAttentionsTarget.append(row);
223
+ }
224
+ }
225
+
141
226
  async function refreshApprovals() {
142
227
  const value = record(
143
228
  await approvalApplication("approval.list", { status: "PENDING" }),
@@ -254,7 +339,9 @@ async function refreshTasks() {
254
339
  async function pageStatus(): Promise<Record<string, unknown>> {
255
340
  if (extensionRuntime)
256
341
  return record(
257
- await extensionRuntime.sendMessage({ type: "PROFLOW_SIDE_PANEL_SNAPSHOT" }),
342
+ await extensionRuntime.sendMessage({
343
+ type: "PROFLOW_SIDE_PANEL_SNAPSHOT",
344
+ }),
258
345
  );
259
346
  const response = await fetch("/tasks/api/status", { cache: "no-store" });
260
347
  const body = record(await response.json());
@@ -267,6 +354,7 @@ async function pageStatus(): Promise<Record<string, unknown>> {
267
354
 
268
355
  async function refreshBrowserStatus() {
269
356
  const snapshot = await pageStatus();
357
+ renderCarrierAttentions(snapshot);
270
358
  connection.textContent =
271
359
  snapshot.taskApplicationConfigured === true &&
272
360
  snapshot.approvalApplicationConfigured === true
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.37",
4
+ "version": "0.1.39",
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.37",
3
+ "version": "0.1.39",
4
4
  "description": "Execution-owned MV3 Browser executor, evidence provider and browser application surface.",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -31,8 +31,8 @@
31
31
  "@tomflow/proflow-module-contract": "^0.1.13"
32
32
  },
33
33
  "devDependencies": {
34
- "@tomflow/proflow-deployment-conformance": "^0.1.13",
35
- "@tomflow/proflow-execution-runtime": "^0.1.16"
34
+ "@tomflow/proflow-execution-runtime": "^0.1.17",
35
+ "@tomflow/proflow-deployment-conformance": "^0.1.13"
36
36
  },
37
37
  "keywords": [
38
38
  "proflow",
@@ -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.37",
6
+ "moduleVersion": "0.1.39",
7
7
  "kind": "browser-extension",
8
8
  "templateVersion": "1.0.0",
9
9
  "platformCompatibility": ">=1.0.0 <2.0.0",