@actiondock/core 2.5.1 → 2.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app/app.d.ts +14 -1
- package/dist/app/app.js +88 -222
- package/dist/app/static-index.d.ts +39 -0
- package/dist/app/static-index.js +164 -0
- package/dist/app/types.d.ts +5 -0
- package/dist/catalog/action-index.js +6 -4
- package/dist/catalog/package-catalog.js +2 -1
- package/dist/doctor/checks/index.d.ts +5 -0
- package/dist/doctor/checks/index.js +5 -0
- package/dist/doctor/checks/project-locate.d.ts +6 -0
- package/dist/doctor/checks/project-locate.js +36 -0
- package/dist/doctor/checks/project.d.ts +6 -0
- package/dist/doctor/checks/project.js +383 -0
- package/dist/doctor/checks/registry.d.ts +13 -0
- package/dist/doctor/checks/registry.js +207 -0
- package/dist/doctor/checks/runtime.d.ts +13 -0
- package/dist/doctor/checks/runtime.js +90 -0
- package/dist/doctor/checks/storage.d.ts +5 -0
- package/dist/doctor/checks/storage.js +34 -0
- package/dist/doctor/context.d.ts +35 -0
- package/dist/doctor/context.js +11 -0
- package/dist/doctor/doctor.d.ts +5 -0
- package/dist/doctor/doctor.js +33 -602
- package/dist/errors.d.ts +22 -0
- package/dist/errors.js +22 -0
- package/dist/execution/service.js +14 -9
- package/dist/export/templates.js +42 -30
- package/dist/host/host.d.ts +33 -0
- package/dist/host/host.js +222 -362
- package/dist/host/routing.d.ts +92 -0
- package/dist/host/routing.js +238 -0
- package/dist/host/types.d.ts +8 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +3 -1
- package/dist/input/advice.d.ts +100 -0
- package/dist/input/advice.js +767 -0
- package/dist/input/describe.d.ts +106 -0
- package/dist/input/describe.js +176 -0
- package/dist/input/file-input.d.ts +54 -0
- package/dist/input/file-input.js +124 -0
- package/dist/input/flat-decode.d.ts +11 -0
- package/dist/input/flat-decode.js +13 -0
- package/dist/input/flat-errors.d.ts +72 -0
- package/dist/input/flat-errors.js +87 -0
- package/dist/input/flat-materializer.d.ts +29 -0
- package/dist/input/flat-materializer.js +179 -0
- package/dist/input/flat-parser.d.ts +50 -0
- package/dist/input/flat-parser.js +169 -0
- package/dist/input/flat-predicates.d.ts +34 -0
- package/dist/input/flat-predicates.js +50 -0
- package/dist/input/index.d.ts +14 -0
- package/dist/input/index.js +14 -0
- package/dist/input/input-resolver.d.ts +63 -0
- package/dist/input/input-resolver.js +254 -0
- package/dist/input/json-depth-scanner.d.ts +17 -0
- package/dist/input/json-depth-scanner.js +49 -0
- package/dist/input/metadata.d.ts +99 -0
- package/dist/input/metadata.js +99 -0
- package/dist/input/stdin-input.d.ts +26 -0
- package/dist/input/stdin-input.js +125 -0
- package/dist/input/utf8.d.ts +19 -0
- package/dist/input/utf8.js +36 -0
- package/dist/input/validation-mapper.d.ts +27 -0
- package/dist/input/validation-mapper.js +144 -0
- package/dist/ipc/host.js +38 -0
- package/dist/json/index.d.ts +1 -0
- package/dist/json/index.js +1 -0
- package/dist/json/value-validator.d.ts +88 -0
- package/dist/json/value-validator.js +330 -0
- package/dist/platform/types.d.ts +7 -0
- package/dist/process/control-arbiter.d.ts +107 -0
- package/dist/process/control-arbiter.js +251 -0
- package/dist/process/diagnostics-sink.d.ts +26 -0
- package/dist/process/diagnostics-sink.js +35 -0
- package/dist/process/index.d.ts +7 -0
- package/dist/process/index.js +7 -0
- package/dist/process/input-dispatcher.d.ts +88 -0
- package/dist/process/input-dispatcher.js +240 -0
- package/dist/process/managed-record.d.ts +78 -0
- package/dist/process/managed-record.js +1 -0
- package/dist/process/process-manager.d.ts +15 -56
- package/dist/process/process-manager.js +119 -707
- package/dist/process/reservation-table.d.ts +39 -0
- package/dist/process/reservation-table.js +80 -0
- package/dist/process/run-executor.d.ts +16 -0
- package/dist/process/run-executor.js +155 -0
- package/dist/process/terminal-output-cache.d.ts +66 -0
- package/dist/process/terminal-output-cache.js +132 -0
- package/dist/profile/client-actions.d.ts +56 -0
- package/dist/profile/client-actions.js +215 -0
- package/dist/profile/client-config.d.ts +20 -0
- package/dist/profile/client-config.js +51 -0
- package/dist/profile/client-health.d.ts +14 -0
- package/dist/profile/client-health.js +69 -0
- package/dist/profile/client-playbooks.d.ts +19 -0
- package/dist/profile/client-playbooks.js +36 -0
- package/dist/profile/client-query.d.ts +30 -0
- package/dist/profile/client-query.js +70 -0
- package/dist/profile/client-runs.d.ts +42 -0
- package/dist/profile/client-runs.js +58 -0
- package/dist/profile/client-state.d.ts +56 -0
- package/dist/profile/client-state.js +83 -0
- package/dist/profile/client-transport.d.ts +61 -0
- package/dist/profile/client-transport.js +156 -0
- package/dist/profile/client.d.ts +23 -142
- package/dist/profile/client.js +28 -588
- package/dist/profile/manager.js +1 -1
- package/dist/project/closure.d.ts +4 -4
- package/dist/project/closure.js +11 -5
- package/dist/project/init.js +2 -2
- package/dist/project/loader.d.ts +0 -25
- package/dist/project/loader.js +1 -154
- package/dist/project/transactions.d.ts +12 -14
- package/dist/project/transactions.js +60 -836
- package/dist/registry/registry.js +6 -4
- package/dist/registry/resolve.js +1 -1
- package/dist/runtime/action-registry.d.ts +45 -0
- package/dist/runtime/action-registry.js +108 -0
- package/dist/runtime/clock.d.ts +5 -17
- package/dist/runtime/clock.js +5 -15
- package/dist/runtime/context.d.ts +3 -0
- package/dist/runtime/context.js +17 -56
- package/dist/runtime/index.d.ts +3 -0
- package/dist/runtime/index.js +3 -0
- package/dist/runtime/package-runner-factory.d.ts +82 -0
- package/dist/runtime/package-runner-factory.js +186 -0
- package/dist/runtime/run-persistence.d.ts +85 -0
- package/dist/runtime/run-persistence.js +111 -0
- package/dist/runtime/runner.d.ts +25 -45
- package/dist/runtime/runner.js +130 -354
- package/dist/runtime/standalone.d.ts +25 -2
- package/dist/runtime/standalone.js +305 -87
- package/dist/schema/index.d.ts +1 -0
- package/dist/schema/index.js +1 -0
- package/dist/schema/validator.d.ts +10 -0
- package/dist/schema/validator.js +48 -54
- package/dist/server/dispatcher.d.ts +8 -3
- package/dist/server/dispatcher.js +16 -40
- package/dist/server/routes/runs.js +6 -4
- package/dist/server/security.d.ts +1 -7
- package/dist/server/security.js +2 -13
- package/dist/storage/clock.d.ts +25 -0
- package/dist/storage/clock.js +23 -0
- package/dist/storage/data-dir-lock.d.ts +3 -36
- package/dist/storage/data-dir-lock.js +48 -881
- package/dist/storage/index.d.ts +8 -1
- package/dist/storage/index.js +8 -2
- package/dist/storage/lazy.js +1 -0
- package/dist/storage/lock-core.d.ts +162 -0
- package/dist/storage/lock-core.js +885 -0
- package/dist/storage/sqlite.d.ts +19 -6
- package/dist/storage/sqlite.js +81 -89
- package/dist/storage/types.d.ts +27 -5
- package/dist/storage/types.js +21 -0
- package/dist/storage/utils.d.ts +22 -0
- package/dist/storage/utils.js +34 -0
- package/dist/target/local.d.ts +1 -0
- package/dist/target/local.js +23 -17
- package/dist/target/remote-errors.d.ts +21 -0
- package/dist/target/remote-errors.js +51 -0
- package/dist/target/remote-polling.d.ts +35 -0
- package/dist/target/remote-polling.js +104 -0
- package/dist/target/remote.d.ts +4 -0
- package/dist/target/remote.js +108 -301
- package/dist/target/sse-parser.d.ts +35 -0
- package/dist/target/sse-parser.js +163 -0
- package/dist/target/target.js +2 -0
- package/dist/target/types.d.ts +6 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +1 -0
- package/dist/utils/net.d.ts +10 -0
- package/dist/utils/net.js +16 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -2
package/dist/runtime/runner.js
CHANGED
|
@@ -2,72 +2,30 @@ import { existsSync } from "node:fs";
|
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
3
|
import { ActionResolver } from "../catalog/action-resolver.js";
|
|
4
4
|
import { loadActions, loadProjectConfig } from "../project/loader.js";
|
|
5
|
-
import { resolveActionProject
|
|
6
|
-
import { ACTION_CALL_CYCLE, ACTION_CYCLE_DETECTED, ACTION_FAILED, ACTION_MAX_DEPTH_EXCEEDED, ACTION_NOT_FOUND, ACTION_SUBRUN_LIMIT, ACTION_TIMEOUT, describeActionLoadFailure, INPUT_NOT_JSON, MAX_SUBRUNS_REACHED, OUTPUT_NOT_JSON,
|
|
7
|
-
import {
|
|
5
|
+
import { resolveActionProject } from "../registry/registry.js";
|
|
6
|
+
import { ACTION_CALL_CYCLE, ACTION_CYCLE_DETECTED, ACTION_FAILED, ACTION_MAX_DEPTH_EXCEEDED, ACTION_NOT_FOUND, ACTION_SUBRUN_LIMIT, ACTION_TIMEOUT, describeActionLoadFailure, INPUT_NOT_JSON, MAX_SUBRUNS_REACHED, OUTPUT_NOT_JSON, ACTION_CANCELLED, PACKAGE_NOT_FOUND, INPUT_VALIDATION_FAILED, OUTPUT_VALIDATION_FAILED, UNDECLARED_ACTION_DEPENDENCY, } from "../errors.js";
|
|
7
|
+
import { validateSchemaOnly } from "../schema/validator.js";
|
|
8
8
|
import { createActionContext, StderrLogger } from "./context.js";
|
|
9
|
+
import { ActionRegistry, findLocalAction, isActionDefinitionObject, parseActionRef, resolveAnonymousActionId, } from "./action-registry.js";
|
|
10
|
+
import { buildInitialRunRecord, createRunFinalizer, createRunOrThrow, tryCreateRun, } from "./run-persistence.js";
|
|
11
|
+
import { PackageRunnerFactory } from "./package-runner-factory.js";
|
|
9
12
|
// 错误码常量已收敛至 src/errors.ts 单一事实源,此处保留 re-export 以维持既有导入路径兼容。
|
|
10
13
|
export { RUN_REPOSITORY_UNAVAILABLE, RUN_PERSISTENCE_FAILED, INPUT_NOT_JSON, OUTPUT_NOT_JSON, ACTION_SUBRUN_LIMIT, MAX_SUBRUNS_REACHED, ACTION_CALL_CYCLE, ACTION_CYCLE_DETECTED, ACTION_MAX_DEPTH_EXCEEDED, } from "../errors.js";
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
* 环路检测采用「当前递归路径栈」语义:对象仅在自身子树遍历期间保持标记,
|
|
15
|
-
* 子树遍历完成后立即移除,从而正确放行共享子对象的有向无环结构(如 {a: shared, b: shared})。
|
|
16
|
-
*/
|
|
17
|
-
export function validateJsonValue(val, stack = new WeakSet()) {
|
|
18
|
-
if (val === null || typeof val === "boolean" || typeof val === "string") {
|
|
19
|
-
return { valid: true };
|
|
20
|
-
}
|
|
21
|
-
if (typeof val === "number") {
|
|
22
|
-
if (!Number.isFinite(val) || Number.isNaN(val)) {
|
|
23
|
-
return { valid: false, reason: `Number is non-finite or NaN (${val})` };
|
|
24
|
-
}
|
|
25
|
-
return { valid: true };
|
|
26
|
-
}
|
|
27
|
-
if (typeof val === "undefined" || typeof val === "function" || typeof val === "symbol" || typeof val === "bigint") {
|
|
28
|
-
return { valid: false, reason: `Unsupported JSON type '${typeof val}'` };
|
|
29
|
-
}
|
|
30
|
-
if (typeof val === "object") {
|
|
31
|
-
if (stack.has(val)) {
|
|
32
|
-
return { valid: false, reason: "Circular reference detected in object structure" };
|
|
33
|
-
}
|
|
34
|
-
stack.add(val);
|
|
35
|
-
try {
|
|
36
|
-
if (Array.isArray(val)) {
|
|
37
|
-
for (const item of val) {
|
|
38
|
-
const res = validateJsonValue(item, stack);
|
|
39
|
-
if (!res.valid)
|
|
40
|
-
return res;
|
|
41
|
-
}
|
|
42
|
-
return { valid: true };
|
|
43
|
-
}
|
|
44
|
-
for (const v of Object.values(val)) {
|
|
45
|
-
if (v !== undefined) {
|
|
46
|
-
const res = validateJsonValue(v, stack);
|
|
47
|
-
if (!res.valid)
|
|
48
|
-
return res;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
return { valid: true };
|
|
52
|
-
}
|
|
53
|
-
finally {
|
|
54
|
-
// 无论正常返回还是提前返回,均须将当前对象移出路径栈,避免祖先对象被误判为环路
|
|
55
|
-
stack.delete(val);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
return { valid: true };
|
|
59
|
-
}
|
|
60
|
-
const anonymousRunnerActionIds = new WeakMap();
|
|
61
|
-
let anonymousRunnerActionCounter = 0;
|
|
14
|
+
import { validateJsonValue, validateActionInputValue, assertJsonValue, } from "../json/value-validator.js";
|
|
15
|
+
// 复用统一迭代式 JsonValue 校验器,杜绝深层递归栈溢出,并保持既有导出兼容
|
|
16
|
+
export { validateJsonValue, validateActionInputValue, assertJsonValue, };
|
|
62
17
|
/**
|
|
63
18
|
* ActionDock 核心执行引擎(ActionRunner)。
|
|
64
19
|
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
* -
|
|
68
|
-
* -
|
|
69
|
-
* -
|
|
70
|
-
*
|
|
20
|
+
* 本类为执行编排壳:负责 Action 执行的全生命周期编排(校验、深度与环路检测、
|
|
21
|
+
* 超时与取消竞态、子调用 uses 授权与错误分类),并将单一职责域委托至独立模块:
|
|
22
|
+
* - Action 注册与检索委托 `ActionRegistry`(action-registry.ts)
|
|
23
|
+
* - RunRecord 构造与落库委托 run-persistence.ts
|
|
24
|
+
* - 跨包 Runner 构建与缓存委托 `PackageRunnerFactory`(package-runner-factory.ts)
|
|
25
|
+
*
|
|
26
|
+
* 入参 (inputSchema) 与出参 (outputSchema) 的 JSON Schema 严格校验、嵌套 Action
|
|
27
|
+
* 相互调用的环路检测(Cycle Detection)、超时 (Timeout) 与中断信号 (AbortSignal)
|
|
28
|
+
* 竞态控制与运行记录持久化编排仍由本类承担。
|
|
71
29
|
*/
|
|
72
30
|
export class ActionRunner {
|
|
73
31
|
packageId;
|
|
@@ -78,19 +36,16 @@ export class ActionRunner {
|
|
|
78
36
|
projectRoot;
|
|
79
37
|
projectConfig;
|
|
80
38
|
configOverrides;
|
|
81
|
-
|
|
39
|
+
registry;
|
|
82
40
|
clock;
|
|
83
41
|
process;
|
|
84
42
|
platform;
|
|
85
43
|
maxCallDepth;
|
|
86
44
|
maxSubRuns;
|
|
87
45
|
activeSubRuns = 0;
|
|
88
|
-
|
|
89
|
-
/** 跨包 Runner 构建中的 in-flight Promise:并发调用 await 同一构建任务,消除 check-then-act 竞态 */
|
|
90
|
-
pendingPackageRunners = new Map();
|
|
46
|
+
packageFactory;
|
|
91
47
|
actionResolver;
|
|
92
48
|
getStorageForPackage;
|
|
93
|
-
packageContextResolver;
|
|
94
49
|
customHome;
|
|
95
50
|
hostSessionId;
|
|
96
51
|
constructor(options) {
|
|
@@ -101,7 +56,7 @@ export class ActionRunner {
|
|
|
101
56
|
this.projectRoot = options.projectRoot;
|
|
102
57
|
this.projectConfig = options.projectConfig;
|
|
103
58
|
this.configOverrides = options.configOverrides || {};
|
|
104
|
-
this.
|
|
59
|
+
this.registry = new ActionRegistry(options.actions);
|
|
105
60
|
this.customHome = options.customHome;
|
|
106
61
|
this.platform = options.platform;
|
|
107
62
|
if (options.platform) {
|
|
@@ -110,6 +65,8 @@ export class ActionRunner {
|
|
|
110
65
|
this.storage = options.storage ?? options.platform.storage.createStorage(this.packageId, {
|
|
111
66
|
projectRoot: this.projectRoot,
|
|
112
67
|
customHome: this.customHome,
|
|
68
|
+
// Runner 作为执行宿主组件属于持有者路径,打开时收割遗留孤儿运行
|
|
69
|
+
recoverOrphans: true,
|
|
113
70
|
});
|
|
114
71
|
this.globalStorage = options.globalStorage ?? options.platform.storage.createGlobalStorage({
|
|
115
72
|
customHome: this.customHome,
|
|
@@ -128,34 +85,38 @@ export class ActionRunner {
|
|
|
128
85
|
this.maxSubRuns = options.maxSubRuns ?? 64;
|
|
129
86
|
this.actionResolver = options.actionResolver;
|
|
130
87
|
this.getStorageForPackage = options.getStorageForPackage;
|
|
131
|
-
this.
|
|
88
|
+
this.packageFactory = new PackageRunnerFactory({
|
|
89
|
+
globalStorage: this.globalStorage,
|
|
90
|
+
projectRoot: this.projectRoot,
|
|
91
|
+
process: this.process,
|
|
92
|
+
clock: this.clock,
|
|
93
|
+
platform: this.platform,
|
|
94
|
+
maxCallDepth: this.maxCallDepth,
|
|
95
|
+
maxSubRuns: this.maxSubRuns,
|
|
96
|
+
actionResolver: this.actionResolver,
|
|
97
|
+
getStorageForPackage: this.getStorageForPackage,
|
|
98
|
+
packageContextResolver: options.packageContextResolver,
|
|
99
|
+
customHome: this.customHome,
|
|
100
|
+
}, (opts) => new ActionRunner(opts));
|
|
101
|
+
}
|
|
102
|
+
/** 本地 Action 注册表底层映射(仅内部与跨包工厂委托使用) */
|
|
103
|
+
get actions() {
|
|
104
|
+
return this.registry.map;
|
|
132
105
|
}
|
|
133
106
|
getStorage() {
|
|
134
107
|
return this.storage;
|
|
135
108
|
}
|
|
136
109
|
getPackageRunners() {
|
|
137
|
-
return this.
|
|
110
|
+
return this.packageFactory.runners;
|
|
138
111
|
}
|
|
139
112
|
registerAction(idOrAction, actionDef) {
|
|
140
|
-
|
|
141
|
-
if (actionDef) {
|
|
142
|
-
this.actions.set(idOrAction, actionDef);
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
else {
|
|
146
|
-
const actObj = idOrAction;
|
|
147
|
-
const id = actObj.id || "anonymous-action";
|
|
148
|
-
const act = actObj.action || (actObj.run ? actObj : undefined);
|
|
149
|
-
if (act) {
|
|
150
|
-
this.actions.set(id, act);
|
|
151
|
-
}
|
|
152
|
-
}
|
|
113
|
+
this.registry.registerAction(idOrAction, actionDef);
|
|
153
114
|
}
|
|
154
115
|
/**
|
|
155
116
|
* 根据 ID 检索注册的 Action。
|
|
156
117
|
*/
|
|
157
118
|
getAction(id) {
|
|
158
|
-
return this.
|
|
119
|
+
return this.registry.getAction(id);
|
|
159
120
|
}
|
|
160
121
|
/**
|
|
161
122
|
* 动态解析 Action(支持本地注册表、自定义解析器委托与已链接包目录索引检索)。
|
|
@@ -164,15 +125,13 @@ export class ActionRunner {
|
|
|
164
125
|
* @returns 解析判别联合结果(found | not_found | load_failed)
|
|
165
126
|
*/
|
|
166
127
|
async resolveAction(actionOrRef) {
|
|
167
|
-
if (
|
|
168
|
-
"run" in actionOrRef &&
|
|
169
|
-
typeof actionOrRef.run === "function") {
|
|
128
|
+
if (isActionDefinitionObject(actionOrRef)) {
|
|
170
129
|
return { status: "found", action: actionOrRef };
|
|
171
130
|
}
|
|
172
131
|
const ref = actionOrRef;
|
|
173
132
|
let parsed;
|
|
174
133
|
try {
|
|
175
|
-
parsed =
|
|
134
|
+
parsed = parseActionRef(ref);
|
|
176
135
|
}
|
|
177
136
|
catch (err) {
|
|
178
137
|
return { status: "not_found", reason: err.message };
|
|
@@ -180,18 +139,9 @@ export class ActionRunner {
|
|
|
180
139
|
const targetActionId = parsed.actionId;
|
|
181
140
|
const targetPackageId = parsed.packageId;
|
|
182
141
|
// 本地 actions 映射表优先检索
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
else {
|
|
189
|
-
if (this.actions.has(targetActionId)) {
|
|
190
|
-
return { status: "found", action: this.actions.get(targetActionId) };
|
|
191
|
-
}
|
|
192
|
-
if (this.packageId && this.actions.has(`${this.packageId}/${targetActionId}`)) {
|
|
193
|
-
return { status: "found", action: this.actions.get(`${this.packageId}/${targetActionId}`) };
|
|
194
|
-
}
|
|
142
|
+
const localMatched = findLocalAction(this.actions, parsed, this.packageId);
|
|
143
|
+
if (localMatched) {
|
|
144
|
+
return { status: "found", action: localMatched };
|
|
195
145
|
}
|
|
196
146
|
// 外部注入的自定义 actionResolver 调度
|
|
197
147
|
if (this.actionResolver) {
|
|
@@ -246,7 +196,6 @@ export class ActionRunner {
|
|
|
246
196
|
let actionsMap;
|
|
247
197
|
try {
|
|
248
198
|
actionsMap = await loadActions(resolved.projectRoot, config.actionsDir, {
|
|
249
|
-
autoInstall: false,
|
|
250
199
|
loader: this.platform?.modules,
|
|
251
200
|
});
|
|
252
201
|
}
|
|
@@ -276,13 +225,13 @@ export class ActionRunner {
|
|
|
276
225
|
* 获取当前 Runner 已注册的所有 Action 列表。
|
|
277
226
|
*/
|
|
278
227
|
listActions() {
|
|
279
|
-
return
|
|
228
|
+
return this.registry.listActions();
|
|
280
229
|
}
|
|
281
230
|
/**
|
|
282
231
|
* 注入或更新跨包运行上下文解析委托。
|
|
283
232
|
*/
|
|
284
233
|
setPackageContextResolver(resolver) {
|
|
285
|
-
this.
|
|
234
|
+
this.packageFactory.setPackageContextResolver(resolver);
|
|
286
235
|
}
|
|
287
236
|
/**
|
|
288
237
|
* 跨包运行时解析与获取(确保跨包执行具备独立的配置、存储、状态与 Action 注册表)。
|
|
@@ -292,95 +241,7 @@ export class ActionRunner {
|
|
|
292
241
|
* 并发方 await 同一构建任务,构建失败时移除该 Promise 以便后续重试。
|
|
293
242
|
*/
|
|
294
243
|
async resolveTargetPackageRunner(targetPackageId) {
|
|
295
|
-
|
|
296
|
-
if (cached) {
|
|
297
|
-
return cached;
|
|
298
|
-
}
|
|
299
|
-
const inFlight = this.pendingPackageRunners.get(targetPackageId);
|
|
300
|
-
if (inFlight) {
|
|
301
|
-
return inFlight;
|
|
302
|
-
}
|
|
303
|
-
const building = this.buildTargetPackageRunner(targetPackageId).finally(() => {
|
|
304
|
-
// 无论成败均移除 in-flight 记录:成功者已写入正式缓存,失败者允许重试
|
|
305
|
-
this.pendingPackageRunners.delete(targetPackageId);
|
|
306
|
-
});
|
|
307
|
-
this.pendingPackageRunners.set(targetPackageId, building);
|
|
308
|
-
return building;
|
|
309
|
-
}
|
|
310
|
-
/**
|
|
311
|
-
* 实际构建跨包 Runner(仅由 resolveTargetPackageRunner 串行调度)。
|
|
312
|
-
*/
|
|
313
|
-
async buildTargetPackageRunner(targetPackageId) {
|
|
314
|
-
if (this.packageContextResolver) {
|
|
315
|
-
const resolved = await this.packageContextResolver(targetPackageId);
|
|
316
|
-
if (resolved) {
|
|
317
|
-
const runner = new ActionRunner({
|
|
318
|
-
packageId: targetPackageId,
|
|
319
|
-
packageInstanceId: resolved.packageInstanceId || resolved.projectConfig?.packageInstanceId || targetPackageId,
|
|
320
|
-
generationId: resolved.generationId || resolved.projectConfig?.generationId || "1",
|
|
321
|
-
storage: resolved.storage,
|
|
322
|
-
globalStorage: this.globalStorage,
|
|
323
|
-
projectRoot: resolved.projectRoot,
|
|
324
|
-
projectConfig: resolved.projectConfig,
|
|
325
|
-
actions: resolved.actions,
|
|
326
|
-
process: this.process,
|
|
327
|
-
clock: this.clock,
|
|
328
|
-
platform: this.platform,
|
|
329
|
-
maxCallDepth: this.maxCallDepth,
|
|
330
|
-
maxSubRuns: this.maxSubRuns,
|
|
331
|
-
actionResolver: this.actionResolver,
|
|
332
|
-
getStorageForPackage: this.getStorageForPackage,
|
|
333
|
-
packageContextResolver: this.packageContextResolver,
|
|
334
|
-
});
|
|
335
|
-
this.packageRunners.set(targetPackageId, runner);
|
|
336
|
-
return runner;
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
const root = resolvePackageRoot(targetPackageId, this.projectRoot, this.customHome);
|
|
340
|
-
if (root && existsSync(root)) {
|
|
341
|
-
const config = loadProjectConfig(root);
|
|
342
|
-
let storage;
|
|
343
|
-
if (this.getStorageForPackage) {
|
|
344
|
-
storage = this.getStorageForPackage(targetPackageId, root);
|
|
345
|
-
}
|
|
346
|
-
else if (this.platform) {
|
|
347
|
-
storage = this.platform.storage.createStorage(targetPackageId, {
|
|
348
|
-
projectRoot: root,
|
|
349
|
-
customHome: this.customHome,
|
|
350
|
-
});
|
|
351
|
-
}
|
|
352
|
-
else {
|
|
353
|
-
// 回退分支与主路径共用 createStorage 单一事实源,确保 run 记录落在统一解析的库文件
|
|
354
|
-
const { createStorage } = await import("../storage/index.js");
|
|
355
|
-
storage = createStorage(targetPackageId, { projectRoot: root, customHome: this.customHome });
|
|
356
|
-
}
|
|
357
|
-
const actionsMap = await loadActions(root, config.actionsDir, {
|
|
358
|
-
autoInstall: false,
|
|
359
|
-
loader: this.platform?.modules,
|
|
360
|
-
});
|
|
361
|
-
const runner = new ActionRunner({
|
|
362
|
-
packageId: targetPackageId,
|
|
363
|
-
packageInstanceId: config.packageInstanceId || targetPackageId,
|
|
364
|
-
generationId: config.generationId || "1",
|
|
365
|
-
storage,
|
|
366
|
-
globalStorage: this.globalStorage,
|
|
367
|
-
projectRoot: root,
|
|
368
|
-
projectConfig: config,
|
|
369
|
-
actions: actionsMap,
|
|
370
|
-
process: this.process,
|
|
371
|
-
clock: this.clock,
|
|
372
|
-
platform: this.platform,
|
|
373
|
-
maxCallDepth: this.maxCallDepth,
|
|
374
|
-
maxSubRuns: this.maxSubRuns,
|
|
375
|
-
actionResolver: this.actionResolver,
|
|
376
|
-
getStorageForPackage: this.getStorageForPackage,
|
|
377
|
-
packageContextResolver: this.packageContextResolver,
|
|
378
|
-
customHome: this.customHome,
|
|
379
|
-
});
|
|
380
|
-
this.packageRunners.set(targetPackageId, runner);
|
|
381
|
-
return runner;
|
|
382
|
-
}
|
|
383
|
-
return undefined;
|
|
244
|
+
return this.packageFactory.resolveTargetPackageRunner(targetPackageId);
|
|
384
245
|
}
|
|
385
246
|
/**
|
|
386
247
|
* 异步启动 Action 的执行并立即返回 ExecutionHandle 句柄。
|
|
@@ -393,15 +254,21 @@ export class ActionRunner {
|
|
|
393
254
|
start(actionOrId, input = {}, options = {}) {
|
|
394
255
|
const runCtx = this.prepareRunContext(actionOrId, input, options);
|
|
395
256
|
const { runId, targetActionId, targetPackageId } = runCtx;
|
|
396
|
-
// 输入参数 JSON 格式与合法性防御校验(拦截 NaN/Infinity/循环引用等非 JSON
|
|
397
|
-
const inputCheck =
|
|
257
|
+
// 输入参数 JSON 格式与合法性防御校验(拦截 NaN/Infinity/循环引用等非 JSON 类型及非法原型键策略违规)
|
|
258
|
+
const inputCheck = validateActionInputValue(input);
|
|
398
259
|
if (!inputCheck.valid) {
|
|
399
|
-
const error =
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
260
|
+
const error = inputCheck.kind === "json-value"
|
|
261
|
+
? {
|
|
262
|
+
code: INPUT_NOT_JSON,
|
|
263
|
+
message: `Input validation failed for action '${targetActionId}': ${inputCheck.reason}`,
|
|
264
|
+
}
|
|
265
|
+
: {
|
|
266
|
+
code: INPUT_VALIDATION_FAILED,
|
|
267
|
+
message: `Input validation failed for action '${targetActionId}': ${inputCheck.reason}`,
|
|
268
|
+
details: [inputCheck.reason],
|
|
269
|
+
};
|
|
270
|
+
// 安全记录 failed 状态(不可序列化或策略违规的非法 input 严禁直接写入持久化存储)
|
|
271
|
+
tryCreateRun(this.storage, buildInitialRunRecord(this.buildRunPersistenceInput(runCtx), "failed", error));
|
|
405
272
|
return {
|
|
406
273
|
runId,
|
|
407
274
|
result: Promise.resolve({ ok: false, runId, error }),
|
|
@@ -409,8 +276,8 @@ export class ActionRunner {
|
|
|
409
276
|
};
|
|
410
277
|
}
|
|
411
278
|
// 始终优先将执行尝试持久化到存储中(确保任意异常与终态都可追溯)
|
|
412
|
-
this.
|
|
413
|
-
const finalizer = this.
|
|
279
|
+
createRunOrThrow(this.storage, buildInitialRunRecord(this.buildRunPersistenceInput(runCtx), "running"));
|
|
280
|
+
const finalizer = createRunFinalizer(this.storage, runCtx.runId);
|
|
414
281
|
// 调用嵌套深度限制检测 (Max Call Depth Check)
|
|
415
282
|
const depthError = this.checkCallDepth(runCtx.callStack, targetActionId, runCtx.options);
|
|
416
283
|
if (depthError) {
|
|
@@ -512,33 +379,24 @@ export class ActionRunner {
|
|
|
512
379
|
let action;
|
|
513
380
|
let targetActionId;
|
|
514
381
|
let targetPackageId = this.packageId;
|
|
515
|
-
if (
|
|
516
|
-
"run" in actionOrId &&
|
|
517
|
-
typeof actionOrId.run === "function") {
|
|
382
|
+
if (isActionDefinitionObject(actionOrId)) {
|
|
518
383
|
action = actionOrId;
|
|
519
384
|
const actObj = action;
|
|
520
385
|
if (actObj.id) {
|
|
521
386
|
targetActionId = actObj.id;
|
|
522
387
|
}
|
|
523
388
|
else {
|
|
524
|
-
targetActionId = this.
|
|
389
|
+
targetActionId = resolveAnonymousActionId(this.actions, action);
|
|
525
390
|
}
|
|
526
391
|
this.actions.set(targetActionId, action);
|
|
527
392
|
}
|
|
528
393
|
else {
|
|
529
|
-
const parsed =
|
|
394
|
+
const parsed = parseActionRef(actionOrId);
|
|
530
395
|
targetActionId = parsed.actionId;
|
|
531
396
|
if (parsed.packageId) {
|
|
532
397
|
targetPackageId = parsed.packageId;
|
|
533
398
|
}
|
|
534
|
-
|
|
535
|
-
action = this.actions.get(`${parsed.packageId}/${targetActionId}`);
|
|
536
|
-
}
|
|
537
|
-
else {
|
|
538
|
-
action =
|
|
539
|
-
this.actions.get(targetActionId) ||
|
|
540
|
-
(this.packageId ? this.actions.get(`${this.packageId}/${targetActionId}`) : undefined);
|
|
541
|
-
}
|
|
399
|
+
action = findLocalAction(this.actions, parsed, this.packageId);
|
|
542
400
|
}
|
|
543
401
|
return {
|
|
544
402
|
runId,
|
|
@@ -554,128 +412,27 @@ export class ActionRunner {
|
|
|
554
412
|
};
|
|
555
413
|
}
|
|
556
414
|
/**
|
|
557
|
-
*
|
|
558
|
-
*/
|
|
559
|
-
resolveAnonymousActionId(action) {
|
|
560
|
-
const actObj = action;
|
|
561
|
-
let foundId;
|
|
562
|
-
for (const [id, a] of this.actions) {
|
|
563
|
-
if (a === action) {
|
|
564
|
-
foundId = id;
|
|
565
|
-
break;
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
if (foundId) {
|
|
569
|
-
return foundId;
|
|
570
|
-
}
|
|
571
|
-
let anonId = anonymousRunnerActionIds.get(action);
|
|
572
|
-
if (!anonId) {
|
|
573
|
-
anonymousRunnerActionCounter++;
|
|
574
|
-
anonId = `anonymous-action-${anonymousRunnerActionCounter}`;
|
|
575
|
-
anonymousRunnerActionIds.set(action, anonId);
|
|
576
|
-
}
|
|
577
|
-
try {
|
|
578
|
-
actObj.id = anonId;
|
|
579
|
-
}
|
|
580
|
-
catch { }
|
|
581
|
-
return anonId;
|
|
582
|
-
}
|
|
583
|
-
/**
|
|
584
|
-
* 尝试将 failed 终态初始记录写入存储(非法输入场景,存储异常静默忽略)。
|
|
585
|
-
*/
|
|
586
|
-
tryPersistInitialRun(runCtx, status, error) {
|
|
587
|
-
const initialRun = this.buildInitialRunRecord(runCtx, status, error);
|
|
588
|
-
try {
|
|
589
|
-
this.storage.createRun(initialRun);
|
|
590
|
-
}
|
|
591
|
-
catch { }
|
|
592
|
-
}
|
|
593
|
-
/**
|
|
594
|
-
* 构建 running 初始记录并强制写入存储(存储不可用时抛出 RUN_REPOSITORY_UNAVAILABLE)。
|
|
415
|
+
* 依据运行期上下文装配落库模块入参(RunRecord 构造领域契约)。
|
|
595
416
|
*/
|
|
596
|
-
|
|
597
|
-
const
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
}
|
|
601
|
-
catch (err) {
|
|
602
|
-
const error = new Error(`RUN_REPOSITORY_UNAVAILABLE: Failed to initialize run record in repository: ${err?.message || String(err)}`);
|
|
603
|
-
error.code = RUN_REPOSITORY_UNAVAILABLE;
|
|
604
|
-
error.details = { originalError: err?.message };
|
|
605
|
-
throw error;
|
|
606
|
-
}
|
|
607
|
-
}
|
|
608
|
-
/**
|
|
609
|
-
* 依据运行期上下文构造 RunRecord 初始记录。
|
|
610
|
-
*/
|
|
611
|
-
buildInitialRunRecord(runCtx, status, error) {
|
|
612
|
-
const { runId, options, targetPackageId, targetActionId, startedAt } = runCtx;
|
|
613
|
-
const record = {
|
|
614
|
-
id: runId,
|
|
417
|
+
buildRunPersistenceInput(runCtx) {
|
|
418
|
+
const { options, targetPackageId, targetActionId, startedAt, input } = runCtx;
|
|
419
|
+
return {
|
|
420
|
+
runId: runCtx.runId,
|
|
615
421
|
rootRunId: this.computeRootRunId(runCtx),
|
|
616
422
|
parentRunId: options.parentRunId,
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
generationId: options.generationId
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
status,
|
|
624
|
-
error,
|
|
423
|
+
ownerId: options.ownerId,
|
|
424
|
+
hostSessionId: options.hostSessionId,
|
|
425
|
+
packageInstanceId: options.packageInstanceId,
|
|
426
|
+
generationId: options.generationId,
|
|
427
|
+
targetPackageId,
|
|
428
|
+
targetActionId,
|
|
625
429
|
startedAt,
|
|
430
|
+
input,
|
|
431
|
+
runnerPackageId: this.packageId,
|
|
432
|
+
runnerPackageInstanceId: this.packageInstanceId,
|
|
433
|
+
runnerGenerationId: this.generationId,
|
|
434
|
+
runnerHostSessionId: this.hostSessionId,
|
|
626
435
|
};
|
|
627
|
-
if (status === "running") {
|
|
628
|
-
record.input = runCtx.input;
|
|
629
|
-
}
|
|
630
|
-
else {
|
|
631
|
-
record.finishedAt = startedAt;
|
|
632
|
-
}
|
|
633
|
-
return record;
|
|
634
|
-
}
|
|
635
|
-
/**
|
|
636
|
-
* 创建运行终态收敛器:封装终态去重、超时定时器清理与落库异常捕获。
|
|
637
|
-
*/
|
|
638
|
-
createRunFinalizer(runCtx) {
|
|
639
|
-
const finalizer = {
|
|
640
|
-
finalized: false,
|
|
641
|
-
isTimeout: false,
|
|
642
|
-
persistError: undefined,
|
|
643
|
-
timeoutTimer: undefined,
|
|
644
|
-
controller: undefined,
|
|
645
|
-
signal: undefined,
|
|
646
|
-
onAbort: undefined,
|
|
647
|
-
startTimeout: (controller, timeoutMs) => {
|
|
648
|
-
finalizer.controller = controller;
|
|
649
|
-
finalizer.timeoutTimer = setTimeout(() => {
|
|
650
|
-
finalizer.isTimeout = true;
|
|
651
|
-
finalizer.controller?.abort(new Error(`Action exceeded timeout of ${timeoutMs}ms`));
|
|
652
|
-
}, timeoutMs);
|
|
653
|
-
},
|
|
654
|
-
finalize: (status, output, error) => {
|
|
655
|
-
if (finalizer.timeoutTimer) {
|
|
656
|
-
clearTimeout(finalizer.timeoutTimer);
|
|
657
|
-
finalizer.timeoutTimer = undefined;
|
|
658
|
-
}
|
|
659
|
-
if (finalizer.signal && finalizer.onAbort) {
|
|
660
|
-
finalizer.signal.removeEventListener("abort", finalizer.onAbort);
|
|
661
|
-
finalizer.onAbort = undefined;
|
|
662
|
-
}
|
|
663
|
-
if (finalizer.finalized)
|
|
664
|
-
return;
|
|
665
|
-
finalizer.finalized = true;
|
|
666
|
-
try {
|
|
667
|
-
this.storage.updateRun(runCtx.runId, status, output, error);
|
|
668
|
-
}
|
|
669
|
-
catch (persistErr) {
|
|
670
|
-
finalizer.persistError = {
|
|
671
|
-
code: RUN_PERSISTENCE_FAILED,
|
|
672
|
-
message: `RUN_PERSISTENCE_FAILED: Failed to persist run state: ${persistErr?.message || String(persistErr)}`,
|
|
673
|
-
details: { originalError: persistErr?.message },
|
|
674
|
-
};
|
|
675
|
-
}
|
|
676
|
-
},
|
|
677
|
-
};
|
|
678
|
-
return finalizer;
|
|
679
436
|
}
|
|
680
437
|
/**
|
|
681
438
|
* 调用嵌套深度限制检测(超限时返回 ACTION_CALL_CYCLE 错误)。
|
|
@@ -719,9 +476,11 @@ export class ActionRunner {
|
|
|
719
476
|
* 输入参数 JSON Schema 校验(若 action 已就绪,返回校验错误或 undefined)。
|
|
720
477
|
*/
|
|
721
478
|
checkActionInputSchema(action, targetActionId, input) {
|
|
722
|
-
const targetInputSchema = action?.inputSchema
|
|
723
|
-
|
|
724
|
-
|
|
479
|
+
const targetInputSchema = action?.inputSchema !== undefined
|
|
480
|
+
? action.inputSchema
|
|
481
|
+
: this.projectConfig?.actions?.[targetActionId]?.inputSchema;
|
|
482
|
+
if (targetInputSchema !== undefined) {
|
|
483
|
+
const val = validateSchemaOnly(targetInputSchema, input);
|
|
725
484
|
if (!val.valid) {
|
|
726
485
|
return {
|
|
727
486
|
code: INPUT_VALIDATION_FAILED,
|
|
@@ -892,11 +651,16 @@ export class ActionRunner {
|
|
|
892
651
|
const { runCtx, actionOrId, input, options, controller, finalizer, ctx } = args;
|
|
893
652
|
const { runId, targetActionId } = runCtx;
|
|
894
653
|
const abortPromise = new Promise((_, reject) => {
|
|
654
|
+
const rejectWithReason = () => reject(controller.signal.reason || new Error("Action execution was cancelled"));
|
|
895
655
|
if (controller.signal.aborted) {
|
|
896
|
-
|
|
656
|
+
rejectWithReason();
|
|
897
657
|
}
|
|
898
658
|
else {
|
|
899
|
-
controller.signal.addEventListener("abort",
|
|
659
|
+
controller.signal.addEventListener("abort", rejectWithReason, { once: true });
|
|
660
|
+
// 成功与失败路径统一注销:finalize 时回收监听句柄,避免信号对象残留引用
|
|
661
|
+
finalizer.raceListenerRemovers.push(() => {
|
|
662
|
+
controller.signal.removeEventListener("abort", rejectWithReason);
|
|
663
|
+
});
|
|
900
664
|
}
|
|
901
665
|
});
|
|
902
666
|
return (async () => {
|
|
@@ -927,26 +691,26 @@ export class ActionRunner {
|
|
|
927
691
|
finalizer.finalize("failed", undefined, error);
|
|
928
692
|
return { ok: false, runId, error };
|
|
929
693
|
}
|
|
930
|
-
const inputCheck =
|
|
694
|
+
const inputCheck = validateActionInputValue(input);
|
|
931
695
|
if (!inputCheck.valid) {
|
|
932
|
-
const error =
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
696
|
+
const error = inputCheck.kind === "json-value"
|
|
697
|
+
? {
|
|
698
|
+
code: INPUT_NOT_JSON,
|
|
699
|
+
message: `Input validation failed for action '${targetActionId}': ${inputCheck.reason}`,
|
|
700
|
+
}
|
|
701
|
+
: {
|
|
702
|
+
code: INPUT_VALIDATION_FAILED,
|
|
703
|
+
message: `Input validation failed for action '${targetActionId}': ${inputCheck.reason}`,
|
|
704
|
+
details: [inputCheck.reason],
|
|
705
|
+
};
|
|
936
706
|
finalizer.finalize("failed", undefined, error);
|
|
937
707
|
return { ok: false, runId, error };
|
|
938
708
|
}
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
message: `Input schema validation failed for action '${targetActionId}'`,
|
|
945
|
-
details: val.errors,
|
|
946
|
-
};
|
|
947
|
-
finalizer.finalize("failed", undefined, error);
|
|
948
|
-
return { ok: false, runId, error };
|
|
949
|
-
}
|
|
709
|
+
// 复用统一校验入口:优先 action 声明 schema,缺失时回退 projectConfig 清单声明
|
|
710
|
+
const schemaError = this.checkActionInputSchema(currentAction, targetActionId, input);
|
|
711
|
+
if (schemaError) {
|
|
712
|
+
finalizer.finalize("failed", undefined, schemaError);
|
|
713
|
+
return { ok: false, runId, error: schemaError };
|
|
950
714
|
}
|
|
951
715
|
}
|
|
952
716
|
const rawOutput = await Promise.race([
|
|
@@ -999,9 +763,11 @@ export class ActionRunner {
|
|
|
999
763
|
* 输出结果 Schema 校验(返回校验错误或 undefined)。
|
|
1000
764
|
*/
|
|
1001
765
|
checkActionOutputSchema(action, targetActionId, rawOutput) {
|
|
1002
|
-
const targetOutputSchema = action?.outputSchema
|
|
1003
|
-
|
|
1004
|
-
|
|
766
|
+
const targetOutputSchema = action?.outputSchema !== undefined
|
|
767
|
+
? action.outputSchema
|
|
768
|
+
: this.projectConfig?.actions?.[targetActionId]?.outputSchema;
|
|
769
|
+
if (targetOutputSchema !== undefined) {
|
|
770
|
+
const outVal = validateSchemaOnly(targetOutputSchema, rawOutput);
|
|
1005
771
|
if (!outVal.valid) {
|
|
1006
772
|
return {
|
|
1007
773
|
code: OUTPUT_VALIDATION_FAILED,
|
|
@@ -1052,6 +818,16 @@ export class ActionRunner {
|
|
|
1052
818
|
error: finalizer.persistError || error,
|
|
1053
819
|
};
|
|
1054
820
|
}
|
|
821
|
+
/**
|
|
822
|
+
* 释放 Runner 持有的跨包资源:遍历关闭全部子包 Runner 及其独立创建的存储连接。
|
|
823
|
+
*
|
|
824
|
+
* 幂等且并发安全:重复调用直接复用首次 Promise;子包存储异常不阻断其余释放;
|
|
825
|
+
* 仅关闭由本 Runner 构建子包 Runner 时独立创建的存储,外部注入的存储(如经
|
|
826
|
+
* packageContextResolver 传入的目标包自身存储)生命周期归所有者管理,不在此误关。
|
|
827
|
+
*/
|
|
828
|
+
async dispose() {
|
|
829
|
+
return this.packageFactory.dispose();
|
|
830
|
+
}
|
|
1055
831
|
/**
|
|
1056
832
|
* 同步等待方式执行指定 Action,直接返回 ExecutionResult 信封结果。
|
|
1057
833
|
*
|