@tomflow/proflow-execution-browser-extension 0.1.0

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.
Files changed (39) hide show
  1. package/README.md +7 -0
  2. package/conformance.json +1 -0
  3. package/deployment/browser-extension.json +6 -0
  4. package/dist/deployment/adapter.d.ts +61 -0
  5. package/dist/deployment/adapter.js +47 -0
  6. package/dist/deployment/descriptor.d.ts +103 -0
  7. package/dist/deployment/descriptor.js +109 -0
  8. package/dist/extension/background.d.ts +1 -0
  9. package/dist/extension/background.js +752 -0
  10. package/dist/extension/content.d.ts +1 -0
  11. package/dist/extension/content.js +90 -0
  12. package/dist/extension/options.d.ts +1 -0
  13. package/dist/extension/options.js +68 -0
  14. package/dist/extension/side-panel.d.ts +1 -0
  15. package/dist/extension/side-panel.js +262 -0
  16. package/dist/src/bridge.d.ts +26 -0
  17. package/dist/src/bridge.js +288 -0
  18. package/dist/src/collaboration-carrier.d.ts +65 -0
  19. package/dist/src/collaboration-carrier.js +138 -0
  20. package/dist/src/index.d.ts +137 -0
  21. package/dist/src/index.js +779 -0
  22. package/dist/src/runtime-composition.d.ts +97 -0
  23. package/dist/src/runtime-composition.js +124 -0
  24. package/dist/src/system-observer.d.ts +86 -0
  25. package/dist/src/system-observer.js +252 -0
  26. package/dist/src/task-observer.d.ts +118 -0
  27. package/dist/src/task-observer.js +105 -0
  28. package/dist/src/vision.d.ts +73 -0
  29. package/dist/src/vision.js +82 -0
  30. package/extension/background.ts +997 -0
  31. package/extension/content.ts +138 -0
  32. package/extension/options.html +54 -0
  33. package/extension/options.ts +98 -0
  34. package/extension/side-panel.html +77 -0
  35. package/extension/side-panel.ts +349 -0
  36. package/manifest.json +20 -0
  37. package/package.json +58 -0
  38. package/proflow.module.json +127 -0
  39. package/self-install.mjs +27 -0
@@ -0,0 +1,349 @@
1
+ export {};
2
+
3
+ type ChromePanel = {
4
+ runtime: { sendMessage(message: unknown): Promise<unknown> };
5
+ };
6
+ declare const chrome: ChromePanel;
7
+
8
+ type TaskSummary = {
9
+ taskId: string;
10
+ title: string;
11
+ status: string;
12
+ version: number;
13
+ canStart?: boolean;
14
+ blockedReason?: string | null;
15
+ };
16
+ type ApprovalView = {
17
+ approvalRef: string;
18
+ executionRef: string;
19
+ capability: string;
20
+ callerRef: string;
21
+ taskId?: string;
22
+ status:
23
+ | "PENDING"
24
+ | "APPROVED"
25
+ | "DENIED"
26
+ | "REVOKED"
27
+ | "CONSUMED"
28
+ | "EXPIRED";
29
+ version: number;
30
+ expiresAt: string;
31
+ };
32
+ type TaskView = TaskSummary & {
33
+ roleBindings: Array<{
34
+ agentPackageRef: string;
35
+ roleRef: string;
36
+ workerRef: string | null;
37
+ conversationLocator: string | null;
38
+ }>;
39
+ nodes: Array<{
40
+ nodeId: string;
41
+ title: string;
42
+ status: string;
43
+ runNo: number;
44
+ version: number;
45
+ }>;
46
+ };
47
+
48
+ function element<T extends HTMLElement>(selector: string): T {
49
+ const value = document.querySelector<T>(selector);
50
+ if (!value) throw new Error(`SIDE_PANEL_TARGET_MISSING:${selector}`);
51
+ return value;
52
+ }
53
+
54
+ const connection = element<HTMLElement>("#connection");
55
+ const browserStatus = element<HTMLElement>("#browser-status");
56
+ const tasksTarget = element<HTMLElement>("#tasks");
57
+ const selectedTarget = element<HTMLElement>("#selected-task");
58
+ const nodesTarget = element<HTMLElement>("#nodes");
59
+ const errorTarget = element<HTMLElement>("#error");
60
+ const resultTarget = element<HTMLElement>("#result");
61
+ const startButton = element<HTMLButtonElement>("#start-task");
62
+ const ensureWorkersButton = element<HTMLButtonElement>("#ensure-workers");
63
+ const newTaskForm = element<HTMLFormElement>("#new-task-form");
64
+ const approvalsTarget = element<HTMLElement>("#approvals");
65
+ const systemAssessmentTarget = element<HTMLElement>("#system-assessment");
66
+
67
+ let selected: TaskView | null = null;
68
+
69
+ function requestId(prefix: string): string {
70
+ return `${prefix}:${crypto.randomUUID()}`;
71
+ }
72
+
73
+ function record(value: unknown): Record<string, unknown> {
74
+ if (typeof value !== "object" || value === null || Array.isArray(value))
75
+ throw new Error("TASK_APPLICATION_RESPONSE_INVALID");
76
+ return value as Record<string, unknown>;
77
+ }
78
+
79
+ async function taskApplication(
80
+ operation: string,
81
+ input: Record<string, unknown>,
82
+ ): Promise<unknown> {
83
+ const raw = await chrome.runtime.sendMessage({
84
+ type: "PROFLOW_TASK_APPLICATION",
85
+ operation,
86
+ input,
87
+ });
88
+ const response = record(raw);
89
+ if (response.ok !== true)
90
+ throw new Error(
91
+ typeof response.error === "string"
92
+ ? response.error
93
+ : "TASK_APPLICATION_FAILED",
94
+ );
95
+ return response.value;
96
+ }
97
+
98
+ async function approvalApplication(
99
+ operation: string,
100
+ input: Record<string, unknown>,
101
+ ): Promise<unknown> {
102
+ const raw = await chrome.runtime.sendMessage({
103
+ type: "PROFLOW_APPROVAL_APPLICATION",
104
+ operation,
105
+ input,
106
+ });
107
+ const response = record(raw);
108
+ if (response.ok !== true)
109
+ throw new Error(
110
+ typeof response.error === "string"
111
+ ? response.error
112
+ : "APPROVAL_APPLICATION_FAILED",
113
+ );
114
+ return response.value;
115
+ }
116
+
117
+ async function refreshApprovals() {
118
+ const value = record(
119
+ await approvalApplication("approval.list", { status: "PENDING" }),
120
+ );
121
+ const approvals = Array.isArray(value.approvals)
122
+ ? (value.approvals as ApprovalView[])
123
+ : [];
124
+ approvalsTarget.replaceChildren();
125
+ for (const approval of approvals) {
126
+ const row = document.createElement("div");
127
+ row.className = "task";
128
+ const label = document.createElement("span");
129
+ label.textContent = `${approval.capability} · ${approval.executionRef} · expires ${approval.expiresAt}`;
130
+ row.append(label);
131
+ const allow = document.createElement("button");
132
+ allow.type = "button";
133
+ allow.textContent = "Allow";
134
+ allow.addEventListener(
135
+ "click",
136
+ () =>
137
+ void run(async () => {
138
+ await approvalApplication("approval.allow", {
139
+ approvalRef: approval.approvalRef,
140
+ expectedVersion: approval.version,
141
+ });
142
+ await refreshApprovals();
143
+ }),
144
+ );
145
+ const deny = document.createElement("button");
146
+ deny.type = "button";
147
+ deny.textContent = "Deny";
148
+ deny.addEventListener(
149
+ "click",
150
+ () =>
151
+ void run(async () => {
152
+ await approvalApplication("approval.deny", {
153
+ approvalRef: approval.approvalRef,
154
+ expectedVersion: approval.version,
155
+ reason: "Denied from Extension Side Panel",
156
+ });
157
+ await refreshApprovals();
158
+ }),
159
+ );
160
+ row.append(allow, deny);
161
+ approvalsTarget.append(row);
162
+ }
163
+ }
164
+
165
+ function setBusy(button: HTMLButtonElement, busy: boolean) {
166
+ button.disabled = busy;
167
+ }
168
+
169
+ async function loadTask(taskId: string) {
170
+ selected = (await taskApplication("task.get", { taskId })) as TaskView;
171
+ selectedTarget.textContent = `${selected.taskId} · ${selected.status} · v${selected.version}`;
172
+ startButton.disabled = selected.status !== "READY";
173
+ ensureWorkersButton.disabled =
174
+ selected.status === "SUCCEEDED" || selected.status === "TERMINATED";
175
+ nodesTarget.replaceChildren();
176
+ for (const node of selected.nodes) {
177
+ const row = document.createElement("div");
178
+ row.className = "task";
179
+ const label = document.createElement("span");
180
+ label.textContent = `${node.title} · ${node.status} · run ${node.runNo}`;
181
+ row.append(label);
182
+ if (["SUCCEEDED", "FAILED", "WAITING"].includes(node.status)) {
183
+ const reopen = document.createElement("button");
184
+ reopen.type = "button";
185
+ reopen.textContent = "Reopen";
186
+ reopen.addEventListener("click", () => {
187
+ void run(async () => {
188
+ if (!selected) return;
189
+ await taskApplication("node.reopen", {
190
+ taskId: selected.taskId,
191
+ nodeId: node.nodeId,
192
+ reason: "Human reopen from Extension Side Panel",
193
+ expectedTaskVersion: selected.version,
194
+ idempotencyKey: requestId("extension-reopen"),
195
+ });
196
+ await loadTask(selected.taskId);
197
+ await refreshTasks();
198
+ });
199
+ });
200
+ row.append(reopen);
201
+ }
202
+ nodesTarget.append(row);
203
+ }
204
+ }
205
+
206
+ async function refreshTasks() {
207
+ const value = record(await taskApplication("task.list", {}));
208
+ const tasks = Array.isArray(value.tasks)
209
+ ? (value.tasks as TaskSummary[])
210
+ : [];
211
+ tasksTarget.replaceChildren();
212
+ for (const task of tasks) {
213
+ const row = document.createElement("div");
214
+ row.className = "task";
215
+ const open = document.createElement("button");
216
+ open.type = "button";
217
+ open.textContent = `${task.title} · ${task.status}`;
218
+ open.addEventListener("click", () => void run(() => loadTask(task.taskId)));
219
+ row.append(open);
220
+ if (task.blockedReason) {
221
+ const detail = document.createElement("div");
222
+ detail.className = "meta";
223
+ detail.textContent = task.blockedReason;
224
+ row.append(detail);
225
+ }
226
+ tasksTarget.append(row);
227
+ }
228
+ }
229
+
230
+ async function refreshBrowserStatus() {
231
+ const snapshot = record(
232
+ await chrome.runtime.sendMessage({ type: "PROFLOW_SIDE_PANEL_SNAPSHOT" }),
233
+ );
234
+ connection.textContent =
235
+ snapshot.taskApplicationConfigured === true &&
236
+ snapshot.approvalApplicationConfigured === true
237
+ ? "Task + Approval applications connected"
238
+ : "Local application credential missing — open Extension Options";
239
+ browserStatus.textContent = JSON.stringify(snapshot, null, 2);
240
+ const observer =
241
+ typeof snapshot.systemObserver === "object" &&
242
+ snapshot.systemObserver !== null &&
243
+ !Array.isArray(snapshot.systemObserver)
244
+ ? (snapshot.systemObserver as Record<string, unknown>)
245
+ : null;
246
+ if (observer === null) {
247
+ systemAssessmentTarget.textContent = "No assessment yet.";
248
+ } else {
249
+ const unresolved = Array.isArray(observer.unresolved)
250
+ ? observer.unresolved.filter(
251
+ (item): item is string => typeof item === "string",
252
+ )
253
+ : [];
254
+ const carry = Array.isArray(observer.carryForward)
255
+ ? observer.carryForward
256
+ : [];
257
+ systemAssessmentTarget.textContent = [
258
+ `assessmentRef: ${String(observer.assessmentRef ?? "?")}`,
259
+ `needsHumanAttention: ${observer.needsHumanAttention === true}`,
260
+ `unresolved: ${unresolved.join(" | ")}`,
261
+ `carryForward: ${carry.length}`,
262
+ ].join("\n");
263
+ }
264
+ if (snapshot.taskApplicationConfigured === true) await refreshTasks();
265
+ if (snapshot.approvalApplicationConfigured === true) await refreshApprovals();
266
+ }
267
+
268
+ async function run(action: () => Promise<void>) {
269
+ errorTarget.textContent = "";
270
+ try {
271
+ await action();
272
+ } catch (error) {
273
+ errorTarget.textContent =
274
+ error instanceof Error ? error.message : "Operation failed";
275
+ }
276
+ }
277
+
278
+ newTaskForm.addEventListener("submit", (event) => {
279
+ event.preventDefault();
280
+ void run(async () => {
281
+ const title = element<HTMLInputElement>("#task-title").value.trim();
282
+ const objective =
283
+ element<HTMLTextAreaElement>("#task-objective").value.trim();
284
+ const nodes = JSON.parse(
285
+ element<HTMLTextAreaElement>("#task-plan").value,
286
+ ) as unknown;
287
+ if (!Array.isArray(nodes) || nodes.length === 0)
288
+ throw new Error("Task plan must be a non-empty JSON array");
289
+ const value = await taskApplication("task.create", {
290
+ title,
291
+ objective,
292
+ plan: { nodes },
293
+ initialDocuments: [],
294
+ idempotencyKey: requestId("extension-new-task"),
295
+ });
296
+ const created = record(value);
297
+ resultTarget.textContent = `Created ${String(created.taskId ?? "Task")}.`;
298
+ if (typeof created.taskId === "string") await loadTask(created.taskId);
299
+ await refreshTasks();
300
+ });
301
+ });
302
+
303
+ element<HTMLButtonElement>("#refresh-tasks").addEventListener(
304
+ "click",
305
+ () => void run(refreshTasks),
306
+ );
307
+
308
+ element<HTMLButtonElement>("#refresh-approvals").addEventListener(
309
+ "click",
310
+ () => void run(refreshApprovals),
311
+ );
312
+
313
+ startButton.addEventListener("click", () => {
314
+ void run(async () => {
315
+ if (!selected) return;
316
+ setBusy(startButton, true);
317
+ try {
318
+ await taskApplication("task.start", {
319
+ taskId: selected.taskId,
320
+ expectedTaskVersion: selected.version,
321
+ idempotencyKey: requestId("extension-start-task"),
322
+ });
323
+ await loadTask(selected.taskId);
324
+ await refreshTasks();
325
+ } finally {
326
+ startButton.disabled = selected?.status !== "READY";
327
+ }
328
+ });
329
+ });
330
+
331
+ ensureWorkersButton.addEventListener("click", () => {
332
+ void run(async () => {
333
+ if (!selected) return;
334
+ setBusy(ensureWorkersButton, true);
335
+ try {
336
+ await taskApplication("task.ensureWorkers", { taskId: selected.taskId });
337
+ await loadTask(selected.taskId);
338
+ await refreshTasks();
339
+ } finally {
340
+ ensureWorkersButton.disabled =
341
+ selected?.status === "SUCCEEDED" || selected?.status === "TERMINATED";
342
+ }
343
+ });
344
+ });
345
+
346
+ void run(refreshBrowserStatus);
347
+ setInterval(() => {
348
+ void run(refreshBrowserStatus);
349
+ }, 5_000);
package/manifest.json ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "manifest_version": 3,
3
+ "name": "ProFlow Execution Browser",
4
+ "version": "0.1.0",
5
+ "permissions": ["tabs", "storage", "sidePanel"],
6
+ "host_permissions": ["https://chatgpt.com/*", "http://127.0.0.1/*"],
7
+ "background": {
8
+ "service_worker": "dist/extension/background.js",
9
+ "type": "module"
10
+ },
11
+ "content_scripts": [
12
+ {
13
+ "matches": ["https://chatgpt.com/g/*"],
14
+ "js": ["dist/extension/content.js"],
15
+ "run_at": "document_idle"
16
+ }
17
+ ],
18
+ "side_panel": { "default_path": "extension/side-panel.html" },
19
+ "options_page": "extension/options.html"
20
+ }
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "@tomflow/proflow-execution-browser-extension",
3
+ "version": "0.1.0",
4
+ "description": "Execution-owned MV3 Browser executor, evidence provider and browser application surface.",
5
+ "type": "module",
6
+ "publishConfig": {
7
+ "access": "public"
8
+ },
9
+ "exports": {
10
+ ".": "./dist/src/index.js",
11
+ "./bridge": "./dist/src/bridge.js",
12
+ "./deployment/adapter": "./dist/deployment/adapter.js",
13
+ "./deployment/descriptor": "./dist/deployment/descriptor.js",
14
+ "./runtime-composition": "./dist/src/runtime-composition.js"
15
+ },
16
+ "files": [
17
+ "dist",
18
+ "extension",
19
+ "manifest.json",
20
+ "deployment/browser-extension.json",
21
+ "proflow.module.json",
22
+ "conformance.json",
23
+ "README.md",
24
+ "self-install.mjs"
25
+ ],
26
+ "dependencies": {
27
+ "@tomflow/proflow-execution-contracts": "^0.1.0"
28
+ },
29
+ "devDependencies": {
30
+ "@tomflow/proflow-deployment-conformance": "^0.1.0",
31
+ "@tomflow/proflow-execution-runtime": "^0.1.0",
32
+ "@tomflow/proflow-module-contract": "^0.1.0"
33
+ },
34
+ "keywords": [
35
+ "proflow",
36
+ "proflow-module",
37
+ "execution"
38
+ ],
39
+ "proflow": {
40
+ "module": true,
41
+ "installClass": "core",
42
+ "installRequires": [
43
+ "@tomflow/proflow-agent-runtime",
44
+ "@tomflow/proflow-execution-contracts",
45
+ "@tomflow/proflow-execution-runtime",
46
+ "@tomflow/proflow-task-orchestration"
47
+ ],
48
+ "descriptor": "./dist/deployment/descriptor.js",
49
+ "manifest": "./proflow.module.json"
50
+ },
51
+ "bin": {
52
+ "proflow-execution-browser-extension": "./self-install.mjs"
53
+ },
54
+ "scripts": {
55
+ "test": "node --test tests/**/*.test.ts",
56
+ "typecheck": "tsc --noEmit"
57
+ }
58
+ }
@@ -0,0 +1,127 @@
1
+ {
2
+ "contract": "module",
3
+ "contractVersion": "1.0.0",
4
+ "moduleRef": "execution-browser-extension",
5
+ "packageName": "@tomflow/proflow-execution-browser-extension",
6
+ "moduleVersion": "0.1.0",
7
+ "kind": "browser-extension",
8
+ "templateVersion": "1.0.0",
9
+ "platformCompatibility": ">=1.0.0 <2.0.0",
10
+ "installClass": "core",
11
+ "identity": {
12
+ "domain": "execution",
13
+ "summary": "Execution-owned MV3 Browser executor, evidence provider and browser application surface."
14
+ },
15
+ "provides": [
16
+ {
17
+ "contractRef": "execution-browser-executor",
18
+ "version": "1.0.0"
19
+ }
20
+ ],
21
+ "requires": [
22
+ {
23
+ "contractRef": "execution",
24
+ "versionRange": ">=1.0.0 <2.0.0"
25
+ },
26
+ {
27
+ "contractRef": "task-orchestration",
28
+ "versionRange": ">=1.0.0 <2.0.0"
29
+ },
30
+ {
31
+ "contractRef": "agent-runtime",
32
+ "versionRange": ">=1.0.0 <2.0.0"
33
+ }
34
+ ],
35
+ "requirements": [
36
+ {
37
+ "kind": "runtime",
38
+ "runtime": "browser",
39
+ "versionRange": ">=1"
40
+ },
41
+ {
42
+ "kind": "human",
43
+ "action": "Load and verify the unpacked MV3 extension in the real Chrome profile"
44
+ }
45
+ ],
46
+ "configSlots": [
47
+ {
48
+ "key": "bridge.endpoint",
49
+ "type": "url",
50
+ "required": true,
51
+ "description": "Loopback Browser Reality Bridge endpoint"
52
+ },
53
+ {
54
+ "key": "bridge.token",
55
+ "type": "secretRef",
56
+ "required": true,
57
+ "sensitive": true,
58
+ "description": "Browser Reality Bridge extension token reference"
59
+ },
60
+ {
61
+ "key": "taskApplication.endpoint",
62
+ "type": "url",
63
+ "required": true,
64
+ "description": "Loopback Platform Host Task application endpoint"
65
+ },
66
+ {
67
+ "key": "taskApplication.token",
68
+ "type": "secretRef",
69
+ "required": true,
70
+ "sensitive": true,
71
+ "description": "Platform Host Task application token reference"
72
+ },
73
+ {
74
+ "key": "approvalApplication.endpoint",
75
+ "type": "url",
76
+ "required": true,
77
+ "description": "Loopback Platform Host Approval application endpoint"
78
+ },
79
+ {
80
+ "key": "approvalApplication.token",
81
+ "type": "secretRef",
82
+ "required": true,
83
+ "sensitive": true,
84
+ "description": "Platform Host Approval application token reference"
85
+ },
86
+ {
87
+ "key": "chromeRuntimeModuleRef",
88
+ "type": "moduleRef",
89
+ "required": false,
90
+ "description": "External resource module governing the Chrome runtime",
91
+ "default": "chrome-runtime"
92
+ },
93
+ {
94
+ "key": "carrierModuleRef",
95
+ "type": "moduleRef",
96
+ "required": false,
97
+ "description": "External resource module governing the Custom GPT carrier",
98
+ "default": "chatgpt-carrier"
99
+ }
100
+ ],
101
+ "lifecycle": {
102
+ "supported": ["describe", "preflight", "status", "verify", "doctor"]
103
+ },
104
+ "verification": {
105
+ "checks": [
106
+ {
107
+ "id": "real-carrier-e3-e4",
108
+ "description": "Real Chrome and ChatGPT E3/E4 evidence is present",
109
+ "lifecycle": "verify"
110
+ }
111
+ ]
112
+ },
113
+ "effects": [
114
+ {
115
+ "kind": "external-resource",
116
+ "description": "Package an MV3 extension that performs Execution-authorized Browser effects",
117
+ "retention": "preserve"
118
+ }
119
+ ],
120
+ "documentation": [
121
+ {
122
+ "id": "overview",
123
+ "path": "./README.md",
124
+ "description": "Package-owned module overview"
125
+ }
126
+ ]
127
+ }
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env node
2
+ import { spawnSync } from "node:child_process";
3
+
4
+ const [command, ...rest] = process.argv.slice(2);
5
+ const usage =
6
+ "Usage: npx @tomflow/proflow-execution-browser-extension install\n";
7
+ if (command === "--help" || command === "-h") {
8
+ process.stdout.write(usage);
9
+ process.exit(0);
10
+ }
11
+ if (command !== "install" || rest.length > 0) {
12
+ process.stderr.write(usage);
13
+ process.exit(2);
14
+ }
15
+ const executable = process.platform === "win32" ? "npx.cmd" : "npx";
16
+ const result = spawnSync(
17
+ executable,
18
+ [
19
+ "--yes",
20
+ "@tomflow/proflow-platform-cli",
21
+ "install",
22
+ "@tomflow/proflow-execution-browser-extension",
23
+ ],
24
+ { cwd: process.cwd(), env: process.env, stdio: "inherit" },
25
+ );
26
+ if (result.error) throw result.error;
27
+ process.exit(result.status ?? 1);