@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,288 @@
1
+ import { randomUUID, timingSafeEqual } from "node:crypto";
2
+ import { createServer, } from "node:http";
3
+ export class BrowserRealityBridgeError extends Error {
4
+ code;
5
+ constructor(code, message) {
6
+ super(message);
7
+ this.name = "BrowserRealityBridgeError";
8
+ this.code = code;
9
+ }
10
+ }
11
+ const jsonHeaders = {
12
+ "content-type": "application/json; charset=utf-8",
13
+ "cache-control": "no-store",
14
+ };
15
+ function isRecord(value) {
16
+ return typeof value === "object" && value !== null && !Array.isArray(value);
17
+ }
18
+ function stringField(value, key) {
19
+ const item = value[key];
20
+ if (typeof item !== "string" || item.length === 0)
21
+ throw new BrowserRealityBridgeError("BRIDGE_INPUT_INVALID", `${key} must be a non-empty string`);
22
+ return item;
23
+ }
24
+ function numberField(value, key) {
25
+ const item = value[key];
26
+ if (typeof item !== "number" || !Number.isFinite(item))
27
+ throw new BrowserRealityBridgeError("BRIDGE_INPUT_INVALID", `${key} must be a finite number`);
28
+ return item;
29
+ }
30
+ function parseObservation(value) {
31
+ if (!isRecord(value))
32
+ throw new BrowserRealityBridgeError("BRIDGE_INPUT_INVALID", "observation must be an object");
33
+ const tabId = value.tabId;
34
+ const windowId = value.windowId;
35
+ const pageState = value.pageState;
36
+ const activityKind = value.activityKind;
37
+ if (!Number.isInteger(tabId) || !Number.isInteger(windowId))
38
+ throw new BrowserRealityBridgeError("BRIDGE_INPUT_INVALID", "observation tab and window identity must be integers");
39
+ if (!["IDLE", "BUSY", "BLOCKED", "UNKNOWN"].includes(String(pageState)))
40
+ throw new BrowserRealityBridgeError("BRIDGE_INPUT_INVALID", "observation page state is invalid");
41
+ if (activityKind !== null &&
42
+ ![
43
+ "GENERATING",
44
+ "ACTION_PERMISSION",
45
+ "ACTION_RUNNING",
46
+ "WAITING_HUMAN",
47
+ "WAITING_PEER",
48
+ "RECOVERING",
49
+ ].includes(String(activityKind)))
50
+ throw new BrowserRealityBridgeError("BRIDGE_INPUT_INVALID", "observation activity kind is invalid");
51
+ return {
52
+ tabId: tabId,
53
+ windowId: windowId,
54
+ url: stringField(value, "url"),
55
+ contentInstanceId: stringField(value, "contentInstanceId"),
56
+ pageState: pageState,
57
+ activityKind: activityKind,
58
+ observedAt: stringField(value, "observedAt"),
59
+ };
60
+ }
61
+ function safeEqual(left, right) {
62
+ const leftBytes = Buffer.from(left);
63
+ const rightBytes = Buffer.from(right);
64
+ return (leftBytes.length === rightBytes.length &&
65
+ timingSafeEqual(leftBytes, rightBytes));
66
+ }
67
+ async function readJson(request) {
68
+ let body = "";
69
+ for await (const chunk of request) {
70
+ body += String(chunk);
71
+ if (body.length > 100_000)
72
+ throw new BrowserRealityBridgeError("BRIDGE_INPUT_INVALID", "bridge body exceeds 100000 characters");
73
+ }
74
+ try {
75
+ return body.length === 0 ? {} : JSON.parse(body);
76
+ }
77
+ catch {
78
+ throw new BrowserRealityBridgeError("BRIDGE_INPUT_INVALID", "bridge body is not valid JSON");
79
+ }
80
+ }
81
+ function send(response, status, value) {
82
+ response.writeHead(status, jsonHeaders);
83
+ response.end(value === undefined ? "" : JSON.stringify(value));
84
+ }
85
+ export async function createBrowserRealityBridgeServer(options) {
86
+ if (options.token.length < 32)
87
+ throw new TypeError("bridge token must contain at least 32 characters");
88
+ if (!/^[a-z]{32}$/.test(options.extensionId))
89
+ throw new TypeError("extensionId must be a canonical Chromium extension id");
90
+ const now = options.now ?? (() => new Date());
91
+ const idFactory = options.idFactory ?? randomUUID;
92
+ const freshnessMs = options.heartbeatFreshnessMs ?? 10_000;
93
+ const commandTimeoutMs = options.commandTimeoutMs ?? 15_000;
94
+ const expectedOrigin = `chrome-extension://${options.extensionId}`;
95
+ const queue = [];
96
+ const pending = new Map();
97
+ let session;
98
+ let closed = false;
99
+ const authenticate = (request) => {
100
+ const authorization = request.headers.authorization;
101
+ const origin = request.headers.origin;
102
+ if (!authorization?.startsWith("Bearer ") ||
103
+ !safeEqual(authorization.slice(7), options.token) ||
104
+ origin !== expectedOrigin)
105
+ throw new BrowserRealityBridgeError("BRIDGE_AUTH_INVALID", "bridge authentication failed");
106
+ };
107
+ const server = createServer(async (request, response) => {
108
+ try {
109
+ response.setHeader("access-control-allow-origin", expectedOrigin);
110
+ response.setHeader("vary", "origin");
111
+ if (request.method === "OPTIONS") {
112
+ response.setHeader("access-control-allow-headers", "authorization, content-type");
113
+ response.setHeader("access-control-allow-methods", "GET, POST, OPTIONS");
114
+ response.writeHead(204);
115
+ response.end();
116
+ return;
117
+ }
118
+ authenticate(request);
119
+ const url = new URL(request.url ?? "/", "http://127.0.0.1");
120
+ if (request.method === "POST" && url.pathname === "/v1/session/hello") {
121
+ const body = await readJson(request);
122
+ if (!isRecord(body) ||
123
+ stringField(body, "extensionId") !== options.extensionId)
124
+ throw new BrowserRealityBridgeError("BRIDGE_AUTH_INVALID", "extension identity mismatch");
125
+ session = {
126
+ extensionInstanceId: stringField(body, "extensionInstanceId"),
127
+ lastHeartbeatAt: now().getTime(),
128
+ };
129
+ send(response, 200, { accepted: true });
130
+ return;
131
+ }
132
+ if (!session)
133
+ throw new BrowserRealityBridgeError("BRIDGE_OFFLINE", "extension session has not completed hello");
134
+ if (url.searchParams.get("extensionInstanceId") !==
135
+ session.extensionInstanceId)
136
+ throw new BrowserRealityBridgeError("BRIDGE_AUTH_INVALID", "stale extension session");
137
+ if (request.method === "POST" &&
138
+ url.pathname === "/v1/session/heartbeat") {
139
+ session.lastHeartbeatAt = now().getTime();
140
+ send(response, 200, { accepted: true });
141
+ return;
142
+ }
143
+ if (request.method === "GET" && url.pathname === "/v1/commands/next") {
144
+ session.lastHeartbeatAt = now().getTime();
145
+ const command = queue.shift();
146
+ if (command) {
147
+ const tracked = pending.get(command.commandId);
148
+ if (tracked)
149
+ tracked.stage = "DELIVERED";
150
+ }
151
+ send(response, command ? 200 : 204, command);
152
+ return;
153
+ }
154
+ if (request.method === "POST" && url.pathname === "/v1/commands/result") {
155
+ const body = await readJson(request);
156
+ if (!isRecord(body))
157
+ throw new BrowserRealityBridgeError("BRIDGE_INPUT_INVALID", "command result must be an object");
158
+ const commandId = stringField(body, "commandId");
159
+ const command = pending.get(commandId);
160
+ if (!command)
161
+ throw new BrowserRealityBridgeError("BRIDGE_INPUT_INVALID", "command result is stale or unknown");
162
+ pending.delete(commandId);
163
+ clearTimeout(command.timer);
164
+ if (body.ok === true)
165
+ command.resolve(body.value);
166
+ else
167
+ command.reject(new BrowserRealityBridgeError("BRIDGE_COMMAND_FAILED", typeof body.error === "string"
168
+ ? body.error
169
+ : "extension command failed"));
170
+ send(response, 200, { accepted: true });
171
+ return;
172
+ }
173
+ send(response, 404, { error: "NOT_FOUND" });
174
+ }
175
+ catch (error) {
176
+ const bridgeError = error instanceof BrowserRealityBridgeError
177
+ ? error
178
+ : new BrowserRealityBridgeError("BRIDGE_INPUT_INVALID", error instanceof Error ? error.message : "bridge request failed");
179
+ send(response, bridgeError.code === "BRIDGE_AUTH_INVALID" ? 401 : 400, {
180
+ error: bridgeError.code,
181
+ });
182
+ }
183
+ });
184
+ await new Promise((resolve, reject) => {
185
+ server.once("error", reject);
186
+ server.listen(options.port ?? 0, options.host ?? "127.0.0.1", () => {
187
+ server.off("error", reject);
188
+ resolve();
189
+ });
190
+ });
191
+ const address = server.address();
192
+ if (!address || typeof address === "string")
193
+ throw new Error("bridge address missing");
194
+ const endpoint = `http://127.0.0.1:${address.port}`;
195
+ const online = () => !closed &&
196
+ session !== undefined &&
197
+ now().getTime() - session.lastHeartbeatAt <= freshnessMs;
198
+ const requestCommand = (command) => {
199
+ if (!online())
200
+ return Promise.reject(new BrowserRealityBridgeError("BRIDGE_OFFLINE", "extension heartbeat is not fresh"));
201
+ const commandId = `browser-command:${idFactory()}`;
202
+ return new Promise((resolve, reject) => {
203
+ const timer = setTimeout(() => {
204
+ const tracked = pending.get(commandId);
205
+ if (tracked?.stage === "QUEUED") {
206
+ const index = queue.findIndex((item) => item.commandId === commandId);
207
+ if (index >= 0)
208
+ queue.splice(index, 1);
209
+ }
210
+ pending.delete(commandId);
211
+ reject(new BrowserRealityBridgeError("BRIDGE_COMMAND_TIMEOUT", "extension command result timed out"));
212
+ }, commandTimeoutMs);
213
+ const materialized = { ...command, commandId };
214
+ pending.set(commandId, {
215
+ command: materialized,
216
+ stage: "QUEUED",
217
+ resolve,
218
+ reject,
219
+ timer,
220
+ });
221
+ queue.push(materialized);
222
+ });
223
+ };
224
+ const browser = {
225
+ async listTabs() {
226
+ const value = await requestCommand({ type: "LIST_TABS" });
227
+ if (!Array.isArray(value))
228
+ throw new BrowserRealityBridgeError("BRIDGE_INPUT_INVALID", "LIST_TABS result must be an array");
229
+ return value.map(parseObservation);
230
+ },
231
+ async open(url) {
232
+ return parseObservation(await requestCommand({ type: "OPEN", url }));
233
+ },
234
+ async observe(tabId) {
235
+ return parseObservation(await requestCommand({ type: "OBSERVE", tabId }));
236
+ },
237
+ async submit(tabId, text, fingerprint) {
238
+ return parseObservation(await requestCommand({ type: "SUBMIT", tabId, text, fingerprint }));
239
+ },
240
+ async hasMessage(tabId, fingerprint) {
241
+ const value = await requestCommand({
242
+ type: "VERIFY",
243
+ tabId,
244
+ fingerprint,
245
+ });
246
+ if (!isRecord(value) || typeof value.verified !== "boolean")
247
+ throw new BrowserRealityBridgeError("BRIDGE_INPUT_INVALID", "VERIFY result is invalid");
248
+ return value.verified;
249
+ },
250
+ async screenshot(tabId) {
251
+ const value = await requestCommand({ type: "SCREENSHOT", tabId });
252
+ if (!isRecord(value))
253
+ throw new BrowserRealityBridgeError("BRIDGE_INPUT_INVALID", "SCREENSHOT result is invalid");
254
+ return {
255
+ evidenceRef: stringField(value, "evidenceRef"),
256
+ dataUrl: stringField(value, "dataUrl"),
257
+ mimeType: stringField(value, "mimeType"),
258
+ sizeBytes: numberField(value, "sizeBytes"),
259
+ hash: stringField(value, "hash"),
260
+ };
261
+ },
262
+ async perform(request, tabId) {
263
+ return parseObservation(await requestCommand({ type: "PERFORM", tabId, request }));
264
+ },
265
+ };
266
+ return Object.freeze({
267
+ endpoint,
268
+ browser,
269
+ status() {
270
+ return {
271
+ online: online(),
272
+ extensionInstanceId: session?.extensionInstanceId ?? null,
273
+ queuedCommands: queue.length,
274
+ pendingCommands: pending.size,
275
+ };
276
+ },
277
+ async close() {
278
+ closed = true;
279
+ for (const item of pending.values()) {
280
+ clearTimeout(item.timer);
281
+ item.reject(new BrowserRealityBridgeError("BRIDGE_OFFLINE", "bridge server closed"));
282
+ }
283
+ pending.clear();
284
+ queue.length = 0;
285
+ await new Promise((resolve, reject) => server.close((error) => (error ? reject(error) : resolve())));
286
+ },
287
+ });
288
+ }
@@ -0,0 +1,65 @@
1
+ import { type ExecuteCapabilityRequest } from "@tomflow/proflow-execution-contracts";
2
+ export type PendingCollaborationCarrierMessage = {
3
+ messageId: string;
4
+ threadId: string;
5
+ taskId: string;
6
+ kind: "QUESTION" | "REPLY";
7
+ fromRoleRef: string;
8
+ fromWorkerRef: string;
9
+ targetRoleRef: string;
10
+ targetWorkerRef: string;
11
+ replyToMessageId: string | null;
12
+ content: string;
13
+ status: "PENDING";
14
+ deliveryAttemptCount: number;
15
+ lastDeliveryErrorCode: string | null;
16
+ executionRef: string | null;
17
+ evidenceRef: string | null;
18
+ };
19
+ export interface CollaborationCarrierTaskPort {
20
+ getWorkerBinding(taskId: string, roleRef: string): Promise<{
21
+ workerRef: string;
22
+ conversationLocator: string | null;
23
+ } | null>;
24
+ }
25
+ export interface CollaborationCarrierAgentPort {
26
+ listPendingMessages(limit: number): Promise<PendingCollaborationCarrierMessage[]>;
27
+ getPendingMessage(messageRef: string): Promise<PendingCollaborationCarrierMessage>;
28
+ reportDeliveryOutcome(input: {
29
+ messageRef: string;
30
+ outcome: "DELIVERED" | "FAILED" | "UNKNOWN";
31
+ executionRef?: string;
32
+ evidenceRef?: string;
33
+ errorCode?: string;
34
+ }): Promise<void>;
35
+ }
36
+ export interface CollaborationCarrierExecutionPort {
37
+ execute(request: ExecuteCapabilityRequest): Promise<unknown>;
38
+ }
39
+ export type CollaborationCarrierOutcome = {
40
+ status: "DELIVERED";
41
+ messageRef: string;
42
+ executionRef: string;
43
+ } | {
44
+ status: "PENDING" | "FAILED" | "UNKNOWN";
45
+ messageRef: string;
46
+ executionRef: string | null;
47
+ };
48
+ /**
49
+ * Event-driven Collaboration Carrier application.
50
+ *
51
+ * The Agent owner keeps the durable PENDING message. This coordinator owns no
52
+ * queue and runs no timer. A normal ask/reply event may call `deliverMessage`
53
+ * once; process startup may call `recoverPending` once as a bounded recovery
54
+ * scan. Physical delivery remains an Execution effect and logical DELIVERED is
55
+ * written only after the durable Execution record proves APPLIED + delivered.
56
+ */
57
+ export declare function createCollaborationCarrierApplication(options: {
58
+ task: CollaborationCarrierTaskPort;
59
+ agent: CollaborationCarrierAgentPort;
60
+ execution: CollaborationCarrierExecutionPort;
61
+ callerRef?: string;
62
+ }): Readonly<{
63
+ deliverMessage: (messageRef: string) => Promise<CollaborationCarrierOutcome>;
64
+ recoverPending: (limit?: number) => Promise<CollaborationCarrierOutcome[]>;
65
+ }>;
@@ -0,0 +1,138 @@
1
+ import { createHash } from "node:crypto";
2
+ import { parseExecutionRecord, } from "@tomflow/proflow-execution-contracts";
3
+ function contentFingerprint(message) {
4
+ return `sha256:${createHash("sha256")
5
+ .update(JSON.stringify({
6
+ messageId: message.messageId,
7
+ taskId: message.taskId,
8
+ targetRoleRef: message.targetRoleRef,
9
+ targetWorkerRef: message.targetWorkerRef,
10
+ content: message.content,
11
+ }))
12
+ .digest("hex")}`;
13
+ }
14
+ /**
15
+ * Event-driven Collaboration Carrier application.
16
+ *
17
+ * The Agent owner keeps the durable PENDING message. This coordinator owns no
18
+ * queue and runs no timer. A normal ask/reply event may call `deliverMessage`
19
+ * once; process startup may call `recoverPending` once as a bounded recovery
20
+ * scan. Physical delivery remains an Execution effect and logical DELIVERED is
21
+ * written only after the durable Execution record proves APPLIED + delivered.
22
+ */
23
+ export function createCollaborationCarrierApplication(options) {
24
+ const callerRef = options.callerRef ?? "extension:collaboration-carrier";
25
+ const deliverMessage = async (messageRef) => {
26
+ const message = await options.agent.getPendingMessage(messageRef);
27
+ // UNKNOWN is a durable hold state. Startup recovery must not turn an
28
+ // uncertain physical side effect into another Browser submission.
29
+ if (message.lastDeliveryErrorCode === "UNKNOWN") {
30
+ return {
31
+ status: "UNKNOWN",
32
+ messageRef,
33
+ executionRef: message.executionRef,
34
+ };
35
+ }
36
+ if (message.deliveryAttemptCount >= 3) {
37
+ return {
38
+ status: "FAILED",
39
+ messageRef,
40
+ executionRef: message.executionRef,
41
+ };
42
+ }
43
+ const binding = await options.task.getWorkerBinding(message.taskId, message.targetRoleRef);
44
+ if (!binding ||
45
+ binding.workerRef !== message.targetWorkerRef ||
46
+ !binding.conversationLocator)
47
+ return { status: "PENDING", messageRef, executionRef: null };
48
+ const request = {
49
+ contract: "execution",
50
+ contractVersion: "1.0.0",
51
+ idempotencyKey: `collaboration-deliver:${message.messageId}`,
52
+ callerRef,
53
+ correlationId: message.messageId,
54
+ taskId: message.taskId,
55
+ roleRef: message.targetRoleRef,
56
+ workerRef: message.targetWorkerRef,
57
+ capability: "collaboration.deliver",
58
+ input: {
59
+ roleRef: message.targetRoleRef,
60
+ workerRef: message.targetWorkerRef,
61
+ messageRef: message.messageId,
62
+ contentFingerprint: contentFingerprint(message),
63
+ },
64
+ };
65
+ const execution = parseExecutionRecord(await options.execution.execute(request));
66
+ if (execution.status === "SUCCEEDED" &&
67
+ execution.sideEffectState === "APPLIED" &&
68
+ execution.result?.capability === "collaboration.deliver" &&
69
+ execution.result.data.delivered === true) {
70
+ await options.agent.reportDeliveryOutcome({
71
+ messageRef: message.messageId,
72
+ outcome: "DELIVERED",
73
+ executionRef: execution.executionRef,
74
+ evidenceRef: execution.result.data.evidenceRef,
75
+ });
76
+ return {
77
+ status: "DELIVERED",
78
+ messageRef,
79
+ executionRef: execution.executionRef,
80
+ };
81
+ }
82
+ if (execution.status === "UNKNOWN" ||
83
+ execution.sideEffectState === "UNKNOWN") {
84
+ await options.agent.reportDeliveryOutcome({
85
+ messageRef: message.messageId,
86
+ outcome: "UNKNOWN",
87
+ executionRef: execution.executionRef,
88
+ errorCode: "UNKNOWN",
89
+ });
90
+ return {
91
+ status: "UNKNOWN",
92
+ messageRef,
93
+ executionRef: execution.executionRef,
94
+ };
95
+ }
96
+ if (execution.status === "FAILED") {
97
+ await options.agent.reportDeliveryOutcome({
98
+ messageRef: message.messageId,
99
+ outcome: "FAILED",
100
+ executionRef: execution.executionRef,
101
+ errorCode: execution.error?.code ?? "EXECUTION_FAILED",
102
+ });
103
+ return {
104
+ status: "FAILED",
105
+ messageRef,
106
+ executionRef: execution.executionRef,
107
+ };
108
+ }
109
+ return {
110
+ status: "PENDING",
111
+ messageRef,
112
+ executionRef: execution.executionRef,
113
+ };
114
+ };
115
+ const recoverPending = async (limit = 50) => {
116
+ if (!Number.isInteger(limit) || limit <= 0 || limit > 100)
117
+ throw new TypeError("limit must be an integer from 1 through 100");
118
+ const messages = await options.agent.listPendingMessages(limit);
119
+ const outcomes = [];
120
+ for (const message of messages) {
121
+ try {
122
+ outcomes.push(await deliverMessage(message.messageId));
123
+ }
124
+ catch {
125
+ // The Agent PENDING fact is authoritative. A failed trigger is left
126
+ // pending for the next explicit event/recovery; never synthesize a
127
+ // second physical intent here.
128
+ outcomes.push({
129
+ status: "PENDING",
130
+ messageRef: message.messageId,
131
+ executionRef: null,
132
+ });
133
+ }
134
+ }
135
+ return outcomes;
136
+ };
137
+ return Object.freeze({ deliverMessage, recoverPending });
138
+ }
@@ -0,0 +1,137 @@
1
+ import { type ExecuteCapabilityRequest, type ExecutionExecutorPort, type ExecutorPrecondition } from "@tomflow/proflow-execution-contracts";
2
+ import type { BrowserVisionObservationContext, BrowserVisionPort, TypedVisionObservation } from "./vision.ts";
3
+ export type { BrowserRealityBridgeOptions } from "./bridge.ts";
4
+ export { BrowserRealityBridgeError, createBrowserRealityBridgeServer, } from "./bridge.ts";
5
+ export { type CollaborationCarrierAgentPort, type CollaborationCarrierExecutionPort, type CollaborationCarrierOutcome, type CollaborationCarrierTaskPort, createCollaborationCarrierApplication, type PendingCollaborationCarrierMessage, } from "./collaboration-carrier.ts";
6
+ export { createSystemObserver, type SystemObserverAssessment, type SystemObserverPriority, type SystemObserverReasonFailure, type SystemObserverReasonRequest, type SystemObserverReasonResult, type SystemObserverSnapshotPort, type SystemObserverView, } from "./system-observer.ts";
7
+ export { createTaskObserver, type TaskDriveProjection, type TaskObserverAnomalySignal, type TaskObserverCarrierPort, type TaskObserverDecision, type TaskObserverDiagnosticAssessment, type TaskObserverDiagnosticFailure, type TaskObserverDiagnosticPort, type TaskObserverOwnerPort, type TaskObserverResumeSignal, } from "./task-observer.ts";
8
+ export type { BrowserVisionDeferral, BrowserVisionDeferralReason, BrowserVisionImage, BrowserVisionObservation, BrowserVisionObservationContext, BrowserVisionPort, TypedVisionObservation, VisionMimeType, VisionRecommendedNext, } from "./vision.ts";
9
+ export { deferVisionObservation, isVisionObservationVerified, parseCapturedScreenshot, VISION_OBSERVATION_MIN_CONFIDENCE, visionMimeTypes, visionRecommendedNext, } from "./vision.ts";
10
+ export type BrowserPageState = "IDLE" | "BUSY" | "BLOCKED" | "UNKNOWN";
11
+ export type BrowserActivityKind = "GENERATING" | "ACTION_PERMISSION" | "ACTION_RUNNING" | "WAITING_HUMAN" | "WAITING_PEER" | "RECOVERING" | null;
12
+ export interface BrowserPageObservation {
13
+ tabId: number;
14
+ windowId: number;
15
+ url: string;
16
+ contentInstanceId: string;
17
+ pageState: BrowserPageState;
18
+ activityKind: BrowserActivityKind;
19
+ observedAt: string;
20
+ }
21
+ export interface BrowserRealityPort {
22
+ listTabs(): Promise<BrowserPageObservation[]>;
23
+ open(url: string): Promise<BrowserPageObservation>;
24
+ observe(tabId: number): Promise<BrowserPageObservation>;
25
+ submit(tabId: number, text: string, fingerprint: string): Promise<BrowserPageObservation>;
26
+ hasMessage(tabId: number, fingerprint: string): Promise<boolean>;
27
+ screenshot(tabId: number): Promise<{
28
+ evidenceRef: string;
29
+ dataUrl: string;
30
+ mimeType: string;
31
+ sizeBytes: number;
32
+ hash: string;
33
+ }>;
34
+ perform?(request: ExecuteCapabilityRequest, tabId: number): Promise<BrowserPageObservation>;
35
+ }
36
+ export interface TaskBrowserPort {
37
+ getWorkerBinding(taskId: string, roleRef: string): Promise<{
38
+ workerRef: string;
39
+ conversationLocator: string | null;
40
+ } | null>;
41
+ bindWorker(input: {
42
+ taskId: string;
43
+ roleRef: string;
44
+ workerRef: string;
45
+ conversationLocator: string;
46
+ }): Promise<void>;
47
+ }
48
+ export interface AgentDeliveryPort {
49
+ getPendingMessage(messageRef: string): Promise<{
50
+ messageId: string;
51
+ threadId: string;
52
+ taskId: string;
53
+ kind: "QUESTION" | "REPLY";
54
+ fromRoleRef: string;
55
+ fromWorkerRef: string;
56
+ targetRoleRef: string;
57
+ targetWorkerRef: string;
58
+ replyToMessageId: string | null;
59
+ content: string;
60
+ status: "PENDING";
61
+ }>;
62
+ reportPhysicalDelivery(messageRef: string, evidenceRef: string, executionRef: string): Promise<void>;
63
+ }
64
+ export interface ExecutionBrowserOptions {
65
+ browser: BrowserRealityPort;
66
+ task: TaskBrowserPort;
67
+ agent: AgentDeliveryPort;
68
+ vision?: BrowserVisionPort;
69
+ idFactory?: () => string;
70
+ now?: () => Date;
71
+ }
72
+ type ExecutorInvocation = Parameters<ExecutionExecutorPort["execute"]>[0];
73
+ type ExecutorResult = Awaited<ReturnType<ExecutionExecutorPort["execute"]>>;
74
+ type Reconciliation = Awaited<ReturnType<ExecutionExecutorPort["reconcile"]>>;
75
+ export declare class ExecutionBrowserError extends Error {
76
+ readonly code: "PRECONDITION_FAILED" | "EXECUTOR_UNAVAILABLE" | "UNKNOWN_SIDE_EFFECT" | "CANCELLED";
77
+ readonly retryable = false;
78
+ constructor(code: ExecutionBrowserError["code"], message: string);
79
+ }
80
+ declare function parseCarrierIdentity(raw: string): {
81
+ roleRef: string;
82
+ workerRef: string | null;
83
+ };
84
+ export declare function createExecutionBrowserExtension(options: ExecutionBrowserOptions): Readonly<{
85
+ extensionInstanceId: string;
86
+ parseCarrierIdentity: typeof parseCarrierIdentity;
87
+ registerContentSession: (observation: BrowserPageObservation) => void;
88
+ isContentSessionCurrent(tabId: number, contentInstanceId: string): boolean;
89
+ classifyProgress(input: {
90
+ pageState: BrowserPageState;
91
+ nodeInProgress: boolean;
92
+ millisecondsWithoutProgress: number;
93
+ legitimateWait: boolean;
94
+ }): "EXPECTED_WAIT" | "NORMAL" | "PROGRESS_GAP" | "RUNTIME_STALL";
95
+ inspectScreenshot(tabId: number, observationContext: BrowserVisionObservationContext): Promise<TypedVisionObservation>;
96
+ handlePermissionFallback(tabId: number, continuationRef: string): Promise<{
97
+ status: "WAITING_HUMAN";
98
+ continuationRef: string;
99
+ evidenceRef: string;
100
+ }>;
101
+ getSidePanelSnapshot(): Readonly<{
102
+ extensionInstanceId: string;
103
+ observedAt: string;
104
+ sessions: {
105
+ tabId: number;
106
+ windowId: number;
107
+ url: string;
108
+ contentInstanceId: string;
109
+ pageState: BrowserPageState;
110
+ activityKind: BrowserActivityKind;
111
+ }[];
112
+ lanes: {
113
+ roleRef: string;
114
+ workerRef: string;
115
+ tabId: number;
116
+ pageState: BrowserPageState;
117
+ activityKind: BrowserActivityKind;
118
+ currentExecutionRef: string | null;
119
+ continuationRef: string | null;
120
+ lastProgressAt: string;
121
+ }[];
122
+ }>;
123
+ execute: (raw: ExecutorInvocation) => Promise<ExecutorResult>;
124
+ observePrecondition: (request: ExecuteCapabilityRequest) => Promise<ExecutorPrecondition | undefined>;
125
+ reconcile: (requestRaw: ExecuteCapabilityRequest, preconditionRaw: ExecutorPrecondition) => Promise<Reconciliation>;
126
+ readArtifact(): Promise<never>;
127
+ recoveryScan(unfinished: Array<{
128
+ request: ExecuteCapabilityRequest;
129
+ effectStarted: boolean;
130
+ }>): Promise<{
131
+ status: "ALREADY_COMPLETED";
132
+ reconciled: never[];
133
+ } | {
134
+ status: "COMPLETED";
135
+ reconciled: import("@tomflow/proflow-execution-contracts").ExecutorReconciliation[];
136
+ }>;
137
+ }>;