@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.
- package/README.md +7 -0
- package/conformance.json +1 -0
- package/deployment/browser-extension.json +6 -0
- package/dist/deployment/adapter.d.ts +61 -0
- package/dist/deployment/adapter.js +47 -0
- package/dist/deployment/descriptor.d.ts +103 -0
- package/dist/deployment/descriptor.js +109 -0
- package/dist/extension/background.d.ts +1 -0
- package/dist/extension/background.js +752 -0
- package/dist/extension/content.d.ts +1 -0
- package/dist/extension/content.js +90 -0
- package/dist/extension/options.d.ts +1 -0
- package/dist/extension/options.js +68 -0
- package/dist/extension/side-panel.d.ts +1 -0
- package/dist/extension/side-panel.js +262 -0
- package/dist/src/bridge.d.ts +26 -0
- package/dist/src/bridge.js +288 -0
- package/dist/src/collaboration-carrier.d.ts +65 -0
- package/dist/src/collaboration-carrier.js +138 -0
- package/dist/src/index.d.ts +137 -0
- package/dist/src/index.js +779 -0
- package/dist/src/runtime-composition.d.ts +97 -0
- package/dist/src/runtime-composition.js +124 -0
- package/dist/src/system-observer.d.ts +86 -0
- package/dist/src/system-observer.js +252 -0
- package/dist/src/task-observer.d.ts +118 -0
- package/dist/src/task-observer.js +105 -0
- package/dist/src/vision.d.ts +73 -0
- package/dist/src/vision.js +82 -0
- package/extension/background.ts +997 -0
- package/extension/content.ts +138 -0
- package/extension/options.html +54 -0
- package/extension/options.ts +98 -0
- package/extension/side-panel.html +77 -0
- package/extension/side-panel.ts +349 -0
- package/manifest.json +20 -0
- package/package.json +58 -0
- package/proflow.module.json +127 -0
- package/self-install.mjs +27 -0
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
type PageState = "IDLE" | "BUSY" | "BLOCKED" | "UNKNOWN";
|
|
2
|
+
type ContentCommand = {
|
|
3
|
+
type: "PROFLOW_PAGE_COMMAND";
|
|
4
|
+
contentInstanceId: string;
|
|
5
|
+
expectedUrl: string;
|
|
6
|
+
operation: "observe" | "input" | "click" | "submit" | "verify";
|
|
7
|
+
selector?: string;
|
|
8
|
+
value?: string;
|
|
9
|
+
fingerprint?: string;
|
|
10
|
+
};
|
|
11
|
+
type ChromeContent = {
|
|
12
|
+
runtime: {
|
|
13
|
+
sendMessage(message: unknown): Promise<unknown>;
|
|
14
|
+
onMessage: {
|
|
15
|
+
addListener(
|
|
16
|
+
listener: (
|
|
17
|
+
message: ContentCommand,
|
|
18
|
+
sender: unknown,
|
|
19
|
+
sendResponse: (value: unknown) => void,
|
|
20
|
+
) => boolean | undefined,
|
|
21
|
+
): void;
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
declare const chrome: ChromeContent;
|
|
26
|
+
|
|
27
|
+
const contentInstanceId = `content:${crypto.randomUUID()}`;
|
|
28
|
+
|
|
29
|
+
function pageState(): { pageState: PageState; activityKind: string | null } {
|
|
30
|
+
if (document.querySelector('[role="dialog"]'))
|
|
31
|
+
return { pageState: "BLOCKED", activityKind: "ACTION_PERMISSION" };
|
|
32
|
+
if (
|
|
33
|
+
document.querySelector(
|
|
34
|
+
'[data-testid="stop-button"], button[aria-label*="Stop"]',
|
|
35
|
+
)
|
|
36
|
+
)
|
|
37
|
+
return { pageState: "BUSY", activityKind: "GENERATING" };
|
|
38
|
+
if (
|
|
39
|
+
document.querySelector(
|
|
40
|
+
'#prompt-textarea, textarea, [contenteditable="true"]',
|
|
41
|
+
)
|
|
42
|
+
)
|
|
43
|
+
return { pageState: "IDLE", activityKind: null };
|
|
44
|
+
return { pageState: "UNKNOWN", activityKind: null };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function observation() {
|
|
48
|
+
return {
|
|
49
|
+
url: location.href,
|
|
50
|
+
contentInstanceId,
|
|
51
|
+
...pageState(),
|
|
52
|
+
observedAt: new Date().toISOString(),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function safeElement(selector: string | undefined): HTMLElement {
|
|
57
|
+
if (!selector || selector.length > 512) throw new Error("SELECTOR_INVALID");
|
|
58
|
+
const element = document.querySelector(selector);
|
|
59
|
+
if (!(element instanceof HTMLElement)) throw new Error("ELEMENT_NOT_FOUND");
|
|
60
|
+
return element;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function hasFingerprint(fingerprint: string | undefined): boolean {
|
|
64
|
+
return Boolean(fingerprint && document.body.innerText.includes(fingerprint));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
chrome.runtime.onMessage.addListener((command, _sender, sendResponse) => {
|
|
68
|
+
void (async () => {
|
|
69
|
+
if (
|
|
70
|
+
command.type !== "PROFLOW_PAGE_COMMAND" ||
|
|
71
|
+
command.contentInstanceId !== contentInstanceId ||
|
|
72
|
+
command.expectedUrl !== location.href
|
|
73
|
+
)
|
|
74
|
+
throw new Error("STALE_CONTENT_SESSION");
|
|
75
|
+
if (command.operation === "observe") return observation();
|
|
76
|
+
if (command.operation === "verify")
|
|
77
|
+
return {
|
|
78
|
+
...observation(),
|
|
79
|
+
verified: hasFingerprint(command.fingerprint),
|
|
80
|
+
};
|
|
81
|
+
if (pageState().pageState === "BLOCKED")
|
|
82
|
+
throw new Error("PAGE_PERMISSION_REQUIRES_HUMAN");
|
|
83
|
+
if (command.operation === "click") {
|
|
84
|
+
safeElement(command.selector).click();
|
|
85
|
+
return observation();
|
|
86
|
+
}
|
|
87
|
+
const input = safeElement(command.selector ?? "#prompt-textarea");
|
|
88
|
+
if (command.value === undefined || command.value.length > 4_096)
|
|
89
|
+
throw new Error("INPUT_BUDGET_EXCEEDED");
|
|
90
|
+
input.focus();
|
|
91
|
+
if (
|
|
92
|
+
input instanceof HTMLTextAreaElement ||
|
|
93
|
+
input instanceof HTMLInputElement
|
|
94
|
+
)
|
|
95
|
+
input.value = command.value;
|
|
96
|
+
else input.textContent = command.value;
|
|
97
|
+
input.dispatchEvent(
|
|
98
|
+
new InputEvent("input", {
|
|
99
|
+
bubbles: true,
|
|
100
|
+
inputType: "insertText",
|
|
101
|
+
data: command.value,
|
|
102
|
+
}),
|
|
103
|
+
);
|
|
104
|
+
if (command.operation === "submit")
|
|
105
|
+
safeElement(
|
|
106
|
+
'button[data-testid="send-button"], button[aria-label*="Send"]',
|
|
107
|
+
).click();
|
|
108
|
+
return observation();
|
|
109
|
+
})().then(
|
|
110
|
+
(value) => sendResponse({ ok: true, value }),
|
|
111
|
+
(error: unknown) =>
|
|
112
|
+
sendResponse({
|
|
113
|
+
ok: false,
|
|
114
|
+
error: error instanceof Error ? error.message : "PAGE_COMMAND_FAILED",
|
|
115
|
+
}),
|
|
116
|
+
);
|
|
117
|
+
return true;
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
const publish = () =>
|
|
121
|
+
chrome.runtime.sendMessage({
|
|
122
|
+
type: "PROFLOW_CONTENT_OBSERVATION",
|
|
123
|
+
observation: observation(),
|
|
124
|
+
});
|
|
125
|
+
void publish();
|
|
126
|
+
let publishTimer: ReturnType<typeof setTimeout> | undefined;
|
|
127
|
+
const observer = new MutationObserver(() => {
|
|
128
|
+
if (publishTimer !== undefined) clearTimeout(publishTimer);
|
|
129
|
+
publishTimer = setTimeout(() => {
|
|
130
|
+
publishTimer = undefined;
|
|
131
|
+
void publish();
|
|
132
|
+
}, 100);
|
|
133
|
+
});
|
|
134
|
+
observer.observe(document.documentElement, {
|
|
135
|
+
subtree: true,
|
|
136
|
+
childList: true,
|
|
137
|
+
attributes: true,
|
|
138
|
+
});
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
|
6
|
+
<title>ProFlow Local Connections</title>
|
|
7
|
+
<style>
|
|
8
|
+
body { font: 14px system-ui; margin: 24px; max-width: 680px; }
|
|
9
|
+
section { border-top: 1px solid #ddd; margin-top: 24px; padding-top: 12px; }
|
|
10
|
+
label { display: block; margin: 16px 0; }
|
|
11
|
+
input { box-sizing: border-box; display: block; margin-top: 6px; padding: 8px; width: 100%; }
|
|
12
|
+
button { padding: 8px 14px; }
|
|
13
|
+
[role="status"] { margin-top: 12px; }
|
|
14
|
+
</style>
|
|
15
|
+
</head>
|
|
16
|
+
<body>
|
|
17
|
+
<h1>ProFlow local connections</h1>
|
|
18
|
+
<p>Extension ID: <code id="extension-id"></code></p>
|
|
19
|
+
|
|
20
|
+
<section>
|
|
21
|
+
<h2>Browser Reality Bridge</h2>
|
|
22
|
+
<form id="bridge-form">
|
|
23
|
+
<label>Loopback endpoint <input id="bridge-endpoint" required placeholder="http://127.0.0.1:47831" /></label>
|
|
24
|
+
<label>Ephemeral bridge token <input id="bridge-token" type="password" required autocomplete="off" /></label>
|
|
25
|
+
<button type="submit">Save bridge</button>
|
|
26
|
+
</form>
|
|
27
|
+
<p id="bridge-status" role="status"></p>
|
|
28
|
+
</section>
|
|
29
|
+
|
|
30
|
+
<section>
|
|
31
|
+
<h2>Task Application</h2>
|
|
32
|
+
<p>Connects the Side Panel to the loopback platform-host application surface. This credential is separate from Role and Browser-bridge credentials.</p>
|
|
33
|
+
<form id="task-application-form">
|
|
34
|
+
<label>Platform-host endpoint <input id="task-application-endpoint" required placeholder="http://127.0.0.1:47830" /></label>
|
|
35
|
+
<label>Task application token <input id="task-application-token" type="password" required autocomplete="off" /></label>
|
|
36
|
+
<button type="submit">Save Task application</button>
|
|
37
|
+
</form>
|
|
38
|
+
<p id="task-application-status" role="status"></p>
|
|
39
|
+
</section>
|
|
40
|
+
|
|
41
|
+
<section>
|
|
42
|
+
<h2>Execution Approval Application</h2>
|
|
43
|
+
<p>Dedicated human Approval credential. It is separate from Task, Role and Browser bridge credentials.</p>
|
|
44
|
+
<form id="approval-application-form">
|
|
45
|
+
<label>Platform-host endpoint <input id="approval-application-endpoint" required placeholder="http://127.0.0.1:47830" /></label>
|
|
46
|
+
<label>Approval application token <input id="approval-application-token" type="password" required autocomplete="off" /></label>
|
|
47
|
+
<button type="submit">Save Approval application</button>
|
|
48
|
+
</form>
|
|
49
|
+
<p id="approval-application-status" role="status"></p>
|
|
50
|
+
</section>
|
|
51
|
+
|
|
52
|
+
<script type="module" src="../dist/extension/options.js"></script>
|
|
53
|
+
</body>
|
|
54
|
+
</html>
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
export {};
|
|
2
|
+
|
|
3
|
+
type ChromeOptions = {
|
|
4
|
+
runtime: { id: string };
|
|
5
|
+
storage: {
|
|
6
|
+
local: {
|
|
7
|
+
get(key: string): Promise<Record<string, unknown>>;
|
|
8
|
+
set(value: Record<string, unknown>): Promise<void>;
|
|
9
|
+
};
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
declare const chrome: ChromeOptions;
|
|
13
|
+
|
|
14
|
+
type LocalConfigForm = {
|
|
15
|
+
storageKey:
|
|
16
|
+
| "proflowRuntimeBridge"
|
|
17
|
+
| "proflowTaskApplication"
|
|
18
|
+
| "proflowApprovalApplication";
|
|
19
|
+
formId: string;
|
|
20
|
+
endpointId: string;
|
|
21
|
+
tokenId: string;
|
|
22
|
+
statusId: string;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
function parseEndpoint(raw: string): string {
|
|
26
|
+
const parsed = new URL(raw);
|
|
27
|
+
if (
|
|
28
|
+
parsed.protocol !== "http:" ||
|
|
29
|
+
parsed.hostname !== "127.0.0.1" ||
|
|
30
|
+
parsed.pathname !== "/" ||
|
|
31
|
+
parsed.search !== "" ||
|
|
32
|
+
parsed.hash !== ""
|
|
33
|
+
)
|
|
34
|
+
throw new Error("Endpoint must be a loopback HTTP origin");
|
|
35
|
+
return raw.replace(/\/$/, "");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function wireConfigForm(config: LocalConfigForm) {
|
|
39
|
+
const endpoint = document.querySelector<HTMLInputElement>(
|
|
40
|
+
`#${config.endpointId}`,
|
|
41
|
+
);
|
|
42
|
+
const token = document.querySelector<HTMLInputElement>(`#${config.tokenId}`);
|
|
43
|
+
const status = document.querySelector<HTMLElement>(`#${config.statusId}`);
|
|
44
|
+
const form = document.querySelector<HTMLFormElement>(`#${config.formId}`);
|
|
45
|
+
if (!endpoint || !token || !status || !form)
|
|
46
|
+
throw new Error("OPTIONS_DOM_INVALID");
|
|
47
|
+
|
|
48
|
+
void chrome.storage.local.get(config.storageKey).then((stored) => {
|
|
49
|
+
const value = stored[config.storageKey];
|
|
50
|
+
if (typeof value !== "object" || value === null) return;
|
|
51
|
+
const record = value as Record<string, unknown>;
|
|
52
|
+
if (typeof record.endpoint === "string") endpoint.value = record.endpoint;
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
form.addEventListener("submit", (event) => {
|
|
56
|
+
event.preventDefault();
|
|
57
|
+
void (async () => {
|
|
58
|
+
const normalizedEndpoint = parseEndpoint(endpoint.value);
|
|
59
|
+
if (token.value.length < 32) throw new Error("Token is too short");
|
|
60
|
+
await chrome.storage.local.set({
|
|
61
|
+
[config.storageKey]: {
|
|
62
|
+
endpoint: normalizedEndpoint,
|
|
63
|
+
token: token.value,
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
token.value = "";
|
|
67
|
+
status.textContent = "Saved.";
|
|
68
|
+
})().catch((error: unknown) => {
|
|
69
|
+
status.textContent =
|
|
70
|
+
error instanceof Error ? error.message : "Save failed";
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
document.querySelector<HTMLElement>("#extension-id")?.append(chrome.runtime.id);
|
|
76
|
+
|
|
77
|
+
wireConfigForm({
|
|
78
|
+
storageKey: "proflowRuntimeBridge",
|
|
79
|
+
formId: "bridge-form",
|
|
80
|
+
endpointId: "bridge-endpoint",
|
|
81
|
+
tokenId: "bridge-token",
|
|
82
|
+
statusId: "bridge-status",
|
|
83
|
+
});
|
|
84
|
+
wireConfigForm({
|
|
85
|
+
storageKey: "proflowTaskApplication",
|
|
86
|
+
formId: "task-application-form",
|
|
87
|
+
endpointId: "task-application-endpoint",
|
|
88
|
+
tokenId: "task-application-token",
|
|
89
|
+
statusId: "task-application-status",
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
wireConfigForm({
|
|
93
|
+
storageKey: "proflowApprovalApplication",
|
|
94
|
+
formId: "approval-application-form",
|
|
95
|
+
endpointId: "approval-application-endpoint",
|
|
96
|
+
tokenId: "approval-application-token",
|
|
97
|
+
statusId: "approval-application-status",
|
|
98
|
+
});
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width" />
|
|
6
|
+
<title>ProFlow</title>
|
|
7
|
+
<style>
|
|
8
|
+
body { font: 13px system-ui; margin: 12px; }
|
|
9
|
+
section { border-top: 1px solid #ddd; margin-top: 14px; padding-top: 10px; }
|
|
10
|
+
label { display: block; margin: 8px 0; }
|
|
11
|
+
input, textarea { box-sizing: border-box; display: block; margin-top: 4px; width: 100%; }
|
|
12
|
+
textarea { min-height: 72px; }
|
|
13
|
+
button { margin: 4px 4px 4px 0; padding: 5px 9px; }
|
|
14
|
+
button[disabled] { opacity: .55; }
|
|
15
|
+
.task { border: 1px solid #ddd; margin: 6px 0; padding: 7px; }
|
|
16
|
+
.meta { color: #555; font-size: 12px; }
|
|
17
|
+
#error { color: #a00; white-space: pre-wrap; }
|
|
18
|
+
#result { white-space: pre-wrap; }
|
|
19
|
+
#browser-status { max-height: 180px; overflow: auto; }
|
|
20
|
+
</style>
|
|
21
|
+
</head>
|
|
22
|
+
<body>
|
|
23
|
+
<header>
|
|
24
|
+
<h1>ProFlow</h1>
|
|
25
|
+
<div id="connection" class="meta">Connecting…</div>
|
|
26
|
+
</header>
|
|
27
|
+
|
|
28
|
+
<section>
|
|
29
|
+
<h2>New Task</h2>
|
|
30
|
+
<form id="new-task-form">
|
|
31
|
+
<label>Title <input id="task-title" required /></label>
|
|
32
|
+
<label>Objective <textarea id="task-objective" required></textarea></label>
|
|
33
|
+
<p class="meta">Requirement — clarified by the Product Worker after binding; not seeded here.</p>
|
|
34
|
+
<label>Ordered plan (JSON array)
|
|
35
|
+
<textarea id="task-plan" required>[{"nodeId":"dev","title":"Implement","objective":"Implement the requirement","requiredAgentPackageRef":"@tomflow/proflow-agent-controller-dev","inputDocuments":["REQUIREMENT"],"outputDocuments":[]},{"nodeId":"test","title":"Verify","objective":"Verify the implementation","requiredAgentPackageRef":"@tomflow/proflow-agent-test-ops","inputDocuments":["REQUIREMENT"],"outputDocuments":[]}]</textarea>
|
|
36
|
+
</label>
|
|
37
|
+
<button id="new-task" type="submit">New Task + 3 Workers</button>
|
|
38
|
+
</form>
|
|
39
|
+
</section>
|
|
40
|
+
|
|
41
|
+
<section>
|
|
42
|
+
<h2>Tasks</h2>
|
|
43
|
+
<button id="refresh-tasks" type="button">Refresh</button>
|
|
44
|
+
<div id="tasks"></div>
|
|
45
|
+
</section>
|
|
46
|
+
|
|
47
|
+
<section>
|
|
48
|
+
<h2>Selected Task</h2>
|
|
49
|
+
<div id="selected-task" class="meta">None selected.</div>
|
|
50
|
+
<button id="start-task" type="button" disabled>Confirm / Start</button>
|
|
51
|
+
<button id="ensure-workers" type="button" disabled>Recover missing Workers</button>
|
|
52
|
+
<div id="nodes"></div>
|
|
53
|
+
</section>
|
|
54
|
+
|
|
55
|
+
<section>
|
|
56
|
+
<h2>Execution Approval</h2>
|
|
57
|
+
<p class="meta">Approval is an Execution-owned durable fact. This panel only sends authenticated human decisions to the Execution Owner.</p>
|
|
58
|
+
<button id="refresh-approvals" type="button">Refresh approvals</button>
|
|
59
|
+
<div id="approvals"></div>
|
|
60
|
+
</section>
|
|
61
|
+
|
|
62
|
+
<section>
|
|
63
|
+
<h2>System Assessment</h2>
|
|
64
|
+
<p class="meta">Read-only derived assessment. It never mutates Task/Approval owner facts.</p>
|
|
65
|
+
<div id="system-assessment" class="meta">No assessment yet.</div>
|
|
66
|
+
</section>
|
|
67
|
+
|
|
68
|
+
<section>
|
|
69
|
+
<h2>Browser Carrier</h2>
|
|
70
|
+
<pre id="browser-status">Connecting…</pre>
|
|
71
|
+
</section>
|
|
72
|
+
|
|
73
|
+
<p id="error" role="alert"></p>
|
|
74
|
+
<pre id="result" role="status"></pre>
|
|
75
|
+
<script type="module" src="../dist/extension/side-panel.js"></script>
|
|
76
|
+
</body>
|
|
77
|
+
</html>
|