@actiondock/core 2.0.12 → 2.2.1
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/README.md +61 -30
- package/dist/app/app.d.ts +73 -0
- package/dist/app/app.js +700 -0
- package/dist/app/index.d.ts +2 -0
- package/dist/app/index.js +2 -0
- package/dist/app/types.d.ts +283 -0
- package/dist/app/types.js +1 -0
- package/dist/catalog/action-index.d.ts +1 -1
- package/dist/catalog/action-index.js +93 -0
- package/dist/catalog/action-resolver.d.ts +2 -2
- package/dist/catalog/action-resolver.js +95 -0
- package/dist/catalog/index.d.ts +5 -5
- package/dist/catalog/index.js +5 -0
- package/dist/catalog/location-registry.d.ts +5 -5
- package/dist/catalog/location-registry.js +138 -0
- package/dist/catalog/package-catalog.d.ts +2 -2
- package/dist/catalog/package-catalog.js +76 -0
- package/dist/catalog/types.d.ts +2 -2
- package/dist/catalog/types.js +1 -0
- package/dist/doctor/doctor.d.ts +2 -1
- package/dist/doctor/doctor.js +613 -0
- package/dist/doctor/index.d.ts +2 -2
- package/dist/doctor/index.js +2 -0
- package/dist/doctor/types.js +1 -0
- package/dist/errors.d.ts +122 -0
- package/dist/errors.js +123 -0
- package/dist/execution/index.d.ts +2 -2
- package/dist/execution/index.js +2 -0
- package/dist/execution/service.d.ts +46 -41
- package/dist/execution/service.js +633 -0
- package/dist/execution/types.d.ts +59 -2
- package/dist/execution/types.js +1 -0
- package/dist/export/index.d.ts +1 -1
- package/dist/export/index.js +1 -0
- package/dist/export/templates.d.ts +45 -7
- package/dist/export/templates.js +631 -0
- package/dist/filter/index.d.ts +1 -1
- package/dist/filter/index.js +1 -0
- package/dist/filter/intent.js +123 -0
- package/dist/host/host.d.ts +55 -0
- package/dist/host/host.js +740 -0
- package/dist/host/index.d.ts +2 -0
- package/dist/host/index.js +2 -0
- package/dist/host/types.d.ts +81 -0
- package/dist/host/types.js +1 -0
- package/dist/index.d.ts +21 -16
- package/dist/index.js +20 -8458
- package/dist/ipc/diagnostic.d.ts +65 -0
- package/dist/ipc/diagnostic.js +129 -0
- package/dist/ipc/host.d.ts +7 -0
- package/dist/ipc/host.js +93 -0
- package/dist/ipc/index.d.ts +4 -0
- package/dist/ipc/index.js +4 -0
- package/dist/ipc/target.d.ts +56 -0
- package/dist/ipc/target.js +297 -0
- package/dist/ipc/types.d.ts +42 -0
- package/dist/ipc/types.js +1 -0
- package/dist/platform/default.d.ts +32 -0
- package/dist/platform/default.js +40 -0
- package/dist/platform/index.d.ts +3 -0
- package/dist/platform/index.js +3 -0
- package/dist/platform/node-fs.d.ts +37 -0
- package/dist/platform/node-fs.js +81 -0
- package/dist/platform/types.d.ts +104 -0
- package/dist/platform/types.js +1 -0
- package/dist/profile/client.d.ts +58 -12
- package/dist/profile/client.js +467 -0
- package/dist/profile/index.d.ts +3 -3
- package/dist/profile/index.js +3 -0
- package/dist/profile/manager.d.ts +2 -2
- package/dist/profile/manager.js +294 -0
- package/dist/profile/types.d.ts +1 -1
- package/dist/profile/types.js +1 -0
- package/dist/project/closure.js +68 -0
- package/dist/project/digest.d.ts +29 -0
- package/dist/project/digest.js +324 -0
- package/dist/project/index.d.ts +10 -5
- package/dist/project/index.js +10 -0
- package/dist/project/init.d.ts +3 -4
- package/dist/project/init.js +175 -0
- package/dist/project/loader.d.ts +21 -27
- package/dist/project/loader.js +484 -0
- package/dist/project/lockfile.d.ts +55 -0
- package/dist/project/lockfile.js +175 -0
- package/dist/project/manifest.d.ts +6 -28
- package/dist/project/manifest.js +220 -0
- package/dist/project/resolver.d.ts +116 -0
- package/dist/project/resolver.js +376 -0
- package/dist/project/transactions.d.ts +103 -0
- package/dist/project/transactions.js +1203 -0
- package/dist/project/types-generator.d.ts +33 -0
- package/dist/project/types-generator.js +168 -0
- package/dist/project/types.d.ts +58 -82
- package/dist/project/types.js +1 -0
- package/dist/registry/index.d.ts +5 -2
- package/dist/registry/index.js +5 -0
- package/dist/registry/lock.d.ts +27 -0
- package/dist/registry/lock.js +93 -0
- package/dist/registry/registry.d.ts +32 -11
- package/dist/registry/registry.js +611 -0
- package/dist/registry/resolve.d.ts +62 -0
- package/dist/registry/resolve.js +195 -0
- package/dist/registry/scan.d.ts +38 -0
- package/dist/registry/scan.js +135 -0
- package/dist/registry/types.d.ts +1 -1
- package/dist/registry/types.js +1 -0
- package/dist/runtime/action-collection.d.ts +27 -0
- package/dist/runtime/action-collection.js +57 -0
- package/dist/runtime/clock.d.ts +0 -2
- package/dist/runtime/clock.js +17 -0
- package/dist/runtime/context.d.ts +16 -5
- package/dist/runtime/context.js +242 -0
- package/dist/runtime/env.d.ts +3 -1
- package/dist/runtime/env.js +159 -0
- package/dist/runtime/events.d.ts +46 -14
- package/dist/runtime/events.js +604 -0
- package/dist/runtime/execution-manager.d.ts +1 -1
- package/dist/runtime/execution-manager.js +66 -0
- package/dist/runtime/index.d.ts +10 -9
- package/dist/runtime/index.js +10 -0
- package/dist/runtime/module-loader.d.ts +0 -8
- package/dist/runtime/module-loader.js +16 -0
- package/dist/runtime/process.d.ts +2 -4
- package/dist/runtime/process.js +146 -0
- package/dist/runtime/runner.d.ts +117 -10
- package/dist/runtime/runner.js +984 -0
- package/dist/runtime/standalone.d.ts +68 -19
- package/dist/runtime/standalone.js +536 -0
- package/dist/schema/validator.js +122 -0
- package/dist/server/body.js +93 -0
- package/dist/server/index.d.ts +5 -6
- package/dist/server/index.js +5 -0
- package/dist/server/routes/actions.d.ts +2 -2
- package/dist/server/routes/actions.js +298 -0
- package/dist/server/routes/common.d.ts +28 -17
- package/dist/server/routes/common.js +104 -0
- package/dist/server/routes/config.d.ts +2 -1
- package/dist/server/routes/config.js +173 -0
- package/dist/server/routes/doctor.d.ts +2 -2
- package/dist/server/routes/doctor.js +63 -0
- package/dist/server/routes/health.d.ts +2 -2
- package/dist/server/routes/health.js +35 -0
- package/dist/server/routes/index.d.ts +9 -9
- package/dist/server/routes/index.js +9 -0
- package/dist/server/routes/info.d.ts +4 -2
- package/dist/server/routes/info.js +158 -0
- package/dist/server/routes/playbooks.d.ts +2 -2
- package/dist/server/routes/playbooks.js +134 -0
- package/dist/server/routes/runs.d.ts +2 -2
- package/dist/server/routes/runs.js +343 -0
- package/dist/server/routes/state.d.ts +2 -1
- package/dist/server/routes/state.js +169 -0
- package/dist/server/security.d.ts +11 -4
- package/dist/server/security.js +101 -0
- package/dist/server/server.d.ts +6 -6
- package/dist/server/server.js +355 -0
- package/dist/server/types.d.ts +27 -8
- package/dist/server/types.js +1 -0
- package/dist/storage/data-dir-lock.d.ts +119 -0
- package/dist/storage/data-dir-lock.js +1016 -0
- package/dist/storage/driver.d.ts +4 -10
- package/dist/storage/driver.js +59 -0
- package/dist/storage/index.d.ts +30 -10
- package/dist/storage/index.js +98 -0
- package/dist/storage/lazy.d.ts +8 -0
- package/dist/storage/lazy.js +44 -0
- package/dist/storage/mask.d.ts +12 -5
- package/dist/storage/mask.js +48 -0
- package/dist/storage/params.d.ts +13 -0
- package/dist/storage/params.js +32 -0
- package/dist/storage/sqlite.d.ts +19 -6
- package/dist/storage/sqlite.js +689 -0
- package/dist/storage/types.d.ts +57 -6
- package/dist/storage/types.js +14 -0
- package/dist/target/index.d.ts +4 -0
- package/dist/target/index.js +4 -0
- package/dist/target/local.d.ts +52 -0
- package/dist/target/local.js +343 -0
- package/dist/target/remote.d.ts +76 -0
- package/dist/target/remote.js +839 -0
- package/dist/target/target.d.ts +6 -0
- package/dist/target/target.js +35 -0
- package/dist/target/types.d.ts +246 -0
- package/dist/target/types.js +34 -0
- package/dist/utils/index.d.ts +22 -2
- package/dist/utils/index.js +180 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +4 -0
- package/package.json +6 -7
- package/dist/build/index.d.ts +0 -1
- package/dist/build/templates.d.ts +0 -19
- package/dist/server/runtime-registry.d.ts +0 -78
|
@@ -0,0 +1,740 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { DefaultActionDockApp } from "../app/app.js";
|
|
4
|
+
import { ActionResolver } from "../catalog/action-resolver.js";
|
|
5
|
+
import { findProjectRoot, loadProjectConfig } from "../project/loader.js";
|
|
6
|
+
import { ActionPackageResolver } from "../project/resolver.js";
|
|
7
|
+
import { hasPendingTransactions, isProjectLockHeld, recoverPendingTransactions } from "../project/transactions.js";
|
|
8
|
+
import { listLinkedPackages } from "../registry/registry.js";
|
|
9
|
+
import { InMemoryEventSink } from "../runtime/events.js";
|
|
10
|
+
import { ACTION_CALL_CYCLE, ACTION_MAX_DEPTH_EXCEEDED, ACTION_NOT_FOUND, ACTION_PACKAGE_VERSION_CONFLICT, ACTION_SUBRUN_LIMIT, INVALID_ACTION_REF, MAX_SUBRUNS_REACHED, PACKAGE_NOT_FOUND, PROJECT_BUSY, PROJECT_RECOVERY_REQUIRED, UNDECLARED_ACTION_DEPENDENCY, } from "../errors.js";
|
|
11
|
+
import { DataDirLock } from "../storage/data-dir-lock.js";
|
|
12
|
+
function isActionDockApp(item) {
|
|
13
|
+
return (typeof item === "object" &&
|
|
14
|
+
item !== null &&
|
|
15
|
+
"info" in item &&
|
|
16
|
+
typeof item.info === "function" &&
|
|
17
|
+
"runAction" in item &&
|
|
18
|
+
typeof item.runAction === "function");
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* ActionDock 统一多包宿主容器默认实现。
|
|
22
|
+
* 负责聚合与调度多个 ActionDockApp 实例,提供跨包引用路由、依赖声明校验与资源配额控制。
|
|
23
|
+
*/
|
|
24
|
+
export class DefaultActionDockHost {
|
|
25
|
+
hostSessionId;
|
|
26
|
+
apps = new Map();
|
|
27
|
+
internallyCreatedApps = new Set();
|
|
28
|
+
activeSubRunsPerRoot = new Map();
|
|
29
|
+
hostPublicPackageIds = new Set();
|
|
30
|
+
resolver;
|
|
31
|
+
maxCallDepth;
|
|
32
|
+
maxSubRuns;
|
|
33
|
+
eventSink;
|
|
34
|
+
isClosed = false;
|
|
35
|
+
dataDirLock;
|
|
36
|
+
failedLinkedPackages = new Map();
|
|
37
|
+
constructor(options = {}) {
|
|
38
|
+
this.hostSessionId = randomUUID();
|
|
39
|
+
this.maxCallDepth = options.maxCallDepth ?? 16;
|
|
40
|
+
this.maxSubRuns = options.maxSubRuns ?? 64;
|
|
41
|
+
this.eventSink = options.eventSink ?? options.platform?.eventSink ?? new InMemoryEventSink();
|
|
42
|
+
// 当指定非内存 dataDir 时获取排他目录锁,防止并发冲突
|
|
43
|
+
if (options.dataDir && !options.inMemory) {
|
|
44
|
+
this.dataDirLock = DataDirLock.acquire(options.dataDir, {
|
|
45
|
+
hostSessionId: this.hostSessionId,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
try {
|
|
49
|
+
// 注册显式传入的 packages 列表
|
|
50
|
+
if (options.packages && Array.isArray(options.packages)) {
|
|
51
|
+
for (const item of options.packages) {
|
|
52
|
+
if (isActionDockApp(item)) {
|
|
53
|
+
this.registerAppInternal(item, true);
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
const app = new DefaultActionDockApp({
|
|
57
|
+
...item,
|
|
58
|
+
hostSessionId: this.hostSessionId,
|
|
59
|
+
platform: item.platform ?? options.platform,
|
|
60
|
+
inMemory: item.inMemory ?? options.inMemory,
|
|
61
|
+
customHome: item.customHome ?? options.customHome,
|
|
62
|
+
dataDir: item.dataDir ?? options.dataDir,
|
|
63
|
+
clock: item.clock ?? options.clock,
|
|
64
|
+
process: item.process ?? options.process,
|
|
65
|
+
logger: item.logger ?? options.logger,
|
|
66
|
+
eventSink: item.eventSink ?? this.eventSink,
|
|
67
|
+
maxCallDepth: item.maxCallDepth ?? this.maxCallDepth,
|
|
68
|
+
maxSubRuns: item.maxSubRuns ?? this.maxSubRuns,
|
|
69
|
+
packageContextResolver: this.resolvePackageContext.bind(this),
|
|
70
|
+
});
|
|
71
|
+
this.internallyCreatedApps.add(app);
|
|
72
|
+
this.registerAppInternal(app, true);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
// 自动加载当前工程(若发现工程根目录且未显式禁用)
|
|
77
|
+
if (options.autoLoadCurrentProject !== false) {
|
|
78
|
+
let root = options.projectRoot;
|
|
79
|
+
if (!root) {
|
|
80
|
+
const detected = findProjectRoot();
|
|
81
|
+
if (detected) {
|
|
82
|
+
root = detected;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (root && existsSync(root)) {
|
|
86
|
+
if (isProjectLockHeld(root)) {
|
|
87
|
+
const err = new Error("PROJECT_BUSY: Project directory is locked by another active process holding project.lock");
|
|
88
|
+
err.code = PROJECT_BUSY;
|
|
89
|
+
throw err;
|
|
90
|
+
}
|
|
91
|
+
if (hasPendingTransactions(root)) {
|
|
92
|
+
const err = new Error(`PROJECT_RECOVERY_REQUIRED: Project directory '${root}' has pending transactions requiring recovery; use async createActionDockHost() to recover automatically`);
|
|
93
|
+
err.code = PROJECT_RECOVERY_REQUIRED;
|
|
94
|
+
throw err;
|
|
95
|
+
}
|
|
96
|
+
try {
|
|
97
|
+
const config = loadProjectConfig(root);
|
|
98
|
+
this.hostPublicPackageIds.add(config.id);
|
|
99
|
+
this.resolver = new ActionPackageResolver({
|
|
100
|
+
projectRoot: root,
|
|
101
|
+
manifest: config,
|
|
102
|
+
allowDevLinks: options.scanLinkedPackages,
|
|
103
|
+
customHome: options.customHome,
|
|
104
|
+
});
|
|
105
|
+
const graph = this.resolver.resolveSync();
|
|
106
|
+
this.hostPublicPackageIds.add(graph.rootPackageId);
|
|
107
|
+
for (const depId of graph.directDependencyIds) {
|
|
108
|
+
this.hostPublicPackageIds.add(depId);
|
|
109
|
+
}
|
|
110
|
+
for (const pkg of graph.packages.values()) {
|
|
111
|
+
if (!this.apps.has(pkg.packageId)) {
|
|
112
|
+
const isDirectOrRoot = this.hostPublicPackageIds.has(pkg.packageId);
|
|
113
|
+
const app = new DefaultActionDockApp({
|
|
114
|
+
packageRoot: pkg.packageRoot,
|
|
115
|
+
projectConfig: pkg.manifest,
|
|
116
|
+
hostSessionId: this.hostSessionId,
|
|
117
|
+
platform: options.platform,
|
|
118
|
+
inMemory: options.inMemory,
|
|
119
|
+
customHome: options.customHome,
|
|
120
|
+
dataDir: options.dataDir,
|
|
121
|
+
clock: options.clock,
|
|
122
|
+
process: options.process,
|
|
123
|
+
logger: options.logger,
|
|
124
|
+
eventSink: this.eventSink,
|
|
125
|
+
maxCallDepth: this.maxCallDepth,
|
|
126
|
+
maxSubRuns: this.maxSubRuns,
|
|
127
|
+
packageContextResolver: this.resolvePackageContext.bind(this),
|
|
128
|
+
});
|
|
129
|
+
this.internallyCreatedApps.add(app);
|
|
130
|
+
this.registerAppInternal(app, isDirectOrRoot);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
catch (err) {
|
|
135
|
+
if (err?.code === ACTION_PACKAGE_VERSION_CONFLICT ||
|
|
136
|
+
err?.code === PROJECT_RECOVERY_REQUIRED ||
|
|
137
|
+
err?.code === PROJECT_BUSY) {
|
|
138
|
+
throw err;
|
|
139
|
+
}
|
|
140
|
+
// 忽略非 ActionDock 工程目录解析异常
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
// 扫描已软链接的外部包并注册至 Host
|
|
145
|
+
if (options.scanLinkedPackages) {
|
|
146
|
+
for (const linked of listLinkedPackages(options.customHome)) {
|
|
147
|
+
if (!this.apps.has(linked.id)) {
|
|
148
|
+
if (!existsSync(linked.path)) {
|
|
149
|
+
const err = `Linked package '${linked.id}' path does not exist on disk: '${linked.path}'`;
|
|
150
|
+
this.failedLinkedPackages.set(linked.id, { path: linked.path, error: err });
|
|
151
|
+
options.logger?.warn?.(`[Host] ${err}`);
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
try {
|
|
155
|
+
const config = loadProjectConfig(linked.path);
|
|
156
|
+
this.hostPublicPackageIds.add(linked.id);
|
|
157
|
+
const app = new DefaultActionDockApp({
|
|
158
|
+
packageRoot: linked.path,
|
|
159
|
+
projectConfig: config,
|
|
160
|
+
hostSessionId: this.hostSessionId,
|
|
161
|
+
platform: options.platform,
|
|
162
|
+
inMemory: options.inMemory,
|
|
163
|
+
customHome: options.customHome,
|
|
164
|
+
dataDir: options.dataDir,
|
|
165
|
+
clock: options.clock,
|
|
166
|
+
process: options.process,
|
|
167
|
+
logger: options.logger,
|
|
168
|
+
eventSink: this.eventSink,
|
|
169
|
+
maxCallDepth: this.maxCallDepth,
|
|
170
|
+
maxSubRuns: this.maxSubRuns,
|
|
171
|
+
packageContextResolver: this.resolvePackageContext.bind(this),
|
|
172
|
+
});
|
|
173
|
+
this.internallyCreatedApps.add(app);
|
|
174
|
+
this.registerAppInternal(app, true);
|
|
175
|
+
}
|
|
176
|
+
catch (err) {
|
|
177
|
+
const errDetail = err?.message || String(err);
|
|
178
|
+
this.failedLinkedPackages.set(linked.id, { path: linked.path, error: errDetail });
|
|
179
|
+
options.logger?.warn?.(`[Host] Failed to load linked package '${linked.id}' from '${linked.path}': ${errDetail}`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
catch (err) {
|
|
186
|
+
try {
|
|
187
|
+
this.dataDirLock?.release();
|
|
188
|
+
}
|
|
189
|
+
catch {
|
|
190
|
+
// 忽略排他锁释放异常
|
|
191
|
+
}
|
|
192
|
+
this.dataDirLock = undefined;
|
|
193
|
+
// 仅安全关闭宿主内部创建的子 app,外部传入的 app 保持调用方生命周期与所有权
|
|
194
|
+
for (const app of this.internallyCreatedApps) {
|
|
195
|
+
try {
|
|
196
|
+
const closePromise = app.close();
|
|
197
|
+
if (closePromise && typeof closePromise.catch === "function") {
|
|
198
|
+
closePromise.catch(() => { });
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
catch {
|
|
202
|
+
// 忽略 app 关闭异常
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
this.internallyCreatedApps.clear();
|
|
206
|
+
this.apps.clear();
|
|
207
|
+
throw err;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
resolvePackageContext(packageId) {
|
|
211
|
+
const targetApp = this.getApp(packageId);
|
|
212
|
+
if (!targetApp)
|
|
213
|
+
return undefined;
|
|
214
|
+
return {
|
|
215
|
+
projectRoot: targetApp.packageRoot,
|
|
216
|
+
projectConfig: targetApp.projectConfig,
|
|
217
|
+
storage: targetApp.storage,
|
|
218
|
+
actions: targetApp.actionsMap,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
bindApp(app) {
|
|
222
|
+
const runner = app.executionService?.runner;
|
|
223
|
+
if (runner && typeof runner.setPackageContextResolver === "function") {
|
|
224
|
+
runner.setPackageContextResolver(this.resolvePackageContext.bind(this));
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
getApp(packageId) {
|
|
228
|
+
return this.apps.get(packageId);
|
|
229
|
+
}
|
|
230
|
+
listApps() {
|
|
231
|
+
return Array.from(this.apps.values());
|
|
232
|
+
}
|
|
233
|
+
registerAppInternal(app, isPublic) {
|
|
234
|
+
if (this.apps.has(app.packageId)) {
|
|
235
|
+
const existing = this.apps.get(app.packageId);
|
|
236
|
+
if (existing === app) {
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
throw new Error(`Package ID conflict: package '${app.packageId}' is already registered in host`);
|
|
240
|
+
}
|
|
241
|
+
this.apps.set(app.packageId, app);
|
|
242
|
+
if (isPublic) {
|
|
243
|
+
this.hostPublicPackageIds.add(app.packageId);
|
|
244
|
+
}
|
|
245
|
+
this.bindApp(app);
|
|
246
|
+
// 接管与恢复:自动将死亡会话或遗留非终态运行收敛为 interrupted
|
|
247
|
+
const st = app.storage;
|
|
248
|
+
if (st && typeof st.recoverDeadSessionRuns === "function") {
|
|
249
|
+
try {
|
|
250
|
+
st.recoverDeadSessionRuns(this.hostSessionId);
|
|
251
|
+
}
|
|
252
|
+
catch {
|
|
253
|
+
// 忽略单包恢复异常
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
else if (st && typeof st.recoverRunningRuns === "function") {
|
|
257
|
+
try {
|
|
258
|
+
st.recoverRunningRuns(this.hostSessionId);
|
|
259
|
+
}
|
|
260
|
+
catch {
|
|
261
|
+
// 忽略单包恢复异常
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
registerApp(app) {
|
|
266
|
+
this.registerAppInternal(app, true);
|
|
267
|
+
}
|
|
268
|
+
async info() {
|
|
269
|
+
return Promise.all(this.listApps().map((app) => app.info()));
|
|
270
|
+
}
|
|
271
|
+
async listActions(options) {
|
|
272
|
+
let results = [];
|
|
273
|
+
const apps = this.listApps();
|
|
274
|
+
for (const app of apps) {
|
|
275
|
+
const isPublic = this.hostPublicPackageIds.has(app.packageId);
|
|
276
|
+
const appSummaries = await app.listActions();
|
|
277
|
+
for (const item of appSummaries) {
|
|
278
|
+
if (!isPublic && this.resolver && !this.resolver.canRootCall(app.packageId, item.id)) {
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
const qualifiedId = apps.length > 1 && !item.id.includes("/")
|
|
282
|
+
? `${app.packageId}/${item.id}`
|
|
283
|
+
: item.id;
|
|
284
|
+
results.push({
|
|
285
|
+
...item,
|
|
286
|
+
id: qualifiedId,
|
|
287
|
+
packageId: app.packageId,
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
if (options?.tags && options.tags.length > 0) {
|
|
292
|
+
results = results.filter((s) => options.tags.every((t) => s.tags?.includes(t)));
|
|
293
|
+
}
|
|
294
|
+
if (options?.query) {
|
|
295
|
+
const q = options.query.toLowerCase();
|
|
296
|
+
results = results.filter((s) => s.id.toLowerCase().includes(q) ||
|
|
297
|
+
(s.description && s.description.toLowerCase().includes(q)));
|
|
298
|
+
}
|
|
299
|
+
if (options?.prefix) {
|
|
300
|
+
results = results.filter((s) => s.id.startsWith(options.prefix));
|
|
301
|
+
}
|
|
302
|
+
return results;
|
|
303
|
+
}
|
|
304
|
+
async describeAction(ref) {
|
|
305
|
+
let parsed;
|
|
306
|
+
try {
|
|
307
|
+
parsed = ActionResolver.parseRef(ref);
|
|
308
|
+
}
|
|
309
|
+
catch {
|
|
310
|
+
parsed = typeof ref === "object" ? ref : { actionId: ref };
|
|
311
|
+
}
|
|
312
|
+
if (parsed.packageId) {
|
|
313
|
+
const isPublic = this.hostPublicPackageIds.has(parsed.packageId);
|
|
314
|
+
if (!isPublic && this.resolver && !this.resolver.canRootCall(parsed.packageId, parsed.actionId)) {
|
|
315
|
+
throw new Error(`UNDECLARED_ACTION_DEPENDENCY: Action '${parsed.packageId}/${parsed.actionId}' is not declared as a direct dependency in actiondock.json and is not delegated by a visible playbook`);
|
|
316
|
+
}
|
|
317
|
+
const app = this.getApp(parsed.packageId);
|
|
318
|
+
if (!app) {
|
|
319
|
+
const failed = this.failedLinkedPackages.get(parsed.packageId);
|
|
320
|
+
if (failed) {
|
|
321
|
+
throw new Error(`Package '${parsed.packageId}' not found in host (failed to load from '${failed.path}': ${failed.error})`);
|
|
322
|
+
}
|
|
323
|
+
throw new Error(`Package '${parsed.packageId}' not found in host`);
|
|
324
|
+
}
|
|
325
|
+
const spec = await app.describeAction(parsed.actionId);
|
|
326
|
+
return {
|
|
327
|
+
...spec,
|
|
328
|
+
packageId: app.packageId,
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
const matches = [];
|
|
332
|
+
for (const app of this.listApps()) {
|
|
333
|
+
try {
|
|
334
|
+
const isPublic = this.hostPublicPackageIds.has(app.packageId);
|
|
335
|
+
if (!isPublic && this.resolver && !this.resolver.canRootCall(app.packageId, parsed.actionId)) {
|
|
336
|
+
continue;
|
|
337
|
+
}
|
|
338
|
+
const spec = await app.describeAction(parsed.actionId);
|
|
339
|
+
matches.push({ app, spec: { ...spec, packageId: app.packageId } });
|
|
340
|
+
}
|
|
341
|
+
catch {
|
|
342
|
+
// 忽略未匹配的包
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
if (matches.length === 1) {
|
|
346
|
+
return {
|
|
347
|
+
...matches[0].spec,
|
|
348
|
+
packageId: matches[0].app.packageId,
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
if (matches.length > 1) {
|
|
352
|
+
const candidates = matches.map((m) => `${m.app.packageId}/${parsed.actionId}`).join(", ");
|
|
353
|
+
const err = new Error(`INVALID_ACTION_REF: Action '${parsed.actionId}' is ambiguous and provided by multiple packages: ${candidates}. Please specify '<package-id>/${parsed.actionId}'. (AMBIGUOUS_ACTION_REF)`);
|
|
354
|
+
err.code = "INVALID_ACTION_REF";
|
|
355
|
+
err.details = { alias: "AMBIGUOUS_ACTION_REF", candidates: matches.map((m) => m.app.packageId) };
|
|
356
|
+
throw err;
|
|
357
|
+
}
|
|
358
|
+
throw new Error(`ACTION_NOT_FOUND: Action '${parsed.actionId}' not found in any registered package`);
|
|
359
|
+
}
|
|
360
|
+
async listPlaybooks() {
|
|
361
|
+
const results = [];
|
|
362
|
+
const apps = this.listApps();
|
|
363
|
+
for (const app of apps) {
|
|
364
|
+
const isPublic = this.hostPublicPackageIds.has(app.packageId);
|
|
365
|
+
if (!isPublic && this.resolver) {
|
|
366
|
+
const graph = this.resolver.resolveSync();
|
|
367
|
+
if (!graph.directDependencyIds.has(app.packageId)) {
|
|
368
|
+
continue;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
const appPlaybooks = await app.listPlaybooks();
|
|
372
|
+
for (const item of appPlaybooks) {
|
|
373
|
+
const qualifiedId = apps.length > 1 && !item.id.includes("/")
|
|
374
|
+
? `${app.packageId}/${item.id}`
|
|
375
|
+
: item.id;
|
|
376
|
+
results.push({
|
|
377
|
+
...item,
|
|
378
|
+
id: qualifiedId,
|
|
379
|
+
packageId: app.packageId,
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
return results;
|
|
384
|
+
}
|
|
385
|
+
async describePlaybook(id) {
|
|
386
|
+
if (id.includes("/")) {
|
|
387
|
+
const lastSlashIndex = id.lastIndexOf("/");
|
|
388
|
+
const packageId = id.slice(0, lastSlashIndex);
|
|
389
|
+
const playbookId = id.slice(lastSlashIndex + 1);
|
|
390
|
+
const isPublic = this.hostPublicPackageIds.has(packageId);
|
|
391
|
+
if (!isPublic && this.resolver) {
|
|
392
|
+
const graph = this.resolver.resolveSync();
|
|
393
|
+
if (!graph.directDependencyIds.has(packageId)) {
|
|
394
|
+
throw new Error(`UNDECLARED_ACTION_DEPENDENCY: Playbook '${id}' belongs to undeclared transitive package '${packageId}'`);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
const app = this.getApp(packageId);
|
|
398
|
+
if (!app) {
|
|
399
|
+
throw new Error(`Package '${packageId}' not found in host`);
|
|
400
|
+
}
|
|
401
|
+
return app.describePlaybook(playbookId);
|
|
402
|
+
}
|
|
403
|
+
for (const app of this.listApps()) {
|
|
404
|
+
try {
|
|
405
|
+
const isPublic = this.hostPublicPackageIds.has(app.packageId);
|
|
406
|
+
if (!isPublic && this.resolver) {
|
|
407
|
+
const graph = this.resolver.resolveSync();
|
|
408
|
+
if (!graph.directDependencyIds.has(app.packageId)) {
|
|
409
|
+
continue;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
return await app.describePlaybook(id);
|
|
413
|
+
}
|
|
414
|
+
catch {
|
|
415
|
+
// 忽略未匹配的包
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
throw new Error(`Playbook '${id}' not found in any registered package`);
|
|
419
|
+
}
|
|
420
|
+
async runAction(ref, input, options = {}) {
|
|
421
|
+
const ticket = await this.startAction(ref, input, options);
|
|
422
|
+
if (!ticket.result) {
|
|
423
|
+
throw new Error(`Execution ticket for run '${ticket.runId}' has no result Promise`);
|
|
424
|
+
}
|
|
425
|
+
return ticket.result;
|
|
426
|
+
}
|
|
427
|
+
async startAction(ref, input, options = {}) {
|
|
428
|
+
if (this.isClosed) {
|
|
429
|
+
throw new Error("ActionDockHost is closed: new tasks rejected");
|
|
430
|
+
}
|
|
431
|
+
// - 解析目标包与 Action 动作标识
|
|
432
|
+
let parsed;
|
|
433
|
+
try {
|
|
434
|
+
parsed = ActionResolver.parseRef(ref);
|
|
435
|
+
}
|
|
436
|
+
catch {
|
|
437
|
+
parsed = typeof ref === "object" ? ref : { actionId: ref };
|
|
438
|
+
}
|
|
439
|
+
let targetApp;
|
|
440
|
+
let targetActionId = parsed.actionId;
|
|
441
|
+
let targetPackageId = parsed.packageId;
|
|
442
|
+
if (targetPackageId) {
|
|
443
|
+
targetApp = this.getApp(targetPackageId);
|
|
444
|
+
if (!targetApp) {
|
|
445
|
+
const runId = randomUUID();
|
|
446
|
+
const failed = this.failedLinkedPackages.get(targetPackageId);
|
|
447
|
+
const errorMsg = failed
|
|
448
|
+
? `Package '${targetPackageId}' not found in host (failed to load from '${failed.path}': ${failed.error})`
|
|
449
|
+
: `Package '${targetPackageId}' not found in host`;
|
|
450
|
+
const error = {
|
|
451
|
+
code: PACKAGE_NOT_FOUND,
|
|
452
|
+
message: errorMsg,
|
|
453
|
+
};
|
|
454
|
+
return {
|
|
455
|
+
runId,
|
|
456
|
+
status: "failed",
|
|
457
|
+
result: Promise.resolve({ ok: false, runId, error }),
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
else {
|
|
462
|
+
const matches = [];
|
|
463
|
+
for (const app of this.listApps()) {
|
|
464
|
+
try {
|
|
465
|
+
const isPublic = this.hostPublicPackageIds.has(app.packageId);
|
|
466
|
+
if (!isPublic && this.resolver && !this.resolver.canRootCall(app.packageId, targetActionId)) {
|
|
467
|
+
continue;
|
|
468
|
+
}
|
|
469
|
+
await app.describeAction(targetActionId);
|
|
470
|
+
matches.push(app);
|
|
471
|
+
}
|
|
472
|
+
catch {
|
|
473
|
+
// 忽略未找到该 Action 的包
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
if (matches.length === 1) {
|
|
477
|
+
targetApp = matches[0];
|
|
478
|
+
targetPackageId = targetApp.packageId;
|
|
479
|
+
}
|
|
480
|
+
else if (matches.length > 1) {
|
|
481
|
+
const candidates = matches.map((m) => `${m.packageId}/${targetActionId}`).join(", ");
|
|
482
|
+
const runId = randomUUID();
|
|
483
|
+
const error = {
|
|
484
|
+
code: INVALID_ACTION_REF,
|
|
485
|
+
message: `Action '${targetActionId}' is ambiguous and provided by multiple packages: ${candidates}. Please specify '<package-id>/${targetActionId}'.`,
|
|
486
|
+
details: { alias: "AMBIGUOUS_ACTION_REF", candidates: matches.map((m) => m.packageId) },
|
|
487
|
+
};
|
|
488
|
+
return {
|
|
489
|
+
runId,
|
|
490
|
+
status: "failed",
|
|
491
|
+
result: Promise.resolve({ ok: false, runId, error }),
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
else {
|
|
495
|
+
const runId = randomUUID();
|
|
496
|
+
const error = {
|
|
497
|
+
code: ACTION_NOT_FOUND,
|
|
498
|
+
message: `Action '${targetActionId}' not found in any registered package`,
|
|
499
|
+
};
|
|
500
|
+
return {
|
|
501
|
+
runId,
|
|
502
|
+
status: "failed",
|
|
503
|
+
result: Promise.resolve({ ok: false, runId, error }),
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
// 根调用可见性鉴权
|
|
508
|
+
const parentRunId = options.parentRunId;
|
|
509
|
+
if (!parentRunId && targetPackageId) {
|
|
510
|
+
const isPublic = this.hostPublicPackageIds.has(targetPackageId);
|
|
511
|
+
if (!isPublic && this.resolver && !this.resolver.canRootCall(targetPackageId, targetActionId)) {
|
|
512
|
+
const runId = randomUUID();
|
|
513
|
+
const error = {
|
|
514
|
+
code: UNDECLARED_ACTION_DEPENDENCY,
|
|
515
|
+
message: `Root call to action '${targetPackageId}/${targetActionId}' is not allowed: package '${targetPackageId}' is not declared as a direct dependency in actiondock.json and is not delegated by a visible playbook`,
|
|
516
|
+
details: {
|
|
517
|
+
target: `${targetPackageId}/${targetActionId}`,
|
|
518
|
+
},
|
|
519
|
+
};
|
|
520
|
+
return {
|
|
521
|
+
runId,
|
|
522
|
+
status: "failed",
|
|
523
|
+
result: Promise.resolve({ ok: false, runId, error }),
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
// - 父子任务血缘关系、调用配额与跨包 uses 声明依赖校验
|
|
528
|
+
let effectiveRootRunId = options.rootRunId;
|
|
529
|
+
if (parentRunId) {
|
|
530
|
+
const parentRun = await this.getRun(parentRunId);
|
|
531
|
+
if (parentRun) {
|
|
532
|
+
effectiveRootRunId = effectiveRootRunId || parentRun.rootRunId || parentRunId;
|
|
533
|
+
// 跨包 uses 依赖声明校验
|
|
534
|
+
const callerPackageId = parentRun.packageId;
|
|
535
|
+
const callerActionId = parentRun.actionId;
|
|
536
|
+
if (callerPackageId && callerPackageId !== targetPackageId) {
|
|
537
|
+
const callerApp = this.getApp(callerPackageId);
|
|
538
|
+
if (callerApp) {
|
|
539
|
+
try {
|
|
540
|
+
const callerSpec = await callerApp.describeAction(callerActionId);
|
|
541
|
+
const usesList = callerSpec.uses || [];
|
|
542
|
+
const targetRef = `${targetPackageId}/${targetActionId}`;
|
|
543
|
+
const isAllowed = this.resolver
|
|
544
|
+
? this.resolver.canCascadeCall(callerPackageId, callerActionId, targetPackageId, targetActionId)
|
|
545
|
+
: usesList.some((u) => u === targetRef || u === `${targetPackageId}/*` || u === targetPackageId);
|
|
546
|
+
if (!isAllowed) {
|
|
547
|
+
const runId = randomUUID();
|
|
548
|
+
const error = {
|
|
549
|
+
code: UNDECLARED_ACTION_DEPENDENCY,
|
|
550
|
+
message: `Action '${callerPackageId}/${callerActionId}' does not declare dependency on '${targetRef}' in 'uses'`,
|
|
551
|
+
details: {
|
|
552
|
+
caller: `${callerPackageId}/${callerActionId}`,
|
|
553
|
+
target: targetRef,
|
|
554
|
+
declaredUses: usesList,
|
|
555
|
+
},
|
|
556
|
+
};
|
|
557
|
+
return {
|
|
558
|
+
runId,
|
|
559
|
+
status: "failed",
|
|
560
|
+
result: Promise.resolve({ ok: false, runId, error }),
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
catch {
|
|
565
|
+
// 忽略规范提取异常,交由执行服务执行
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
// 调用嵌套深度限制校验
|
|
570
|
+
let depth = 1;
|
|
571
|
+
let cur = parentRun;
|
|
572
|
+
while (cur && cur.parentRunId) {
|
|
573
|
+
depth++;
|
|
574
|
+
if (depth > this.maxCallDepth)
|
|
575
|
+
break;
|
|
576
|
+
cur = await this.getRun(cur.parentRunId);
|
|
577
|
+
}
|
|
578
|
+
if (depth >= this.maxCallDepth) {
|
|
579
|
+
const runId = randomUUID();
|
|
580
|
+
const error = {
|
|
581
|
+
code: ACTION_CALL_CYCLE,
|
|
582
|
+
message: `Maximum call depth of ${this.maxCallDepth} exceeded`,
|
|
583
|
+
details: { alias: ACTION_MAX_DEPTH_EXCEEDED, reason: "depth_exceeded", maxDepth: this.maxCallDepth },
|
|
584
|
+
};
|
|
585
|
+
return {
|
|
586
|
+
runId,
|
|
587
|
+
status: "failed",
|
|
588
|
+
result: Promise.resolve({ ok: false, runId, error }),
|
|
589
|
+
};
|
|
590
|
+
}
|
|
591
|
+
// 针对根运行的并发子任务数限制校验
|
|
592
|
+
if (effectiveRootRunId) {
|
|
593
|
+
const currentSubRuns = this.activeSubRunsPerRoot.get(effectiveRootRunId) || 0;
|
|
594
|
+
if (currentSubRuns >= this.maxSubRuns) {
|
|
595
|
+
const runId = randomUUID();
|
|
596
|
+
const error = {
|
|
597
|
+
code: ACTION_SUBRUN_LIMIT,
|
|
598
|
+
message: `Maximum concurrent sub-runs (${this.maxSubRuns}) reached for root run '${effectiveRootRunId}'`,
|
|
599
|
+
details: { alias: MAX_SUBRUNS_REACHED, limit: this.maxSubRuns },
|
|
600
|
+
};
|
|
601
|
+
return {
|
|
602
|
+
runId,
|
|
603
|
+
status: "failed",
|
|
604
|
+
result: Promise.resolve({ ok: false, runId, error }),
|
|
605
|
+
};
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
// - 构造子运行参数并调度至目标 App 执行
|
|
611
|
+
const execOptions = {
|
|
612
|
+
...options,
|
|
613
|
+
rootRunId: effectiveRootRunId,
|
|
614
|
+
parentRunId,
|
|
615
|
+
hostSessionId: this.hostSessionId,
|
|
616
|
+
maxCallDepth: options.maxCallDepth ?? this.maxCallDepth,
|
|
617
|
+
};
|
|
618
|
+
if (parentRunId && effectiveRootRunId) {
|
|
619
|
+
this.activeSubRunsPerRoot.set(effectiveRootRunId, (this.activeSubRunsPerRoot.get(effectiveRootRunId) || 0) + 1);
|
|
620
|
+
}
|
|
621
|
+
const ticket = await targetApp.startAction(targetActionId, input, execOptions);
|
|
622
|
+
if (parentRunId && effectiveRootRunId && ticket.result) {
|
|
623
|
+
const rootId = effectiveRootRunId;
|
|
624
|
+
ticket.result = ticket.result.finally(() => {
|
|
625
|
+
const cnt = this.activeSubRunsPerRoot.get(rootId) || 1;
|
|
626
|
+
if (cnt <= 1) {
|
|
627
|
+
this.activeSubRunsPerRoot.delete(rootId);
|
|
628
|
+
}
|
|
629
|
+
else {
|
|
630
|
+
this.activeSubRunsPerRoot.set(rootId, cnt - 1);
|
|
631
|
+
}
|
|
632
|
+
});
|
|
633
|
+
}
|
|
634
|
+
return ticket;
|
|
635
|
+
}
|
|
636
|
+
async getRun(runId) {
|
|
637
|
+
for (const app of this.listApps()) {
|
|
638
|
+
const record = await app.getRun(runId);
|
|
639
|
+
if (record)
|
|
640
|
+
return record;
|
|
641
|
+
}
|
|
642
|
+
return undefined;
|
|
643
|
+
}
|
|
644
|
+
async cancelRun(runId, reason) {
|
|
645
|
+
for (const app of this.listApps()) {
|
|
646
|
+
const res = await app.cancelRun(runId, reason);
|
|
647
|
+
if (res.outcome !== "not_found") {
|
|
648
|
+
return res;
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
return { outcome: "not_found", runId };
|
|
652
|
+
}
|
|
653
|
+
events(runId, options) {
|
|
654
|
+
for (const app of this.listApps()) {
|
|
655
|
+
if (app.executionService?.getActiveHandle?.(runId)) {
|
|
656
|
+
return app.events(runId, options);
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
for (const app of this.listApps()) {
|
|
660
|
+
const storageRecord = app.storage?.getRun?.(runId);
|
|
661
|
+
if (storageRecord) {
|
|
662
|
+
return app.events(runId, options);
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
const firstApp = this.listApps()[0];
|
|
666
|
+
if (firstApp) {
|
|
667
|
+
return firstApp.events(runId, options);
|
|
668
|
+
}
|
|
669
|
+
return (async function* () { })();
|
|
670
|
+
}
|
|
671
|
+
/**
|
|
672
|
+
* 优雅关闭宿主容器。
|
|
673
|
+
* 仅对内部创建的 App 实例执行 close 并安全释放底层资源;
|
|
674
|
+
* 外部传入借用的 App 实例生命周期完全由调用方负责管理,Host 关闭时仅解绑引用并清理自身内部实例。
|
|
675
|
+
*/
|
|
676
|
+
async close(options) {
|
|
677
|
+
if (this.isClosed)
|
|
678
|
+
return;
|
|
679
|
+
this.isClosed = true;
|
|
680
|
+
const internalApps = Array.from(this.internallyCreatedApps);
|
|
681
|
+
await Promise.all(internalApps.map(async (app) => {
|
|
682
|
+
try {
|
|
683
|
+
await app.close(options);
|
|
684
|
+
}
|
|
685
|
+
catch {
|
|
686
|
+
// 忽略内部 App 关闭异常,确保全部安全释放
|
|
687
|
+
}
|
|
688
|
+
}));
|
|
689
|
+
this.internallyCreatedApps.clear();
|
|
690
|
+
this.apps.clear();
|
|
691
|
+
try {
|
|
692
|
+
this.dataDirLock?.release();
|
|
693
|
+
}
|
|
694
|
+
catch {
|
|
695
|
+
// 忽略目录排他锁释放异常
|
|
696
|
+
}
|
|
697
|
+
this.dataDirLock = undefined;
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
/**
|
|
701
|
+
* 工厂函数:创建并初始化 ActionDockHost 宿主容器。
|
|
702
|
+
*/
|
|
703
|
+
export async function createActionDockHost(options = {}) {
|
|
704
|
+
let createdHost;
|
|
705
|
+
try {
|
|
706
|
+
if (options.autoLoadCurrentProject !== false) {
|
|
707
|
+
let root = options.projectRoot;
|
|
708
|
+
if (!root) {
|
|
709
|
+
const detected = findProjectRoot();
|
|
710
|
+
if (detected) {
|
|
711
|
+
root = detected;
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
if (root && existsSync(root)) {
|
|
715
|
+
if (isProjectLockHeld(root)) {
|
|
716
|
+
const err = new Error("PROJECT_BUSY: Project directory is locked by another active process holding project.lock");
|
|
717
|
+
err.code = PROJECT_BUSY;
|
|
718
|
+
throw err;
|
|
719
|
+
}
|
|
720
|
+
// 依据事务日志恢复未完成提交的悬空事务(锁持有者存活时严禁判定为崩溃事务并禁止自动恢复)
|
|
721
|
+
if (hasPendingTransactions(root)) {
|
|
722
|
+
await recoverPendingTransactions(root, { frozenInstall: true });
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
createdHost = new DefaultActionDockHost(options);
|
|
727
|
+
return createdHost;
|
|
728
|
+
}
|
|
729
|
+
catch (err) {
|
|
730
|
+
if (createdHost) {
|
|
731
|
+
try {
|
|
732
|
+
await createdHost.close();
|
|
733
|
+
}
|
|
734
|
+
catch {
|
|
735
|
+
// 忽略宿主关闭异常
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
throw err;
|
|
739
|
+
}
|
|
740
|
+
}
|