@actiondock/core 2.2.2 → 2.3.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/README.md +8 -3
- package/dist/app/app.d.ts +2 -0
- package/dist/app/app.js +6 -0
- package/dist/app/types.d.ts +12 -0
- package/dist/errors.d.ts +38 -0
- package/dist/errors.js +44 -0
- package/dist/execution/service.d.ts +2 -0
- package/dist/execution/service.js +13 -2
- package/dist/execution/types.d.ts +17 -0
- package/dist/host/host.js +46 -5
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/ipc/host.d.ts +5 -0
- package/dist/ipc/host.js +43 -1
- package/dist/ipc/target.d.ts +18 -0
- package/dist/ipc/target.js +74 -6
- package/dist/ipc/types.d.ts +10 -1
- package/dist/platform/default.d.ts +7 -1
- package/dist/platform/default.js +40 -2
- package/dist/process/context-process.d.ts +83 -0
- package/dist/process/context-process.js +168 -0
- package/dist/process/cursor.d.ts +52 -0
- package/dist/process/cursor.js +144 -0
- package/dist/process/driver.d.ts +214 -0
- package/dist/process/driver.js +197 -0
- package/dist/process/index.d.ts +6 -0
- package/dist/process/index.js +6 -0
- package/dist/process/metadata-store.d.ts +205 -0
- package/dist/process/metadata-store.js +462 -0
- package/dist/process/output-log.d.ts +136 -0
- package/dist/process/output-log.js +331 -0
- package/dist/process/process-manager.d.ts +279 -0
- package/dist/process/process-manager.js +1879 -0
- package/dist/profile/client.js +63 -21
- package/dist/project/init.js +2 -2
- package/dist/registry/registry.js +12 -3
- package/dist/runtime/context.d.ts +9 -6
- package/dist/runtime/context.js +31 -8
- package/dist/runtime/process.d.ts +19 -2
- package/dist/runtime/process.js +103 -131
- package/dist/runtime/runner.d.ts +45 -24
- package/dist/runtime/runner.js +106 -25
- package/dist/target/remote.js +7 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -68,11 +68,16 @@ ActionDock 2.0 核心领域模型与调度引擎。
|
|
|
68
68
|
- `transaction<T>(fn: () => T): T`:同步事务执行器,在出现异常时自动回滚,并在驱动层严格拦截异步 Promise 以避免事务泄漏。
|
|
69
69
|
- `close(): void`:释放数据库连接与文件句柄。
|
|
70
70
|
|
|
71
|
-
###
|
|
71
|
+
### ProcessDriver 进程驱动契约与受管进程架构
|
|
72
72
|
|
|
73
|
-
|
|
73
|
+
Core 层内置完整的工业级受管进程治理体系,全面解耦具体操作系统运行时:
|
|
74
74
|
|
|
75
|
-
-
|
|
75
|
+
- ProcessManager 进程调度引擎:承载受管进程全生命周期状态机(`starting`、`running`、`stopping`、`exited`、`failed`、`lost`),提供独占控制权令牌分配与续租排队、有界环形输出日志管理、输入队列异步调度、资源硬性限额与宿主/作用域配额审计。
|
|
76
|
+
- ContextProcessAPI 运行上下文适配器:将 ProcessAPI 绑定至具体的 ActionContext 执行链路,自动注入所有者身份与运行标识,跟踪持有的控制令牌;当 Run 结束或异常而未显式释放控制权时,自动触发目标进程隔离或终止。
|
|
77
|
+
- ProcessDriver 平台驱动契约:定义平台底层派生与进程控制的抽象接口(`getCapabilities`、`spawn`、`write`、`inputEOF`、`terminate` 等),屏蔽各操作系统底层实现差异。
|
|
78
|
+
- ProcessOutputLog 有界环形输出日志:单进程独立的有界内存日志缓冲区,支持基于字节游标的分页查询与长轮询等待,自动识别缓冲区淘汰断层。
|
|
79
|
+
- ProcessMetadataStore 元数据存储接口:提供进程运行记录与幂等请求键索引的持久化抽象,默认提供纯内存实现 MemoryProcessMetadataStore。
|
|
80
|
+
- ProcessExecutor 进程执行器接口:抽象跨平台的子进程操作,向后兼容一次性命令执行。
|
|
76
81
|
|
|
77
82
|
---
|
|
78
83
|
|
package/dist/app/app.d.ts
CHANGED
|
@@ -10,6 +10,8 @@ import type { ActionDockApp, ActionDockAppOptions, ActionSpec, ActionSummary, Co
|
|
|
10
10
|
*/
|
|
11
11
|
export declare class DefaultActionDockApp implements ActionDockApp {
|
|
12
12
|
readonly packageId: string;
|
|
13
|
+
readonly packageInstanceId: string;
|
|
14
|
+
readonly generationId: string;
|
|
13
15
|
readonly packageRoot?: string;
|
|
14
16
|
readonly projectConfig: ProjectConfig;
|
|
15
17
|
readonly platform: RuntimePlatform;
|
package/dist/app/app.js
CHANGED
|
@@ -15,6 +15,8 @@ import { decodeStateKey, SqliteRuntimeStorage } from "../storage/sqlite.js";
|
|
|
15
15
|
*/
|
|
16
16
|
export class DefaultActionDockApp {
|
|
17
17
|
packageId;
|
|
18
|
+
packageInstanceId;
|
|
19
|
+
generationId;
|
|
18
20
|
packageRoot;
|
|
19
21
|
projectConfig;
|
|
20
22
|
platform;
|
|
@@ -57,6 +59,8 @@ export class DefaultActionDockApp {
|
|
|
57
59
|
this.packageRoot = packageRoot;
|
|
58
60
|
this.projectConfig = projectConfig;
|
|
59
61
|
this.packageId = projectConfig.id;
|
|
62
|
+
this.packageInstanceId = options.packageInstanceId || projectConfig.packageInstanceId || this.packageId;
|
|
63
|
+
this.generationId = options.generationId || projectConfig.generationId || "1";
|
|
60
64
|
// 2. 转换 Action 集合:委托归一化单一入口
|
|
61
65
|
this.actionsMap = normalizeActionCollection(options.actions).actionsMap;
|
|
62
66
|
// 3. 确定平台适配层
|
|
@@ -122,6 +126,8 @@ export class DefaultActionDockApp {
|
|
|
122
126
|
// 6. 初始化唯一执行协调服务
|
|
123
127
|
this.executionService = new DefaultExecutionService({
|
|
124
128
|
packageId: this.packageId,
|
|
129
|
+
packageInstanceId: this.packageInstanceId,
|
|
130
|
+
generationId: this.generationId,
|
|
125
131
|
hostSessionId: options.hostSessionId,
|
|
126
132
|
storage: this.storage,
|
|
127
133
|
globalStorage: this.globalStorage,
|
package/dist/app/types.d.ts
CHANGED
|
@@ -173,12 +173,20 @@ export interface ActionDockAppOptions {
|
|
|
173
173
|
projectConfig?: ProjectConfig;
|
|
174
174
|
storage: RuntimeStorage;
|
|
175
175
|
actions?: Map<string, ActionDefinition>;
|
|
176
|
+
packageInstanceId?: string;
|
|
177
|
+
generationId?: string;
|
|
176
178
|
} | undefined> | {
|
|
177
179
|
projectRoot?: string;
|
|
178
180
|
projectConfig?: ProjectConfig;
|
|
179
181
|
storage: RuntimeStorage;
|
|
180
182
|
actions?: Map<string, ActionDefinition>;
|
|
183
|
+
packageInstanceId?: string;
|
|
184
|
+
generationId?: string;
|
|
181
185
|
} | undefined;
|
|
186
|
+
/** 包物理实例标识 */
|
|
187
|
+
packageInstanceId?: string;
|
|
188
|
+
/** 快照代次标识 */
|
|
189
|
+
generationId?: string;
|
|
182
190
|
/** 临时配置覆写字典 */
|
|
183
191
|
configOverrides?: Record<string, unknown>;
|
|
184
192
|
/** 宿主所有者标识 */
|
|
@@ -207,6 +215,10 @@ export interface ActionDockAppOptions {
|
|
|
207
215
|
export interface ActionDockApp {
|
|
208
216
|
/** 包唯一标识 */
|
|
209
217
|
readonly packageId: string;
|
|
218
|
+
/** 包物理实例标识 */
|
|
219
|
+
readonly packageInstanceId?: string;
|
|
220
|
+
/** 快照代次标识 */
|
|
221
|
+
readonly generationId?: string;
|
|
210
222
|
/** 包根目录绝对物理路径 */
|
|
211
223
|
readonly packageRoot?: string;
|
|
212
224
|
/** 项目配置对象(actiondock.json 解析结果) */
|
package/dist/errors.d.ts
CHANGED
|
@@ -120,3 +120,41 @@ export declare function describeActionLoadFailure(err: unknown, context: ActionL
|
|
|
120
120
|
hint: string | undefined;
|
|
121
121
|
};
|
|
122
122
|
};
|
|
123
|
+
/** 访问被拒绝或缺少必要权限 */
|
|
124
|
+
export declare const ACCESS_DENIED = "ACCESS_DENIED";
|
|
125
|
+
/** 目标运行环境不支持所声明的能力 */
|
|
126
|
+
export declare const UNSUPPORTED_CAPABILITY = "UNSUPPORTED_CAPABILITY";
|
|
127
|
+
/** 控制权已被其他调用方持有处于忙碌状态 */
|
|
128
|
+
export declare const CONTROL_BUSY = "CONTROL_BUSY";
|
|
129
|
+
/** 控制权有效租约已过期 */
|
|
130
|
+
export declare const CONTROL_EXPIRED = "CONTROL_EXPIRED";
|
|
131
|
+
/** 控制权已被主动撤销或强制收回 */
|
|
132
|
+
export declare const CONTROL_REVOKED = "CONTROL_REVOKED";
|
|
133
|
+
/** 进程由于异常控制失效已进入隔离状态 */
|
|
134
|
+
export declare const PROCESS_QUARANTINED = "PROCESS_QUARANTINED";
|
|
135
|
+
/** 进程已被宿主标记丢失且状态不可恢复 */
|
|
136
|
+
export declare const PROCESS_LOST = "PROCESS_LOST";
|
|
137
|
+
/** 具有相同请求标识但携带不同负载的冲突操作 */
|
|
138
|
+
export declare const REQUEST_CONFLICT = "REQUEST_CONFLICT";
|
|
139
|
+
/** 输入交付结果不确定 */
|
|
140
|
+
export declare const INPUT_OUTCOME_UNKNOWN = "INPUT_OUTCOME_UNKNOWN";
|
|
141
|
+
/** 输出日志发生淘汰缺口且策略要求报错阻断 */
|
|
142
|
+
export declare const OUTPUT_GAP = "OUTPUT_GAP";
|
|
143
|
+
/** 历史输出已不可用 */
|
|
144
|
+
export declare const OUTPUT_UNAVAILABLE = "OUTPUT_UNAVAILABLE";
|
|
145
|
+
/** 游标格式错误或超出有效范围 */
|
|
146
|
+
export declare const INVALID_CURSOR = "INVALID_CURSOR";
|
|
147
|
+
/** 输入待写入队列已达容量上限 */
|
|
148
|
+
export declare const QUEUE_FULL = "QUEUE_FULL";
|
|
149
|
+
/** 资源使用超出配额限制 */
|
|
150
|
+
export declare const QUOTA_EXCEEDED = "QUOTA_EXCEEDED";
|
|
151
|
+
/** 输入通道已关闭拒绝继续写入 */
|
|
152
|
+
export declare const INPUT_CLOSED = "INPUT_CLOSED";
|
|
153
|
+
/**
|
|
154
|
+
* 进程领域结构化异常类。
|
|
155
|
+
*/
|
|
156
|
+
export declare class ProcessError extends Error {
|
|
157
|
+
readonly code: string;
|
|
158
|
+
readonly details?: Record<string, unknown>;
|
|
159
|
+
constructor(code: string, message: string, details?: Record<string, unknown>);
|
|
160
|
+
}
|
package/dist/errors.js
CHANGED
|
@@ -121,3 +121,47 @@ export function describeActionLoadFailure(err, context) {
|
|
|
121
121
|
},
|
|
122
122
|
};
|
|
123
123
|
}
|
|
124
|
+
/** 访问被拒绝或缺少必要权限 */
|
|
125
|
+
export const ACCESS_DENIED = "ACCESS_DENIED";
|
|
126
|
+
/** 目标运行环境不支持所声明的能力 */
|
|
127
|
+
export const UNSUPPORTED_CAPABILITY = "UNSUPPORTED_CAPABILITY";
|
|
128
|
+
/** 控制权已被其他调用方持有处于忙碌状态 */
|
|
129
|
+
export const CONTROL_BUSY = "CONTROL_BUSY";
|
|
130
|
+
/** 控制权有效租约已过期 */
|
|
131
|
+
export const CONTROL_EXPIRED = "CONTROL_EXPIRED";
|
|
132
|
+
/** 控制权已被主动撤销或强制收回 */
|
|
133
|
+
export const CONTROL_REVOKED = "CONTROL_REVOKED";
|
|
134
|
+
/** 进程由于异常控制失效已进入隔离状态 */
|
|
135
|
+
export const PROCESS_QUARANTINED = "PROCESS_QUARANTINED";
|
|
136
|
+
/** 进程已被宿主标记丢失且状态不可恢复 */
|
|
137
|
+
export const PROCESS_LOST = "PROCESS_LOST";
|
|
138
|
+
/** 具有相同请求标识但携带不同负载的冲突操作 */
|
|
139
|
+
export const REQUEST_CONFLICT = "REQUEST_CONFLICT";
|
|
140
|
+
/** 输入交付结果不确定 */
|
|
141
|
+
export const INPUT_OUTCOME_UNKNOWN = "INPUT_OUTCOME_UNKNOWN";
|
|
142
|
+
/** 输出日志发生淘汰缺口且策略要求报错阻断 */
|
|
143
|
+
export const OUTPUT_GAP = "OUTPUT_GAP";
|
|
144
|
+
/** 历史输出已不可用 */
|
|
145
|
+
export const OUTPUT_UNAVAILABLE = "OUTPUT_UNAVAILABLE";
|
|
146
|
+
/** 游标格式错误或超出有效范围 */
|
|
147
|
+
export const INVALID_CURSOR = "INVALID_CURSOR";
|
|
148
|
+
/** 输入待写入队列已达容量上限 */
|
|
149
|
+
export const QUEUE_FULL = "QUEUE_FULL";
|
|
150
|
+
/** 资源使用超出配额限制 */
|
|
151
|
+
export const QUOTA_EXCEEDED = "QUOTA_EXCEEDED";
|
|
152
|
+
/** 输入通道已关闭拒绝继续写入 */
|
|
153
|
+
export const INPUT_CLOSED = "INPUT_CLOSED";
|
|
154
|
+
/**
|
|
155
|
+
* 进程领域结构化异常类。
|
|
156
|
+
*/
|
|
157
|
+
export class ProcessError extends Error {
|
|
158
|
+
code;
|
|
159
|
+
details;
|
|
160
|
+
constructor(code, message, details) {
|
|
161
|
+
super(message);
|
|
162
|
+
this.name = "ProcessError";
|
|
163
|
+
this.code = code;
|
|
164
|
+
this.details = details;
|
|
165
|
+
Object.setPrototypeOf(this, ProcessError.prototype);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
@@ -11,6 +11,8 @@ export declare class DefaultExecutionService implements ExecutionService {
|
|
|
11
11
|
private storage;
|
|
12
12
|
private projectConfig?;
|
|
13
13
|
readonly eventSink: EventSink;
|
|
14
|
+
readonly packageInstanceId: string;
|
|
15
|
+
readonly generationId: string;
|
|
14
16
|
private maxActiveRuns;
|
|
15
17
|
private ownerId;
|
|
16
18
|
hostSessionId?: string;
|
|
@@ -12,6 +12,8 @@ export class DefaultExecutionService {
|
|
|
12
12
|
storage;
|
|
13
13
|
projectConfig;
|
|
14
14
|
eventSink;
|
|
15
|
+
packageInstanceId;
|
|
16
|
+
generationId;
|
|
15
17
|
maxActiveRuns;
|
|
16
18
|
ownerId;
|
|
17
19
|
hostSessionId;
|
|
@@ -30,6 +32,8 @@ export class DefaultExecutionService {
|
|
|
30
32
|
constructor(options) {
|
|
31
33
|
this.platform = options.platform;
|
|
32
34
|
this.packageId = options.packageId;
|
|
35
|
+
this.packageInstanceId = options.packageInstanceId || this.packageId;
|
|
36
|
+
this.generationId = options.generationId || "1";
|
|
33
37
|
this.hostSessionId = options.hostSessionId;
|
|
34
38
|
this.projectConfig = options.projectConfig;
|
|
35
39
|
this.eventSink = options.eventSink || options.platform?.eventSink || new InMemoryEventSink();
|
|
@@ -63,6 +67,8 @@ export class DefaultExecutionService {
|
|
|
63
67
|
this.globalStorage = globalStorage;
|
|
64
68
|
this._runner = new ActionRunner({
|
|
65
69
|
packageId: this.packageId,
|
|
70
|
+
packageInstanceId: this.packageInstanceId,
|
|
71
|
+
generationId: this.generationId,
|
|
66
72
|
hostSessionId: this.hostSessionId,
|
|
67
73
|
storage: this.storage,
|
|
68
74
|
globalStorage,
|
|
@@ -220,6 +226,11 @@ export class DefaultExecutionService {
|
|
|
220
226
|
logger: bridge.executionLogger,
|
|
221
227
|
process: options.process || options.platform?.process || this.process,
|
|
222
228
|
platform: options.platform || this.platform,
|
|
229
|
+
packageInstanceId: options.packageInstanceId,
|
|
230
|
+
generationId: options.generationId,
|
|
231
|
+
tenantId: options.tenantId,
|
|
232
|
+
principalId: options.principalId,
|
|
233
|
+
owner: options.owner,
|
|
223
234
|
});
|
|
224
235
|
const activeItem = {
|
|
225
236
|
runId: handle.runId,
|
|
@@ -394,9 +405,9 @@ export class DefaultExecutionService {
|
|
|
394
405
|
rootRunId,
|
|
395
406
|
parentRunId: options.parentRunId,
|
|
396
407
|
packageId: targetPackageId,
|
|
397
|
-
packageInstanceId: targetPackageId,
|
|
408
|
+
packageInstanceId: options.packageInstanceId || target.runner.packageInstanceId || targetPackageId,
|
|
398
409
|
actionId: targetActionId,
|
|
399
|
-
generationId: "1",
|
|
410
|
+
generationId: options.generationId || target.runner.generationId || "1",
|
|
400
411
|
ownerId: this.ownerId,
|
|
401
412
|
hostSessionId: options.hostSessionId || this.hostSessionId,
|
|
402
413
|
status: "failed",
|
|
@@ -5,6 +5,7 @@ import type { Clock } from "../runtime/clock.js";
|
|
|
5
5
|
import type { EventSink } from "../runtime/events.js";
|
|
6
6
|
import type { RuntimeStorage } from "../storage/types.js";
|
|
7
7
|
import type { RuntimePlatform } from "../platform/types.js";
|
|
8
|
+
import type { ProcessOwner } from "../process/process-manager.js";
|
|
8
9
|
/**
|
|
9
10
|
* 执行参数选项。
|
|
10
11
|
*/
|
|
@@ -33,6 +34,16 @@ export interface ExecuteOptions {
|
|
|
33
34
|
platform?: RuntimePlatform;
|
|
34
35
|
/** 执行宿主会话标识 */
|
|
35
36
|
hostSessionId?: string;
|
|
37
|
+
/** 包物理实例标识 */
|
|
38
|
+
packageInstanceId?: string;
|
|
39
|
+
/** 快照代次标识 */
|
|
40
|
+
generationId?: string;
|
|
41
|
+
/** 租户标识 */
|
|
42
|
+
tenantId?: string;
|
|
43
|
+
/** 主体标识 */
|
|
44
|
+
principalId?: string;
|
|
45
|
+
/** 执行归属所有者契约 */
|
|
46
|
+
owner?: ProcessOwner;
|
|
36
47
|
}
|
|
37
48
|
/**
|
|
38
49
|
* 统一执行协调服务配置选项。
|
|
@@ -55,17 +66,23 @@ export interface ExecutionServiceOptions {
|
|
|
55
66
|
maxSubRuns?: number;
|
|
56
67
|
ownerId?: string;
|
|
57
68
|
actionResolver?: (ref: ActionRef | string) => ActionDefinition | undefined | Promise<ActionDefinition | undefined>;
|
|
69
|
+
packageInstanceId?: string;
|
|
70
|
+
generationId?: string;
|
|
58
71
|
getStorageForPackage?: (packageId: string, projectRoot?: string) => RuntimeStorage;
|
|
59
72
|
packageContextResolver?: (packageId: string) => Promise<{
|
|
60
73
|
projectRoot?: string;
|
|
61
74
|
projectConfig?: ProjectConfig;
|
|
62
75
|
storage: RuntimeStorage;
|
|
63
76
|
actions?: Map<string, ActionDefinition>;
|
|
77
|
+
packageInstanceId?: string;
|
|
78
|
+
generationId?: string;
|
|
64
79
|
} | undefined> | {
|
|
65
80
|
projectRoot?: string;
|
|
66
81
|
projectConfig?: ProjectConfig;
|
|
67
82
|
storage: RuntimeStorage;
|
|
68
83
|
actions?: Map<string, ActionDefinition>;
|
|
84
|
+
packageInstanceId?: string;
|
|
85
|
+
generationId?: string;
|
|
69
86
|
} | undefined;
|
|
70
87
|
customHome?: string;
|
|
71
88
|
platform?: RuntimePlatform;
|
package/dist/host/host.js
CHANGED
|
@@ -17,6 +17,23 @@ function isActionDockApp(item) {
|
|
|
17
17
|
"runAction" in item &&
|
|
18
18
|
typeof item.runAction === "function");
|
|
19
19
|
}
|
|
20
|
+
/**
|
|
21
|
+
* 判定异常是否为 not-found 语义(包内确实不存在该 Action)。
|
|
22
|
+
*
|
|
23
|
+
* 遍历匹配短标识符时仅跳过此类错误;存储损坏、模块加载失败等内部错误必须向调用方透传,
|
|
24
|
+
* 严禁被吞没后伪装成 ACTION_NOT_FOUND。
|
|
25
|
+
*/
|
|
26
|
+
function isActionNotFoundLikeError(err) {
|
|
27
|
+
if (!err) {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
if (err.code === ACTION_NOT_FOUND || err.code === "NOT_FOUND") {
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
const message = typeof err.message === "string" ? err.message : String(err);
|
|
34
|
+
return (message.startsWith("ACTION_NOT_FOUND:") ||
|
|
35
|
+
/Action '[^']*' not found in package/.test(message));
|
|
36
|
+
}
|
|
20
37
|
/**
|
|
21
38
|
* ActionDock 统一多包宿主容器默认实现。
|
|
22
39
|
* 负责聚合与调度多个 ActionDockApp 实例,提供跨包引用路由、依赖声明校验与资源配额控制。
|
|
@@ -216,6 +233,8 @@ export class DefaultActionDockHost {
|
|
|
216
233
|
projectConfig: targetApp.projectConfig,
|
|
217
234
|
storage: targetApp.storage,
|
|
218
235
|
actions: targetApp.actionsMap,
|
|
236
|
+
packageInstanceId: targetApp.packageInstanceId,
|
|
237
|
+
generationId: targetApp.generationId,
|
|
219
238
|
};
|
|
220
239
|
}
|
|
221
240
|
bindApp(app) {
|
|
@@ -338,8 +357,11 @@ export class DefaultActionDockHost {
|
|
|
338
357
|
const spec = await app.describeAction(parsed.actionId);
|
|
339
358
|
matches.push({ app, spec: { ...spec, packageId: app.packageId } });
|
|
340
359
|
}
|
|
341
|
-
catch {
|
|
342
|
-
//
|
|
360
|
+
catch (err) {
|
|
361
|
+
// 仅将 not-found 语义视为「包内无此 Action」;其余异常必须透传,避免内部错误伪装成 ACTION_NOT_FOUND
|
|
362
|
+
if (!isActionNotFoundLikeError(err)) {
|
|
363
|
+
throw err;
|
|
364
|
+
}
|
|
343
365
|
}
|
|
344
366
|
}
|
|
345
367
|
if (matches.length === 1) {
|
|
@@ -469,8 +491,11 @@ export class DefaultActionDockHost {
|
|
|
469
491
|
await app.describeAction(targetActionId);
|
|
470
492
|
matches.push(app);
|
|
471
493
|
}
|
|
472
|
-
catch {
|
|
473
|
-
//
|
|
494
|
+
catch (err) {
|
|
495
|
+
// 仅将 not-found 语义视为「包内无此 Action」;其余异常必须透传,避免内部错误伪装成 ACTION_NOT_FOUND
|
|
496
|
+
if (!isActionNotFoundLikeError(err)) {
|
|
497
|
+
throw err;
|
|
498
|
+
}
|
|
474
499
|
}
|
|
475
500
|
}
|
|
476
501
|
if (matches.length === 1) {
|
|
@@ -618,7 +643,23 @@ export class DefaultActionDockHost {
|
|
|
618
643
|
if (parentRunId && effectiveRootRunId) {
|
|
619
644
|
this.activeSubRunsPerRoot.set(effectiveRootRunId, (this.activeSubRunsPerRoot.get(effectiveRootRunId) || 0) + 1);
|
|
620
645
|
}
|
|
621
|
-
|
|
646
|
+
let ticket;
|
|
647
|
+
try {
|
|
648
|
+
ticket = await targetApp.startAction(targetActionId, input, execOptions);
|
|
649
|
+
}
|
|
650
|
+
catch (err) {
|
|
651
|
+
// 启动失败(并发上限、仓储不可用、幂等冲突等)时必须回滚配额计数,避免泄漏后误拒后续合法子任务
|
|
652
|
+
if (parentRunId && effectiveRootRunId) {
|
|
653
|
+
const cnt = this.activeSubRunsPerRoot.get(effectiveRootRunId) || 1;
|
|
654
|
+
if (cnt <= 1) {
|
|
655
|
+
this.activeSubRunsPerRoot.delete(effectiveRootRunId);
|
|
656
|
+
}
|
|
657
|
+
else {
|
|
658
|
+
this.activeSubRunsPerRoot.set(effectiveRootRunId, cnt - 1);
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
throw err;
|
|
662
|
+
}
|
|
622
663
|
if (parentRunId && effectiveRootRunId && ticket.result) {
|
|
623
664
|
const rootId = effectiveRootRunId;
|
|
624
665
|
ticket.result = ticket.result.finally(() => {
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
package/dist/ipc/host.d.ts
CHANGED
|
@@ -2,6 +2,11 @@ import type { ActionDockTarget } from "../target/types.js";
|
|
|
2
2
|
/**
|
|
3
3
|
* 在 Host 子进程中启动 Node IPC 服务,向父进程暴露 ActionDockTarget 门面能力。
|
|
4
4
|
*
|
|
5
|
+
* 跨进程取消链路:监督进程序列化执行选项时把 AbortSignal 替换为占位标记,
|
|
6
|
+
* 本侧反序列化时识别标记并重建 AbortController 传入目标调用,同时维护
|
|
7
|
+
* 调用 id 到控制器的映射;收到 abort 消息时触发对应控制器中止,
|
|
8
|
+
* 调用完成后清理映射,确保取消信号全链路透传。
|
|
9
|
+
*
|
|
5
10
|
* @param target 已构造好的 ActionDockTarget 实例
|
|
6
11
|
*/
|
|
7
12
|
export declare function serveParentIpc(target: ActionDockTarget): Promise<void>;
|
package/dist/ipc/host.js
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
|
+
import { hasIpcSignalMarker, IPC_SIGNAL_MARKER } from "./target.js";
|
|
1
2
|
/**
|
|
2
3
|
* 在 Host 子进程中启动 Node IPC 服务,向父进程暴露 ActionDockTarget 门面能力。
|
|
3
4
|
*
|
|
5
|
+
* 跨进程取消链路:监督进程序列化执行选项时把 AbortSignal 替换为占位标记,
|
|
6
|
+
* 本侧反序列化时识别标记并重建 AbortController 传入目标调用,同时维护
|
|
7
|
+
* 调用 id 到控制器的映射;收到 abort 消息时触发对应控制器中止,
|
|
8
|
+
* 调用完成后清理映射,确保取消信号全链路透传。
|
|
9
|
+
*
|
|
4
10
|
* @param target 已构造好的 ActionDockTarget 实例
|
|
5
11
|
*/
|
|
6
12
|
export async function serveParentIpc(target) {
|
|
@@ -20,6 +26,24 @@ export async function serveParentIpc(target) {
|
|
|
20
26
|
// 忽略关闭异常
|
|
21
27
|
}
|
|
22
28
|
};
|
|
29
|
+
// 进行中调用的取消控制器映射:id -> AbortController
|
|
30
|
+
const activeControllers = new Map();
|
|
31
|
+
/**
|
|
32
|
+
* 反序列化执行选项:识别取消信号占位标记后重建 AbortController。
|
|
33
|
+
*
|
|
34
|
+
* @param id 当前调用唯一标识
|
|
35
|
+
* @param arg 待反序列化的参数值
|
|
36
|
+
* @returns 替换后的参数值(原样返回非标记对象)
|
|
37
|
+
*/
|
|
38
|
+
const reviveIpcSignal = (id, arg) => {
|
|
39
|
+
if (!hasIpcSignalMarker(arg)) {
|
|
40
|
+
return arg;
|
|
41
|
+
}
|
|
42
|
+
const { [IPC_SIGNAL_MARKER]: _marker, ...rest } = arg;
|
|
43
|
+
const controller = new AbortController();
|
|
44
|
+
activeControllers.set(id, controller);
|
|
45
|
+
return { ...rest, signal: controller.signal };
|
|
46
|
+
};
|
|
23
47
|
// 监听父进程断连或退出信号
|
|
24
48
|
process.once("disconnect", async () => {
|
|
25
49
|
await safeClose();
|
|
@@ -45,7 +69,9 @@ export async function serveParentIpc(target) {
|
|
|
45
69
|
if (typeof fn !== "function") {
|
|
46
70
|
throw new Error(`Target method '${method}' not found`);
|
|
47
71
|
}
|
|
48
|
-
|
|
72
|
+
// 反序列化执行选项:识别取消信号占位标记并重建控制器接入取消链路
|
|
73
|
+
const revivedArgs = (args || []).map((arg) => reviveIpcSignal(id, arg));
|
|
74
|
+
const result = await fn.apply(target, revivedArgs);
|
|
49
75
|
const response = {
|
|
50
76
|
id,
|
|
51
77
|
type: "response",
|
|
@@ -72,6 +98,18 @@ export async function serveParentIpc(target) {
|
|
|
72
98
|
process.send(response);
|
|
73
99
|
}
|
|
74
100
|
}
|
|
101
|
+
finally {
|
|
102
|
+
// 调用结束(无论成败)清理取消控制器映射,杜绝映射泄漏
|
|
103
|
+
activeControllers.delete(id);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
else if (msg.type === "abort") {
|
|
107
|
+
// 跨进程取消:触发对应调用控制器中止;未知 id 防御性忽略(旧版或已完成的调用)
|
|
108
|
+
const abortMsg = msg;
|
|
109
|
+
const controller = activeControllers.get(abortMsg.id);
|
|
110
|
+
if (controller) {
|
|
111
|
+
controller.abort(new Error(abortMsg.reason || "Execution was aborted via IPC"));
|
|
112
|
+
}
|
|
75
113
|
}
|
|
76
114
|
else if (msg.type === "close") {
|
|
77
115
|
await safeClose();
|
|
@@ -85,6 +123,10 @@ export async function serveParentIpc(target) {
|
|
|
85
123
|
process.send({ type: "pong" });
|
|
86
124
|
}
|
|
87
125
|
}
|
|
126
|
+
else {
|
|
127
|
+
// 向后兼容:旧版本对端可能推送未知消息类型,防御性忽略并记录诊断,不中断通道
|
|
128
|
+
process.stderr.write(`[IPC Host] Ignoring unknown IPC message type: ${JSON.stringify(msg.type)}\n`);
|
|
129
|
+
}
|
|
88
130
|
});
|
|
89
131
|
// 通知监督父进程:Host 已就绪
|
|
90
132
|
if (process.send) {
|
package/dist/ipc/target.d.ts
CHANGED
|
@@ -3,6 +3,18 @@ import type { ActionRef, ExecutionEvent, ExecutionResult, JsonValue, RunRecord }
|
|
|
3
3
|
import type { ActionSpec, ActionSummary, ListActionsOptions, PackageInfo, PlaybookSpec, PlaybookSummary } from "../app/types.js";
|
|
4
4
|
import type { CancelResult, ExecuteOptions, ExecutionTicket } from "../execution/types.js";
|
|
5
5
|
import type { ActionDockTarget, ConfigValueView, IpcTargetOptions, StateScopeOptions, TargetInfo } from "../target/types.js";
|
|
6
|
+
/**
|
|
7
|
+
* 跨进程取消信号占位标记字段名。
|
|
8
|
+
* AbortSignal 不可序列化,序列化 options 时把 signal 字段替换为该标记,
|
|
9
|
+
* 宿主侧识别后重建 AbortController 并接入 abort 消息通知链路。
|
|
10
|
+
*/
|
|
11
|
+
export declare const IPC_SIGNAL_MARKER = "__ipcSignal";
|
|
12
|
+
/**
|
|
13
|
+
* 判断执行选项中是否携带跨进程取消信号占位标记。
|
|
14
|
+
*
|
|
15
|
+
* @param value 待检查的选项值
|
|
16
|
+
*/
|
|
17
|
+
export declare function hasIpcSignalMarker(value: unknown): value is Record<string, unknown>;
|
|
6
18
|
/**
|
|
7
19
|
* 基于 Node IPC 监督进程通信通道的 ActionDockTarget 实现。
|
|
8
20
|
*
|
|
@@ -28,6 +40,12 @@ export declare class IpcActionDockTarget implements ActionDockTarget {
|
|
|
28
40
|
*/
|
|
29
41
|
get process(): ChildProcess;
|
|
30
42
|
private callRemote;
|
|
43
|
+
/**
|
|
44
|
+
* 向宿主子进程发送跨进程取消通知,发送异常不阻断父侧调用链路。
|
|
45
|
+
*
|
|
46
|
+
* @param id 目标调用的唯一标识
|
|
47
|
+
*/
|
|
48
|
+
private sendAbort;
|
|
31
49
|
info(): Promise<TargetInfo>;
|
|
32
50
|
listPackages(): Promise<PackageInfo[]>;
|
|
33
51
|
listActions(options?: ListActionsOptions): Promise<ActionSummary[]>;
|
package/dist/ipc/target.js
CHANGED
|
@@ -2,6 +2,36 @@ import { randomUUID } from "node:crypto";
|
|
|
2
2
|
import { fork } from "node:child_process";
|
|
3
3
|
import { DiagnosticForwarder } from "./diagnostic.js";
|
|
4
4
|
import { EXECUTION_ABORTED, HOST_PROCESS_EXITED } from "../errors.js";
|
|
5
|
+
/**
|
|
6
|
+
* 跨进程取消信号占位标记字段名。
|
|
7
|
+
* AbortSignal 不可序列化,序列化 options 时把 signal 字段替换为该标记,
|
|
8
|
+
* 宿主侧识别后重建 AbortController 并接入 abort 消息通知链路。
|
|
9
|
+
*/
|
|
10
|
+
export const IPC_SIGNAL_MARKER = "__ipcSignal";
|
|
11
|
+
/**
|
|
12
|
+
* 判断执行选项中是否携带跨进程取消信号占位标记。
|
|
13
|
+
*
|
|
14
|
+
* @param value 待检查的选项值
|
|
15
|
+
*/
|
|
16
|
+
export function hasIpcSignalMarker(value) {
|
|
17
|
+
return (typeof value === "object" &&
|
|
18
|
+
value !== null &&
|
|
19
|
+
value[IPC_SIGNAL_MARKER] === true);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* 序列化执行选项:剥离不可序列化的 AbortSignal 并写入占位标记。
|
|
23
|
+
* 未携带 signal 时返回浅拷贝,避免消息通道直接持有调用方原始对象。
|
|
24
|
+
*
|
|
25
|
+
* @param options 原始执行选项
|
|
26
|
+
*/
|
|
27
|
+
function markIpcSignal(options) {
|
|
28
|
+
if (!options)
|
|
29
|
+
return undefined;
|
|
30
|
+
const { signal: _signal, ...rest } = options;
|
|
31
|
+
if (!_signal)
|
|
32
|
+
return rest;
|
|
33
|
+
return { ...rest, [IPC_SIGNAL_MARKER]: true };
|
|
34
|
+
}
|
|
5
35
|
/**
|
|
6
36
|
* 基于 Node IPC 监督进程通信通道的 ActionDockTarget 实现。
|
|
7
37
|
*
|
|
@@ -126,7 +156,7 @@ export class IpcActionDockTarget {
|
|
|
126
156
|
get process() {
|
|
127
157
|
return this.child;
|
|
128
158
|
}
|
|
129
|
-
async callRemote(method, args) {
|
|
159
|
+
async callRemote(method, args, signal) {
|
|
130
160
|
if (this.isClosed || this.exitError) {
|
|
131
161
|
if (method === "runAction") {
|
|
132
162
|
return {
|
|
@@ -147,6 +177,16 @@ export class IpcActionDockTarget {
|
|
|
147
177
|
method,
|
|
148
178
|
args,
|
|
149
179
|
};
|
|
180
|
+
// 事件驱动跨进程取消:signal 存在且未中止时注册 abort 监听,
|
|
181
|
+
// 触发即向宿主子进程发送 abort 消息,由宿主侧中止同调用控制器;
|
|
182
|
+
// 调用结束(无论成败)后注销监听,避免监听器泄漏
|
|
183
|
+
let onAbort;
|
|
184
|
+
if (signal && !signal.aborted) {
|
|
185
|
+
onAbort = () => {
|
|
186
|
+
this.sendAbort(id);
|
|
187
|
+
};
|
|
188
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
189
|
+
}
|
|
150
190
|
return new Promise((resolve, reject) => {
|
|
151
191
|
this.pendingCalls.set(id, { resolve, reject, method });
|
|
152
192
|
try {
|
|
@@ -162,8 +202,31 @@ export class IpcActionDockTarget {
|
|
|
162
202
|
this.pendingCalls.delete(id);
|
|
163
203
|
reject(err);
|
|
164
204
|
}
|
|
205
|
+
}).finally(() => {
|
|
206
|
+
if (onAbort && signal) {
|
|
207
|
+
signal.removeEventListener("abort", onAbort);
|
|
208
|
+
}
|
|
165
209
|
});
|
|
166
210
|
}
|
|
211
|
+
/**
|
|
212
|
+
* 向宿主子进程发送跨进程取消通知,发送异常不阻断父侧调用链路。
|
|
213
|
+
*
|
|
214
|
+
* @param id 目标调用的唯一标识
|
|
215
|
+
*/
|
|
216
|
+
sendAbort(id) {
|
|
217
|
+
if (this.isClosed)
|
|
218
|
+
return;
|
|
219
|
+
try {
|
|
220
|
+
const abortMsg = { id, type: "abort" };
|
|
221
|
+
if (this.child.send) {
|
|
222
|
+
this.child.send(abortMsg);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
catch (err) {
|
|
226
|
+
// 通道已断开时发送失败属预期场景:宿主退出路径会另行以结构化错误回包
|
|
227
|
+
process.stderr.write(`[IPC Target] Failed to send abort for call '${id}': ${err instanceof Error ? err.message : String(err)}\n`);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
167
230
|
async info() {
|
|
168
231
|
return this.callRemote("info", []);
|
|
169
232
|
}
|
|
@@ -183,8 +246,7 @@ export class IpcActionDockTarget {
|
|
|
183
246
|
return this.callRemote("describePlaybook", [id]);
|
|
184
247
|
}
|
|
185
248
|
async runAction(ref, input, options) {
|
|
186
|
-
|
|
187
|
-
if (signal?.aborted) {
|
|
249
|
+
if (options?.signal?.aborted) {
|
|
188
250
|
return {
|
|
189
251
|
ok: false,
|
|
190
252
|
runId: randomUUID(),
|
|
@@ -194,11 +256,17 @@ export class IpcActionDockTarget {
|
|
|
194
256
|
},
|
|
195
257
|
};
|
|
196
258
|
}
|
|
197
|
-
|
|
259
|
+
// AbortSignal 不可序列化:以占位标记替换后随消息通道传递,宿主侧重建控制器
|
|
260
|
+
const serializableOptions = markIpcSignal(options);
|
|
261
|
+
return this.callRemote("runAction", [ref, input, serializableOptions], options?.signal);
|
|
198
262
|
}
|
|
199
263
|
async startAction(ref, input, options) {
|
|
200
|
-
|
|
201
|
-
|
|
264
|
+
if (options?.signal?.aborted) {
|
|
265
|
+
throw new Error("Execution was aborted before starting");
|
|
266
|
+
}
|
|
267
|
+
// AbortSignal 不可序列化:以占位标记替换后随消息通道传递,宿主侧重建控制器
|
|
268
|
+
const serializableOptions = markIpcSignal(options);
|
|
269
|
+
return this.callRemote("startAction", [ref, input, serializableOptions], options?.signal);
|
|
202
270
|
}
|
|
203
271
|
async getRun(runId) {
|
|
204
272
|
const res = await this.callRemote("getRun", [runId]);
|
package/dist/ipc/types.d.ts
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 监督进程向宿主子进程发送的跨进程取消通知。
|
|
3
|
+
* id 与对应 IpcCallMessage 的调用 id 一致,宿主侧据此中止同调用的 AbortController。
|
|
4
|
+
*/
|
|
5
|
+
export interface IpcAbortMessage {
|
|
6
|
+
id: string;
|
|
7
|
+
type: "abort";
|
|
8
|
+
reason?: string;
|
|
9
|
+
}
|
|
1
10
|
/**
|
|
2
11
|
* 监督进程与宿主子进程间调用的 IPC 请求消息。
|
|
3
12
|
*/
|
|
@@ -39,4 +48,4 @@ export interface IpcReadyMessage {
|
|
|
39
48
|
/**
|
|
40
49
|
* 联合 IPC 消息类型。
|
|
41
50
|
*/
|
|
42
|
-
export type IpcMessage = IpcCallMessage | IpcResponseMessage | IpcEventMessage | IpcReadyMessage;
|
|
51
|
+
export type IpcMessage = IpcCallMessage | IpcResponseMessage | IpcAbortMessage | IpcEventMessage | IpcReadyMessage;
|