@tomflow/proflow-execution-runtime 0.1.18 → 0.1.19
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/DOCS.md +3 -3
- package/dist/deployment/adapter.d.ts +10 -10
- package/dist/deployment/adapter.js +29 -52
- package/dist/deployment/descriptor.d.ts +4 -1
- package/dist/deployment/descriptor.js +5 -1
- package/dist/src/formal-process.d.ts +5 -0
- package/dist/src/formal-process.js +81 -38
- package/dist/src/service.d.ts +4 -0
- package/dist/src/service.js +86 -24
- package/package.json +4 -4
- package/proflow.module.json +5 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# @tomflow/proflow-execution-runtime
|
|
2
2
|
|
|
3
|
+
## 0.1.19
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Close the Real-3 runtime reconciliation, health, browser effect-result boundary, observer pagination, and standalone execution-runtime configuration gaps for final human acceptance.
|
|
8
|
+
|
|
3
9
|
## 0.1.18
|
|
4
10
|
|
|
5
11
|
### Patch Changes
|
package/DOCS.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## 模块定位与作用
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
接收内部 durable Execution Intent,选择受控 Local/Browser Executor,管理执行、恢复和 Evidence;GPT-facing Repomix / Local Dev / CodeGraph Direct Tools 不进入该 lifecycle。
|
|
6
6
|
|
|
7
7
|
## 主要能力
|
|
8
8
|
|
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
|
|
18
18
|
## 依赖的 Module、Contract 和外部资源
|
|
19
19
|
|
|
20
|
-
- 依赖 `execution-local
|
|
21
|
-
-
|
|
20
|
+
- 依赖 `execution-browser-executor` 与 `execution-local`,兼容版本均为 `>=1.0.0 <2.0.0`。
|
|
21
|
+
- `execution-browser-executor` 由 execution-browser-extension 发布;Execution Runtime 只消费该 Browser lane client/readiness,不拥有或关闭 Extension bridge listener。
|
|
22
22
|
|
|
23
23
|
## 运行形态与生命周期
|
|
24
24
|
|
|
@@ -6,7 +6,7 @@ export declare const behaviorAdapter: {
|
|
|
6
6
|
ok: true;
|
|
7
7
|
status: "SUCCEEDED";
|
|
8
8
|
moduleRef: "execution-runtime";
|
|
9
|
-
moduleVersion: "0.1.
|
|
9
|
+
moduleVersion: "0.1.19";
|
|
10
10
|
data: {
|
|
11
11
|
endpoint: string;
|
|
12
12
|
transportCredentialFile: string;
|
|
@@ -23,7 +23,7 @@ export declare const behaviorAdapter: {
|
|
|
23
23
|
readonly ok: true;
|
|
24
24
|
readonly status: "SUCCEEDED";
|
|
25
25
|
readonly moduleRef: "execution-runtime";
|
|
26
|
-
readonly moduleVersion: "0.1.
|
|
26
|
+
readonly moduleVersion: "0.1.19";
|
|
27
27
|
};
|
|
28
28
|
observedEffects: ("Persists execution state, output references and evidence in WAL SQLite" | "Runs the single backend Execution Runtime service")[];
|
|
29
29
|
}>;
|
|
@@ -33,7 +33,7 @@ export declare const behaviorAdapter: {
|
|
|
33
33
|
ok: true;
|
|
34
34
|
status: "SUCCEEDED";
|
|
35
35
|
moduleRef: "execution-runtime";
|
|
36
|
-
moduleVersion: "0.1.
|
|
36
|
+
moduleVersion: "0.1.19";
|
|
37
37
|
data: {
|
|
38
38
|
setupStatus: "READY";
|
|
39
39
|
runtimeStatus: "RUNNING" | "STOPPED";
|
|
@@ -54,7 +54,7 @@ export declare const behaviorAdapter: {
|
|
|
54
54
|
ok: true;
|
|
55
55
|
status: "SUCCEEDED";
|
|
56
56
|
moduleRef: "execution-runtime";
|
|
57
|
-
moduleVersion: "0.1.
|
|
57
|
+
moduleVersion: "0.1.19";
|
|
58
58
|
data: {
|
|
59
59
|
endpoint: string;
|
|
60
60
|
transportCredentialFile: string;
|
|
@@ -71,7 +71,7 @@ export declare const behaviorAdapter: {
|
|
|
71
71
|
ok: true;
|
|
72
72
|
status: "SUCCEEDED";
|
|
73
73
|
moduleRef: "execution-runtime";
|
|
74
|
-
moduleVersion: "0.1.
|
|
74
|
+
moduleVersion: "0.1.19";
|
|
75
75
|
data: {
|
|
76
76
|
docs: string;
|
|
77
77
|
};
|
|
@@ -84,7 +84,7 @@ export declare const behaviorAdapter: {
|
|
|
84
84
|
ok: true;
|
|
85
85
|
status: "SUCCEEDED";
|
|
86
86
|
moduleRef: "execution-runtime";
|
|
87
|
-
moduleVersion: "0.1.
|
|
87
|
+
moduleVersion: "0.1.19";
|
|
88
88
|
data: unknown;
|
|
89
89
|
};
|
|
90
90
|
observedEffects: ("Persists execution state, output references and evidence in WAL SQLite" | "Runs the single backend Execution Runtime service")[];
|
|
@@ -92,7 +92,7 @@ export declare const behaviorAdapter: {
|
|
|
92
92
|
result: {
|
|
93
93
|
contract: "deployment.result.v1";
|
|
94
94
|
moduleRef: "execution-runtime";
|
|
95
|
-
moduleVersion: "0.1.
|
|
95
|
+
moduleVersion: "0.1.19";
|
|
96
96
|
ok: false;
|
|
97
97
|
status: "FAILED";
|
|
98
98
|
error: {
|
|
@@ -109,7 +109,7 @@ export declare const behaviorAdapter: {
|
|
|
109
109
|
readonly ok: true;
|
|
110
110
|
readonly status: "SUCCEEDED";
|
|
111
111
|
readonly moduleRef: "execution-runtime";
|
|
112
|
-
readonly moduleVersion: "0.1.
|
|
112
|
+
readonly moduleVersion: "0.1.19";
|
|
113
113
|
};
|
|
114
114
|
observedEffects: ("Persists execution state, output references and evidence in WAL SQLite" | "Runs the single backend Execution Runtime service")[];
|
|
115
115
|
} | {
|
|
@@ -118,11 +118,11 @@ export declare const behaviorAdapter: {
|
|
|
118
118
|
readonly ok: true;
|
|
119
119
|
readonly status: "SUCCEEDED";
|
|
120
120
|
readonly moduleRef: "execution-runtime";
|
|
121
|
-
readonly moduleVersion: "0.1.
|
|
121
|
+
readonly moduleVersion: "0.1.19";
|
|
122
122
|
} | {
|
|
123
123
|
contract: "deployment.result.v1";
|
|
124
124
|
moduleRef: "execution-runtime";
|
|
125
|
-
moduleVersion: "0.1.
|
|
125
|
+
moduleVersion: "0.1.19";
|
|
126
126
|
ok: false;
|
|
127
127
|
status: "FAILED";
|
|
128
128
|
error: {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
|
-
import { mkdir
|
|
2
|
+
import { mkdir } from "node:fs/promises";
|
|
3
3
|
import { join, resolve } from "node:path";
|
|
4
4
|
import { deterministicLoopbackPort, ensureModuleSecretFile, moduleWorkspaceStateDirectory, readModuleSharedFacts, writeModuleSharedFacts, } from "@tomflow/proflow-module-contract";
|
|
5
5
|
import { descriptor } from "./descriptor.js";
|
|
@@ -48,45 +48,18 @@ async function readOwnFacts(context) {
|
|
|
48
48
|
}
|
|
49
49
|
: undefined;
|
|
50
50
|
}
|
|
51
|
-
async function
|
|
52
|
-
const host = await readModuleSharedFacts(context, "platform-host");
|
|
53
|
-
const model = await readModuleSharedFacts(context, "model-runtime");
|
|
54
|
-
const identityEndpoint = factString(host, "endpoint");
|
|
55
|
-
const identityTokenFile = factString(host, "identityTokenFile");
|
|
56
|
-
const modelEndpoint = factString(model, "endpoint");
|
|
57
|
-
const modelCredentialFile = factString(model, "transportCredentialFile");
|
|
58
|
-
return identityEndpoint &&
|
|
59
|
-
identityTokenFile &&
|
|
60
|
-
modelEndpoint &&
|
|
61
|
-
modelCredentialFile
|
|
62
|
-
? {
|
|
63
|
-
identityEndpoint,
|
|
64
|
-
identityTokenFile,
|
|
65
|
-
modelEndpoint,
|
|
66
|
-
modelCredentialFile,
|
|
67
|
-
}
|
|
68
|
-
: undefined;
|
|
69
|
-
}
|
|
70
|
-
async function dependencies(context) {
|
|
71
|
-
const base = await baseDependencies(context);
|
|
51
|
+
async function startupDependencies(context) {
|
|
72
52
|
const browser = await readModuleSharedFacts(context, "execution-browser-extension");
|
|
73
53
|
const browserExecutorConfigPath = factString(browser, "browserExecutorConfigPath");
|
|
74
|
-
return
|
|
75
|
-
? {
|
|
76
|
-
...base,
|
|
77
|
-
browserExecutorConfigPath,
|
|
78
|
-
}
|
|
79
|
-
: undefined;
|
|
54
|
+
return browserExecutorConfigPath ? { browserExecutorConfigPath } : undefined;
|
|
80
55
|
}
|
|
81
56
|
async function running(context) {
|
|
82
57
|
const own = await readOwnFacts(context);
|
|
83
58
|
if (!own)
|
|
84
59
|
return false;
|
|
85
60
|
try {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
headers: { authorization: `Bearer ${token}` },
|
|
89
|
-
signal: AbortSignal.timeout(500),
|
|
61
|
+
return (await fetch(`${own.endpoint}/health`, {
|
|
62
|
+
signal: AbortSignal.timeout(2_000),
|
|
90
63
|
})).ok;
|
|
91
64
|
}
|
|
92
65
|
catch {
|
|
@@ -95,31 +68,39 @@ async function running(context) {
|
|
|
95
68
|
}
|
|
96
69
|
async function compose(context) {
|
|
97
70
|
const own = await ownFacts(context);
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
|
|
71
|
+
const browser = await readModuleSharedFacts(context, "execution-browser-extension");
|
|
72
|
+
const browserExecutorConfigPath = factString(browser, "browserExecutorConfigPath");
|
|
73
|
+
if (!browserExecutorConfigPath)
|
|
74
|
+
throw new Error("Browser Executor shared facts are unavailable");
|
|
101
75
|
const [{ createFormalExecutionRuntimeLifecycle }, { parseExecutionRuntimeProcessConfig },] = await Promise.all([
|
|
102
76
|
import("../src/formal-process.js"),
|
|
103
77
|
import("../src/service.js"),
|
|
104
78
|
]);
|
|
105
79
|
const url = new URL(own.endpoint);
|
|
106
80
|
return createFormalExecutionRuntimeLifecycle({
|
|
81
|
+
resolveDependencies: async () => {
|
|
82
|
+
const host = await readModuleSharedFacts(context, "platform-host");
|
|
83
|
+
const model = await readModuleSharedFacts(context, "model-runtime");
|
|
84
|
+
const endpoint = factString(host, "endpoint"), tokenFile = factString(host, "identityTokenFile"), taskToken = factString(host, "taskApplicationTokenFile");
|
|
85
|
+
const modelEndpoint = factString(model, "endpoint"), credentialFile = factString(model, "transportCredentialFile");
|
|
86
|
+
return {
|
|
87
|
+
...(endpoint && tokenFile ? { identity: { endpoint, tokenFile } } : {}),
|
|
88
|
+
...(endpoint && taskToken
|
|
89
|
+
? { platformHost: { endpoint, tokenFile: taskToken } }
|
|
90
|
+
: {}),
|
|
91
|
+
...(modelEndpoint && credentialFile
|
|
92
|
+
? { modelDecision: { endpoint: modelEndpoint, credentialFile } }
|
|
93
|
+
: {}),
|
|
94
|
+
};
|
|
95
|
+
},
|
|
107
96
|
config: parseExecutionRuntimeProcessConfig({
|
|
108
97
|
host: url.hostname,
|
|
109
98
|
port: Number(url.port),
|
|
110
99
|
databasePath: own.databasePath,
|
|
111
100
|
projectRoot: own.projectRoot,
|
|
112
101
|
artifactRoot: own.artifactRoot,
|
|
113
|
-
browserExecutorConfigPath
|
|
102
|
+
browserExecutorConfigPath,
|
|
114
103
|
transportCredentialFile: own.transportCredentialFile,
|
|
115
|
-
identity: {
|
|
116
|
-
endpoint: deps.identityEndpoint,
|
|
117
|
-
tokenFile: deps.identityTokenFile,
|
|
118
|
-
},
|
|
119
|
-
modelDecision: {
|
|
120
|
-
endpoint: deps.modelEndpoint,
|
|
121
|
-
credentialFile: deps.modelCredentialFile,
|
|
122
|
-
},
|
|
123
104
|
exactNetworkTargets: [],
|
|
124
105
|
}),
|
|
125
106
|
});
|
|
@@ -149,7 +130,7 @@ export const behaviorAdapter = {
|
|
|
149
130
|
};
|
|
150
131
|
},
|
|
151
132
|
status: async (context) => {
|
|
152
|
-
const
|
|
133
|
+
const startup = await startupDependencies(context);
|
|
153
134
|
return {
|
|
154
135
|
result: {
|
|
155
136
|
...base,
|
|
@@ -158,18 +139,14 @@ export const behaviorAdapter = {
|
|
|
158
139
|
runtimeStatus: (await running(context))
|
|
159
140
|
? "RUNNING"
|
|
160
141
|
: "STOPPED",
|
|
161
|
-
...(!
|
|
142
|
+
...(!startup
|
|
162
143
|
? {
|
|
163
144
|
issues: [
|
|
164
145
|
{
|
|
165
146
|
scope: "RUNTIME",
|
|
166
147
|
code: "UPSTREAM_NOT_READY",
|
|
167
|
-
message: "等待
|
|
168
|
-
relatedModuleRefs: [
|
|
169
|
-
"platform-host",
|
|
170
|
-
"model-runtime",
|
|
171
|
-
"execution-browser-extension",
|
|
172
|
-
],
|
|
148
|
+
message: "等待 execution-browser-extension 发布 Browser Executor client 配置",
|
|
149
|
+
relatedModuleRefs: ["execution-browser-extension"],
|
|
173
150
|
nextCommand: "platform setup",
|
|
174
151
|
},
|
|
175
152
|
],
|
|
@@ -3,7 +3,7 @@ export declare const descriptor: {
|
|
|
3
3
|
readonly contractVersion: "1.0.0";
|
|
4
4
|
readonly moduleRef: "execution-runtime";
|
|
5
5
|
readonly packageName: "@tomflow/proflow-execution-runtime";
|
|
6
|
-
readonly moduleVersion: "0.1.
|
|
6
|
+
readonly moduleVersion: "0.1.19";
|
|
7
7
|
readonly kind: "service";
|
|
8
8
|
readonly templateVersion: "1.0.0";
|
|
9
9
|
readonly platformCompatibility: ">=1.0.0 <2.0.0";
|
|
@@ -16,6 +16,9 @@ export declare const descriptor: {
|
|
|
16
16
|
readonly version: "1.0.0";
|
|
17
17
|
}];
|
|
18
18
|
readonly requires: readonly [{
|
|
19
|
+
readonly contractRef: "execution-browser-executor";
|
|
20
|
+
readonly versionRange: ">=1.0.0 <2.0.0";
|
|
21
|
+
}, {
|
|
19
22
|
readonly contractRef: "execution-local";
|
|
20
23
|
readonly versionRange: ">=1.0.0 <2.0.0";
|
|
21
24
|
}];
|
|
@@ -3,7 +3,7 @@ export const descriptor = {
|
|
|
3
3
|
contractVersion: "1.0.0",
|
|
4
4
|
moduleRef: "execution-runtime",
|
|
5
5
|
packageName: "@tomflow/proflow-execution-runtime",
|
|
6
|
-
moduleVersion: "0.1.
|
|
6
|
+
moduleVersion: "0.1.19",
|
|
7
7
|
kind: "service",
|
|
8
8
|
templateVersion: "1.0.0",
|
|
9
9
|
platformCompatibility: ">=1.0.0 <2.0.0",
|
|
@@ -18,6 +18,10 @@ export const descriptor = {
|
|
|
18
18
|
},
|
|
19
19
|
],
|
|
20
20
|
requires: [
|
|
21
|
+
{
|
|
22
|
+
contractRef: "execution-browser-executor",
|
|
23
|
+
versionRange: ">=1.0.0 <2.0.0",
|
|
24
|
+
},
|
|
21
25
|
{
|
|
22
26
|
contractRef: "execution-local",
|
|
23
27
|
versionRange: ">=1.0.0 <2.0.0",
|
|
@@ -2,6 +2,11 @@ import { type ExecutionRuntimeProcessConfig } from "./service.ts";
|
|
|
2
2
|
export declare function createFormalExecutionRuntimeLifecycle(input: {
|
|
3
3
|
config: ExecutionRuntimeProcessConfig;
|
|
4
4
|
log?: (entry: Record<string, unknown>) => void;
|
|
5
|
+
resolveDependencies?: () => Promise<{
|
|
6
|
+
identity?: ExecutionRuntimeProcessConfig["identity"];
|
|
7
|
+
modelDecision?: ExecutionRuntimeProcessConfig["modelDecision"];
|
|
8
|
+
platformHost?: ExecutionRuntimeProcessConfig["platformHost"];
|
|
9
|
+
}>;
|
|
5
10
|
}): Readonly<{
|
|
6
11
|
status: () => import("./service.ts").ExecutionRuntimeProcessStatus | {
|
|
7
12
|
readiness: "NOT_READY";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { readFile, stat } from "node:fs/promises";
|
|
2
|
-
import {
|
|
2
|
+
import { createBrowserExecutorClientComposition } from "@tomflow/proflow-execution-browser-extension/runtime-composition";
|
|
3
3
|
import { createExecutionBrowserVisionClient } from "./browser-vision-client.js";
|
|
4
4
|
import { createExecutionModelDecisionClient } from "./model-decision-client.js";
|
|
5
5
|
import { createExecutionRuntimeProcess, } from "./service.js";
|
|
@@ -28,7 +28,6 @@ async function createIdentityClient(config) {
|
|
|
28
28
|
}
|
|
29
29
|
return ready;
|
|
30
30
|
};
|
|
31
|
-
await probe();
|
|
32
31
|
return {
|
|
33
32
|
port: {
|
|
34
33
|
async authorize(request) {
|
|
@@ -56,45 +55,81 @@ async function createIdentityClient(config) {
|
|
|
56
55
|
};
|
|
57
56
|
}
|
|
58
57
|
function assertFormalConfig(config) {
|
|
59
|
-
if (!config.identity)
|
|
60
|
-
throw new Error("formal execution-runtime requires identity configuration");
|
|
61
58
|
if (!config.transportCredentialFile)
|
|
62
59
|
throw new Error("formal execution-runtime requires transportCredentialFile");
|
|
63
60
|
if (!config.browserExecutorConfigPath)
|
|
64
61
|
throw new Error("formal execution-runtime requires browserExecutorConfigPath");
|
|
65
|
-
if (!config.modelDecision)
|
|
66
|
-
throw new Error("formal execution-runtime requires modelDecision configuration");
|
|
67
|
-
if (!config.modelDecision.credentialFile)
|
|
68
|
-
throw new Error("formal execution-runtime requires modelDecision.credentialFile");
|
|
69
62
|
return {
|
|
70
63
|
identity: config.identity,
|
|
71
64
|
transportCredentialFile: config.transportCredentialFile,
|
|
72
65
|
browserExecutorConfigPath: config.browserExecutorConfigPath,
|
|
73
|
-
modelDecision: {
|
|
74
|
-
...config.modelDecision,
|
|
75
|
-
credentialFile: config.modelDecision.credentialFile,
|
|
76
|
-
},
|
|
77
66
|
};
|
|
78
67
|
}
|
|
79
68
|
export function createFormalExecutionRuntimeLifecycle(input) {
|
|
80
69
|
const required = assertFormalConfig(input.config);
|
|
70
|
+
if (!input.resolveDependencies && !input.config.platformHost)
|
|
71
|
+
throw new Error("formal execution-runtime requires platformHost or resolveDependencies");
|
|
81
72
|
let current;
|
|
82
73
|
const build = async () => {
|
|
83
|
-
const
|
|
74
|
+
const dependencies = input.resolveDependencies ??
|
|
75
|
+
(async () => ({
|
|
76
|
+
identity: input.config.identity,
|
|
77
|
+
modelDecision: input.config.modelDecision,
|
|
78
|
+
platformHost: input.config.platformHost,
|
|
79
|
+
}));
|
|
80
|
+
let identityReady = false;
|
|
81
|
+
let modelReady = false;
|
|
82
|
+
const resolveIdentity = async () => {
|
|
83
|
+
const config = (await dependencies()).identity;
|
|
84
|
+
if (!config)
|
|
85
|
+
throw new Error("EXECUTION_IDENTITY_UNAVAILABLE");
|
|
86
|
+
return createIdentityClient(config);
|
|
87
|
+
};
|
|
88
|
+
const modelConfig = async () => {
|
|
89
|
+
const config = (await dependencies()).modelDecision;
|
|
90
|
+
if (!config?.credentialFile)
|
|
91
|
+
throw new Error("MODEL_DECISION_UNAVAILABLE");
|
|
92
|
+
return {
|
|
93
|
+
...config,
|
|
94
|
+
credential: await readSecret(config.credentialFile, "model credential"),
|
|
95
|
+
};
|
|
96
|
+
};
|
|
84
97
|
const transportCredential = await readSecret(required.transportCredentialFile, "execution transport credential");
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
98
|
+
const browserComposition = await createBrowserExecutorClientComposition({
|
|
99
|
+
configPath: required.browserExecutorConfigPath,
|
|
100
|
+
platformHost: async () => {
|
|
101
|
+
const config = (await dependencies()).platformHost;
|
|
102
|
+
if (!config)
|
|
103
|
+
throw new Error("PLATFORM_HOST_APPLICATION_UNAVAILABLE");
|
|
104
|
+
return {
|
|
105
|
+
endpoint: config.endpoint,
|
|
106
|
+
token: await readSecret(config.tokenFile, "task application credential"),
|
|
107
|
+
};
|
|
108
|
+
},
|
|
109
|
+
vision: {
|
|
110
|
+
inspect: async (request) => createExecutionBrowserVisionClient(await modelConfig()).port.inspect(request),
|
|
111
|
+
},
|
|
92
112
|
});
|
|
93
113
|
let dependencyRefresh;
|
|
94
114
|
const refreshDependencies = () => {
|
|
95
115
|
dependencyRefresh ??= Promise.allSettled([
|
|
96
|
-
|
|
97
|
-
|
|
116
|
+
resolveIdentity()
|
|
117
|
+
.then((client) => client.probe())
|
|
118
|
+
.then((ready) => {
|
|
119
|
+
identityReady = ready;
|
|
120
|
+
})
|
|
121
|
+
.catch(() => {
|
|
122
|
+
identityReady = false;
|
|
123
|
+
}),
|
|
124
|
+
modelConfig()
|
|
125
|
+
.then((config) => createExecutionModelDecisionClient(config).probe())
|
|
126
|
+
.then((ready) => {
|
|
127
|
+
modelReady = ready;
|
|
128
|
+
})
|
|
129
|
+
.catch(() => {
|
|
130
|
+
modelReady = false;
|
|
131
|
+
}),
|
|
132
|
+
browserComposition.refreshStatus(),
|
|
98
133
|
])
|
|
99
134
|
.then(() => undefined)
|
|
100
135
|
.finally(() => {
|
|
@@ -102,27 +137,35 @@ export function createFormalExecutionRuntimeLifecycle(input) {
|
|
|
102
137
|
});
|
|
103
138
|
return dependencyRefresh;
|
|
104
139
|
};
|
|
105
|
-
await refreshDependencies();
|
|
106
|
-
const browserVisionClient = createExecutionBrowserVisionClient({
|
|
107
|
-
endpoint: required.modelDecision.endpoint,
|
|
108
|
-
...(required.modelDecision.timeoutMs === undefined
|
|
109
|
-
? {}
|
|
110
|
-
: { timeoutMs: required.modelDecision.timeoutMs }),
|
|
111
|
-
credential: modelDecisionCredential,
|
|
112
|
-
});
|
|
113
|
-
const browserComposition = await createBrowserExecutorComposition({
|
|
114
|
-
...(await loadBrowserExecutorCompositionConfig(required.browserExecutorConfigPath)),
|
|
115
|
-
vision: browserVisionClient.port,
|
|
116
|
-
});
|
|
117
140
|
try {
|
|
118
141
|
const service = await createExecutionRuntimeProcess({
|
|
119
142
|
config: input.config,
|
|
120
|
-
identity:
|
|
121
|
-
|
|
143
|
+
identity: {
|
|
144
|
+
authorize: async (request) => {
|
|
145
|
+
try {
|
|
146
|
+
const client = await resolveIdentity();
|
|
147
|
+
const allowed = await client.port.authorize(request);
|
|
148
|
+
identityReady = client.readiness();
|
|
149
|
+
return allowed;
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
identityReady = false;
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
identityReadiness: () => identityReady,
|
|
122
158
|
transportCredential,
|
|
123
159
|
requireModelDecision: true,
|
|
124
|
-
modelDecision:
|
|
125
|
-
|
|
160
|
+
modelDecision: {
|
|
161
|
+
decide: async (request, context) => {
|
|
162
|
+
const client = createExecutionModelDecisionClient(await modelConfig());
|
|
163
|
+
await client.probe();
|
|
164
|
+
modelReady = client.readiness();
|
|
165
|
+
return client.port.decide(request, context);
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
modelDecisionReadiness: () => modelReady,
|
|
126
169
|
refreshDependencies,
|
|
127
170
|
browserExecutor: browserComposition.browserExecutor,
|
|
128
171
|
browserReadiness: () => browserComposition.bridgeStatus().online,
|
package/dist/src/service.d.ts
CHANGED
package/dist/src/service.js
CHANGED
|
@@ -2,6 +2,7 @@ import { timingSafeEqual } from "node:crypto";
|
|
|
2
2
|
import { readFile } from "node:fs/promises";
|
|
3
3
|
import { createServer } from "node:http";
|
|
4
4
|
import { dirname, isAbsolute, resolve } from "node:path";
|
|
5
|
+
import { DatabaseSync } from "node:sqlite";
|
|
5
6
|
import { artifactMaterializationResponseSchema, EXECUTION_CONTRACT_VERSION, materializeContextPackRequestSchema, materializeExternalFilesRequestSchema, materializeExternalFilesResponseSchema, materializePatchProposalRequestSchema, parseExecutionRef, } from "@tomflow/proflow-execution-contracts";
|
|
6
7
|
import { createLocalExecutorPort } from "./executors/local-adapter.js";
|
|
7
8
|
import { createExecutionRuntime, } from "./index.js";
|
|
@@ -15,6 +16,22 @@ function string(value, name) {
|
|
|
15
16
|
throw new TypeError(`${name} must be a non-empty string`);
|
|
16
17
|
return value;
|
|
17
18
|
}
|
|
19
|
+
function parseLoopbackOwner(value, name) {
|
|
20
|
+
const input = object(value, name);
|
|
21
|
+
const endpoint = new URL(string(input.endpoint, `${name}.endpoint`));
|
|
22
|
+
if (endpoint.protocol !== "http:" ||
|
|
23
|
+
!new Set(["127.0.0.1", "localhost", "::1", "[::1]"]).has(endpoint.hostname) ||
|
|
24
|
+
endpoint.pathname !== "/" ||
|
|
25
|
+
endpoint.username ||
|
|
26
|
+
endpoint.password ||
|
|
27
|
+
endpoint.search ||
|
|
28
|
+
endpoint.hash)
|
|
29
|
+
throw new TypeError(`${name}.endpoint must be loopback HTTP root`);
|
|
30
|
+
const tokenFile = string(input.tokenFile, `${name}.tokenFile`);
|
|
31
|
+
if (!isAbsolute(tokenFile))
|
|
32
|
+
throw new TypeError(`${name}.tokenFile must be absolute`);
|
|
33
|
+
return { endpoint: endpoint.origin, tokenFile };
|
|
34
|
+
}
|
|
18
35
|
export function parseExecutionRuntimeProcessConfig(value) {
|
|
19
36
|
const input = object(value, "execution-runtime config");
|
|
20
37
|
const databasePath = resolve(string(input.databasePath, "databasePath"));
|
|
@@ -63,21 +80,12 @@ export function parseExecutionRuntimeProcessConfig(value) {
|
|
|
63
80
|
...(credentialFile === undefined ? {} : { credentialFile }),
|
|
64
81
|
};
|
|
65
82
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
endpoint.pathname !== "/" ||
|
|
73
|
-
endpoint.search ||
|
|
74
|
-
endpoint.hash)
|
|
75
|
-
throw new TypeError("identity.endpoint must be loopback HTTP root");
|
|
76
|
-
const tokenFile = string(value.tokenFile, "identity.tokenFile");
|
|
77
|
-
if (!isAbsolute(tokenFile))
|
|
78
|
-
throw new TypeError("identity.tokenFile must be absolute");
|
|
79
|
-
identity = { endpoint: endpoint.origin, tokenFile };
|
|
80
|
-
}
|
|
83
|
+
const identity = input.identity === undefined
|
|
84
|
+
? undefined
|
|
85
|
+
: parseLoopbackOwner(input.identity, "identity");
|
|
86
|
+
const platformHost = input.platformHost === undefined
|
|
87
|
+
? undefined
|
|
88
|
+
: parseLoopbackOwner(input.platformHost, "platformHost");
|
|
81
89
|
if (!Array.isArray(exactNetworkTargets) ||
|
|
82
90
|
exactNetworkTargets.some((item) => typeof item !== "string"))
|
|
83
91
|
throw new TypeError("exactNetworkTargets must be a string array");
|
|
@@ -91,12 +99,57 @@ export function parseExecutionRuntimeProcessConfig(value) {
|
|
|
91
99
|
...(browserExecutorConfigPath ? { browserExecutorConfigPath } : {}),
|
|
92
100
|
...(transportCredentialFile ? { transportCredentialFile } : {}),
|
|
93
101
|
...(identity ? { identity } : {}),
|
|
102
|
+
...(platformHost ? { platformHost } : {}),
|
|
94
103
|
...(modelDecision ? { modelDecision } : {}),
|
|
95
104
|
};
|
|
96
105
|
}
|
|
97
106
|
export async function loadExecutionRuntimeProcessConfig(path) {
|
|
98
107
|
return parseExecutionRuntimeProcessConfig(JSON.parse(await readFile(resolve(path), "utf8")));
|
|
99
108
|
}
|
|
109
|
+
function createObserverSignalPager(databasePath) {
|
|
110
|
+
const database = new DatabaseSync(databasePath, { readOnly: true });
|
|
111
|
+
let cursor;
|
|
112
|
+
let upperBound;
|
|
113
|
+
const page = (limit, after) => {
|
|
114
|
+
if (!upperBound)
|
|
115
|
+
return [];
|
|
116
|
+
return database.prepare(`SELECT record_json, created_at, signal_ref FROM execution_observer_signals
|
|
117
|
+
WHERE acknowledged_at IS NULL
|
|
118
|
+
AND (created_at, signal_ref) <= (?, ?)
|
|
119
|
+
${after ? "AND (created_at, signal_ref) > (?, ?)" : ""}
|
|
120
|
+
ORDER BY created_at, signal_ref LIMIT ?`).all(upperBound.createdAt, upperBound.signalRef, ...(after ? [after.createdAt, after.signalRef] : []), limit);
|
|
121
|
+
};
|
|
122
|
+
const beginCycle = () => {
|
|
123
|
+
cursor = undefined;
|
|
124
|
+
const last = database.prepare("SELECT created_at, signal_ref FROM execution_observer_signals WHERE acknowledged_at IS NULL ORDER BY created_at DESC, signal_ref DESC LIMIT 1").get();
|
|
125
|
+
upperBound = last ? {
|
|
126
|
+
createdAt: String(last.created_at), signalRef: String(last.signal_ref),
|
|
127
|
+
} : undefined;
|
|
128
|
+
};
|
|
129
|
+
return Object.freeze({
|
|
130
|
+
list(limit = 50) {
|
|
131
|
+
const normalized = Number.isFinite(limit) ? Math.trunc(limit) : 50;
|
|
132
|
+
const bounded = Math.max(1, Math.min(100, normalized));
|
|
133
|
+
if (!upperBound)
|
|
134
|
+
beginCycle();
|
|
135
|
+
let rows = page(bounded, cursor);
|
|
136
|
+
if (rows.length === 0) {
|
|
137
|
+
beginCycle();
|
|
138
|
+
rows = page(bounded);
|
|
139
|
+
}
|
|
140
|
+
const last = rows.at(-1);
|
|
141
|
+
if (last)
|
|
142
|
+
cursor = {
|
|
143
|
+
createdAt: String(last.created_at),
|
|
144
|
+
signalRef: String(last.signal_ref),
|
|
145
|
+
};
|
|
146
|
+
return rows.map((row) => JSON.parse(String(row.record_json)));
|
|
147
|
+
},
|
|
148
|
+
close() {
|
|
149
|
+
database.close();
|
|
150
|
+
},
|
|
151
|
+
});
|
|
152
|
+
}
|
|
100
153
|
export async function createExecutionRuntimeProcess(input) {
|
|
101
154
|
if (input.transportCredential && input.transportCredential.length < 32)
|
|
102
155
|
throw new TypeError("transport credential must contain at least 32 characters");
|
|
@@ -104,6 +157,7 @@ export async function createExecutionRuntimeProcess(input) {
|
|
|
104
157
|
let accepting = false;
|
|
105
158
|
let server;
|
|
106
159
|
let runtime;
|
|
160
|
+
let observerSignals;
|
|
107
161
|
const active = new Set();
|
|
108
162
|
const log = (event, fields = {}) => input.log?.({
|
|
109
163
|
timestamp: new Date().toISOString(),
|
|
@@ -117,29 +171,29 @@ export async function createExecutionRuntimeProcess(input) {
|
|
|
117
171
|
? "UNAVAILABLE"
|
|
118
172
|
: "READY"
|
|
119
173
|
: "UNAVAILABLE";
|
|
120
|
-
const browserRequirementSatisfied = input.config.browserExecutorConfigPath === undefined ||
|
|
121
|
-
browserExecutor === "READY";
|
|
122
174
|
const identity = input.identity && input.identityReadiness?.() !== false
|
|
123
175
|
? "READY"
|
|
124
176
|
: "UNAVAILABLE";
|
|
125
|
-
const identityRequirementSatisfied = input.config.identity === undefined || identity === "READY";
|
|
126
177
|
const transportAuth = input.transportCredential ? "READY" : "UNAVAILABLE";
|
|
127
178
|
const transportRequirementSatisfied = input.config.transportCredentialFile === undefined ||
|
|
128
179
|
transportAuth === "READY";
|
|
129
180
|
const modelDecision = input.modelDecision && input.modelDecisionReadiness?.() !== false
|
|
130
181
|
? "READY"
|
|
131
182
|
: "UNAVAILABLE";
|
|
132
|
-
const
|
|
183
|
+
const browserRequirementSatisfied = input.browserExecutor === undefined || browserExecutor === "READY";
|
|
184
|
+
const identityRequirementSatisfied = (input.identity === undefined && input.config.identity === undefined) ||
|
|
185
|
+
identity === "READY";
|
|
186
|
+
const modelDecisionRequirementSatisfied = input.requireModelDecision !== true || modelDecision === "READY";
|
|
133
187
|
return {
|
|
134
188
|
process: state,
|
|
135
189
|
liveness: state === "STOPPED" ? "DOWN" : "UP",
|
|
136
190
|
readiness: state === "RUNNING" &&
|
|
137
191
|
accepting &&
|
|
138
192
|
runtime &&
|
|
193
|
+
transportRequirementSatisfied &&
|
|
139
194
|
browserRequirementSatisfied &&
|
|
140
195
|
identityRequirementSatisfied &&
|
|
141
|
-
|
|
142
|
-
modelRequirementSatisfied
|
|
196
|
+
modelDecisionRequirementSatisfied
|
|
143
197
|
? "READY"
|
|
144
198
|
: "NOT_READY",
|
|
145
199
|
accepting,
|
|
@@ -180,6 +234,7 @@ export async function createExecutionRuntimeProcess(input) {
|
|
|
180
234
|
...(input.modelDecision ? { modelDecision: input.modelDecision } : {}),
|
|
181
235
|
...(input.approval ? { approval: input.approval } : {}),
|
|
182
236
|
});
|
|
237
|
+
observerSignals = createObserverSignalPager(input.config.databasePath);
|
|
183
238
|
server = createServer((request, response) => {
|
|
184
239
|
const work = (async () => {
|
|
185
240
|
const url = new URL(request.url ?? "/", "http://execution.local");
|
|
@@ -187,12 +242,12 @@ export async function createExecutionRuntimeProcess(input) {
|
|
|
187
242
|
return respond(response, 200, { status: "UP" });
|
|
188
243
|
if (request.method === "GET" && url.pathname === "/ready") {
|
|
189
244
|
let current = status();
|
|
190
|
-
if (
|
|
245
|
+
if (input.refreshDependencies) {
|
|
191
246
|
try {
|
|
192
247
|
await input.refreshDependencies();
|
|
193
248
|
}
|
|
194
249
|
catch {
|
|
195
|
-
//
|
|
250
|
+
// Owner readiness is reported separately from process readiness.
|
|
196
251
|
}
|
|
197
252
|
current = status();
|
|
198
253
|
}
|
|
@@ -394,8 +449,11 @@ export async function createExecutionRuntimeProcess(input) {
|
|
|
394
449
|
if (request.method === "POST" &&
|
|
395
450
|
url.pathname === "/observer-signals/list") {
|
|
396
451
|
const value = object(body ?? {}, "observer signal list input");
|
|
452
|
+
const limit = typeof value.limit === "number" ? value.limit : 50;
|
|
397
453
|
return respond(response, 200, {
|
|
398
|
-
signals:
|
|
454
|
+
signals: value.consumer === "task-reconciliation"
|
|
455
|
+
? observerSignals?.list(limit) ?? []
|
|
456
|
+
: runtime.listExecutionObserverSignals(limit),
|
|
399
457
|
});
|
|
400
458
|
}
|
|
401
459
|
if (request.method === "POST" &&
|
|
@@ -449,6 +507,8 @@ export async function createExecutionRuntimeProcess(input) {
|
|
|
449
507
|
catch (error) {
|
|
450
508
|
state = "STOPPED";
|
|
451
509
|
accepting = false;
|
|
510
|
+
observerSignals?.close();
|
|
511
|
+
observerSignals = undefined;
|
|
452
512
|
runtime?.close();
|
|
453
513
|
runtime = undefined;
|
|
454
514
|
throw error;
|
|
@@ -464,6 +524,8 @@ export async function createExecutionRuntimeProcess(input) {
|
|
|
464
524
|
if (running)
|
|
465
525
|
await new Promise((resolveStop, reject) => running.close((error) => (error ? reject(error) : resolveStop())));
|
|
466
526
|
await Promise.allSettled([...active]);
|
|
527
|
+
observerSignals?.close();
|
|
528
|
+
observerSignals = undefined;
|
|
467
529
|
runtime?.close();
|
|
468
530
|
runtime = undefined;
|
|
469
531
|
state = "STOPPED";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tomflow/proflow-execution-runtime",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.19",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -24,11 +24,11 @@
|
|
|
24
24
|
"SETUP.md"
|
|
25
25
|
],
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@tomflow/proflow-execution-browser-extension": "^0.1.42",
|
|
28
|
-
"@tomflow/proflow-execution-contracts": "^0.1.10",
|
|
29
27
|
"@tomflow/proflow-model-contracts": "^0.1.10",
|
|
28
|
+
"@tomflow/proflow-execution-browser-extension": "^0.1.51",
|
|
30
29
|
"@tomflow/proflow-module-contract": "^0.1.13",
|
|
31
|
-
"@tomflow/proflow-execution-local": "^0.1.
|
|
30
|
+
"@tomflow/proflow-execution-local": "^0.1.16",
|
|
31
|
+
"@tomflow/proflow-execution-contracts": "^0.1.11"
|
|
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-runtime",
|
|
5
5
|
"packageName": "@tomflow/proflow-execution-runtime",
|
|
6
|
-
"moduleVersion": "0.1.
|
|
6
|
+
"moduleVersion": "0.1.19",
|
|
7
7
|
"kind": "service",
|
|
8
8
|
"templateVersion": "1.0.0",
|
|
9
9
|
"platformCompatibility": ">=1.0.0 <2.0.0",
|
|
@@ -18,6 +18,10 @@
|
|
|
18
18
|
}
|
|
19
19
|
],
|
|
20
20
|
"requires": [
|
|
21
|
+
{
|
|
22
|
+
"contractRef": "execution-browser-executor",
|
|
23
|
+
"versionRange": ">=1.0.0 <2.0.0"
|
|
24
|
+
},
|
|
21
25
|
{
|
|
22
26
|
"contractRef": "execution-local",
|
|
23
27
|
"versionRange": ">=1.0.0 <2.0.0"
|