@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 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
const contentInstanceId = `content:${crypto.randomUUID()}`;
|
|
2
|
+
function pageState() {
|
|
3
|
+
if (document.querySelector('[role="dialog"]'))
|
|
4
|
+
return { pageState: "BLOCKED", activityKind: "ACTION_PERMISSION" };
|
|
5
|
+
if (document.querySelector('[data-testid="stop-button"], button[aria-label*="Stop"]'))
|
|
6
|
+
return { pageState: "BUSY", activityKind: "GENERATING" };
|
|
7
|
+
if (document.querySelector('#prompt-textarea, textarea, [contenteditable="true"]'))
|
|
8
|
+
return { pageState: "IDLE", activityKind: null };
|
|
9
|
+
return { pageState: "UNKNOWN", activityKind: null };
|
|
10
|
+
}
|
|
11
|
+
function observation() {
|
|
12
|
+
return {
|
|
13
|
+
url: location.href,
|
|
14
|
+
contentInstanceId,
|
|
15
|
+
...pageState(),
|
|
16
|
+
observedAt: new Date().toISOString(),
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
function safeElement(selector) {
|
|
20
|
+
if (!selector || selector.length > 512)
|
|
21
|
+
throw new Error("SELECTOR_INVALID");
|
|
22
|
+
const element = document.querySelector(selector);
|
|
23
|
+
if (!(element instanceof HTMLElement))
|
|
24
|
+
throw new Error("ELEMENT_NOT_FOUND");
|
|
25
|
+
return element;
|
|
26
|
+
}
|
|
27
|
+
function hasFingerprint(fingerprint) {
|
|
28
|
+
return Boolean(fingerprint && document.body.innerText.includes(fingerprint));
|
|
29
|
+
}
|
|
30
|
+
chrome.runtime.onMessage.addListener((command, _sender, sendResponse) => {
|
|
31
|
+
void (async () => {
|
|
32
|
+
if (command.type !== "PROFLOW_PAGE_COMMAND" ||
|
|
33
|
+
command.contentInstanceId !== contentInstanceId ||
|
|
34
|
+
command.expectedUrl !== location.href)
|
|
35
|
+
throw new Error("STALE_CONTENT_SESSION");
|
|
36
|
+
if (command.operation === "observe")
|
|
37
|
+
return observation();
|
|
38
|
+
if (command.operation === "verify")
|
|
39
|
+
return {
|
|
40
|
+
...observation(),
|
|
41
|
+
verified: hasFingerprint(command.fingerprint),
|
|
42
|
+
};
|
|
43
|
+
if (pageState().pageState === "BLOCKED")
|
|
44
|
+
throw new Error("PAGE_PERMISSION_REQUIRES_HUMAN");
|
|
45
|
+
if (command.operation === "click") {
|
|
46
|
+
safeElement(command.selector).click();
|
|
47
|
+
return observation();
|
|
48
|
+
}
|
|
49
|
+
const input = safeElement(command.selector ?? "#prompt-textarea");
|
|
50
|
+
if (command.value === undefined || command.value.length > 4_096)
|
|
51
|
+
throw new Error("INPUT_BUDGET_EXCEEDED");
|
|
52
|
+
input.focus();
|
|
53
|
+
if (input instanceof HTMLTextAreaElement ||
|
|
54
|
+
input instanceof HTMLInputElement)
|
|
55
|
+
input.value = command.value;
|
|
56
|
+
else
|
|
57
|
+
input.textContent = command.value;
|
|
58
|
+
input.dispatchEvent(new InputEvent("input", {
|
|
59
|
+
bubbles: true,
|
|
60
|
+
inputType: "insertText",
|
|
61
|
+
data: command.value,
|
|
62
|
+
}));
|
|
63
|
+
if (command.operation === "submit")
|
|
64
|
+
safeElement('button[data-testid="send-button"], button[aria-label*="Send"]').click();
|
|
65
|
+
return observation();
|
|
66
|
+
})().then((value) => sendResponse({ ok: true, value }), (error) => sendResponse({
|
|
67
|
+
ok: false,
|
|
68
|
+
error: error instanceof Error ? error.message : "PAGE_COMMAND_FAILED",
|
|
69
|
+
}));
|
|
70
|
+
return true;
|
|
71
|
+
});
|
|
72
|
+
const publish = () => chrome.runtime.sendMessage({
|
|
73
|
+
type: "PROFLOW_CONTENT_OBSERVATION",
|
|
74
|
+
observation: observation(),
|
|
75
|
+
});
|
|
76
|
+
void publish();
|
|
77
|
+
let publishTimer;
|
|
78
|
+
const observer = new MutationObserver(() => {
|
|
79
|
+
if (publishTimer !== undefined)
|
|
80
|
+
clearTimeout(publishTimer);
|
|
81
|
+
publishTimer = setTimeout(() => {
|
|
82
|
+
publishTimer = undefined;
|
|
83
|
+
void publish();
|
|
84
|
+
}, 100);
|
|
85
|
+
});
|
|
86
|
+
observer.observe(document.documentElement, {
|
|
87
|
+
subtree: true,
|
|
88
|
+
childList: true,
|
|
89
|
+
attributes: true,
|
|
90
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
export {};
|
|
2
|
+
function parseEndpoint(raw) {
|
|
3
|
+
const parsed = new URL(raw);
|
|
4
|
+
if (parsed.protocol !== "http:" ||
|
|
5
|
+
parsed.hostname !== "127.0.0.1" ||
|
|
6
|
+
parsed.pathname !== "/" ||
|
|
7
|
+
parsed.search !== "" ||
|
|
8
|
+
parsed.hash !== "")
|
|
9
|
+
throw new Error("Endpoint must be a loopback HTTP origin");
|
|
10
|
+
return raw.replace(/\/$/, "");
|
|
11
|
+
}
|
|
12
|
+
function wireConfigForm(config) {
|
|
13
|
+
const endpoint = document.querySelector(`#${config.endpointId}`);
|
|
14
|
+
const token = document.querySelector(`#${config.tokenId}`);
|
|
15
|
+
const status = document.querySelector(`#${config.statusId}`);
|
|
16
|
+
const form = document.querySelector(`#${config.formId}`);
|
|
17
|
+
if (!endpoint || !token || !status || !form)
|
|
18
|
+
throw new Error("OPTIONS_DOM_INVALID");
|
|
19
|
+
void chrome.storage.local.get(config.storageKey).then((stored) => {
|
|
20
|
+
const value = stored[config.storageKey];
|
|
21
|
+
if (typeof value !== "object" || value === null)
|
|
22
|
+
return;
|
|
23
|
+
const record = value;
|
|
24
|
+
if (typeof record.endpoint === "string")
|
|
25
|
+
endpoint.value = record.endpoint;
|
|
26
|
+
});
|
|
27
|
+
form.addEventListener("submit", (event) => {
|
|
28
|
+
event.preventDefault();
|
|
29
|
+
void (async () => {
|
|
30
|
+
const normalizedEndpoint = parseEndpoint(endpoint.value);
|
|
31
|
+
if (token.value.length < 32)
|
|
32
|
+
throw new Error("Token is too short");
|
|
33
|
+
await chrome.storage.local.set({
|
|
34
|
+
[config.storageKey]: {
|
|
35
|
+
endpoint: normalizedEndpoint,
|
|
36
|
+
token: token.value,
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
token.value = "";
|
|
40
|
+
status.textContent = "Saved.";
|
|
41
|
+
})().catch((error) => {
|
|
42
|
+
status.textContent =
|
|
43
|
+
error instanceof Error ? error.message : "Save failed";
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
document.querySelector("#extension-id")?.append(chrome.runtime.id);
|
|
48
|
+
wireConfigForm({
|
|
49
|
+
storageKey: "proflowRuntimeBridge",
|
|
50
|
+
formId: "bridge-form",
|
|
51
|
+
endpointId: "bridge-endpoint",
|
|
52
|
+
tokenId: "bridge-token",
|
|
53
|
+
statusId: "bridge-status",
|
|
54
|
+
});
|
|
55
|
+
wireConfigForm({
|
|
56
|
+
storageKey: "proflowTaskApplication",
|
|
57
|
+
formId: "task-application-form",
|
|
58
|
+
endpointId: "task-application-endpoint",
|
|
59
|
+
tokenId: "task-application-token",
|
|
60
|
+
statusId: "task-application-status",
|
|
61
|
+
});
|
|
62
|
+
wireConfigForm({
|
|
63
|
+
storageKey: "proflowApprovalApplication",
|
|
64
|
+
formId: "approval-application-form",
|
|
65
|
+
endpointId: "approval-application-endpoint",
|
|
66
|
+
tokenId: "approval-application-token",
|
|
67
|
+
statusId: "approval-application-status",
|
|
68
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
export {};
|
|
2
|
+
function element(selector) {
|
|
3
|
+
const value = document.querySelector(selector);
|
|
4
|
+
if (!value)
|
|
5
|
+
throw new Error(`SIDE_PANEL_TARGET_MISSING:${selector}`);
|
|
6
|
+
return value;
|
|
7
|
+
}
|
|
8
|
+
const connection = element("#connection");
|
|
9
|
+
const browserStatus = element("#browser-status");
|
|
10
|
+
const tasksTarget = element("#tasks");
|
|
11
|
+
const selectedTarget = element("#selected-task");
|
|
12
|
+
const nodesTarget = element("#nodes");
|
|
13
|
+
const errorTarget = element("#error");
|
|
14
|
+
const resultTarget = element("#result");
|
|
15
|
+
const startButton = element("#start-task");
|
|
16
|
+
const ensureWorkersButton = element("#ensure-workers");
|
|
17
|
+
const newTaskForm = element("#new-task-form");
|
|
18
|
+
const approvalsTarget = element("#approvals");
|
|
19
|
+
const systemAssessmentTarget = element("#system-assessment");
|
|
20
|
+
let selected = null;
|
|
21
|
+
function requestId(prefix) {
|
|
22
|
+
return `${prefix}:${crypto.randomUUID()}`;
|
|
23
|
+
}
|
|
24
|
+
function record(value) {
|
|
25
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
26
|
+
throw new Error("TASK_APPLICATION_RESPONSE_INVALID");
|
|
27
|
+
return value;
|
|
28
|
+
}
|
|
29
|
+
async function taskApplication(operation, input) {
|
|
30
|
+
const raw = await chrome.runtime.sendMessage({
|
|
31
|
+
type: "PROFLOW_TASK_APPLICATION",
|
|
32
|
+
operation,
|
|
33
|
+
input,
|
|
34
|
+
});
|
|
35
|
+
const response = record(raw);
|
|
36
|
+
if (response.ok !== true)
|
|
37
|
+
throw new Error(typeof response.error === "string"
|
|
38
|
+
? response.error
|
|
39
|
+
: "TASK_APPLICATION_FAILED");
|
|
40
|
+
return response.value;
|
|
41
|
+
}
|
|
42
|
+
async function approvalApplication(operation, input) {
|
|
43
|
+
const raw = await chrome.runtime.sendMessage({
|
|
44
|
+
type: "PROFLOW_APPROVAL_APPLICATION",
|
|
45
|
+
operation,
|
|
46
|
+
input,
|
|
47
|
+
});
|
|
48
|
+
const response = record(raw);
|
|
49
|
+
if (response.ok !== true)
|
|
50
|
+
throw new Error(typeof response.error === "string"
|
|
51
|
+
? response.error
|
|
52
|
+
: "APPROVAL_APPLICATION_FAILED");
|
|
53
|
+
return response.value;
|
|
54
|
+
}
|
|
55
|
+
async function refreshApprovals() {
|
|
56
|
+
const value = record(await approvalApplication("approval.list", { status: "PENDING" }));
|
|
57
|
+
const approvals = Array.isArray(value.approvals)
|
|
58
|
+
? value.approvals
|
|
59
|
+
: [];
|
|
60
|
+
approvalsTarget.replaceChildren();
|
|
61
|
+
for (const approval of approvals) {
|
|
62
|
+
const row = document.createElement("div");
|
|
63
|
+
row.className = "task";
|
|
64
|
+
const label = document.createElement("span");
|
|
65
|
+
label.textContent = `${approval.capability} · ${approval.executionRef} · expires ${approval.expiresAt}`;
|
|
66
|
+
row.append(label);
|
|
67
|
+
const allow = document.createElement("button");
|
|
68
|
+
allow.type = "button";
|
|
69
|
+
allow.textContent = "Allow";
|
|
70
|
+
allow.addEventListener("click", () => void run(async () => {
|
|
71
|
+
await approvalApplication("approval.allow", {
|
|
72
|
+
approvalRef: approval.approvalRef,
|
|
73
|
+
expectedVersion: approval.version,
|
|
74
|
+
});
|
|
75
|
+
await refreshApprovals();
|
|
76
|
+
}));
|
|
77
|
+
const deny = document.createElement("button");
|
|
78
|
+
deny.type = "button";
|
|
79
|
+
deny.textContent = "Deny";
|
|
80
|
+
deny.addEventListener("click", () => void run(async () => {
|
|
81
|
+
await approvalApplication("approval.deny", {
|
|
82
|
+
approvalRef: approval.approvalRef,
|
|
83
|
+
expectedVersion: approval.version,
|
|
84
|
+
reason: "Denied from Extension Side Panel",
|
|
85
|
+
});
|
|
86
|
+
await refreshApprovals();
|
|
87
|
+
}));
|
|
88
|
+
row.append(allow, deny);
|
|
89
|
+
approvalsTarget.append(row);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
function setBusy(button, busy) {
|
|
93
|
+
button.disabled = busy;
|
|
94
|
+
}
|
|
95
|
+
async function loadTask(taskId) {
|
|
96
|
+
selected = (await taskApplication("task.get", { taskId }));
|
|
97
|
+
selectedTarget.textContent = `${selected.taskId} · ${selected.status} · v${selected.version}`;
|
|
98
|
+
startButton.disabled = selected.status !== "READY";
|
|
99
|
+
ensureWorkersButton.disabled =
|
|
100
|
+
selected.status === "SUCCEEDED" || selected.status === "TERMINATED";
|
|
101
|
+
nodesTarget.replaceChildren();
|
|
102
|
+
for (const node of selected.nodes) {
|
|
103
|
+
const row = document.createElement("div");
|
|
104
|
+
row.className = "task";
|
|
105
|
+
const label = document.createElement("span");
|
|
106
|
+
label.textContent = `${node.title} · ${node.status} · run ${node.runNo}`;
|
|
107
|
+
row.append(label);
|
|
108
|
+
if (["SUCCEEDED", "FAILED", "WAITING"].includes(node.status)) {
|
|
109
|
+
const reopen = document.createElement("button");
|
|
110
|
+
reopen.type = "button";
|
|
111
|
+
reopen.textContent = "Reopen";
|
|
112
|
+
reopen.addEventListener("click", () => {
|
|
113
|
+
void run(async () => {
|
|
114
|
+
if (!selected)
|
|
115
|
+
return;
|
|
116
|
+
await taskApplication("node.reopen", {
|
|
117
|
+
taskId: selected.taskId,
|
|
118
|
+
nodeId: node.nodeId,
|
|
119
|
+
reason: "Human reopen from Extension Side Panel",
|
|
120
|
+
expectedTaskVersion: selected.version,
|
|
121
|
+
idempotencyKey: requestId("extension-reopen"),
|
|
122
|
+
});
|
|
123
|
+
await loadTask(selected.taskId);
|
|
124
|
+
await refreshTasks();
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
row.append(reopen);
|
|
128
|
+
}
|
|
129
|
+
nodesTarget.append(row);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
async function refreshTasks() {
|
|
133
|
+
const value = record(await taskApplication("task.list", {}));
|
|
134
|
+
const tasks = Array.isArray(value.tasks)
|
|
135
|
+
? value.tasks
|
|
136
|
+
: [];
|
|
137
|
+
tasksTarget.replaceChildren();
|
|
138
|
+
for (const task of tasks) {
|
|
139
|
+
const row = document.createElement("div");
|
|
140
|
+
row.className = "task";
|
|
141
|
+
const open = document.createElement("button");
|
|
142
|
+
open.type = "button";
|
|
143
|
+
open.textContent = `${task.title} · ${task.status}`;
|
|
144
|
+
open.addEventListener("click", () => void run(() => loadTask(task.taskId)));
|
|
145
|
+
row.append(open);
|
|
146
|
+
if (task.blockedReason) {
|
|
147
|
+
const detail = document.createElement("div");
|
|
148
|
+
detail.className = "meta";
|
|
149
|
+
detail.textContent = task.blockedReason;
|
|
150
|
+
row.append(detail);
|
|
151
|
+
}
|
|
152
|
+
tasksTarget.append(row);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
async function refreshBrowserStatus() {
|
|
156
|
+
const snapshot = record(await chrome.runtime.sendMessage({ type: "PROFLOW_SIDE_PANEL_SNAPSHOT" }));
|
|
157
|
+
connection.textContent =
|
|
158
|
+
snapshot.taskApplicationConfigured === true &&
|
|
159
|
+
snapshot.approvalApplicationConfigured === true
|
|
160
|
+
? "Task + Approval applications connected"
|
|
161
|
+
: "Local application credential missing — open Extension Options";
|
|
162
|
+
browserStatus.textContent = JSON.stringify(snapshot, null, 2);
|
|
163
|
+
const observer = typeof snapshot.systemObserver === "object" &&
|
|
164
|
+
snapshot.systemObserver !== null &&
|
|
165
|
+
!Array.isArray(snapshot.systemObserver)
|
|
166
|
+
? snapshot.systemObserver
|
|
167
|
+
: null;
|
|
168
|
+
if (observer === null) {
|
|
169
|
+
systemAssessmentTarget.textContent = "No assessment yet.";
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
const unresolved = Array.isArray(observer.unresolved)
|
|
173
|
+
? observer.unresolved.filter((item) => typeof item === "string")
|
|
174
|
+
: [];
|
|
175
|
+
const carry = Array.isArray(observer.carryForward)
|
|
176
|
+
? observer.carryForward
|
|
177
|
+
: [];
|
|
178
|
+
systemAssessmentTarget.textContent = [
|
|
179
|
+
`assessmentRef: ${String(observer.assessmentRef ?? "?")}`,
|
|
180
|
+
`needsHumanAttention: ${observer.needsHumanAttention === true}`,
|
|
181
|
+
`unresolved: ${unresolved.join(" | ")}`,
|
|
182
|
+
`carryForward: ${carry.length}`,
|
|
183
|
+
].join("\n");
|
|
184
|
+
}
|
|
185
|
+
if (snapshot.taskApplicationConfigured === true)
|
|
186
|
+
await refreshTasks();
|
|
187
|
+
if (snapshot.approvalApplicationConfigured === true)
|
|
188
|
+
await refreshApprovals();
|
|
189
|
+
}
|
|
190
|
+
async function run(action) {
|
|
191
|
+
errorTarget.textContent = "";
|
|
192
|
+
try {
|
|
193
|
+
await action();
|
|
194
|
+
}
|
|
195
|
+
catch (error) {
|
|
196
|
+
errorTarget.textContent =
|
|
197
|
+
error instanceof Error ? error.message : "Operation failed";
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
newTaskForm.addEventListener("submit", (event) => {
|
|
201
|
+
event.preventDefault();
|
|
202
|
+
void run(async () => {
|
|
203
|
+
const title = element("#task-title").value.trim();
|
|
204
|
+
const objective = element("#task-objective").value.trim();
|
|
205
|
+
const nodes = JSON.parse(element("#task-plan").value);
|
|
206
|
+
if (!Array.isArray(nodes) || nodes.length === 0)
|
|
207
|
+
throw new Error("Task plan must be a non-empty JSON array");
|
|
208
|
+
const value = await taskApplication("task.create", {
|
|
209
|
+
title,
|
|
210
|
+
objective,
|
|
211
|
+
plan: { nodes },
|
|
212
|
+
initialDocuments: [],
|
|
213
|
+
idempotencyKey: requestId("extension-new-task"),
|
|
214
|
+
});
|
|
215
|
+
const created = record(value);
|
|
216
|
+
resultTarget.textContent = `Created ${String(created.taskId ?? "Task")}.`;
|
|
217
|
+
if (typeof created.taskId === "string")
|
|
218
|
+
await loadTask(created.taskId);
|
|
219
|
+
await refreshTasks();
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
element("#refresh-tasks").addEventListener("click", () => void run(refreshTasks));
|
|
223
|
+
element("#refresh-approvals").addEventListener("click", () => void run(refreshApprovals));
|
|
224
|
+
startButton.addEventListener("click", () => {
|
|
225
|
+
void run(async () => {
|
|
226
|
+
if (!selected)
|
|
227
|
+
return;
|
|
228
|
+
setBusy(startButton, true);
|
|
229
|
+
try {
|
|
230
|
+
await taskApplication("task.start", {
|
|
231
|
+
taskId: selected.taskId,
|
|
232
|
+
expectedTaskVersion: selected.version,
|
|
233
|
+
idempotencyKey: requestId("extension-start-task"),
|
|
234
|
+
});
|
|
235
|
+
await loadTask(selected.taskId);
|
|
236
|
+
await refreshTasks();
|
|
237
|
+
}
|
|
238
|
+
finally {
|
|
239
|
+
startButton.disabled = selected?.status !== "READY";
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
ensureWorkersButton.addEventListener("click", () => {
|
|
244
|
+
void run(async () => {
|
|
245
|
+
if (!selected)
|
|
246
|
+
return;
|
|
247
|
+
setBusy(ensureWorkersButton, true);
|
|
248
|
+
try {
|
|
249
|
+
await taskApplication("task.ensureWorkers", { taskId: selected.taskId });
|
|
250
|
+
await loadTask(selected.taskId);
|
|
251
|
+
await refreshTasks();
|
|
252
|
+
}
|
|
253
|
+
finally {
|
|
254
|
+
ensureWorkersButton.disabled =
|
|
255
|
+
selected?.status === "SUCCEEDED" || selected?.status === "TERMINATED";
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
});
|
|
259
|
+
void run(refreshBrowserStatus);
|
|
260
|
+
setInterval(() => {
|
|
261
|
+
void run(refreshBrowserStatus);
|
|
262
|
+
}, 5_000);
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { BrowserRealityPort } from "./index.ts";
|
|
2
|
+
export interface BrowserRealityBridgeOptions {
|
|
3
|
+
token: string;
|
|
4
|
+
extensionId: string;
|
|
5
|
+
host?: "127.0.0.1";
|
|
6
|
+
port?: number;
|
|
7
|
+
heartbeatFreshnessMs?: number;
|
|
8
|
+
commandTimeoutMs?: number;
|
|
9
|
+
now?: () => Date;
|
|
10
|
+
idFactory?: () => string;
|
|
11
|
+
}
|
|
12
|
+
export declare class BrowserRealityBridgeError extends Error {
|
|
13
|
+
readonly code: "BRIDGE_AUTH_INVALID" | "BRIDGE_INPUT_INVALID" | "BRIDGE_OFFLINE" | "BRIDGE_COMMAND_TIMEOUT" | "BRIDGE_COMMAND_FAILED";
|
|
14
|
+
constructor(code: BrowserRealityBridgeError["code"], message: string);
|
|
15
|
+
}
|
|
16
|
+
export declare function createBrowserRealityBridgeServer(options: BrowserRealityBridgeOptions): Promise<Readonly<{
|
|
17
|
+
endpoint: string;
|
|
18
|
+
browser: BrowserRealityPort;
|
|
19
|
+
status(): {
|
|
20
|
+
online: boolean;
|
|
21
|
+
extensionInstanceId: string | null;
|
|
22
|
+
queuedCommands: number;
|
|
23
|
+
pendingCommands: number;
|
|
24
|
+
};
|
|
25
|
+
close(): Promise<void>;
|
|
26
|
+
}>>;
|