@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,105 @@
1
+ /**
2
+ * Deterministic Task Observer.
3
+ *
4
+ * The Task Observer is a progression detector in the Extension application
5
+ * layer. It reads the bounded read-only drive projection exposed by the Task
6
+ * owner and, on a deterministic condition, requests a typed carrier action
7
+ * such as WAKE or RESUME. It never writes Task or Node state itself; the
8
+ * Worker performs formal work acceptance after being woken.
9
+ */
10
+ const NODE_READY_TRIGGER = "NODE_READY";
11
+ const REOPEN_TRIGGER = "REOPEN";
12
+ export function createTaskObserver(options) {
13
+ const advance = async (taskId, resumeSignal, anomalySignal) => {
14
+ const projection = await options.owner.getTaskDriveProjection(taskId);
15
+ // Terminal Tasks stop driving; history and bindings are retained.
16
+ if (projection.terminal || projection.currentNode === null)
17
+ return { kind: "STOP_DRIVING", taskId, reason: "TERMINAL" };
18
+ const node = projection.currentNode;
19
+ if (anomalySignal) {
20
+ if (!options.diagnostic)
21
+ return { kind: "NOOP", taskId, reason: "DIAGNOSTIC_UNAVAILABLE" };
22
+ const assessment = await options.diagnostic.assess({
23
+ taskId,
24
+ nodeId: node.nodeId,
25
+ runNo: node.runNo,
26
+ anomaly: anomalySignal,
27
+ });
28
+ if ("ok" in assessment)
29
+ return {
30
+ kind: "NOOP",
31
+ taskId,
32
+ reason: assessment.errorCode === "REASON_UNAVAILABLE"
33
+ ? "DIAGNOSTIC_UNAVAILABLE"
34
+ : `DIAGNOSTIC_DEFERRED:${assessment.errorCode}`,
35
+ };
36
+ return {
37
+ kind: "DIAGNOSTIC",
38
+ taskId,
39
+ nodeId: node.nodeId,
40
+ runNo: node.runNo,
41
+ anomalyRef: anomalySignal.ref,
42
+ assessment,
43
+ };
44
+ }
45
+ const binding = projection.roleBinding;
46
+ if (!projection.canDrive ||
47
+ !binding?.workerRef ||
48
+ !binding.conversationLocator)
49
+ return { kind: "NOOP", taskId, reason: "BINDING_NOT_READY" };
50
+ if (node.status === "READY") {
51
+ // WAKE the correct Worker with a minimal trigger; the Worker then
52
+ // performs formal work acceptance through the Task owner. A reopened
53
+ // run reuses the same TaskRoleBinding/Conversation but keeps the reason
54
+ // distinct from a first-run NODE_READY wake.
55
+ return {
56
+ kind: "WAKE",
57
+ taskId,
58
+ nodeId: node.nodeId,
59
+ runNo: node.runNo,
60
+ roleRef: binding.roleRef,
61
+ workerRef: binding.workerRef,
62
+ trigger: node.runNo > 1 ? REOPEN_TRIGGER : NODE_READY_TRIGGER,
63
+ conversationLocator: binding.conversationLocator,
64
+ };
65
+ }
66
+ if (resumeSignal) {
67
+ if (resumeSignal.targetWorkerRef !== binding.workerRef)
68
+ return {
69
+ kind: "NOOP",
70
+ taskId,
71
+ reason: "RESUME_TARGET_NOT_CURRENT_WORKER",
72
+ };
73
+ return {
74
+ kind: "RESUME",
75
+ taskId,
76
+ nodeId: node.nodeId,
77
+ runNo: node.runNo,
78
+ roleRef: binding.roleRef,
79
+ workerRef: binding.workerRef,
80
+ trigger: resumeSignal.trigger,
81
+ conversationLocator: binding.conversationLocator,
82
+ underlyingRef: resumeSignal.ref,
83
+ };
84
+ }
85
+ return { kind: "NOOP", taskId, reason: "NO_NEXT_STEP" };
86
+ };
87
+ const drive = async (taskId, resumeSignal, anomalySignal) => {
88
+ const decision = await advance(taskId, resumeSignal, anomalySignal);
89
+ if (decision.kind === "WAKE" || decision.kind === "RESUME")
90
+ await options.carrier.requestWake({
91
+ taskId: decision.taskId,
92
+ nodeId: decision.nodeId,
93
+ runNo: decision.runNo,
94
+ roleRef: decision.roleRef,
95
+ workerRef: decision.workerRef,
96
+ trigger: decision.trigger,
97
+ conversationLocator: decision.conversationLocator,
98
+ ...(decision.kind === "RESUME"
99
+ ? { underlyingRef: decision.underlyingRef }
100
+ : {}),
101
+ });
102
+ return decision;
103
+ };
104
+ return Object.freeze({ advance, drive });
105
+ }
@@ -0,0 +1,73 @@
1
+ import type { BrowserActivityKind, BrowserPageState } from "./index.ts";
2
+ /**
3
+ * Browser screenshot → Model Vision fallback port.
4
+ *
5
+ * Deterministic DOM/URL/runtime observation is primary. Only when the page
6
+ * cannot be explained deterministically does the Carrier capture a screenshot
7
+ * and ask an injected `BrowserVisionPort` for a spec-ized semantic observation
8
+ * (see MODEL-DOC-03-05 §6: pageState / activityKind / confidence /
9
+ * recommendedNext / reasonCode). The port returns an OBSERVED interpretation or
10
+ * a typed DEFERRED fail-safe; it never decides Task/Execution/Approval truth
11
+ * and never fabricates a page state.
12
+ *
13
+ * Raw screenshot bytes (dataUrl/base64) never enter structured logs: only the
14
+ * bounded typed observation and image mimeType/size/hash are retained.
15
+ */
16
+ export declare const visionMimeTypes: readonly ["image/png", "image/jpeg", "image/webp"];
17
+ export type VisionMimeType = (typeof visionMimeTypes)[number];
18
+ export declare const visionRecommendedNext: readonly ["NONE", "RECOVER", "WAIT", "REQUEST_HUMAN"];
19
+ export type VisionRecommendedNext = (typeof visionRecommendedNext)[number];
20
+ /** A runtime-validated screenshot payload ready to hand to a Vision port. */
21
+ export interface BrowserVisionImage {
22
+ dataUrl: string;
23
+ base64: string;
24
+ mimeType: VisionMimeType;
25
+ hash: string;
26
+ sizeBytes: number;
27
+ }
28
+ /** Bounded deterministic facts that accompany the image for interpretation. */
29
+ export interface BrowserVisionObservationContext {
30
+ targetRef: string;
31
+ pageState: BrowserPageState;
32
+ activityKind: BrowserActivityKind;
33
+ observedAt: string;
34
+ }
35
+ export interface BrowserVisionObservation {
36
+ status: "OBSERVED";
37
+ observationRef: string;
38
+ pageState: BrowserPageState;
39
+ activityKind: BrowserActivityKind;
40
+ confidence: number;
41
+ recommendedNext: VisionRecommendedNext;
42
+ reasonCode: string;
43
+ rationale: string;
44
+ }
45
+ export type BrowserVisionDeferralReason = "VISION_PORT_UNAVAILABLE" | "VISION_IMAGE_INVALID" | "VISION_INFERENCE_FAILED";
46
+ export interface BrowserVisionDeferral {
47
+ status: "DEFERRED";
48
+ reasonCode: BrowserVisionDeferralReason;
49
+ message: string;
50
+ }
51
+ export type TypedVisionObservation = BrowserVisionObservation | BrowserVisionDeferral;
52
+ export interface BrowserVisionPort {
53
+ inspect(input: {
54
+ image: BrowserVisionImage;
55
+ observationContext: BrowserVisionObservationContext;
56
+ }): Promise<TypedVisionObservation>;
57
+ }
58
+ export declare function deferVisionObservation(reasonCode: BrowserVisionDeferralReason, message: string): BrowserVisionDeferral;
59
+ export declare const VISION_OBSERVATION_MIN_CONFIDENCE = 0.75;
60
+ /**
61
+ * Deterministic Carrier policy for deciding whether a model observation is
62
+ * strong enough to count as a verified Browser observation. A successful
63
+ * model response is diagnostic evidence only; UNKNOWN, low-confidence, or
64
+ * explicit human-escalation recommendations remain unverified.
65
+ */
66
+ export declare function isVisionObservationVerified(value: TypedVisionObservation): value is BrowserVisionObservation;
67
+ /**
68
+ * Runtime-validate a raw screenshot capture into a trusted image payload before
69
+ * it may reach a Vision port. Only supported image MIME types are accepted; the
70
+ * MIME must match the dataUrl prefix, sizeBytes must be a positive integer, and
71
+ * the hash must be present. The raw dataUrl never leaves this boundary.
72
+ */
73
+ export declare function parseCapturedScreenshot(value: unknown): BrowserVisionImage;
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Browser screenshot → Model Vision fallback port.
3
+ *
4
+ * Deterministic DOM/URL/runtime observation is primary. Only when the page
5
+ * cannot be explained deterministically does the Carrier capture a screenshot
6
+ * and ask an injected `BrowserVisionPort` for a spec-ized semantic observation
7
+ * (see MODEL-DOC-03-05 §6: pageState / activityKind / confidence /
8
+ * recommendedNext / reasonCode). The port returns an OBSERVED interpretation or
9
+ * a typed DEFERRED fail-safe; it never decides Task/Execution/Approval truth
10
+ * and never fabricates a page state.
11
+ *
12
+ * Raw screenshot bytes (dataUrl/base64) never enter structured logs: only the
13
+ * bounded typed observation and image mimeType/size/hash are retained.
14
+ */
15
+ export const visionMimeTypes = [
16
+ "image/png",
17
+ "image/jpeg",
18
+ "image/webp",
19
+ ];
20
+ export const visionRecommendedNext = [
21
+ "NONE",
22
+ "RECOVER",
23
+ "WAIT",
24
+ "REQUEST_HUMAN",
25
+ ];
26
+ export function deferVisionObservation(reasonCode, message) {
27
+ return Object.freeze({ status: "DEFERRED", reasonCode, message });
28
+ }
29
+ export const VISION_OBSERVATION_MIN_CONFIDENCE = 0.75;
30
+ /**
31
+ * Deterministic Carrier policy for deciding whether a model observation is
32
+ * strong enough to count as a verified Browser observation. A successful
33
+ * model response is diagnostic evidence only; UNKNOWN, low-confidence, or
34
+ * explicit human-escalation recommendations remain unverified.
35
+ */
36
+ export function isVisionObservationVerified(value) {
37
+ return (value.status === "OBSERVED" &&
38
+ value.pageState !== "UNKNOWN" &&
39
+ value.confidence >= VISION_OBSERVATION_MIN_CONFIDENCE &&
40
+ value.recommendedNext !== "REQUEST_HUMAN");
41
+ }
42
+ /**
43
+ * Runtime-validate a raw screenshot capture into a trusted image payload before
44
+ * it may reach a Vision port. Only supported image MIME types are accepted; the
45
+ * MIME must match the dataUrl prefix, sizeBytes must be a positive integer, and
46
+ * the hash must be present. The raw dataUrl never leaves this boundary.
47
+ */
48
+ export function parseCapturedScreenshot(value) {
49
+ if (typeof value !== "object" || value === null || Array.isArray(value))
50
+ throw new TypeError("screenshot capture must be an object");
51
+ const record = value;
52
+ const dataUrl = record.dataUrl;
53
+ const mimeType = record.mimeType;
54
+ const sizeBytes = record.sizeBytes;
55
+ const hash = record.hash;
56
+ if (typeof dataUrl !== "string" || dataUrl.length === 0)
57
+ throw new TypeError("screenshot dataUrl must be a non-empty string");
58
+ if (typeof mimeType !== "string" ||
59
+ !visionMimeTypes.includes(mimeType))
60
+ throw new TypeError("screenshot mimeType is not a supported image type");
61
+ if (typeof sizeBytes !== "number" ||
62
+ !Number.isInteger(sizeBytes) ||
63
+ sizeBytes <= 0)
64
+ throw new TypeError("screenshot sizeBytes must be a positive integer");
65
+ if (typeof hash !== "string" || hash.length === 0)
66
+ throw new TypeError("screenshot hash must be a non-empty string");
67
+ const prefix = `data:${mimeType};base64,`;
68
+ if (!dataUrl.startsWith(prefix))
69
+ throw new TypeError("screenshot dataUrl does not match its mimeType");
70
+ const base64 = dataUrl.slice(prefix.length);
71
+ if (base64.length === 0)
72
+ throw new TypeError("screenshot dataUrl has no base64 payload");
73
+ if (Buffer.from(base64, "base64").length === 0)
74
+ throw new TypeError("screenshot image payload is empty");
75
+ return Object.freeze({
76
+ dataUrl,
77
+ base64,
78
+ mimeType: mimeType,
79
+ hash,
80
+ sizeBytes,
81
+ });
82
+ }