@tomflow/proflow-platform-host 0.1.31 → 0.1.32
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/src/index.js +88 -22
- package/dist/src/monitor-drive-relay.d.ts +13 -0
- package/dist/src/monitor-drive-relay.js +107 -0
- package/dist/src/monitor-rotation-coordinator.d.ts +5 -68
- package/dist/src/monitor-rotation-coordinator.js +14 -772
- package/package.json +9 -9
- package/proflow.module.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,7 +6,7 @@ export declare const behaviorAdapter: {
|
|
|
6
6
|
ok: true;
|
|
7
7
|
status: "SUCCEEDED";
|
|
8
8
|
moduleRef: "platform-host";
|
|
9
|
-
moduleVersion: "0.1.
|
|
9
|
+
moduleVersion: "0.1.32";
|
|
10
10
|
data: {
|
|
11
11
|
endpoint: string;
|
|
12
12
|
stateRoot: string;
|
|
@@ -24,7 +24,7 @@ export declare const behaviorAdapter: {
|
|
|
24
24
|
readonly ok: true;
|
|
25
25
|
readonly status: "SUCCEEDED";
|
|
26
26
|
readonly moduleRef: "platform-host";
|
|
27
|
-
readonly moduleVersion: "0.1.
|
|
27
|
+
readonly moduleVersion: "0.1.32";
|
|
28
28
|
};
|
|
29
29
|
observedEffects: string[];
|
|
30
30
|
}>;
|
|
@@ -34,7 +34,7 @@ export declare const behaviorAdapter: {
|
|
|
34
34
|
ok: true;
|
|
35
35
|
status: "SUCCEEDED";
|
|
36
36
|
moduleRef: "platform-host";
|
|
37
|
-
moduleVersion: "0.1.
|
|
37
|
+
moduleVersion: "0.1.32";
|
|
38
38
|
data: {
|
|
39
39
|
setupStatus: "READY";
|
|
40
40
|
runtimeStatus: "RUNNING" | "STOPPED";
|
|
@@ -48,7 +48,7 @@ export declare const behaviorAdapter: {
|
|
|
48
48
|
ok: true;
|
|
49
49
|
status: "SUCCEEDED";
|
|
50
50
|
moduleRef: "platform-host";
|
|
51
|
-
moduleVersion: "0.1.
|
|
51
|
+
moduleVersion: "0.1.32";
|
|
52
52
|
data: {
|
|
53
53
|
endpoint: string;
|
|
54
54
|
stateRoot: string;
|
|
@@ -66,7 +66,7 @@ export declare const behaviorAdapter: {
|
|
|
66
66
|
ok: true;
|
|
67
67
|
status: "SUCCEEDED";
|
|
68
68
|
moduleRef: "platform-host";
|
|
69
|
-
moduleVersion: "0.1.
|
|
69
|
+
moduleVersion: "0.1.32";
|
|
70
70
|
data: {
|
|
71
71
|
docs: string;
|
|
72
72
|
};
|
|
@@ -79,7 +79,7 @@ export declare const behaviorAdapter: {
|
|
|
79
79
|
ok: true;
|
|
80
80
|
status: "SUCCEEDED";
|
|
81
81
|
moduleRef: "platform-host";
|
|
82
|
-
moduleVersion: "0.1.
|
|
82
|
+
moduleVersion: "0.1.32";
|
|
83
83
|
data: {
|
|
84
84
|
host: string;
|
|
85
85
|
port: number;
|
|
@@ -90,7 +90,7 @@ export declare const behaviorAdapter: {
|
|
|
90
90
|
result: {
|
|
91
91
|
contract: "deployment.result.v1";
|
|
92
92
|
moduleRef: "platform-host";
|
|
93
|
-
moduleVersion: "0.1.
|
|
93
|
+
moduleVersion: "0.1.32";
|
|
94
94
|
ok: false;
|
|
95
95
|
status: "FAILED";
|
|
96
96
|
error: {
|
|
@@ -107,7 +107,7 @@ export declare const behaviorAdapter: {
|
|
|
107
107
|
readonly ok: true;
|
|
108
108
|
readonly status: "SUCCEEDED";
|
|
109
109
|
readonly moduleRef: "platform-host";
|
|
110
|
-
readonly moduleVersion: "0.1.
|
|
110
|
+
readonly moduleVersion: "0.1.32";
|
|
111
111
|
};
|
|
112
112
|
observedEffects: string[];
|
|
113
113
|
} | {
|
|
@@ -116,11 +116,11 @@ export declare const behaviorAdapter: {
|
|
|
116
116
|
readonly ok: true;
|
|
117
117
|
readonly status: "SUCCEEDED";
|
|
118
118
|
readonly moduleRef: "platform-host";
|
|
119
|
-
readonly moduleVersion: "0.1.
|
|
119
|
+
readonly moduleVersion: "0.1.32";
|
|
120
120
|
} | {
|
|
121
121
|
contract: "deployment.result.v1";
|
|
122
122
|
moduleRef: "platform-host";
|
|
123
|
-
moduleVersion: "0.1.
|
|
123
|
+
moduleVersion: "0.1.32";
|
|
124
124
|
ok: false;
|
|
125
125
|
status: "FAILED";
|
|
126
126
|
error: {
|
|
@@ -3,7 +3,7 @@ export declare const descriptor: {
|
|
|
3
3
|
readonly contractVersion: "1.0.0";
|
|
4
4
|
readonly moduleRef: "platform-host";
|
|
5
5
|
readonly packageName: "@tomflow/proflow-platform-host";
|
|
6
|
-
readonly moduleVersion: "0.1.
|
|
6
|
+
readonly moduleVersion: "0.1.32";
|
|
7
7
|
readonly kind: "service";
|
|
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: "platform-host",
|
|
5
5
|
packageName: "@tomflow/proflow-platform-host",
|
|
6
|
-
moduleVersion: "0.1.
|
|
6
|
+
moduleVersion: "0.1.32",
|
|
7
7
|
kind: "service",
|
|
8
8
|
templateVersion: "1.0.0",
|
|
9
9
|
platformCompatibility: ">=1.0.0 <2.0.0",
|
package/dist/src/index.js
CHANGED
|
@@ -7,6 +7,7 @@ import { basename, dirname, isAbsolute, join, resolve } from "node:path";
|
|
|
7
7
|
import { createAgentRuntime } from "@tomflow/proflow-agent-runtime";
|
|
8
8
|
import { createMonitorControlClient, } from "@tomflow/proflow-execution-browser-extension";
|
|
9
9
|
import { createLocalToolBridgeHostClient, LocalToolBridgeError, } from "@tomflow/proflow-execution-browser-extension/local-tool-bridge";
|
|
10
|
+
import { browserActionInputSchema, parseMonitorEffectRequest } from "@tomflow/proflow-execution-contracts";
|
|
10
11
|
import { applyMigrations } from "@tomflow/proflow-task-migration-runner";
|
|
11
12
|
import { createTaskServices, publicOperationNames, } from "@tomflow/proflow-task-orchestration";
|
|
12
13
|
import { SqliteTaskStore } from "@tomflow/proflow-task-store-sqlite";
|
|
@@ -14,7 +15,8 @@ import { taskMigrations } from "@tomflow/proflow-task-store-sqlite/migrations";
|
|
|
14
15
|
import { z } from "zod";
|
|
15
16
|
import { resolveBrowserPermissionTaskBinding } from "./browser-permission-context.js";
|
|
16
17
|
import { classifyBrowserPermission, } from "./browser-permission-policy.js";
|
|
17
|
-
import { authorizeMonitorExecution
|
|
18
|
+
import { authorizeMonitorExecution } from "./monitor-rotation-coordinator.js";
|
|
19
|
+
import { createMonitorDriveRelay } from "./monitor-drive-relay.js";
|
|
18
20
|
import { canonicalizeProductOperation, createProductCampaignContinuationCoordinator, } from "./product-campaign-continuation.js";
|
|
19
21
|
import { createProductDiscussionHost } from "./product-discussion-host.js";
|
|
20
22
|
import { createReconciliationCoordinator } from "./reconciliation-coordinator.js";
|
|
@@ -276,6 +278,8 @@ function createOwnerHttpClient(owner, baseUrl, credential, resolveConnection, sh
|
|
|
276
278
|
}
|
|
277
279
|
else if (operationId === "materializeExternalFiles")
|
|
278
280
|
path = "/external-files/materialize";
|
|
281
|
+
else if (operationId === "effect.execute")
|
|
282
|
+
path = "/effects/execute";
|
|
279
283
|
else if (operationId === "executeCapability")
|
|
280
284
|
path = "/executions";
|
|
281
285
|
else if (operationId === "lookupExecutionIntent")
|
|
@@ -334,8 +338,8 @@ function createOwnerHttpClient(owner, baseUrl, credential, resolveConnection, sh
|
|
|
334
338
|
? { body: JSON.stringify(requestBody) }
|
|
335
339
|
: {}),
|
|
336
340
|
signal: shutdownSignal
|
|
337
|
-
? AbortSignal.any([shutdownSignal, AbortSignal.timeout(OWNER_INVOKE_WATCHDOG_MS)])
|
|
338
|
-
: AbortSignal.timeout(OWNER_INVOKE_WATCHDOG_MS),
|
|
341
|
+
? AbortSignal.any([shutdownSignal, AbortSignal.timeout(operationId === "effect.execute" ? 190_000 : OWNER_INVOKE_WATCHDOG_MS)])
|
|
342
|
+
: AbortSignal.timeout(operationId === "effect.execute" ? 190_000 : OWNER_INVOKE_WATCHDOG_MS),
|
|
339
343
|
}));
|
|
340
344
|
},
|
|
341
345
|
});
|
|
@@ -477,21 +481,11 @@ async function constructGraph(config, executionCredential, modelCredential, reso
|
|
|
477
481
|
},
|
|
478
482
|
})
|
|
479
483
|
: undefined;
|
|
484
|
+
const monitorDriveRelay = monitor
|
|
485
|
+
? createMonitorDriveRelay({ monitor, execution })
|
|
486
|
+
: undefined;
|
|
480
487
|
let reconciliationCoordinator;
|
|
481
488
|
let productCampaignContinuationCoordinator;
|
|
482
|
-
const monitorRotationCoordinator = monitor
|
|
483
|
-
? createMonitorRotationCoordinator({
|
|
484
|
-
monitor,
|
|
485
|
-
execution,
|
|
486
|
-
onError: (errorCode) => sink.write("platform-host", {
|
|
487
|
-
timestamp: new Date().toISOString(),
|
|
488
|
-
level: "WARN",
|
|
489
|
-
component: "platform-host-monitor-rotation",
|
|
490
|
-
event: "MONITOR_ROTATION_POLL_FAILED",
|
|
491
|
-
errorCode,
|
|
492
|
-
}),
|
|
493
|
-
})
|
|
494
|
-
: undefined;
|
|
495
489
|
const boundedSystemView = async (view) => {
|
|
496
490
|
if (view === "task") {
|
|
497
491
|
const tasks = unwrap(task.queries.listTasks({})).tasks;
|
|
@@ -869,6 +863,30 @@ async function constructGraph(config, executionCredential, modelCredential, reso
|
|
|
869
863
|
});
|
|
870
864
|
const authorizeExecution = async (request) => {
|
|
871
865
|
try {
|
|
866
|
+
if (request.capability === "browser.action") {
|
|
867
|
+
if (request.callerRef !== "platform-host:browser-action")
|
|
868
|
+
return false;
|
|
869
|
+
const input = browserActionInputSchema.parse(request.input);
|
|
870
|
+
// Authenticated local Action owner; reload must also work before Monitor initialization.
|
|
871
|
+
if (input.kind === "RELOAD")
|
|
872
|
+
return true;
|
|
873
|
+
if (!monitor)
|
|
874
|
+
return false;
|
|
875
|
+
const state = object(await monitor.invoke("state.read", {}), "monitor state");
|
|
876
|
+
const chats = object(state.chats, "monitor chats");
|
|
877
|
+
const config = object(state.config, "monitor config");
|
|
878
|
+
const allowed = new Set(Object.values(chats).flatMap((chat) => {
|
|
879
|
+
if (!chat || typeof chat !== "object" || !("conversationLocator" in chat) ||
|
|
880
|
+
typeof chat.conversationLocator !== "string")
|
|
881
|
+
return [];
|
|
882
|
+
return [chat.conversationLocator];
|
|
883
|
+
}));
|
|
884
|
+
if (typeof config.projectLocator === "string")
|
|
885
|
+
allowed.add(config.projectLocator);
|
|
886
|
+
allowed.add("chrome://extensions/?id=eehdadpmjffomabiedcjijiakconalab");
|
|
887
|
+
allowed.add("chrome://extensions/?errors=eehdadpmjffomabiedcjijiakconalab");
|
|
888
|
+
return input.targets.every((target) => allowed.has(target.url));
|
|
889
|
+
}
|
|
872
890
|
if (request.capability === "product.discussion.bootstrap.deliver")
|
|
873
891
|
return productDiscussionHost
|
|
874
892
|
? productDiscussionHost.authorizeBootstrapExecution(request)
|
|
@@ -877,7 +895,15 @@ async function constructGraph(config, executionCredential, modelCredential, reso
|
|
|
877
895
|
request.capability === "monitor.chat.create") {
|
|
878
896
|
if (!monitor)
|
|
879
897
|
return false;
|
|
880
|
-
|
|
898
|
+
if (request.callerRef === "platform-host:monitor-drive") {
|
|
899
|
+
const authorization = object(await monitor.invoke("drive.authorize", { request }), "monitor drive authorization");
|
|
900
|
+
return authorization.authorized === true;
|
|
901
|
+
}
|
|
902
|
+
// Transitional compatibility until legacy Monitor lifecycle code is removed.
|
|
903
|
+
return authorizeMonitorExecution({
|
|
904
|
+
monitor,
|
|
905
|
+
request: request,
|
|
906
|
+
});
|
|
881
907
|
}
|
|
882
908
|
if (request.capability === "product.review.deliver") {
|
|
883
909
|
if (request.callerRef !== "platform-host:campaign-continuation")
|
|
@@ -1477,6 +1503,20 @@ async function constructGraph(config, executionCredential, modelCredential, reso
|
|
|
1477
1503
|
throw new Error(`PRODUCT_REVIEW_DELIVERY_NOT_CONFIRMED:${status}:${sideEffectState}`);
|
|
1478
1504
|
},
|
|
1479
1505
|
});
|
|
1506
|
+
const monitorApplication = observer.port("monitor", {
|
|
1507
|
+
async invoke(operation, rawInput) {
|
|
1508
|
+
object(rawInput, "monitor application input");
|
|
1509
|
+
if (operation === "effect.execute") {
|
|
1510
|
+
parseMonitorEffectRequest(rawInput);
|
|
1511
|
+
return execution.invoke("effect.execute", rawInput);
|
|
1512
|
+
}
|
|
1513
|
+
if (operation !== "drive")
|
|
1514
|
+
throw new Error("UNSUPPORTED_MONITOR_APPLICATION_OPERATION");
|
|
1515
|
+
if (!monitorDriveRelay)
|
|
1516
|
+
throw new Error("MONITOR_CONTROL_UNAVAILABLE");
|
|
1517
|
+
return monitorDriveRelay.drive();
|
|
1518
|
+
},
|
|
1519
|
+
});
|
|
1480
1520
|
const taskApplication = observer.port("task", {
|
|
1481
1521
|
async invoke(operation, rawInput) {
|
|
1482
1522
|
const value = object(rawInput, "task application input");
|
|
@@ -1794,9 +1834,7 @@ async function constructGraph(config, executionCredential, modelCredential, reso
|
|
|
1794
1834
|
taskApplication,
|
|
1795
1835
|
approvalApplication,
|
|
1796
1836
|
observerApplication,
|
|
1797
|
-
|
|
1798
|
-
monitorRotationCoordinator?.start();
|
|
1799
|
-
},
|
|
1837
|
+
monitorApplication,
|
|
1800
1838
|
async lookup(_operationId, _authenticatedRoleRef, _input) {
|
|
1801
1839
|
throw Object.assign(new Error("ACTION_RESULT_LOOKUP_UNSUPPORTED"), { httpStatus: 409 });
|
|
1802
1840
|
},
|
|
@@ -1829,7 +1867,6 @@ async function constructGraph(config, executionCredential, modelCredential, reso
|
|
|
1829
1867
|
async close() {
|
|
1830
1868
|
productCampaignContinuationCoordinator?.stop();
|
|
1831
1869
|
reconciliationCoordinator?.stop();
|
|
1832
|
-
await monitorRotationCoordinator?.stop();
|
|
1833
1870
|
ownerShutdown.abort();
|
|
1834
1871
|
await Promise.all([
|
|
1835
1872
|
productCampaignContinuationCoordinator?.drain(),
|
|
@@ -2166,6 +2203,36 @@ export function createPlatformHost(input) {
|
|
|
2166
2203
|
});
|
|
2167
2204
|
}
|
|
2168
2205
|
}
|
|
2206
|
+
if (request.method === "POST" && url.pathname === "/application/monitor") {
|
|
2207
|
+
if (!taskApplicationCredential ||
|
|
2208
|
+
!managementCredentialMatches(request.headers.authorization, taskApplicationCredential))
|
|
2209
|
+
return respond(response, 401, {
|
|
2210
|
+
error: "MONITOR_APPLICATION_AUTH_FAILED",
|
|
2211
|
+
});
|
|
2212
|
+
const chunks = [];
|
|
2213
|
+
let bytes = 0;
|
|
2214
|
+
for await (const chunk of request) {
|
|
2215
|
+
const buffer = Buffer.isBuffer(chunk)
|
|
2216
|
+
? chunk
|
|
2217
|
+
: Buffer.from(chunk);
|
|
2218
|
+
bytes += buffer.byteLength;
|
|
2219
|
+
if (bytes > 65_536)
|
|
2220
|
+
throw new TypeError("REQUEST_BODY_TOO_LARGE");
|
|
2221
|
+
chunks.push(buffer);
|
|
2222
|
+
}
|
|
2223
|
+
const body = object(JSON.parse(Buffer.concat(chunks).toString("utf8")), "monitor application request");
|
|
2224
|
+
try {
|
|
2225
|
+
const result = await graph.monitorApplication.invoke(string(body.operation, "operation"), body.input ?? {});
|
|
2226
|
+
return respond(response, 200, result);
|
|
2227
|
+
}
|
|
2228
|
+
catch (error) {
|
|
2229
|
+
return respond(response, 400, {
|
|
2230
|
+
error: error instanceof Error
|
|
2231
|
+
? error.message
|
|
2232
|
+
: "INVALID_REQUEST",
|
|
2233
|
+
});
|
|
2234
|
+
}
|
|
2235
|
+
}
|
|
2169
2236
|
if (request.method === "POST" && url.pathname === "/application/observer") {
|
|
2170
2237
|
if (!taskApplicationCredential ||
|
|
2171
2238
|
!managementCredentialMatches(request.headers.authorization, taskApplicationCredential))
|
|
@@ -2296,7 +2363,6 @@ export function createPlatformHost(input) {
|
|
|
2296
2363
|
const address = server.address();
|
|
2297
2364
|
if (!address || typeof address === "string")
|
|
2298
2365
|
throw new Error("platform-host missing TCP address");
|
|
2299
|
-
graph.startMonitorRotation();
|
|
2300
2366
|
log("SERVICE_STARTED", { host: input.config.host, port: address.port });
|
|
2301
2367
|
return { host: input.config.host, port: address.port };
|
|
2302
2368
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
type MonitorPort = {
|
|
2
|
+
invoke(operation: string, input?: unknown): Promise<unknown>;
|
|
3
|
+
};
|
|
4
|
+
type ExecutionPort = {
|
|
5
|
+
invoke(operation: string, input: unknown): Promise<unknown>;
|
|
6
|
+
};
|
|
7
|
+
export declare function createMonitorDriveRelay(options: {
|
|
8
|
+
monitor: MonitorPort;
|
|
9
|
+
execution: ExecutionPort;
|
|
10
|
+
}): Readonly<{
|
|
11
|
+
drive(): Promise<unknown>;
|
|
12
|
+
}>;
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { parseExecuteCapabilityRequest, parseMonitorEffectReceipt, } from "@tomflow/proflow-execution-contracts";
|
|
2
|
+
function record(value, name) {
|
|
3
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
4
|
+
throw new Error(`${name}_INVALID`);
|
|
5
|
+
return value;
|
|
6
|
+
}
|
|
7
|
+
function string(value, name) {
|
|
8
|
+
if (typeof value !== "string" || value.length === 0)
|
|
9
|
+
throw new Error(`${name}_INVALID`);
|
|
10
|
+
return value;
|
|
11
|
+
}
|
|
12
|
+
function capability(value) {
|
|
13
|
+
if (value !== "monitor.chat.submit" && value !== "monitor.chat.create")
|
|
14
|
+
throw new Error("MONITOR_DRIVE_CAPABILITY_INVALID");
|
|
15
|
+
return value;
|
|
16
|
+
}
|
|
17
|
+
function parsePlan(value) {
|
|
18
|
+
if (value === null)
|
|
19
|
+
return null;
|
|
20
|
+
const raw = record(value, "MONITOR_DRIVE_PLAN");
|
|
21
|
+
return {
|
|
22
|
+
driveId: string(raw.driveId, "MONITOR_DRIVE_ID"),
|
|
23
|
+
capability: capability(raw.capability),
|
|
24
|
+
idempotencyKey: string(raw.idempotencyKey, "MONITOR_DRIVE_IDEMPOTENCY_KEY"),
|
|
25
|
+
request: record(raw.request, "MONITOR_DRIVE_REQUEST"),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
export function createMonitorDriveRelay(options) {
|
|
29
|
+
let inFlight = null;
|
|
30
|
+
const driveOnce = async () => {
|
|
31
|
+
const plan = parsePlan(await options.monitor.invoke("drive.next", {}));
|
|
32
|
+
if (!plan) {
|
|
33
|
+
let notificationDelivery = "SETTLED";
|
|
34
|
+
try {
|
|
35
|
+
await options.monitor.invoke("notification.deliver", { limit: 10 });
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
notificationDelivery = "FAILED";
|
|
39
|
+
}
|
|
40
|
+
return { state: "IDLE", notificationDelivery };
|
|
41
|
+
}
|
|
42
|
+
if (plan.driveId !== plan.idempotencyKey)
|
|
43
|
+
throw new Error("MONITOR_EFFECT_IDENTITY_MISMATCH");
|
|
44
|
+
const request = parseExecuteCapabilityRequest({
|
|
45
|
+
contract: "execution",
|
|
46
|
+
contractVersion: "1.0.0",
|
|
47
|
+
callerRef: "platform-host:monitor-drive",
|
|
48
|
+
idempotencyKey: plan.idempotencyKey,
|
|
49
|
+
capability: plan.capability,
|
|
50
|
+
input: plan.request,
|
|
51
|
+
});
|
|
52
|
+
const authorization = record(await options.monitor.invoke("drive.authorize", { request }), "MONITOR_DRIVE_AUTHORIZATION");
|
|
53
|
+
if (authorization.authorized !== true)
|
|
54
|
+
throw new Error("MONITOR_DRIVE_NOT_AUTHORIZED");
|
|
55
|
+
let execution;
|
|
56
|
+
try {
|
|
57
|
+
execution = await options.execution.invoke("effect.execute", { effectId: plan.driveId, action: plan.capability, payload: plan.request });
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
// The effect may already be durable or applied. Preserve the pending
|
|
61
|
+
// drive; a later trigger reads the same durable effect; PENDING never replays.
|
|
62
|
+
return {
|
|
63
|
+
state: "PENDING",
|
|
64
|
+
driveId: plan.driveId,
|
|
65
|
+
errorCode: "EXECUTION_OWNER_RESPONSE_UNKNOWN",
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
const executionRecord = parseMonitorEffectReceipt(execution);
|
|
69
|
+
if (executionRecord.effectId !== plan.driveId || executionRecord.action !== plan.capability)
|
|
70
|
+
throw new Error("MONITOR_EFFECT_IDENTITY_MISMATCH");
|
|
71
|
+
let settlement;
|
|
72
|
+
try {
|
|
73
|
+
settlement = record(await options.monitor.invoke("drive.result", {
|
|
74
|
+
driveId: plan.driveId, execution: executionRecord,
|
|
75
|
+
}), "MONITOR_DRIVE_SETTLEMENT");
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
return { state: "PENDING", driveId: plan.driveId,
|
|
79
|
+
errorCode: "MONITOR_SETTLEMENT_RESPONSE_LOST" };
|
|
80
|
+
}
|
|
81
|
+
let notificationDelivery = "SETTLED";
|
|
82
|
+
try {
|
|
83
|
+
await options.monitor.invoke("notification.deliver", { limit: 10 });
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
notificationDelivery = "FAILED";
|
|
87
|
+
}
|
|
88
|
+
return {
|
|
89
|
+
state: string(settlement.state, "MONITOR_DRIVE_SETTLEMENT_STATE"),
|
|
90
|
+
driveId: plan.driveId,
|
|
91
|
+
...(typeof executionRecord.executionRef === "string"
|
|
92
|
+
? { executionRef: executionRecord.executionRef }
|
|
93
|
+
: {}),
|
|
94
|
+
notificationDelivery,
|
|
95
|
+
};
|
|
96
|
+
};
|
|
97
|
+
return Object.freeze({
|
|
98
|
+
drive() {
|
|
99
|
+
if (inFlight)
|
|
100
|
+
return inFlight;
|
|
101
|
+
inFlight = driveOnce().finally(() => {
|
|
102
|
+
inFlight = null;
|
|
103
|
+
});
|
|
104
|
+
return inFlight;
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
}
|
|
@@ -2,75 +2,12 @@ import type { ExecuteCapabilityRequest } from "@tomflow/proflow-execution-contra
|
|
|
2
2
|
export type MonitorControlPort = {
|
|
3
3
|
invoke(operation: string, input?: unknown): Promise<unknown>;
|
|
4
4
|
};
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
shiftId: string;
|
|
11
|
-
generation: number;
|
|
12
|
-
}): string;
|
|
13
|
-
export declare function monitorBootstrapMessageRef(input: {
|
|
14
|
-
runId: string;
|
|
15
|
-
shiftId: string;
|
|
16
|
-
generation: number;
|
|
17
|
-
}): string;
|
|
18
|
-
export declare function monitorBootstrapText(input: {
|
|
19
|
-
runId: string;
|
|
20
|
-
shiftId: string;
|
|
21
|
-
}): string;
|
|
22
|
-
export declare function monitorTurnWakeMessageRef(input: {
|
|
23
|
-
runId: string;
|
|
24
|
-
shiftId: string;
|
|
25
|
-
generation: number;
|
|
26
|
-
settledRef: string;
|
|
27
|
-
}): string;
|
|
28
|
-
export declare function monitorTurnWakeText(): string;
|
|
29
|
-
export declare function monitorNextShiftId(input: {
|
|
30
|
-
runId: string;
|
|
31
|
-
shiftId: string;
|
|
32
|
-
generation: number;
|
|
33
|
-
}): string;
|
|
34
|
-
export declare function monitorNextChatMessageRef(input: {
|
|
35
|
-
runId: string;
|
|
36
|
-
shiftId: string;
|
|
37
|
-
generation: number;
|
|
38
|
-
nextShiftId: string;
|
|
39
|
-
}): string;
|
|
40
|
-
export declare function monitorHandoffRequestText(_input: {
|
|
41
|
-
runId: string;
|
|
42
|
-
shiftId: string;
|
|
43
|
-
generation: number;
|
|
44
|
-
projectRef: string;
|
|
45
|
-
}): string;
|
|
5
|
+
/**
|
|
6
|
+
* Transitional symbol retained only while Platform Host still imports it.
|
|
7
|
+
* The legacy polling/rotation coordinator is removed: Browser timing asks the
|
|
8
|
+
* Node owner for a concrete drive, and Node is the only authorization owner.
|
|
9
|
+
*/
|
|
46
10
|
export declare function authorizeMonitorExecution(input: {
|
|
47
11
|
monitor: MonitorControlPort;
|
|
48
12
|
request: ExecuteCapabilityRequest;
|
|
49
|
-
now?: () => Date;
|
|
50
13
|
}): Promise<boolean>;
|
|
51
|
-
export declare function createMonitorRotationCoordinator(options: {
|
|
52
|
-
monitor: MonitorControlPort;
|
|
53
|
-
execution: MonitorExecutionPort;
|
|
54
|
-
intervalMs?: number;
|
|
55
|
-
now?: () => Date;
|
|
56
|
-
onError?: (code: "MONITOR_ROTATION_POLL_FAILED") => void | Promise<void>;
|
|
57
|
-
}): Readonly<{
|
|
58
|
-
tick: () => Promise<{
|
|
59
|
-
state: "DISABLED";
|
|
60
|
-
runs: never[];
|
|
61
|
-
notification?: never;
|
|
62
|
-
} | {
|
|
63
|
-
state: "PROJECT_BINDING_REQUIRED";
|
|
64
|
-
runs: never[];
|
|
65
|
-
notification?: never;
|
|
66
|
-
} | {
|
|
67
|
-
state: "CHECKED";
|
|
68
|
-
runs: {
|
|
69
|
-
runId: string;
|
|
70
|
-
outcome: unknown;
|
|
71
|
-
}[];
|
|
72
|
-
notification: unknown;
|
|
73
|
-
}>;
|
|
74
|
-
start(): void;
|
|
75
|
-
stop(): Promise<void>;
|
|
76
|
-
}>;
|
|
@@ -1,782 +1,24 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
}
|
|
7
|
-
function string(value, name) {
|
|
8
|
-
if (typeof value !== "string" || value.length === 0)
|
|
9
|
-
throw new Error(`${name}_INVALID`);
|
|
10
|
-
return value;
|
|
11
|
-
}
|
|
12
|
-
function positiveInteger(value, name) {
|
|
13
|
-
if (!Number.isInteger(value) || Number(value) <= 0)
|
|
14
|
-
throw new Error(`${name}_INVALID`);
|
|
15
|
-
return Number(value);
|
|
16
|
-
}
|
|
17
|
-
function optionalString(value, name) {
|
|
18
|
-
return value === undefined ? undefined : string(value, name);
|
|
19
|
-
}
|
|
20
|
-
function parseConfig(value) {
|
|
21
|
-
const raw = object(value, "MONITOR_CONFIG");
|
|
22
|
-
if (raw.contract !== "proflow.monitor-config.v1")
|
|
23
|
-
throw new Error("MONITOR_CONFIG_INVALID");
|
|
24
|
-
const projectRef = raw.projectRef === null ? null : string(raw.projectRef, "MONITOR_PROJECT_REF");
|
|
25
|
-
const projectLocator = raw.projectLocator === null
|
|
26
|
-
? null
|
|
27
|
-
: string(raw.projectLocator, "MONITOR_PROJECT_LOCATOR");
|
|
28
|
-
return {
|
|
29
|
-
revision: positiveInteger(raw.revision, "MONITOR_CONFIG_REVISION"),
|
|
30
|
-
enabled: raw.enabled === true,
|
|
31
|
-
notificationsEnabled: raw.notificationsEnabled === true,
|
|
32
|
-
projectRef,
|
|
33
|
-
projectLocator,
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
function parseShift(value, expectedShiftId) {
|
|
37
|
-
const raw = object(value, "MONITOR_SHIFT");
|
|
38
|
-
const shiftId = string(raw.shiftId, "MONITOR_SHIFT_ID");
|
|
39
|
-
if (shiftId !== expectedShiftId)
|
|
40
|
-
throw new Error("MONITOR_SHIFT_ID_MISMATCH");
|
|
41
|
-
const state = raw.state;
|
|
42
|
-
if (!new Set(["BOOTING", "ACTIVE", "DRAINING", "RETIRED", "STOPPED"]).has(String(state)))
|
|
43
|
-
throw new Error("MONITOR_SHIFT_STATE_INVALID");
|
|
44
|
-
const rotationDueAt = string(raw.rotationDueAt, "MONITOR_ROTATION_DUE_AT");
|
|
45
|
-
const deadlineAt = string(raw.deadlineAt, "MONITOR_DEADLINE_AT");
|
|
46
|
-
if (Number.isNaN(Date.parse(rotationDueAt)) ||
|
|
47
|
-
Number.isNaN(Date.parse(deadlineAt)))
|
|
48
|
-
throw new Error("MONITOR_SHIFT_TIME_INVALID");
|
|
49
|
-
return {
|
|
50
|
-
shiftId,
|
|
51
|
-
chatRef: string(raw.chatRef, "MONITOR_CHAT_REF"),
|
|
52
|
-
generation: positiveInteger(raw.generation, "MONITOR_SHIFT_GENERATION"),
|
|
53
|
-
state: state,
|
|
54
|
-
rotationDueAt,
|
|
55
|
-
deadlineAt,
|
|
56
|
-
...(raw.bootProof === undefined ? {} : { bootProof: raw.bootProof }),
|
|
57
|
-
...(optionalString(raw.handoffRef, "MONITOR_HANDOFF_REF") === undefined
|
|
58
|
-
? {}
|
|
59
|
-
: { handoffRef: String(raw.handoffRef) }),
|
|
60
|
-
...(optionalString(raw.nextChatPromptRef, "MONITOR_NEXT_CHAT_PROMPT_REF") === undefined
|
|
61
|
-
? {}
|
|
62
|
-
: { nextChatPromptRef: String(raw.nextChatPromptRef) }),
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
function parseRun(value) {
|
|
66
|
-
const raw = object(value, "MONITOR_RUN");
|
|
67
|
-
if (raw.contract !== "proflow.monitor-run.v1")
|
|
68
|
-
throw new Error("MONITOR_RUN_INVALID");
|
|
69
|
-
const runId = string(raw.runId, "MONITOR_RUN_ID");
|
|
70
|
-
const shiftsRaw = object(raw.shifts, "MONITOR_SHIFTS");
|
|
71
|
-
const shifts = {};
|
|
72
|
-
for (const [shiftId, shift] of Object.entries(shiftsRaw))
|
|
73
|
-
shifts[shiftId] = parseShift(shift, shiftId);
|
|
74
|
-
const activeShiftId = raw.activeShiftId === null
|
|
75
|
-
? null
|
|
76
|
-
: string(raw.activeShiftId, "MONITOR_ACTIVE_SHIFT_ID");
|
|
77
|
-
if (activeShiftId !== null && !shifts[activeShiftId])
|
|
78
|
-
throw new Error("MONITOR_ACTIVE_SHIFT_MISSING");
|
|
79
|
-
return {
|
|
80
|
-
runId,
|
|
81
|
-
version: positiveInteger(raw.version, "MONITOR_RUN_VERSION"),
|
|
82
|
-
activeShiftId,
|
|
83
|
-
shifts,
|
|
84
|
-
};
|
|
85
|
-
}
|
|
86
|
-
function parseRuns(value) {
|
|
87
|
-
if (!Array.isArray(value))
|
|
88
|
-
throw new Error("MONITOR_RUN_LIST_INVALID");
|
|
89
|
-
return value.map(parseRun);
|
|
90
|
-
}
|
|
91
|
-
function parseObservationIdentity(value) {
|
|
92
|
-
const raw = object(value, "MONITOR_OBSERVATION");
|
|
93
|
-
return {
|
|
94
|
-
chatRef: string(raw.chatRef, "MONITOR_CHAT_REF"),
|
|
95
|
-
url: string(raw.url, "MONITOR_CHAT_URL"),
|
|
96
|
-
};
|
|
97
|
-
}
|
|
98
|
-
function parseObservation(value) {
|
|
99
|
-
const raw = object(value, "MONITOR_OBSERVATION");
|
|
100
|
-
const identity = parseObservationIdentity(raw);
|
|
101
|
-
const state = raw.state;
|
|
102
|
-
if (!new Set([
|
|
103
|
-
"IDLE",
|
|
104
|
-
"GENERATING",
|
|
105
|
-
"TOOL_RUNNING",
|
|
106
|
-
"AWAITING_USER",
|
|
107
|
-
"UNKNOWN",
|
|
108
|
-
]).has(String(state)))
|
|
109
|
-
throw new Error("MONITOR_CHAT_STATE_INVALID");
|
|
110
|
-
const settledRef = optionalString(raw.settledRef, "MONITOR_SETTLED_REF");
|
|
111
|
-
if (settledRef !== undefined && state !== "IDLE")
|
|
112
|
-
throw new Error("MONITOR_SETTLED_REF_INVALID");
|
|
113
|
-
return {
|
|
114
|
-
...identity,
|
|
115
|
-
state: state,
|
|
116
|
-
...(settledRef === undefined ? {} : { settledRef }),
|
|
117
|
-
};
|
|
118
|
-
}
|
|
119
|
-
function parseArtifact(value) {
|
|
120
|
-
const raw = object(value, "MONITOR_ARTIFACT");
|
|
121
|
-
return {
|
|
122
|
-
artifactRef: string(raw.artifactRef, "MONITOR_ARTIFACT_REF"),
|
|
123
|
-
content: string(raw.content, "MONITOR_ARTIFACT_CONTENT"),
|
|
124
|
-
};
|
|
125
|
-
}
|
|
126
|
-
function stableRef(kind, parts) {
|
|
127
|
-
return `monitor-${kind}:${createHash("sha256")
|
|
128
|
-
.update(JSON.stringify(parts))
|
|
129
|
-
.digest("hex")}`;
|
|
130
|
-
}
|
|
131
|
-
export function monitorHandoffMessageRef(input) {
|
|
132
|
-
return stableRef("handoff-request", [
|
|
133
|
-
input.runId,
|
|
134
|
-
input.shiftId,
|
|
135
|
-
input.generation,
|
|
136
|
-
]);
|
|
137
|
-
}
|
|
138
|
-
export function monitorBootstrapMessageRef(input) {
|
|
139
|
-
return stableRef("bootstrap", [
|
|
140
|
-
input.runId,
|
|
141
|
-
input.shiftId,
|
|
142
|
-
input.generation,
|
|
143
|
-
]);
|
|
144
|
-
}
|
|
145
|
-
export function monitorBootstrapText(input) {
|
|
146
|
-
return [
|
|
147
|
-
`你是现有 ProFlow Monitor shift:${input.shiftId}。现在只完成正式 bootstrap recovery,不创建新 Chat 或新 shift,也不要继续旧断点。`,
|
|
148
|
-
"使用 Local Dev 按当前 chat-local-engineering-protocol 与 proflow-chat-loop 恢复 authority:读取公共上下文 README、长期规则 01/02/05、CURRENT、CURRENT.REQUIRED_CONTEXT、Phase 4 的 03/04 与 latest handoff,并重新读取当前 owner/runtime facts。",
|
|
149
|
-
`完成真实读取后,使用 Workspace 的 automation/proflow-browser-extension/monitor-boot-proof.mjs 为 run ${input.runId} / shift ${input.shiftId} 发布 boot proof(必须由你这个真实 Monitor Chat 自己执行 --authorities-read)。`,
|
|
150
|
-
"boot proof 成功后停止本轮并等待系统激活;不要替维护 Chat、Product、Dev 或 Test 执行业务工作。",
|
|
151
|
-
].join("\n");
|
|
152
|
-
}
|
|
153
|
-
export function monitorTurnWakeMessageRef(input) {
|
|
154
|
-
return stableRef("turn-wake", [
|
|
155
|
-
input.runId,
|
|
156
|
-
input.shiftId,
|
|
157
|
-
input.generation,
|
|
158
|
-
input.settledRef,
|
|
159
|
-
]);
|
|
160
|
-
}
|
|
161
|
-
export function monitorTurnWakeText() {
|
|
162
|
-
return "请依据当前上下文、当前真实状态与既有规则,自主判断并执行当前应采取的动作。";
|
|
163
|
-
}
|
|
164
|
-
export function monitorNextShiftId(input) {
|
|
165
|
-
return stableRef("shift", [
|
|
166
|
-
input.runId,
|
|
167
|
-
input.shiftId,
|
|
168
|
-
input.generation + 1,
|
|
169
|
-
]);
|
|
170
|
-
}
|
|
171
|
-
export function monitorNextChatMessageRef(input) {
|
|
172
|
-
return stableRef("next-chat", [
|
|
173
|
-
input.runId,
|
|
174
|
-
input.shiftId,
|
|
175
|
-
input.generation,
|
|
176
|
-
input.nextShiftId,
|
|
177
|
-
]);
|
|
178
|
-
}
|
|
179
|
-
export function monitorHandoffRequestText(_input) {
|
|
180
|
-
return "请依据当前上下文、当前真实状态与既有规则,自主完成当前班次交接。";
|
|
181
|
-
}
|
|
182
|
-
function monitorCapability(request) {
|
|
183
|
-
return (request.capability === "monitor.chat.submit" ||
|
|
184
|
-
request.capability === "monitor.chat.create");
|
|
185
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Transitional symbol retained only while Platform Host still imports it.
|
|
3
|
+
* The legacy polling/rotation coordinator is removed: Browser timing asks the
|
|
4
|
+
* Node owner for a concrete drive, and Node is the only authorization owner.
|
|
5
|
+
*/
|
|
186
6
|
export async function authorizeMonitorExecution(input) {
|
|
187
7
|
try {
|
|
188
|
-
|
|
189
|
-
|
|
8
|
+
if (input.request.capability !== "monitor.chat.submit" &&
|
|
9
|
+
input.request.capability !== "monitor.chat.create")
|
|
190
10
|
return false;
|
|
191
|
-
if (request.callerRef !== "platform-host:monitor-
|
|
11
|
+
if (input.request.callerRef !== "platform-host:monitor-drive")
|
|
192
12
|
return false;
|
|
193
|
-
|
|
194
|
-
request.
|
|
195
|
-
request.runNo !== undefined ||
|
|
196
|
-
request.roleRef !== undefined ||
|
|
197
|
-
request.workerRef !== undefined ||
|
|
198
|
-
request.projectRoot !== undefined)
|
|
199
|
-
return false;
|
|
200
|
-
if (request.input.contentFingerprint !== request.input.messageRef)
|
|
201
|
-
return false;
|
|
202
|
-
const config = parseConfig(await input.monitor.invoke("config.read", {}));
|
|
203
|
-
if (!config.enabled ||
|
|
204
|
-
config.projectRef === null ||
|
|
205
|
-
config.projectLocator === null ||
|
|
206
|
-
config.revision !== request.input.configRevision ||
|
|
207
|
-
config.projectRef !== request.input.projectRef)
|
|
208
|
-
return false;
|
|
209
|
-
const run = parseRun(await input.monitor.invoke("run.read", {
|
|
210
|
-
runId: request.input.runId,
|
|
211
|
-
}));
|
|
212
|
-
if (run.version !== request.input.expectedRunVersion)
|
|
213
|
-
return false;
|
|
214
|
-
const shift = run.shifts[request.input.shiftId];
|
|
215
|
-
if (!shift || shift.generation !== request.input.generation)
|
|
216
|
-
return false;
|
|
217
|
-
const nowMs = (input.now ?? (() => new Date()))().getTime();
|
|
218
|
-
if (request.capability === "monitor.chat.create") {
|
|
219
|
-
if (shift.state !== "DRAINING" ||
|
|
220
|
-
!shift.handoffRef ||
|
|
221
|
-
!shift.nextChatPromptRef ||
|
|
222
|
-
request.input.projectLocator !== config.projectLocator)
|
|
223
|
-
return false;
|
|
224
|
-
const nextShiftId = monitorNextShiftId({
|
|
225
|
-
runId: run.runId,
|
|
226
|
-
shiftId: shift.shiftId,
|
|
227
|
-
generation: shift.generation,
|
|
228
|
-
});
|
|
229
|
-
if (request.input.nextShiftId !== nextShiftId ||
|
|
230
|
-
run.shifts[nextShiftId])
|
|
231
|
-
return false;
|
|
232
|
-
const expectedRef = monitorNextChatMessageRef({
|
|
233
|
-
runId: run.runId,
|
|
234
|
-
shiftId: shift.shiftId,
|
|
235
|
-
generation: shift.generation,
|
|
236
|
-
nextShiftId,
|
|
237
|
-
});
|
|
238
|
-
if (request.input.messageRef !== expectedRef)
|
|
239
|
-
return false;
|
|
240
|
-
const prompt = parseArtifact(await input.monitor.invoke("artifact.read", {
|
|
241
|
-
artifactRef: shift.nextChatPromptRef,
|
|
242
|
-
}));
|
|
243
|
-
return (prompt.artifactRef === shift.nextChatPromptRef &&
|
|
244
|
-
request.input.text === prompt.content);
|
|
245
|
-
}
|
|
246
|
-
if (shift.chatRef !== request.input.chatRef)
|
|
247
|
-
return false;
|
|
248
|
-
const rawObservation = await input.monitor.invoke("observation.read", {
|
|
249
|
-
chatRef: shift.chatRef,
|
|
13
|
+
const value = await input.monitor.invoke("drive.authorize", {
|
|
14
|
+
request: input.request,
|
|
250
15
|
});
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
if (shift.state === "BOOTING") {
|
|
256
|
-
const observation = parseObservation(rawObservation);
|
|
257
|
-
if (run.activeShiftId !== null ||
|
|
258
|
-
observation.state !== "IDLE")
|
|
259
|
-
return false;
|
|
260
|
-
const expectedRef = monitorBootstrapMessageRef({
|
|
261
|
-
runId: run.runId,
|
|
262
|
-
shiftId: shift.shiftId,
|
|
263
|
-
generation: shift.generation,
|
|
264
|
-
});
|
|
265
|
-
return (request.input.messageRef === expectedRef &&
|
|
266
|
-
request.input.text ===
|
|
267
|
-
monitorBootstrapText({
|
|
268
|
-
runId: run.runId,
|
|
269
|
-
shiftId: shift.shiftId,
|
|
270
|
-
}));
|
|
271
|
-
}
|
|
272
|
-
if (shift.state === "ACTIVE") {
|
|
273
|
-
const observation = parseObservation(rawObservation);
|
|
274
|
-
if (run.activeShiftId !== shift.shiftId ||
|
|
275
|
-
nowMs >= Date.parse(shift.rotationDueAt) ||
|
|
276
|
-
observation.state !== "IDLE" ||
|
|
277
|
-
!observation.settledRef)
|
|
278
|
-
return false;
|
|
279
|
-
const expectedRef = monitorTurnWakeMessageRef({
|
|
280
|
-
runId: run.runId,
|
|
281
|
-
shiftId: shift.shiftId,
|
|
282
|
-
generation: shift.generation,
|
|
283
|
-
settledRef: observation.settledRef,
|
|
284
|
-
});
|
|
285
|
-
if (request.input.messageRef !== expectedRef ||
|
|
286
|
-
request.input.text !== monitorTurnWakeText())
|
|
287
|
-
return false;
|
|
288
|
-
return true;
|
|
289
|
-
}
|
|
290
|
-
if (shift.state === "DRAINING") {
|
|
291
|
-
if (nowMs > Date.parse(shift.deadlineAt))
|
|
292
|
-
return false;
|
|
293
|
-
const expectedRef = monitorHandoffMessageRef({
|
|
294
|
-
runId: run.runId,
|
|
295
|
-
shiftId: shift.shiftId,
|
|
296
|
-
generation: shift.generation,
|
|
297
|
-
});
|
|
298
|
-
return (request.input.messageRef === expectedRef &&
|
|
299
|
-
request.input.text ===
|
|
300
|
-
monitorHandoffRequestText({
|
|
301
|
-
runId: run.runId,
|
|
302
|
-
shiftId: shift.shiftId,
|
|
303
|
-
generation: shift.generation,
|
|
304
|
-
projectRef: config.projectRef,
|
|
305
|
-
}));
|
|
306
|
-
}
|
|
307
|
-
return false;
|
|
16
|
+
return (typeof value === "object" &&
|
|
17
|
+
value !== null &&
|
|
18
|
+
!Array.isArray(value) &&
|
|
19
|
+
Reflect.get(value, "authorized") === true);
|
|
308
20
|
}
|
|
309
21
|
catch {
|
|
310
22
|
return false;
|
|
311
23
|
}
|
|
312
24
|
}
|
|
313
|
-
function appliedExecutionResult(value, capability) {
|
|
314
|
-
const record = object(value, "MONITOR_EXECUTION_RECORD");
|
|
315
|
-
if (record.status !== "SUCCEEDED" ||
|
|
316
|
-
record.sideEffectState !== "APPLIED")
|
|
317
|
-
return null;
|
|
318
|
-
const result = object(record.result, "MONITOR_EXECUTION_RESULT");
|
|
319
|
-
if (result.capability !== capability)
|
|
320
|
-
return null;
|
|
321
|
-
return object(result.data, "MONITOR_EXECUTION_RESULT_DATA");
|
|
322
|
-
}
|
|
323
|
-
function drainingShift(run) {
|
|
324
|
-
return Object.values(run.shifts)
|
|
325
|
-
.filter((shift) => shift.state === "DRAINING")
|
|
326
|
-
.sort((left, right) => right.generation - left.generation)[0];
|
|
327
|
-
}
|
|
328
|
-
function initialBootingShift(run) {
|
|
329
|
-
const booting = Object.values(run.shifts).filter((shift) => shift.state === "BOOTING" && shift.generation === 1);
|
|
330
|
-
return booting.length === 1 ? booting[0] : undefined;
|
|
331
|
-
}
|
|
332
|
-
export function createMonitorRotationCoordinator(options) {
|
|
333
|
-
const intervalMs = options.intervalMs ?? 15_000;
|
|
334
|
-
if (!Number.isInteger(intervalMs) || intervalMs < 1_000)
|
|
335
|
-
throw new TypeError("monitor rotation interval must be an integer >= 1000");
|
|
336
|
-
const now = options.now ?? (() => new Date());
|
|
337
|
-
const drivenSettledRefs = new Map();
|
|
338
|
-
let timer;
|
|
339
|
-
let inFlight = null;
|
|
340
|
-
let stopping = false;
|
|
341
|
-
const assertRunning = () => {
|
|
342
|
-
if (stopping)
|
|
343
|
-
throw new Error("MONITOR_ROTATION_STOPPED");
|
|
344
|
-
};
|
|
345
|
-
const notifyDrive = async (config, run, shift, input) => {
|
|
346
|
-
if (!config.notificationsEnabled)
|
|
347
|
-
return { status: "DISABLED" };
|
|
348
|
-
await options.monitor.invoke("notification.enqueue", {
|
|
349
|
-
event: {
|
|
350
|
-
contract: "proflow.monitor-notification.v1",
|
|
351
|
-
eventId: stableRef("notification", [
|
|
352
|
-
input.event,
|
|
353
|
-
input.messageRef,
|
|
354
|
-
]),
|
|
355
|
-
lane: "PROFLOW_SELF_ITERATION",
|
|
356
|
-
source: "MONITOR",
|
|
357
|
-
event: input.event,
|
|
358
|
-
severity: "INFO",
|
|
359
|
-
time: now().toISOString(),
|
|
360
|
-
runRef: {
|
|
361
|
-
runId: run.runId,
|
|
362
|
-
shiftId: shift.shiftId,
|
|
363
|
-
chatRef: shift.chatRef,
|
|
364
|
-
},
|
|
365
|
-
summary: input.summary,
|
|
366
|
-
evidenceRefs: [input.messageRef],
|
|
367
|
-
},
|
|
368
|
-
fence: {
|
|
369
|
-
runId: run.runId,
|
|
370
|
-
shiftId: shift.shiftId,
|
|
371
|
-
generation: shift.generation,
|
|
372
|
-
expectedVersion: run.version,
|
|
373
|
-
configRevision: config.revision,
|
|
374
|
-
},
|
|
375
|
-
});
|
|
376
|
-
if (!input.deliverNow)
|
|
377
|
-
return { status: "QUEUED" };
|
|
378
|
-
try {
|
|
379
|
-
return await options.monitor.invoke("notification.deliver", {
|
|
380
|
-
limit: 10,
|
|
381
|
-
});
|
|
382
|
-
}
|
|
383
|
-
catch (error) {
|
|
384
|
-
return {
|
|
385
|
-
status: "FAILED",
|
|
386
|
-
error: error instanceof Error
|
|
387
|
-
? error.message
|
|
388
|
-
: "MONITOR_NOTIFICATION_FAILED",
|
|
389
|
-
};
|
|
390
|
-
}
|
|
391
|
-
};
|
|
392
|
-
const requestBootstrap = async (config, run, shift) => {
|
|
393
|
-
if (config.projectRef === null)
|
|
394
|
-
return { state: "PROJECT_BINDING_MISSING" };
|
|
395
|
-
const observation = parseObservation(await options.monitor.invoke("observation.read", {
|
|
396
|
-
chatRef: shift.chatRef,
|
|
397
|
-
}));
|
|
398
|
-
if (observation.state !== "IDLE")
|
|
399
|
-
return {
|
|
400
|
-
state: `BOOTSTRAP_${observation.state}`,
|
|
401
|
-
};
|
|
402
|
-
const messageRef = monitorBootstrapMessageRef({
|
|
403
|
-
runId: run.runId,
|
|
404
|
-
shiftId: shift.shiftId,
|
|
405
|
-
generation: shift.generation,
|
|
406
|
-
});
|
|
407
|
-
const request = {
|
|
408
|
-
contract: "execution",
|
|
409
|
-
contractVersion: "1.0.0",
|
|
410
|
-
callerRef: "platform-host:monitor-rotation",
|
|
411
|
-
idempotencyKey: messageRef,
|
|
412
|
-
capability: "monitor.chat.submit",
|
|
413
|
-
input: {
|
|
414
|
-
runId: run.runId,
|
|
415
|
-
shiftId: shift.shiftId,
|
|
416
|
-
generation: shift.generation,
|
|
417
|
-
expectedRunVersion: run.version,
|
|
418
|
-
configRevision: config.revision,
|
|
419
|
-
projectRef: config.projectRef,
|
|
420
|
-
chatRef: shift.chatRef,
|
|
421
|
-
messageRef,
|
|
422
|
-
conversationLocator: observation.url,
|
|
423
|
-
contentFingerprint: messageRef,
|
|
424
|
-
text: monitorBootstrapText({
|
|
425
|
-
runId: run.runId,
|
|
426
|
-
shiftId: shift.shiftId,
|
|
427
|
-
}),
|
|
428
|
-
},
|
|
429
|
-
};
|
|
430
|
-
assertRunning();
|
|
431
|
-
const result = await options.execution.invoke("executeCapability", request);
|
|
432
|
-
return appliedExecutionResult(result, "monitor.chat.submit")
|
|
433
|
-
? { state: "BOOTSTRAP_DISPATCHED", messageRef }
|
|
434
|
-
: { state: "BOOTSTRAP_EFFECT_PENDING", messageRef };
|
|
435
|
-
};
|
|
436
|
-
const requestTurnWake = async (config, run, shift) => {
|
|
437
|
-
if (config.projectRef === null)
|
|
438
|
-
return { state: "PROJECT_BINDING_MISSING" };
|
|
439
|
-
const observation = parseObservation(await options.monitor.invoke("observation.read", {
|
|
440
|
-
chatRef: shift.chatRef,
|
|
441
|
-
}));
|
|
442
|
-
if (observation.state !== "IDLE")
|
|
443
|
-
return {
|
|
444
|
-
state: `ACTIVE_${observation.state}`,
|
|
445
|
-
};
|
|
446
|
-
if (!observation.settledRef)
|
|
447
|
-
return {
|
|
448
|
-
state: "ACTIVE_IDLE_WITHOUT_SETTLED_REF",
|
|
449
|
-
};
|
|
450
|
-
const drivenKey = `${run.runId}:${shift.shiftId}:${shift.generation}`;
|
|
451
|
-
if (drivenSettledRefs.get(drivenKey) === observation.settledRef)
|
|
452
|
-
return { state: "ACTIVE_TURN_ALREADY_DRIVEN" };
|
|
453
|
-
const messageRef = monitorTurnWakeMessageRef({
|
|
454
|
-
runId: run.runId,
|
|
455
|
-
shiftId: shift.shiftId,
|
|
456
|
-
generation: shift.generation,
|
|
457
|
-
settledRef: observation.settledRef,
|
|
458
|
-
});
|
|
459
|
-
const request = {
|
|
460
|
-
contract: "execution",
|
|
461
|
-
contractVersion: "1.0.0",
|
|
462
|
-
callerRef: "platform-host:monitor-rotation",
|
|
463
|
-
idempotencyKey: messageRef,
|
|
464
|
-
capability: "monitor.chat.submit",
|
|
465
|
-
input: {
|
|
466
|
-
runId: run.runId,
|
|
467
|
-
shiftId: shift.shiftId,
|
|
468
|
-
generation: shift.generation,
|
|
469
|
-
expectedRunVersion: run.version,
|
|
470
|
-
configRevision: config.revision,
|
|
471
|
-
projectRef: config.projectRef,
|
|
472
|
-
chatRef: shift.chatRef,
|
|
473
|
-
messageRef,
|
|
474
|
-
conversationLocator: observation.url,
|
|
475
|
-
contentFingerprint: messageRef,
|
|
476
|
-
text: monitorTurnWakeText(),
|
|
477
|
-
},
|
|
478
|
-
};
|
|
479
|
-
assertRunning();
|
|
480
|
-
const result = await options.execution.invoke("executeCapability", request);
|
|
481
|
-
if (!appliedExecutionResult(result, "monitor.chat.submit"))
|
|
482
|
-
return { state: "TURN_WAKE_EFFECT_PENDING" };
|
|
483
|
-
await notifyDrive(config, run, shift, {
|
|
484
|
-
event: "CHAT_WAKE",
|
|
485
|
-
messageRef,
|
|
486
|
-
summary: "Monitor turn drive dispatched.",
|
|
487
|
-
});
|
|
488
|
-
drivenSettledRefs.set(drivenKey, observation.settledRef);
|
|
489
|
-
return {
|
|
490
|
-
state: "TURN_WAKE_DISPATCHED",
|
|
491
|
-
messageRef,
|
|
492
|
-
};
|
|
493
|
-
};
|
|
494
|
-
const requestHandoff = async (config, run, shift) => {
|
|
495
|
-
if (config.projectRef === null)
|
|
496
|
-
return { state: "PROJECT_BINDING_MISSING" };
|
|
497
|
-
const observation = parseObservationIdentity(await options.monitor.invoke("observation.read", {
|
|
498
|
-
chatRef: shift.chatRef,
|
|
499
|
-
}));
|
|
500
|
-
const messageRef = monitorHandoffMessageRef({
|
|
501
|
-
runId: run.runId,
|
|
502
|
-
shiftId: shift.shiftId,
|
|
503
|
-
generation: shift.generation,
|
|
504
|
-
});
|
|
505
|
-
const request = {
|
|
506
|
-
contract: "execution",
|
|
507
|
-
contractVersion: "1.0.0",
|
|
508
|
-
callerRef: "platform-host:monitor-rotation",
|
|
509
|
-
idempotencyKey: messageRef,
|
|
510
|
-
capability: "monitor.chat.submit",
|
|
511
|
-
input: {
|
|
512
|
-
runId: run.runId,
|
|
513
|
-
shiftId: shift.shiftId,
|
|
514
|
-
generation: shift.generation,
|
|
515
|
-
expectedRunVersion: run.version,
|
|
516
|
-
configRevision: config.revision,
|
|
517
|
-
projectRef: config.projectRef,
|
|
518
|
-
chatRef: shift.chatRef,
|
|
519
|
-
messageRef,
|
|
520
|
-
conversationLocator: observation.url,
|
|
521
|
-
contentFingerprint: messageRef,
|
|
522
|
-
text: monitorHandoffRequestText({
|
|
523
|
-
runId: run.runId,
|
|
524
|
-
shiftId: shift.shiftId,
|
|
525
|
-
generation: shift.generation,
|
|
526
|
-
projectRef: config.projectRef,
|
|
527
|
-
}),
|
|
528
|
-
},
|
|
529
|
-
};
|
|
530
|
-
assertRunning();
|
|
531
|
-
const result = await options.execution.invoke("executeCapability", request);
|
|
532
|
-
return appliedExecutionResult(result, "monitor.chat.submit")
|
|
533
|
-
? { state: "HANDOFF_REQUESTED" }
|
|
534
|
-
: { state: "HANDOFF_EFFECT_PENDING" };
|
|
535
|
-
};
|
|
536
|
-
const createNextChat = async (config, run, shift) => {
|
|
537
|
-
if (config.projectRef === null ||
|
|
538
|
-
config.projectLocator === null)
|
|
539
|
-
return { state: "PROJECT_BINDING_MISSING" };
|
|
540
|
-
if (!shift.nextChatPromptRef)
|
|
541
|
-
return { state: "NEXT_CHAT_PROMPT_MISSING" };
|
|
542
|
-
const prompt = parseArtifact(await options.monitor.invoke("artifact.read", {
|
|
543
|
-
artifactRef: shift.nextChatPromptRef,
|
|
544
|
-
}));
|
|
545
|
-
const nextShiftId = monitorNextShiftId({
|
|
546
|
-
runId: run.runId,
|
|
547
|
-
shiftId: shift.shiftId,
|
|
548
|
-
generation: shift.generation,
|
|
549
|
-
});
|
|
550
|
-
const messageRef = monitorNextChatMessageRef({
|
|
551
|
-
runId: run.runId,
|
|
552
|
-
shiftId: shift.shiftId,
|
|
553
|
-
generation: shift.generation,
|
|
554
|
-
nextShiftId,
|
|
555
|
-
});
|
|
556
|
-
const request = {
|
|
557
|
-
contract: "execution",
|
|
558
|
-
contractVersion: "1.0.0",
|
|
559
|
-
callerRef: "platform-host:monitor-rotation",
|
|
560
|
-
idempotencyKey: messageRef,
|
|
561
|
-
capability: "monitor.chat.create",
|
|
562
|
-
input: {
|
|
563
|
-
runId: run.runId,
|
|
564
|
-
shiftId: shift.shiftId,
|
|
565
|
-
generation: shift.generation,
|
|
566
|
-
expectedRunVersion: run.version,
|
|
567
|
-
configRevision: config.revision,
|
|
568
|
-
projectRef: config.projectRef,
|
|
569
|
-
nextShiftId,
|
|
570
|
-
messageRef,
|
|
571
|
-
projectLocator: config.projectLocator,
|
|
572
|
-
contentFingerprint: messageRef,
|
|
573
|
-
text: prompt.content,
|
|
574
|
-
},
|
|
575
|
-
};
|
|
576
|
-
assertRunning();
|
|
577
|
-
const execution = await options.execution.invoke("executeCapability", request);
|
|
578
|
-
const data = appliedExecutionResult(execution, "monitor.chat.create");
|
|
579
|
-
if (!data)
|
|
580
|
-
return { state: "NEXT_CHAT_EFFECT_PENDING" };
|
|
581
|
-
const chatRef = string(data.chatRef, "MONITOR_NEXT_CHAT_REF");
|
|
582
|
-
string(data.conversationLocator, "MONITOR_NEXT_CHAT_LOCATOR");
|
|
583
|
-
assertRunning();
|
|
584
|
-
const nextRun = parseRun(await options.monitor.invoke("shift.prepareNext", {
|
|
585
|
-
runId: run.runId,
|
|
586
|
-
fromShiftId: shift.shiftId,
|
|
587
|
-
fromGeneration: shift.generation,
|
|
588
|
-
expectedVersion: run.version,
|
|
589
|
-
configRevision: config.revision,
|
|
590
|
-
shiftId: nextShiftId,
|
|
591
|
-
chatRef,
|
|
592
|
-
}));
|
|
593
|
-
return {
|
|
594
|
-
state: "NEXT_SHIFT_PREPARED",
|
|
595
|
-
run: nextRun,
|
|
596
|
-
};
|
|
597
|
-
};
|
|
598
|
-
const completeBoot = async (config, run, shift, previous) => {
|
|
599
|
-
if (!shift.bootProof)
|
|
600
|
-
return requestBootstrap(config, run, shift);
|
|
601
|
-
assertRunning();
|
|
602
|
-
const activated = parseRun(previous
|
|
603
|
-
? await options.monitor.invoke("shift.takeover", {
|
|
604
|
-
runId: run.runId,
|
|
605
|
-
fromShiftId: previous.shiftId,
|
|
606
|
-
fromGeneration: previous.generation,
|
|
607
|
-
toShiftId: shift.shiftId,
|
|
608
|
-
toGeneration: shift.generation,
|
|
609
|
-
expectedVersion: run.version,
|
|
610
|
-
configRevision: config.revision,
|
|
611
|
-
})
|
|
612
|
-
: await options.monitor.invoke("shift.activate", {
|
|
613
|
-
runId: run.runId,
|
|
614
|
-
shiftId: shift.shiftId,
|
|
615
|
-
generation: shift.generation,
|
|
616
|
-
expectedVersion: run.version,
|
|
617
|
-
configRevision: config.revision,
|
|
618
|
-
}));
|
|
619
|
-
return {
|
|
620
|
-
state: "TAKEOVER_COMPLETED",
|
|
621
|
-
run: activated,
|
|
622
|
-
};
|
|
623
|
-
};
|
|
624
|
-
const tickRun = async (config, run) => {
|
|
625
|
-
if (run.activeShiftId) {
|
|
626
|
-
const active = run.shifts[run.activeShiftId];
|
|
627
|
-
if (!active || active.state !== "ACTIVE")
|
|
628
|
-
throw new Error("MONITOR_ACTIVE_SHIFT_INVALID");
|
|
629
|
-
const status = object(await options.monitor.invoke("shift.rotationStatus", {
|
|
630
|
-
runId: run.runId,
|
|
631
|
-
shiftId: active.shiftId,
|
|
632
|
-
}), "MONITOR_ROTATION_STATUS");
|
|
633
|
-
if (status.due !== true)
|
|
634
|
-
return requestTurnWake(config, run, active);
|
|
635
|
-
const handoffMessageRef = monitorHandoffMessageRef({
|
|
636
|
-
runId: run.runId,
|
|
637
|
-
shiftId: active.shiftId,
|
|
638
|
-
generation: active.generation,
|
|
639
|
-
});
|
|
640
|
-
await notifyDrive(config, run, active, {
|
|
641
|
-
event: "HANDOFF_STARTED",
|
|
642
|
-
messageRef: handoffMessageRef,
|
|
643
|
-
summary: "Monitor handoff drive dispatched.",
|
|
644
|
-
deliverNow: true,
|
|
645
|
-
});
|
|
646
|
-
assertRunning();
|
|
647
|
-
const drained = parseRun(await options.monitor.invoke("shift.beginDrain", {
|
|
648
|
-
runId: run.runId,
|
|
649
|
-
shiftId: active.shiftId,
|
|
650
|
-
generation: active.generation,
|
|
651
|
-
expectedVersion: run.version,
|
|
652
|
-
configRevision: config.revision,
|
|
653
|
-
reason: "ROTATION_DUE",
|
|
654
|
-
}));
|
|
655
|
-
const shift = drained.shifts[active.shiftId];
|
|
656
|
-
if (!shift)
|
|
657
|
-
throw new Error("MONITOR_DRAINING_SHIFT_MISSING");
|
|
658
|
-
return requestHandoff(config, drained, shift);
|
|
659
|
-
}
|
|
660
|
-
const previous = drainingShift(run);
|
|
661
|
-
if (previous) {
|
|
662
|
-
const nextShiftId = monitorNextShiftId({
|
|
663
|
-
runId: run.runId,
|
|
664
|
-
shiftId: previous.shiftId,
|
|
665
|
-
generation: previous.generation,
|
|
666
|
-
});
|
|
667
|
-
const next = run.shifts[nextShiftId];
|
|
668
|
-
if (!previous.handoffRef ||
|
|
669
|
-
!previous.nextChatPromptRef) {
|
|
670
|
-
const status = object(await options.monitor.invoke("shift.rotationStatus", {
|
|
671
|
-
runId: run.runId,
|
|
672
|
-
shiftId: previous.shiftId,
|
|
673
|
-
}), "MONITOR_ROTATION_STATUS");
|
|
674
|
-
if (status.expired === true)
|
|
675
|
-
return {
|
|
676
|
-
state: "HANDOFF_DEADLINE_EXPIRED",
|
|
677
|
-
};
|
|
678
|
-
return requestHandoff(config, run, previous);
|
|
679
|
-
}
|
|
680
|
-
if (!next)
|
|
681
|
-
return createNextChat(config, run, previous);
|
|
682
|
-
if (next.state !== "BOOTING")
|
|
683
|
-
return {
|
|
684
|
-
state: `NEXT_SHIFT_${next.state}`,
|
|
685
|
-
};
|
|
686
|
-
return completeBoot(config, run, next, previous);
|
|
687
|
-
}
|
|
688
|
-
const initial = initialBootingShift(run);
|
|
689
|
-
if (initial)
|
|
690
|
-
return completeBoot(config, run, initial);
|
|
691
|
-
return { state: "NO_ACTIVE_ROTATION" };
|
|
692
|
-
};
|
|
693
|
-
const tick = async () => {
|
|
694
|
-
assertRunning();
|
|
695
|
-
const config = parseConfig(await options.monitor.invoke("config.read", {}));
|
|
696
|
-
if (!config.enabled)
|
|
697
|
-
return { state: "DISABLED", runs: [] };
|
|
698
|
-
if (config.projectRef === null ||
|
|
699
|
-
config.projectLocator === null)
|
|
700
|
-
return {
|
|
701
|
-
state: "PROJECT_BINDING_REQUIRED",
|
|
702
|
-
runs: [],
|
|
703
|
-
};
|
|
704
|
-
const outcomes = [];
|
|
705
|
-
for (const run of parseRuns(await options.monitor.invoke("run.list", {}))) {
|
|
706
|
-
assertRunning();
|
|
707
|
-
try {
|
|
708
|
-
outcomes.push({
|
|
709
|
-
runId: run.runId,
|
|
710
|
-
outcome: await tickRun(config, run),
|
|
711
|
-
});
|
|
712
|
-
}
|
|
713
|
-
catch (error) {
|
|
714
|
-
outcomes.push({
|
|
715
|
-
runId: run.runId,
|
|
716
|
-
outcome: {
|
|
717
|
-
state: "BLOCKED",
|
|
718
|
-
error: error instanceof Error
|
|
719
|
-
? error.message
|
|
720
|
-
: "MONITOR_ROTATION_FAILED",
|
|
721
|
-
},
|
|
722
|
-
});
|
|
723
|
-
}
|
|
724
|
-
}
|
|
725
|
-
let notification = { status: "DISABLED" };
|
|
726
|
-
if (config.notificationsEnabled) {
|
|
727
|
-
try {
|
|
728
|
-
assertRunning();
|
|
729
|
-
notification = await options.monitor.invoke("notification.deliver", { limit: 10 });
|
|
730
|
-
}
|
|
731
|
-
catch (error) {
|
|
732
|
-
notification = {
|
|
733
|
-
status: "FAILED",
|
|
734
|
-
error: error instanceof Error
|
|
735
|
-
? error.message
|
|
736
|
-
: "MONITOR_NOTIFICATION_FAILED",
|
|
737
|
-
};
|
|
738
|
-
}
|
|
739
|
-
}
|
|
740
|
-
return {
|
|
741
|
-
state: "CHECKED",
|
|
742
|
-
runs: outcomes,
|
|
743
|
-
notification,
|
|
744
|
-
};
|
|
745
|
-
};
|
|
746
|
-
const runTick = () => {
|
|
747
|
-
if (stopping || inFlight)
|
|
748
|
-
return inFlight;
|
|
749
|
-
inFlight = tick().finally(() => {
|
|
750
|
-
inFlight = null;
|
|
751
|
-
});
|
|
752
|
-
return inFlight;
|
|
753
|
-
};
|
|
754
|
-
const scheduleTick = () => {
|
|
755
|
-
void runTick()?.catch(async () => {
|
|
756
|
-
if (stopping)
|
|
757
|
-
return;
|
|
758
|
-
try {
|
|
759
|
-
await options.onError?.("MONITOR_ROTATION_POLL_FAILED");
|
|
760
|
-
}
|
|
761
|
-
catch { }
|
|
762
|
-
});
|
|
763
|
-
};
|
|
764
|
-
return Object.freeze({
|
|
765
|
-
tick,
|
|
766
|
-
start() {
|
|
767
|
-
if (timer)
|
|
768
|
-
return;
|
|
769
|
-
stopping = false;
|
|
770
|
-
scheduleTick();
|
|
771
|
-
timer = setInterval(scheduleTick, intervalMs);
|
|
772
|
-
timer.unref?.();
|
|
773
|
-
},
|
|
774
|
-
async stop() {
|
|
775
|
-
stopping = true;
|
|
776
|
-
if (timer)
|
|
777
|
-
clearInterval(timer);
|
|
778
|
-
timer = undefined;
|
|
779
|
-
await inFlight?.catch(() => undefined);
|
|
780
|
-
},
|
|
781
|
-
});
|
|
782
|
-
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tomflow/proflow-platform-host",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.32",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -24,19 +24,19 @@
|
|
|
24
24
|
],
|
|
25
25
|
"dependencies": {
|
|
26
26
|
"zod": "4.1.12",
|
|
27
|
-
"@tomflow/proflow-agent-runtime": "^0.1.
|
|
27
|
+
"@tomflow/proflow-agent-runtime": "^0.1.18",
|
|
28
28
|
"@tomflow/proflow-module-contract": "^0.1.13",
|
|
29
|
-
"@tomflow/proflow-execution-contracts": "^0.1.11",
|
|
30
|
-
"@tomflow/proflow-execution-browser-extension": "^0.1.65",
|
|
31
|
-
"@tomflow/proflow-task-store-sqlite": "^0.1.12",
|
|
32
29
|
"@tomflow/proflow-task-migration-runner": "^0.1.11",
|
|
33
|
-
"@tomflow/proflow-
|
|
30
|
+
"@tomflow/proflow-execution-browser-extension": "^0.1.74",
|
|
31
|
+
"@tomflow/proflow-execution-contracts": "^0.1.12",
|
|
32
|
+
"@tomflow/proflow-task-orchestration": "^0.1.12",
|
|
33
|
+
"@tomflow/proflow-task-store-sqlite": "^0.1.12"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
|
-
"@tomflow/proflow-execution-runtime": "^0.1.19",
|
|
37
|
-
"@tomflow/proflow-deployment-conformance": "^0.1.13",
|
|
38
36
|
"@tomflow/proflow-agent-gateway": "^0.1.19",
|
|
39
|
-
"@tomflow/proflow-
|
|
37
|
+
"@tomflow/proflow-execution-runtime": "^0.1.20",
|
|
38
|
+
"@tomflow/proflow-model-runtime": "^0.1.26",
|
|
39
|
+
"@tomflow/proflow-deployment-conformance": "^0.1.13"
|
|
40
40
|
},
|
|
41
41
|
"description": "Provides the ProFlow local application composition root that binds Task, Agent, Execution and Model owner transports.",
|
|
42
42
|
"keywords": [
|
package/proflow.module.json
CHANGED