@tomflow/proflow-execution-browser-extension 0.1.44 → 0.1.45
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/CHANGELOG.md +6 -0
- package/dist/deployment/adapter.d.ts +10 -10
- package/dist/deployment/descriptor.d.ts +1 -1
- package/dist/deployment/descriptor.js +1 -1
- package/dist/extension/background.js +26 -7
- package/dist/extension/tasks.js +54 -1
- package/dist/src/bridge.js +8 -0
- package/dist/src/task-observer.d.ts +2 -1
- package/dist/src/task-observer.js +18 -6
- package/extension/background.ts +25 -4
- package/extension/tasks.html +2 -0
- package/extension/tasks.ts +59 -1
- package/manifest.json +1 -1
- package/package.json +3 -3
- package/proflow.module.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# @tomflow/proflow-execution-browser-extension
|
|
2
2
|
|
|
3
|
+
## 0.1.45
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Release the verified Real-3 B1 task recovery, exact execution identity, durable observer resume, bounded gateway error, and matching Agent Action contract fixes.
|
|
8
|
+
|
|
3
9
|
## 0.1.44
|
|
4
10
|
|
|
5
11
|
### Patch Changes
|
|
@@ -47,7 +47,7 @@ export declare const behaviorAdapter: {
|
|
|
47
47
|
ok: true;
|
|
48
48
|
status: "SUCCEEDED";
|
|
49
49
|
moduleRef: "execution-browser-extension";
|
|
50
|
-
moduleVersion: "0.1.
|
|
50
|
+
moduleVersion: "0.1.45";
|
|
51
51
|
data: {
|
|
52
52
|
loadDir: string;
|
|
53
53
|
};
|
|
@@ -60,7 +60,7 @@ export declare const behaviorAdapter: {
|
|
|
60
60
|
readonly ok: true;
|
|
61
61
|
readonly status: "SUCCEEDED";
|
|
62
62
|
readonly moduleRef: "execution-browser-extension";
|
|
63
|
-
readonly moduleVersion: "0.1.
|
|
63
|
+
readonly moduleVersion: "0.1.45";
|
|
64
64
|
};
|
|
65
65
|
observedEffects: never[];
|
|
66
66
|
}>;
|
|
@@ -70,7 +70,7 @@ export declare const behaviorAdapter: {
|
|
|
70
70
|
ok: true;
|
|
71
71
|
status: "SUCCEEDED";
|
|
72
72
|
moduleRef: "execution-browser-extension";
|
|
73
|
-
moduleVersion: "0.1.
|
|
73
|
+
moduleVersion: "0.1.45";
|
|
74
74
|
data: {
|
|
75
75
|
setupStatus: "ACTION_REQUIRED" | "READY";
|
|
76
76
|
runtimeStatus: "NOT_APPLICABLE";
|
|
@@ -95,7 +95,7 @@ export declare const behaviorAdapter: {
|
|
|
95
95
|
result: {
|
|
96
96
|
contract: "deployment.result.v1";
|
|
97
97
|
moduleRef: "execution-browser-extension";
|
|
98
|
-
moduleVersion: "0.1.
|
|
98
|
+
moduleVersion: "0.1.45";
|
|
99
99
|
ok: false;
|
|
100
100
|
status: "ACTION_REQUIRED";
|
|
101
101
|
data: {
|
|
@@ -127,14 +127,14 @@ export declare const behaviorAdapter: {
|
|
|
127
127
|
readonly ok: true;
|
|
128
128
|
readonly status: "SUCCEEDED";
|
|
129
129
|
readonly moduleRef: "execution-browser-extension";
|
|
130
|
-
readonly moduleVersion: "0.1.
|
|
130
|
+
readonly moduleVersion: "0.1.45";
|
|
131
131
|
};
|
|
132
132
|
observedEffects: string[];
|
|
133
133
|
} | {
|
|
134
134
|
result: {
|
|
135
135
|
contract: "deployment.result.v1";
|
|
136
136
|
moduleRef: "execution-browser-extension";
|
|
137
|
-
moduleVersion: "0.1.
|
|
137
|
+
moduleVersion: "0.1.45";
|
|
138
138
|
ok: false;
|
|
139
139
|
status: "FAILED";
|
|
140
140
|
error: {
|
|
@@ -167,7 +167,7 @@ export declare const behaviorAdapter: {
|
|
|
167
167
|
ok: true;
|
|
168
168
|
status: "SUCCEEDED";
|
|
169
169
|
moduleRef: "execution-browser-extension";
|
|
170
|
-
moduleVersion: "0.1.
|
|
170
|
+
moduleVersion: "0.1.45";
|
|
171
171
|
data: {
|
|
172
172
|
docs: string;
|
|
173
173
|
};
|
|
@@ -180,11 +180,11 @@ export declare const behaviorAdapter: {
|
|
|
180
180
|
readonly ok: true;
|
|
181
181
|
readonly status: "SUCCEEDED";
|
|
182
182
|
readonly moduleRef: "execution-browser-extension";
|
|
183
|
-
readonly moduleVersion: "0.1.
|
|
183
|
+
readonly moduleVersion: "0.1.45";
|
|
184
184
|
} | {
|
|
185
185
|
contract: "deployment.result.v1";
|
|
186
186
|
moduleRef: "execution-browser-extension";
|
|
187
|
-
moduleVersion: "0.1.
|
|
187
|
+
moduleVersion: "0.1.45";
|
|
188
188
|
ok: false;
|
|
189
189
|
status: "FAILED";
|
|
190
190
|
error: {
|
|
@@ -201,7 +201,7 @@ export declare const behaviorAdapter: {
|
|
|
201
201
|
readonly ok: true;
|
|
202
202
|
readonly status: "SUCCEEDED";
|
|
203
203
|
readonly moduleRef: "execution-browser-extension";
|
|
204
|
-
readonly moduleVersion: "0.1.
|
|
204
|
+
readonly moduleVersion: "0.1.45";
|
|
205
205
|
};
|
|
206
206
|
observedEffects: never[];
|
|
207
207
|
}>;
|
|
@@ -3,7 +3,7 @@ export declare const descriptor: {
|
|
|
3
3
|
readonly contractVersion: "1.0.0";
|
|
4
4
|
readonly moduleRef: "execution-browser-extension";
|
|
5
5
|
readonly packageName: "@tomflow/proflow-execution-browser-extension";
|
|
6
|
-
readonly moduleVersion: "0.1.
|
|
6
|
+
readonly moduleVersion: "0.1.45";
|
|
7
7
|
readonly kind: "browser-extension";
|
|
8
8
|
readonly templateVersion: "1.0.0";
|
|
9
9
|
readonly platformCompatibility: ">=1.0.0 <2.0.0";
|
|
@@ -3,7 +3,7 @@ export const descriptor = {
|
|
|
3
3
|
contractVersion: "1.0.0",
|
|
4
4
|
moduleRef: "execution-browser-extension",
|
|
5
5
|
packageName: "@tomflow/proflow-execution-browser-extension",
|
|
6
|
-
moduleVersion: "0.1.
|
|
6
|
+
moduleVersion: "0.1.45",
|
|
7
7
|
kind: "browser-extension",
|
|
8
8
|
templateVersion: "1.0.0",
|
|
9
9
|
platformCompatibility: ">=1.0.0 <2.0.0",
|
|
@@ -14137,16 +14137,23 @@ function createTaskObserver(options) {
|
|
|
14137
14137
|
conversationLocator: binding.conversationLocator
|
|
14138
14138
|
};
|
|
14139
14139
|
}
|
|
14140
|
-
|
|
14140
|
+
const effectiveResumeSignal = resumeSignal ?? (projection.resumeSignalRef && projection.taskStatus === "ACTIVE" && node.status === "IN_PROGRESS" ? {
|
|
14141
|
+
trigger: "TASK_RESUMED",
|
|
14142
|
+
ref: projection.resumeSignalRef,
|
|
14143
|
+
targetWorkerRef: binding.workerRef,
|
|
14144
|
+
nodeId: node.nodeId,
|
|
14145
|
+
runNo: node.runNo
|
|
14146
|
+
} : void 0);
|
|
14147
|
+
if (effectiveResumeSignal) {
|
|
14141
14148
|
if (projection.taskStatus !== "ACTIVE" || node.status !== "IN_PROGRESS")
|
|
14142
14149
|
return { kind: "NOOP", taskId, reason: "BINDING_NOT_READY" };
|
|
14143
|
-
if (
|
|
14150
|
+
if (effectiveResumeSignal.nodeId !== node.nodeId || effectiveResumeSignal.runNo !== node.runNo)
|
|
14144
14151
|
return {
|
|
14145
14152
|
kind: "NOOP",
|
|
14146
14153
|
taskId,
|
|
14147
14154
|
reason: "RESUME_GENERATION_MISMATCH"
|
|
14148
14155
|
};
|
|
14149
|
-
if (
|
|
14156
|
+
if (effectiveResumeSignal.targetWorkerRef !== binding.workerRef)
|
|
14150
14157
|
return {
|
|
14151
14158
|
kind: "NOOP",
|
|
14152
14159
|
taskId,
|
|
@@ -14159,9 +14166,9 @@ function createTaskObserver(options) {
|
|
|
14159
14166
|
runNo: node.runNo,
|
|
14160
14167
|
roleRef: binding.roleRef,
|
|
14161
14168
|
workerRef: binding.workerRef,
|
|
14162
|
-
trigger:
|
|
14169
|
+
trigger: effectiveResumeSignal.trigger,
|
|
14163
14170
|
conversationLocator: binding.conversationLocator,
|
|
14164
|
-
underlyingRef:
|
|
14171
|
+
underlyingRef: effectiveResumeSignal.ref
|
|
14165
14172
|
};
|
|
14166
14173
|
}
|
|
14167
14174
|
return { kind: "NOOP", taskId, reason: "NO_NEXT_STEP" };
|
|
@@ -14457,6 +14464,14 @@ var taskObserver = createTaskObserver({
|
|
|
14457
14464
|
}
|
|
14458
14465
|
}
|
|
14459
14466
|
});
|
|
14467
|
+
async function resumeTaskWorker(taskId) {
|
|
14468
|
+
const projection = await invokeObserverApplication("task.projection", {
|
|
14469
|
+
taskId
|
|
14470
|
+
});
|
|
14471
|
+
if (projection.taskId !== taskId || projection.taskStatus !== "ACTIVE" || projection.currentNode?.status !== "IN_PROGRESS" || !projection.resumeSignalRef)
|
|
14472
|
+
throw new Error("TASK_RESUMED_OWNER_FACTS_INVALID");
|
|
14473
|
+
return taskObserver.drive(taskId);
|
|
14474
|
+
}
|
|
14460
14475
|
async function resumeAfterApprovalDecision(approvalRef) {
|
|
14461
14476
|
const context = await invokeObserverApplication("approval.executionContext", {
|
|
14462
14477
|
approvalRef
|
|
@@ -15042,6 +15057,8 @@ async function executeCommand(command) {
|
|
|
15042
15057
|
void runObserverRecovery();
|
|
15043
15058
|
return { scheduled: true };
|
|
15044
15059
|
}
|
|
15060
|
+
if (command.type === "TASK_OBSERVER_RESUME")
|
|
15061
|
+
return resumeTaskWorker(text(command.taskId, "TASK_ID"));
|
|
15045
15062
|
if (command.type === "CARRIER_ATTENTION_ACTION") {
|
|
15046
15063
|
if (command.action !== "allowOnce" && command.action !== "deny")
|
|
15047
15064
|
throw new Error("ATTENTION_ACTION_INVALID");
|
|
@@ -15525,8 +15542,10 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
|
|
15525
15542
|
return;
|
|
15526
15543
|
}
|
|
15527
15544
|
void invokeTaskApplication(message.operation, message.input).then(
|
|
15528
|
-
(value) => {
|
|
15529
|
-
|
|
15545
|
+
async (value) => {
|
|
15546
|
+
if (message.operation === "task.resume")
|
|
15547
|
+
await resumeTaskWorker(text(message.input?.taskId, "TASK_ID"));
|
|
15548
|
+
else void runObserverRecovery();
|
|
15530
15549
|
sendResponse({ ok: true, value });
|
|
15531
15550
|
},
|
|
15532
15551
|
(error46) => sendResponse({
|
package/dist/extension/tasks.js
CHANGED
|
@@ -70,7 +70,9 @@ const nodesTarget = element("#nodes");
|
|
|
70
70
|
const errorTarget = element("#error");
|
|
71
71
|
const resultTarget = element("#result");
|
|
72
72
|
const startButton = element("#start-task");
|
|
73
|
+
const resumeButton = element("#resume-task");
|
|
73
74
|
const ensureWorkersButton = element("#ensure-workers");
|
|
75
|
+
const pendingMessagesTarget = element("#pending-messages");
|
|
74
76
|
const newTaskForm = element("#new-task-form");
|
|
75
77
|
const approvalsTarget = element("#approvals");
|
|
76
78
|
const carrierAttentionsTarget = element("#carrier-attentions");
|
|
@@ -236,8 +238,38 @@ async function loadTask(taskId) {
|
|
|
236
238
|
selected = (await taskApplication("task.get", { taskId }));
|
|
237
239
|
selectedTarget.textContent = `${selected.taskId} · ${selected.status} · v${selected.version}`;
|
|
238
240
|
startButton.disabled = selected.status !== "READY";
|
|
241
|
+
resumeButton.hidden = !["WAITING", "PAUSED"].includes(selected.status);
|
|
242
|
+
resumeButton.disabled =
|
|
243
|
+
selected.status === "WAITING" && selected.pendingMessages.length > 0;
|
|
239
244
|
ensureWorkersButton.disabled =
|
|
240
245
|
selected.status === "SUCCEEDED" || selected.status === "TERMINATED";
|
|
246
|
+
pendingMessagesTarget.replaceChildren();
|
|
247
|
+
for (const message of selected.pendingMessages) {
|
|
248
|
+
const row = document.createElement("div");
|
|
249
|
+
row.className = "task";
|
|
250
|
+
const label = document.createElement("div");
|
|
251
|
+
label.textContent = `${message.messageType} · ${message.reasonCode}`;
|
|
252
|
+
const detail = document.createElement("div");
|
|
253
|
+
detail.className = "meta";
|
|
254
|
+
detail.textContent = message.message;
|
|
255
|
+
const acknowledge = document.createElement("button");
|
|
256
|
+
acknowledge.type = "button";
|
|
257
|
+
acknowledge.textContent = "Resolve blocker";
|
|
258
|
+
acknowledge.addEventListener("click", () => {
|
|
259
|
+
void run(async () => {
|
|
260
|
+
if (!selected)
|
|
261
|
+
return;
|
|
262
|
+
await taskApplication("message.acknowledge", {
|
|
263
|
+
messageId: message.messageId,
|
|
264
|
+
resolution: "Resolved from Extension Task Page",
|
|
265
|
+
idempotencyKey: requestId("extension-acknowledge-message"),
|
|
266
|
+
});
|
|
267
|
+
await loadTask(selected.taskId);
|
|
268
|
+
});
|
|
269
|
+
});
|
|
270
|
+
row.append(label, detail, acknowledge);
|
|
271
|
+
pendingMessagesTarget.append(row);
|
|
272
|
+
}
|
|
241
273
|
nodesTarget.replaceChildren();
|
|
242
274
|
for (const node of selected.nodes) {
|
|
243
275
|
const row = document.createElement("div");
|
|
@@ -245,7 +277,7 @@ async function loadTask(taskId) {
|
|
|
245
277
|
const label = document.createElement("span");
|
|
246
278
|
label.textContent = `${node.title} · ${node.status} · run ${node.runNo}`;
|
|
247
279
|
row.append(label);
|
|
248
|
-
if (["SUCCEEDED", "FAILED"
|
|
280
|
+
if (["SUCCEEDED", "FAILED"].includes(node.status)) {
|
|
249
281
|
const reopen = document.createElement("button");
|
|
250
282
|
reopen.type = "button";
|
|
251
283
|
reopen.textContent = "Reopen";
|
|
@@ -392,6 +424,27 @@ startButton.addEventListener("click", () => {
|
|
|
392
424
|
}
|
|
393
425
|
});
|
|
394
426
|
});
|
|
427
|
+
resumeButton.addEventListener("click", () => {
|
|
428
|
+
void run(async () => {
|
|
429
|
+
if (!selected)
|
|
430
|
+
return;
|
|
431
|
+
setBusy(resumeButton, true);
|
|
432
|
+
try {
|
|
433
|
+
await taskApplication("task.resume", {
|
|
434
|
+
taskId: selected.taskId,
|
|
435
|
+
expectedTaskVersion: selected.version,
|
|
436
|
+
idempotencyKey: requestId("extension-resume-task"),
|
|
437
|
+
});
|
|
438
|
+
await loadTask(selected.taskId);
|
|
439
|
+
await refreshTasks();
|
|
440
|
+
}
|
|
441
|
+
finally {
|
|
442
|
+
resumeButton.disabled =
|
|
443
|
+
selected?.status === "WAITING" &&
|
|
444
|
+
(selected.pendingMessages?.length ?? 0) > 0;
|
|
445
|
+
}
|
|
446
|
+
});
|
|
447
|
+
});
|
|
395
448
|
ensureWorkersButton.addEventListener("click", () => {
|
|
396
449
|
void run(async () => {
|
|
397
450
|
if (!selected)
|
package/dist/src/bridge.js
CHANGED
|
@@ -268,6 +268,14 @@ export async function createBrowserRealityBridgeServer(options) {
|
|
|
268
268
|
? await options.taskWeb.invokeTask(body.operation, body.input)
|
|
269
269
|
: await options.taskWeb.invokeApproval(body.operation, body.input);
|
|
270
270
|
if (taskRequest &&
|
|
271
|
+
body.operation === "task.resume" &&
|
|
272
|
+
commandConsumerReady()) {
|
|
273
|
+
void requestCommand({
|
|
274
|
+
type: "TASK_OBSERVER_RESUME",
|
|
275
|
+
taskId: stringField(body.input, "taskId"),
|
|
276
|
+
}).catch(() => undefined);
|
|
277
|
+
}
|
|
278
|
+
else if (taskRequest &&
|
|
271
279
|
taskWebObserverRecoveryOperations.has(body.operation) &&
|
|
272
280
|
commandConsumerReady()) {
|
|
273
281
|
// The Task mutation is already durable and must not be coupled to
|
|
@@ -27,9 +27,10 @@ export type TaskDriveProjection = {
|
|
|
27
27
|
} | null;
|
|
28
28
|
canDrive: boolean;
|
|
29
29
|
blockedReason: string | null;
|
|
30
|
+
resumeSignalRef: string | null;
|
|
30
31
|
};
|
|
31
32
|
export type TaskObserverResumeSignal = {
|
|
32
|
-
trigger: "EXECUTION_RESULT_READY" | "PEER_REPLY_READY" | "RECOVERY_RESUME";
|
|
33
|
+
trigger: "EXECUTION_RESULT_READY" | "PEER_REPLY_READY" | "RECOVERY_RESUME" | "TASK_RESUMED";
|
|
33
34
|
ref: string;
|
|
34
35
|
targetWorkerRef: string;
|
|
35
36
|
nodeId: string;
|
|
@@ -71,17 +71,29 @@ export function createTaskObserver(options) {
|
|
|
71
71
|
conversationLocator: binding.conversationLocator,
|
|
72
72
|
};
|
|
73
73
|
}
|
|
74
|
-
|
|
74
|
+
const effectiveResumeSignal = resumeSignal ??
|
|
75
|
+
(projection.resumeSignalRef &&
|
|
76
|
+
projection.taskStatus === "ACTIVE" &&
|
|
77
|
+
node.status === "IN_PROGRESS"
|
|
78
|
+
? {
|
|
79
|
+
trigger: "TASK_RESUMED",
|
|
80
|
+
ref: projection.resumeSignalRef,
|
|
81
|
+
targetWorkerRef: binding.workerRef,
|
|
82
|
+
nodeId: node.nodeId,
|
|
83
|
+
runNo: node.runNo,
|
|
84
|
+
}
|
|
85
|
+
: undefined);
|
|
86
|
+
if (effectiveResumeSignal) {
|
|
75
87
|
if (projection.taskStatus !== "ACTIVE" || node.status !== "IN_PROGRESS")
|
|
76
88
|
return { kind: "NOOP", taskId, reason: "BINDING_NOT_READY" };
|
|
77
|
-
if (
|
|
78
|
-
|
|
89
|
+
if (effectiveResumeSignal.nodeId !== node.nodeId ||
|
|
90
|
+
effectiveResumeSignal.runNo !== node.runNo)
|
|
79
91
|
return {
|
|
80
92
|
kind: "NOOP",
|
|
81
93
|
taskId,
|
|
82
94
|
reason: "RESUME_GENERATION_MISMATCH",
|
|
83
95
|
};
|
|
84
|
-
if (
|
|
96
|
+
if (effectiveResumeSignal.targetWorkerRef !== binding.workerRef)
|
|
85
97
|
return {
|
|
86
98
|
kind: "NOOP",
|
|
87
99
|
taskId,
|
|
@@ -94,9 +106,9 @@ export function createTaskObserver(options) {
|
|
|
94
106
|
runNo: node.runNo,
|
|
95
107
|
roleRef: binding.roleRef,
|
|
96
108
|
workerRef: binding.workerRef,
|
|
97
|
-
trigger:
|
|
109
|
+
trigger: effectiveResumeSignal.trigger,
|
|
98
110
|
conversationLocator: binding.conversationLocator,
|
|
99
|
-
underlyingRef:
|
|
111
|
+
underlyingRef: effectiveResumeSignal.ref,
|
|
100
112
|
};
|
|
101
113
|
}
|
|
102
114
|
return { kind: "NOOP", taskId, reason: "NO_NEXT_STEP" };
|
package/extension/background.ts
CHANGED
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
import {
|
|
30
30
|
createTaskObserver,
|
|
31
31
|
type TaskDriveProjection,
|
|
32
|
+
type TaskObserverDecision,
|
|
32
33
|
type TaskObserverDiagnosticAssessment,
|
|
33
34
|
type TaskObserverDiagnosticFailure,
|
|
34
35
|
} from "../src/task-observer.js";
|
|
@@ -77,7 +78,8 @@ type BridgeCommand = {
|
|
|
77
78
|
| "SCREENSHOT"
|
|
78
79
|
| "PERFORM"
|
|
79
80
|
| "CARRIER_ATTENTION_ACTION"
|
|
80
|
-
| "TASK_OBSERVER_RECOVER"
|
|
81
|
+
| "TASK_OBSERVER_RECOVER"
|
|
82
|
+
| "TASK_OBSERVER_RESUME";
|
|
81
83
|
tabId?: number;
|
|
82
84
|
url?: string;
|
|
83
85
|
text?: string;
|
|
@@ -85,6 +87,7 @@ type BridgeCommand = {
|
|
|
85
87
|
request?: Record<string, unknown>;
|
|
86
88
|
attentionRef?: string;
|
|
87
89
|
action?: "allowOnce" | "deny";
|
|
90
|
+
taskId?: string;
|
|
88
91
|
};
|
|
89
92
|
type ContentSnapshotRequest = { type: "PROFLOW_PAGE_SNAPSHOT_REQUEST" };
|
|
90
93
|
type ContentCommand = {
|
|
@@ -540,6 +543,20 @@ const taskObserver = createTaskObserver({
|
|
|
540
543
|
},
|
|
541
544
|
});
|
|
542
545
|
|
|
546
|
+
async function resumeTaskWorker(taskId: string) {
|
|
547
|
+
const projection = (await invokeObserverApplication("task.projection", {
|
|
548
|
+
taskId,
|
|
549
|
+
})) as TaskDriveProjection;
|
|
550
|
+
if (
|
|
551
|
+
projection.taskId !== taskId ||
|
|
552
|
+
projection.taskStatus !== "ACTIVE" ||
|
|
553
|
+
projection.currentNode?.status !== "IN_PROGRESS" ||
|
|
554
|
+
!projection.resumeSignalRef
|
|
555
|
+
)
|
|
556
|
+
throw new Error("TASK_RESUMED_OWNER_FACTS_INVALID");
|
|
557
|
+
return taskObserver.drive(taskId);
|
|
558
|
+
}
|
|
559
|
+
|
|
543
560
|
async function resumeAfterApprovalDecision(approvalRef: string) {
|
|
544
561
|
const context = await invokeObserverApplication("approval.executionContext", {
|
|
545
562
|
approvalRef,
|
|
@@ -734,7 +751,7 @@ function runObserverRecovery() {
|
|
|
734
751
|
)
|
|
735
752
|
)
|
|
736
753
|
continue;
|
|
737
|
-
let decision;
|
|
754
|
+
let decision: TaskObserverDecision | null;
|
|
738
755
|
if (candidate.kind === "RECOVERY_RESUME") {
|
|
739
756
|
if (
|
|
740
757
|
typeof candidate.nodeId !== "string" ||
|
|
@@ -1355,6 +1372,8 @@ async function executeCommand(command: BridgeCommand): Promise<unknown> {
|
|
|
1355
1372
|
void runObserverRecovery();
|
|
1356
1373
|
return { scheduled: true };
|
|
1357
1374
|
}
|
|
1375
|
+
if (command.type === "TASK_OBSERVER_RESUME")
|
|
1376
|
+
return resumeTaskWorker(text(command.taskId, "TASK_ID"));
|
|
1358
1377
|
if (command.type === "CARRIER_ATTENTION_ACTION") {
|
|
1359
1378
|
if (command.action !== "allowOnce" && command.action !== "deny")
|
|
1360
1379
|
throw new Error("ATTENTION_ACTION_INVALID");
|
|
@@ -1941,8 +1960,10 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
|
|
1941
1960
|
return;
|
|
1942
1961
|
}
|
|
1943
1962
|
void invokeTaskApplication(message.operation, message.input).then(
|
|
1944
|
-
(value) => {
|
|
1945
|
-
|
|
1963
|
+
async (value) => {
|
|
1964
|
+
if (message.operation === "task.resume")
|
|
1965
|
+
await resumeTaskWorker(text(message.input?.taskId, "TASK_ID"));
|
|
1966
|
+
else void runObserverRecovery();
|
|
1946
1967
|
sendResponse({ ok: true, value });
|
|
1947
1968
|
},
|
|
1948
1969
|
(error: unknown) =>
|
package/extension/tasks.html
CHANGED
|
@@ -51,7 +51,9 @@
|
|
|
51
51
|
<h2>Selected Task</h2>
|
|
52
52
|
<div id="selected-task" class="meta">None selected.</div>
|
|
53
53
|
<button id="start-task" type="button" disabled>Confirm / Start</button>
|
|
54
|
+
<button id="resume-task" type="button" hidden>Resume</button>
|
|
54
55
|
<button id="ensure-workers" type="button" disabled>Recover missing Workers</button>
|
|
56
|
+
<div id="pending-messages"></div>
|
|
55
57
|
<div id="nodes"></div>
|
|
56
58
|
</section>
|
|
57
59
|
|
package/extension/tasks.ts
CHANGED
|
@@ -108,6 +108,13 @@ type TaskView = TaskSummary & {
|
|
|
108
108
|
runNo: number;
|
|
109
109
|
version: number;
|
|
110
110
|
}>;
|
|
111
|
+
pendingMessages: Array<{
|
|
112
|
+
messageId: string;
|
|
113
|
+
nodeId: string | null;
|
|
114
|
+
messageType: string;
|
|
115
|
+
reasonCode: string;
|
|
116
|
+
message: string;
|
|
117
|
+
}>;
|
|
111
118
|
};
|
|
112
119
|
|
|
113
120
|
function element<T extends HTMLElement>(selector: string): T {
|
|
@@ -124,7 +131,9 @@ const nodesTarget = element<HTMLElement>("#nodes");
|
|
|
124
131
|
const errorTarget = element<HTMLElement>("#error");
|
|
125
132
|
const resultTarget = element<HTMLElement>("#result");
|
|
126
133
|
const startButton = element<HTMLButtonElement>("#start-task");
|
|
134
|
+
const resumeButton = element<HTMLButtonElement>("#resume-task");
|
|
127
135
|
const ensureWorkersButton = element<HTMLButtonElement>("#ensure-workers");
|
|
136
|
+
const pendingMessagesTarget = element<HTMLElement>("#pending-messages");
|
|
128
137
|
const newTaskForm = element<HTMLFormElement>("#new-task-form");
|
|
129
138
|
const approvalsTarget = element<HTMLElement>("#approvals");
|
|
130
139
|
const carrierAttentionsTarget = element<HTMLElement>("#carrier-attentions");
|
|
@@ -342,8 +351,37 @@ async function loadTask(taskId: string) {
|
|
|
342
351
|
selected = (await taskApplication("task.get", { taskId })) as TaskView;
|
|
343
352
|
selectedTarget.textContent = `${selected.taskId} · ${selected.status} · v${selected.version}`;
|
|
344
353
|
startButton.disabled = selected.status !== "READY";
|
|
354
|
+
resumeButton.hidden = !["WAITING", "PAUSED"].includes(selected.status);
|
|
355
|
+
resumeButton.disabled =
|
|
356
|
+
selected.status === "WAITING" && selected.pendingMessages.length > 0;
|
|
345
357
|
ensureWorkersButton.disabled =
|
|
346
358
|
selected.status === "SUCCEEDED" || selected.status === "TERMINATED";
|
|
359
|
+
pendingMessagesTarget.replaceChildren();
|
|
360
|
+
for (const message of selected.pendingMessages) {
|
|
361
|
+
const row = document.createElement("div");
|
|
362
|
+
row.className = "task";
|
|
363
|
+
const label = document.createElement("div");
|
|
364
|
+
label.textContent = `${message.messageType} · ${message.reasonCode}`;
|
|
365
|
+
const detail = document.createElement("div");
|
|
366
|
+
detail.className = "meta";
|
|
367
|
+
detail.textContent = message.message;
|
|
368
|
+
const acknowledge = document.createElement("button");
|
|
369
|
+
acknowledge.type = "button";
|
|
370
|
+
acknowledge.textContent = "Resolve blocker";
|
|
371
|
+
acknowledge.addEventListener("click", () => {
|
|
372
|
+
void run(async () => {
|
|
373
|
+
if (!selected) return;
|
|
374
|
+
await taskApplication("message.acknowledge", {
|
|
375
|
+
messageId: message.messageId,
|
|
376
|
+
resolution: "Resolved from Extension Task Page",
|
|
377
|
+
idempotencyKey: requestId("extension-acknowledge-message"),
|
|
378
|
+
});
|
|
379
|
+
await loadTask(selected.taskId);
|
|
380
|
+
});
|
|
381
|
+
});
|
|
382
|
+
row.append(label, detail, acknowledge);
|
|
383
|
+
pendingMessagesTarget.append(row);
|
|
384
|
+
}
|
|
347
385
|
nodesTarget.replaceChildren();
|
|
348
386
|
for (const node of selected.nodes) {
|
|
349
387
|
const row = document.createElement("div");
|
|
@@ -351,7 +389,7 @@ async function loadTask(taskId: string) {
|
|
|
351
389
|
const label = document.createElement("span");
|
|
352
390
|
label.textContent = `${node.title} · ${node.status} · run ${node.runNo}`;
|
|
353
391
|
row.append(label);
|
|
354
|
-
if (["SUCCEEDED", "FAILED"
|
|
392
|
+
if (["SUCCEEDED", "FAILED"].includes(node.status)) {
|
|
355
393
|
const reopen = document.createElement("button");
|
|
356
394
|
reopen.type = "button";
|
|
357
395
|
reopen.textContent = "Reopen";
|
|
@@ -515,6 +553,26 @@ startButton.addEventListener("click", () => {
|
|
|
515
553
|
});
|
|
516
554
|
});
|
|
517
555
|
|
|
556
|
+
resumeButton.addEventListener("click", () => {
|
|
557
|
+
void run(async () => {
|
|
558
|
+
if (!selected) return;
|
|
559
|
+
setBusy(resumeButton, true);
|
|
560
|
+
try {
|
|
561
|
+
await taskApplication("task.resume", {
|
|
562
|
+
taskId: selected.taskId,
|
|
563
|
+
expectedTaskVersion: selected.version,
|
|
564
|
+
idempotencyKey: requestId("extension-resume-task"),
|
|
565
|
+
});
|
|
566
|
+
await loadTask(selected.taskId);
|
|
567
|
+
await refreshTasks();
|
|
568
|
+
} finally {
|
|
569
|
+
resumeButton.disabled =
|
|
570
|
+
selected?.status === "WAITING" &&
|
|
571
|
+
(selected.pendingMessages?.length ?? 0) > 0;
|
|
572
|
+
}
|
|
573
|
+
});
|
|
574
|
+
});
|
|
575
|
+
|
|
518
576
|
ensureWorkersButton.addEventListener("click", () => {
|
|
519
577
|
void run(async () => {
|
|
520
578
|
if (!selected) return;
|
package/manifest.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tomflow/proflow-execution-browser-extension",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.45",
|
|
4
4
|
"description": "Execution-owned MV3 Browser executor, evidence provider and browser application surface.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
@@ -27,8 +27,8 @@
|
|
|
27
27
|
"SETUP.md"
|
|
28
28
|
],
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@tomflow/proflow-
|
|
31
|
-
"@tomflow/proflow-
|
|
30
|
+
"@tomflow/proflow-module-contract": "^0.1.13",
|
|
31
|
+
"@tomflow/proflow-execution-contracts": "^0.1.10"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
34
|
"@tomflow/proflow-deployment-conformance": "^0.1.13",
|
package/proflow.module.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"contractVersion": "1.0.0",
|
|
4
4
|
"moduleRef": "execution-browser-extension",
|
|
5
5
|
"packageName": "@tomflow/proflow-execution-browser-extension",
|
|
6
|
-
"moduleVersion": "0.1.
|
|
6
|
+
"moduleVersion": "0.1.45",
|
|
7
7
|
"kind": "browser-extension",
|
|
8
8
|
"templateVersion": "1.0.0",
|
|
9
9
|
"platformCompatibility": ">=1.0.0 <2.0.0",
|