@actiondock/core 2.5.0 → 2.6.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 -215
- 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 +2 -0
- package/dist/errors.js +2 -0
- package/dist/execution/service.js +14 -9
- 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/ipc/host.js +38 -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 +68 -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 +155 -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 +23 -33
- package/dist/runtime/runner.js +92 -286
- package/dist/runtime/standalone.d.ts +11 -1
- package/dist/runtime/standalone.js +137 -25
- 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,10 +2,13 @@ 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,
|
|
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
7
|
import { validateSchema } 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
14
|
/**
|
|
@@ -57,17 +60,18 @@ export function validateJsonValue(val, stack = new WeakSet()) {
|
|
|
57
60
|
}
|
|
58
61
|
return { valid: true };
|
|
59
62
|
}
|
|
60
|
-
const anonymousRunnerActionIds = new WeakMap();
|
|
61
|
-
let anonymousRunnerActionCounter = 0;
|
|
62
63
|
/**
|
|
63
64
|
* ActionDock 核心执行引擎(ActionRunner)。
|
|
64
65
|
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
* -
|
|
68
|
-
* -
|
|
69
|
-
* -
|
|
70
|
-
*
|
|
66
|
+
* 本类为执行编排壳:负责 Action 执行的全生命周期编排(校验、深度与环路检测、
|
|
67
|
+
* 超时与取消竞态、子调用 uses 授权与错误分类),并将单一职责域委托至独立模块:
|
|
68
|
+
* - Action 注册与检索委托 `ActionRegistry`(action-registry.ts)
|
|
69
|
+
* - RunRecord 构造与落库委托 run-persistence.ts
|
|
70
|
+
* - 跨包 Runner 构建与缓存委托 `PackageRunnerFactory`(package-runner-factory.ts)
|
|
71
|
+
*
|
|
72
|
+
* 入参 (inputSchema) 与出参 (outputSchema) 的 JSON Schema 严格校验、嵌套 Action
|
|
73
|
+
* 相互调用的环路检测(Cycle Detection)、超时 (Timeout) 与中断信号 (AbortSignal)
|
|
74
|
+
* 竞态控制与运行记录持久化编排仍由本类承担。
|
|
71
75
|
*/
|
|
72
76
|
export class ActionRunner {
|
|
73
77
|
packageId;
|
|
@@ -78,19 +82,16 @@ export class ActionRunner {
|
|
|
78
82
|
projectRoot;
|
|
79
83
|
projectConfig;
|
|
80
84
|
configOverrides;
|
|
81
|
-
|
|
85
|
+
registry;
|
|
82
86
|
clock;
|
|
83
87
|
process;
|
|
84
88
|
platform;
|
|
85
89
|
maxCallDepth;
|
|
86
90
|
maxSubRuns;
|
|
87
91
|
activeSubRuns = 0;
|
|
88
|
-
|
|
89
|
-
/** 跨包 Runner 构建中的 in-flight Promise:并发调用 await 同一构建任务,消除 check-then-act 竞态 */
|
|
90
|
-
pendingPackageRunners = new Map();
|
|
92
|
+
packageFactory;
|
|
91
93
|
actionResolver;
|
|
92
94
|
getStorageForPackage;
|
|
93
|
-
packageContextResolver;
|
|
94
95
|
customHome;
|
|
95
96
|
hostSessionId;
|
|
96
97
|
constructor(options) {
|
|
@@ -101,7 +102,7 @@ export class ActionRunner {
|
|
|
101
102
|
this.projectRoot = options.projectRoot;
|
|
102
103
|
this.projectConfig = options.projectConfig;
|
|
103
104
|
this.configOverrides = options.configOverrides || {};
|
|
104
|
-
this.
|
|
105
|
+
this.registry = new ActionRegistry(options.actions);
|
|
105
106
|
this.customHome = options.customHome;
|
|
106
107
|
this.platform = options.platform;
|
|
107
108
|
if (options.platform) {
|
|
@@ -110,6 +111,8 @@ export class ActionRunner {
|
|
|
110
111
|
this.storage = options.storage ?? options.platform.storage.createStorage(this.packageId, {
|
|
111
112
|
projectRoot: this.projectRoot,
|
|
112
113
|
customHome: this.customHome,
|
|
114
|
+
// Runner 作为执行宿主组件属于持有者路径,打开时收割遗留孤儿运行
|
|
115
|
+
recoverOrphans: true,
|
|
113
116
|
});
|
|
114
117
|
this.globalStorage = options.globalStorage ?? options.platform.storage.createGlobalStorage({
|
|
115
118
|
customHome: this.customHome,
|
|
@@ -128,34 +131,38 @@ export class ActionRunner {
|
|
|
128
131
|
this.maxSubRuns = options.maxSubRuns ?? 64;
|
|
129
132
|
this.actionResolver = options.actionResolver;
|
|
130
133
|
this.getStorageForPackage = options.getStorageForPackage;
|
|
131
|
-
this.
|
|
134
|
+
this.packageFactory = new PackageRunnerFactory({
|
|
135
|
+
globalStorage: this.globalStorage,
|
|
136
|
+
projectRoot: this.projectRoot,
|
|
137
|
+
process: this.process,
|
|
138
|
+
clock: this.clock,
|
|
139
|
+
platform: this.platform,
|
|
140
|
+
maxCallDepth: this.maxCallDepth,
|
|
141
|
+
maxSubRuns: this.maxSubRuns,
|
|
142
|
+
actionResolver: this.actionResolver,
|
|
143
|
+
getStorageForPackage: this.getStorageForPackage,
|
|
144
|
+
packageContextResolver: options.packageContextResolver,
|
|
145
|
+
customHome: this.customHome,
|
|
146
|
+
}, (opts) => new ActionRunner(opts));
|
|
147
|
+
}
|
|
148
|
+
/** 本地 Action 注册表底层映射(仅内部与跨包工厂委托使用) */
|
|
149
|
+
get actions() {
|
|
150
|
+
return this.registry.map;
|
|
132
151
|
}
|
|
133
152
|
getStorage() {
|
|
134
153
|
return this.storage;
|
|
135
154
|
}
|
|
136
155
|
getPackageRunners() {
|
|
137
|
-
return this.
|
|
156
|
+
return this.packageFactory.runners;
|
|
138
157
|
}
|
|
139
158
|
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
|
-
}
|
|
159
|
+
this.registry.registerAction(idOrAction, actionDef);
|
|
153
160
|
}
|
|
154
161
|
/**
|
|
155
162
|
* 根据 ID 检索注册的 Action。
|
|
156
163
|
*/
|
|
157
164
|
getAction(id) {
|
|
158
|
-
return this.
|
|
165
|
+
return this.registry.getAction(id);
|
|
159
166
|
}
|
|
160
167
|
/**
|
|
161
168
|
* 动态解析 Action(支持本地注册表、自定义解析器委托与已链接包目录索引检索)。
|
|
@@ -164,15 +171,13 @@ export class ActionRunner {
|
|
|
164
171
|
* @returns 解析判别联合结果(found | not_found | load_failed)
|
|
165
172
|
*/
|
|
166
173
|
async resolveAction(actionOrRef) {
|
|
167
|
-
if (
|
|
168
|
-
"run" in actionOrRef &&
|
|
169
|
-
typeof actionOrRef.run === "function") {
|
|
174
|
+
if (isActionDefinitionObject(actionOrRef)) {
|
|
170
175
|
return { status: "found", action: actionOrRef };
|
|
171
176
|
}
|
|
172
177
|
const ref = actionOrRef;
|
|
173
178
|
let parsed;
|
|
174
179
|
try {
|
|
175
|
-
parsed =
|
|
180
|
+
parsed = parseActionRef(ref);
|
|
176
181
|
}
|
|
177
182
|
catch (err) {
|
|
178
183
|
return { status: "not_found", reason: err.message };
|
|
@@ -180,18 +185,9 @@ export class ActionRunner {
|
|
|
180
185
|
const targetActionId = parsed.actionId;
|
|
181
186
|
const targetPackageId = parsed.packageId;
|
|
182
187
|
// 本地 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
|
-
}
|
|
188
|
+
const localMatched = findLocalAction(this.actions, parsed, this.packageId);
|
|
189
|
+
if (localMatched) {
|
|
190
|
+
return { status: "found", action: localMatched };
|
|
195
191
|
}
|
|
196
192
|
// 外部注入的自定义 actionResolver 调度
|
|
197
193
|
if (this.actionResolver) {
|
|
@@ -246,7 +242,6 @@ export class ActionRunner {
|
|
|
246
242
|
let actionsMap;
|
|
247
243
|
try {
|
|
248
244
|
actionsMap = await loadActions(resolved.projectRoot, config.actionsDir, {
|
|
249
|
-
autoInstall: false,
|
|
250
245
|
loader: this.platform?.modules,
|
|
251
246
|
});
|
|
252
247
|
}
|
|
@@ -276,13 +271,13 @@ export class ActionRunner {
|
|
|
276
271
|
* 获取当前 Runner 已注册的所有 Action 列表。
|
|
277
272
|
*/
|
|
278
273
|
listActions() {
|
|
279
|
-
return
|
|
274
|
+
return this.registry.listActions();
|
|
280
275
|
}
|
|
281
276
|
/**
|
|
282
277
|
* 注入或更新跨包运行上下文解析委托。
|
|
283
278
|
*/
|
|
284
279
|
setPackageContextResolver(resolver) {
|
|
285
|
-
this.
|
|
280
|
+
this.packageFactory.setPackageContextResolver(resolver);
|
|
286
281
|
}
|
|
287
282
|
/**
|
|
288
283
|
* 跨包运行时解析与获取(确保跨包执行具备独立的配置、存储、状态与 Action 注册表)。
|
|
@@ -292,95 +287,7 @@ export class ActionRunner {
|
|
|
292
287
|
* 并发方 await 同一构建任务,构建失败时移除该 Promise 以便后续重试。
|
|
293
288
|
*/
|
|
294
289
|
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;
|
|
290
|
+
return this.packageFactory.resolveTargetPackageRunner(targetPackageId);
|
|
384
291
|
}
|
|
385
292
|
/**
|
|
386
293
|
* 异步启动 Action 的执行并立即返回 ExecutionHandle 句柄。
|
|
@@ -401,7 +308,7 @@ export class ActionRunner {
|
|
|
401
308
|
message: `Input validation failed for action '${targetActionId}': ${inputCheck.reason}`,
|
|
402
309
|
};
|
|
403
310
|
// 安全记录 failed 状态(不可序列化的非法 input 严禁直接写入持久化存储)
|
|
404
|
-
this.
|
|
311
|
+
tryCreateRun(this.storage, buildInitialRunRecord(this.buildRunPersistenceInput(runCtx), "failed", error));
|
|
405
312
|
return {
|
|
406
313
|
runId,
|
|
407
314
|
result: Promise.resolve({ ok: false, runId, error }),
|
|
@@ -409,8 +316,8 @@ export class ActionRunner {
|
|
|
409
316
|
};
|
|
410
317
|
}
|
|
411
318
|
// 始终优先将执行尝试持久化到存储中(确保任意异常与终态都可追溯)
|
|
412
|
-
this.
|
|
413
|
-
const finalizer = this.
|
|
319
|
+
createRunOrThrow(this.storage, buildInitialRunRecord(this.buildRunPersistenceInput(runCtx), "running"));
|
|
320
|
+
const finalizer = createRunFinalizer(this.storage, runCtx.runId);
|
|
414
321
|
// 调用嵌套深度限制检测 (Max Call Depth Check)
|
|
415
322
|
const depthError = this.checkCallDepth(runCtx.callStack, targetActionId, runCtx.options);
|
|
416
323
|
if (depthError) {
|
|
@@ -512,33 +419,24 @@ export class ActionRunner {
|
|
|
512
419
|
let action;
|
|
513
420
|
let targetActionId;
|
|
514
421
|
let targetPackageId = this.packageId;
|
|
515
|
-
if (
|
|
516
|
-
"run" in actionOrId &&
|
|
517
|
-
typeof actionOrId.run === "function") {
|
|
422
|
+
if (isActionDefinitionObject(actionOrId)) {
|
|
518
423
|
action = actionOrId;
|
|
519
424
|
const actObj = action;
|
|
520
425
|
if (actObj.id) {
|
|
521
426
|
targetActionId = actObj.id;
|
|
522
427
|
}
|
|
523
428
|
else {
|
|
524
|
-
targetActionId = this.
|
|
429
|
+
targetActionId = resolveAnonymousActionId(this.actions, action);
|
|
525
430
|
}
|
|
526
431
|
this.actions.set(targetActionId, action);
|
|
527
432
|
}
|
|
528
433
|
else {
|
|
529
|
-
const parsed =
|
|
434
|
+
const parsed = parseActionRef(actionOrId);
|
|
530
435
|
targetActionId = parsed.actionId;
|
|
531
436
|
if (parsed.packageId) {
|
|
532
437
|
targetPackageId = parsed.packageId;
|
|
533
438
|
}
|
|
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
|
-
}
|
|
439
|
+
action = findLocalAction(this.actions, parsed, this.packageId);
|
|
542
440
|
}
|
|
543
441
|
return {
|
|
544
442
|
runId,
|
|
@@ -554,128 +452,27 @@ export class ActionRunner {
|
|
|
554
452
|
};
|
|
555
453
|
}
|
|
556
454
|
/**
|
|
557
|
-
*
|
|
455
|
+
* 依据运行期上下文装配落库模块入参(RunRecord 构造领域契约)。
|
|
558
456
|
*/
|
|
559
|
-
|
|
560
|
-
const
|
|
561
|
-
|
|
562
|
-
|
|
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)。
|
|
595
|
-
*/
|
|
596
|
-
persistInitialRun(runCtx, status) {
|
|
597
|
-
const initialRun = this.buildInitialRunRecord(runCtx, status);
|
|
598
|
-
try {
|
|
599
|
-
this.storage.createRun(initialRun);
|
|
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,
|
|
457
|
+
buildRunPersistenceInput(runCtx) {
|
|
458
|
+
const { options, targetPackageId, targetActionId, startedAt, input } = runCtx;
|
|
459
|
+
return {
|
|
460
|
+
runId: runCtx.runId,
|
|
615
461
|
rootRunId: this.computeRootRunId(runCtx),
|
|
616
462
|
parentRunId: options.parentRunId,
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
generationId: options.generationId
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
status,
|
|
624
|
-
error,
|
|
463
|
+
ownerId: options.ownerId,
|
|
464
|
+
hostSessionId: options.hostSessionId,
|
|
465
|
+
packageInstanceId: options.packageInstanceId,
|
|
466
|
+
generationId: options.generationId,
|
|
467
|
+
targetPackageId,
|
|
468
|
+
targetActionId,
|
|
625
469
|
startedAt,
|
|
470
|
+
input,
|
|
471
|
+
runnerPackageId: this.packageId,
|
|
472
|
+
runnerPackageInstanceId: this.packageInstanceId,
|
|
473
|
+
runnerGenerationId: this.generationId,
|
|
474
|
+
runnerHostSessionId: this.hostSessionId,
|
|
626
475
|
};
|
|
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
476
|
}
|
|
680
477
|
/**
|
|
681
478
|
* 调用嵌套深度限制检测(超限时返回 ACTION_CALL_CYCLE 错误)。
|
|
@@ -892,11 +689,16 @@ export class ActionRunner {
|
|
|
892
689
|
const { runCtx, actionOrId, input, options, controller, finalizer, ctx } = args;
|
|
893
690
|
const { runId, targetActionId } = runCtx;
|
|
894
691
|
const abortPromise = new Promise((_, reject) => {
|
|
692
|
+
const rejectWithReason = () => reject(controller.signal.reason || new Error("Action execution was cancelled"));
|
|
895
693
|
if (controller.signal.aborted) {
|
|
896
|
-
|
|
694
|
+
rejectWithReason();
|
|
897
695
|
}
|
|
898
696
|
else {
|
|
899
|
-
controller.signal.addEventListener("abort",
|
|
697
|
+
controller.signal.addEventListener("abort", rejectWithReason, { once: true });
|
|
698
|
+
// 成功与失败路径统一注销:finalize 时回收监听句柄,避免信号对象残留引用
|
|
699
|
+
finalizer.raceListenerRemovers.push(() => {
|
|
700
|
+
controller.signal.removeEventListener("abort", rejectWithReason);
|
|
701
|
+
});
|
|
900
702
|
}
|
|
901
703
|
});
|
|
902
704
|
return (async () => {
|
|
@@ -936,17 +738,11 @@ export class ActionRunner {
|
|
|
936
738
|
finalizer.finalize("failed", undefined, error);
|
|
937
739
|
return { ok: false, runId, error };
|
|
938
740
|
}
|
|
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
|
-
}
|
|
741
|
+
// 复用统一校验入口:优先 action 声明 schema,缺失时回退 projectConfig 清单声明
|
|
742
|
+
const schemaError = this.checkActionInputSchema(currentAction, targetActionId, input);
|
|
743
|
+
if (schemaError) {
|
|
744
|
+
finalizer.finalize("failed", undefined, schemaError);
|
|
745
|
+
return { ok: false, runId, error: schemaError };
|
|
950
746
|
}
|
|
951
747
|
}
|
|
952
748
|
const rawOutput = await Promise.race([
|
|
@@ -1052,6 +848,16 @@ export class ActionRunner {
|
|
|
1052
848
|
error: finalizer.persistError || error,
|
|
1053
849
|
};
|
|
1054
850
|
}
|
|
851
|
+
/**
|
|
852
|
+
* 释放 Runner 持有的跨包资源:遍历关闭全部子包 Runner 及其独立创建的存储连接。
|
|
853
|
+
*
|
|
854
|
+
* 幂等且并发安全:重复调用直接复用首次 Promise;子包存储异常不阻断其余释放;
|
|
855
|
+
* 仅关闭由本 Runner 构建子包 Runner 时独立创建的存储,外部注入的存储(如经
|
|
856
|
+
* packageContextResolver 传入的目标包自身存储)生命周期归所有者管理,不在此误关。
|
|
857
|
+
*/
|
|
858
|
+
async dispose() {
|
|
859
|
+
return this.packageFactory.dispose();
|
|
860
|
+
}
|
|
1055
861
|
/**
|
|
1056
862
|
* 同步等待方式执行指定 Action,直接返回 ExecutionResult 信封结果。
|
|
1057
863
|
*
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type ActionDefinition } from "@actiondock/sdk";
|
|
2
2
|
import type { ActionSpec } from "../app/types.js";
|
|
3
3
|
import type { ConfigItemDefinition } from "../project/types.js";
|
|
4
4
|
import type { ActionDockTarget } from "../target/types.js";
|
|
@@ -71,6 +71,16 @@ export declare class StandaloneDispatcher {
|
|
|
71
71
|
private handleRun;
|
|
72
72
|
private handleConfig;
|
|
73
73
|
private handleState;
|
|
74
|
+
/**
|
|
75
|
+
* 判定配置读取异常是否为目标层能力缺失(键未配置语义)而非内部故障。
|
|
76
|
+
*/
|
|
77
|
+
private isTargetCapabilityError;
|
|
78
|
+
/**
|
|
79
|
+
* 解析携带命名空间前缀的状态键参数:委托 sdk 单一事实源 decodeStateKey。
|
|
80
|
+
*
|
|
81
|
+
* 解析失败(如存在多个未转义冒号的歧义键)时按纯键处理,交由存储层兜底。
|
|
82
|
+
*/
|
|
83
|
+
private decodeStateKeyArg;
|
|
74
84
|
private printHelp;
|
|
75
85
|
}
|
|
76
86
|
/**
|