@actiondock/core 2.7.0 → 2.8.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 +61 -68
- package/dist/catalog/action-catalog.d.ts +56 -0
- package/dist/catalog/action-catalog.js +202 -0
- package/dist/catalog/discovery.d.ts +60 -0
- package/dist/catalog/discovery.js +119 -0
- package/dist/catalog/graph.d.ts +190 -0
- package/dist/catalog/graph.js +589 -0
- package/dist/catalog/index.d.ts +5 -5
- package/dist/catalog/index.js +5 -5
- package/dist/catalog/resolve-action.d.ts +43 -0
- package/dist/catalog/resolve-action.js +119 -0
- package/dist/catalog/resolve-playbook.d.ts +26 -0
- package/dist/catalog/resolve-playbook.js +114 -0
- package/dist/doctor/checks/project.js +27 -8
- package/dist/doctor/checks/registry.js +2 -2
- package/dist/doctor/checks/runtime.js +34 -1
- package/dist/errors.d.ts +102 -10
- package/dist/errors.js +103 -10
- package/dist/execution/service.d.ts +18 -16
- package/dist/execution/service.js +183 -252
- package/dist/execution/types.d.ts +25 -69
- package/dist/execution/types.js +2 -1
- package/dist/filter/intent.d.ts +8 -9
- package/dist/filter/intent.js +49 -10
- package/dist/graph/index.d.ts +4 -0
- package/dist/graph/index.js +4 -0
- package/dist/host/host.d.ts +74 -32
- package/dist/host/host.js +585 -336
- package/dist/host/routing.d.ts +15 -48
- package/dist/host/routing.js +90 -153
- package/dist/host/types.d.ts +50 -17
- package/dist/index.d.ts +21 -24
- package/dist/index.js +19 -23
- package/dist/input/advice.d.ts +5 -1
- package/dist/input/advice.js +163 -214
- package/dist/input/describe.d.ts +1 -1
- package/dist/input/flat-errors.d.ts +5 -6
- package/dist/input/flat-errors.js +6 -6
- package/dist/input/flat-materializer.js +42 -44
- package/dist/invocation/index.d.ts +2 -0
- package/dist/invocation/index.js +2 -0
- package/dist/invocation/policy.d.ts +157 -0
- package/dist/invocation/policy.js +231 -0
- package/dist/invocation/types.d.ts +172 -0
- package/dist/invocation/types.js +97 -0
- package/dist/ipc/diagnostic.d.ts +0 -12
- package/dist/ipc/diagnostic.js +0 -21
- package/dist/ipc/host.d.ts +4 -4
- package/dist/ipc/host.js +44 -47
- package/dist/ipc/index.d.ts +1 -1
- package/dist/ipc/index.js +1 -1
- package/dist/ipc/service.d.ts +40 -0
- package/dist/ipc/service.js +349 -0
- package/dist/ipc/types.d.ts +21 -15
- package/dist/node/index.d.ts +1 -0
- package/dist/node/index.js +1 -0
- package/dist/node/module-loader.d.ts +63 -0
- package/dist/node/module-loader.js +112 -0
- package/dist/package/index.d.ts +23 -0
- package/dist/package/index.js +18 -0
- package/dist/{app/app.d.ts → package/runtime.d.ts} +41 -20
- package/dist/{app/app.js → package/runtime.js} +140 -95
- package/dist/{app → package}/static-index.js +58 -41
- package/dist/package/storage-view.d.ts +13 -0
- package/dist/package/storage-view.js +22 -0
- package/dist/{app → package}/types.d.ts +76 -57
- package/dist/package/types.js +22 -0
- package/dist/platform/index.d.ts +1 -1
- package/dist/platform/index.js +1 -1
- package/dist/platform/node.d.ts +48 -0
- package/dist/platform/node.js +96 -0
- package/dist/platform/types.d.ts +0 -9
- package/dist/process/control-arbiter.d.ts +2 -1
- package/dist/process/control-arbiter.js +1 -2
- package/dist/process/driver.d.ts +9 -8
- package/dist/process/driver.js +11 -22
- package/dist/process/index.d.ts +2 -0
- package/dist/process/index.js +2 -0
- package/dist/process/managed-record.d.ts +2 -2
- package/dist/process/process-driver.d.ts +109 -0
- package/dist/process/process-driver.js +650 -0
- package/dist/process/process-executor.d.ts +47 -0
- package/dist/process/process-executor.js +296 -0
- package/dist/process/process-manager.js +48 -26
- package/dist/process/run-executor.js +12 -12
- package/dist/profile/client-actions.js +2 -2
- package/dist/profile/client-health.js +2 -2
- package/dist/profile/client-query.js +5 -6
- package/dist/profile/client-transport.d.ts +19 -17
- package/dist/profile/client-transport.js +47 -80
- package/dist/profile/client.d.ts +2 -2
- package/dist/profile/client.js +1 -1
- package/dist/profile/index.d.ts +1 -0
- package/dist/profile/index.js +1 -0
- package/dist/profile/manager.d.ts +5 -0
- package/dist/profile/manager.js +41 -6
- package/dist/project/closure.js +2 -2
- package/dist/project/index.d.ts +8 -1
- package/dist/project/index.js +10 -1
- package/dist/project/init.js +2 -2
- package/dist/project/loader.d.ts +1 -1
- package/dist/project/loader.js +26 -29
- package/dist/project/transactions.js +5 -10
- package/dist/registry/index.d.ts +3 -1
- package/dist/registry/index.js +2 -1
- package/dist/registry/lock.d.ts +28 -7
- package/dist/registry/lock.js +136 -16
- package/dist/registry/registry.d.ts +1 -5
- package/dist/registry/registry.js +1 -53
- package/dist/registry/scan.d.ts +0 -15
- package/dist/registry/scan.js +0 -59
- package/dist/registry/store.d.ts +42 -0
- package/dist/registry/store.js +43 -0
- package/dist/registry/types.d.ts +0 -24
- package/dist/runtime/action-collection.d.ts +1 -1
- package/dist/runtime/action-registry.d.ts +0 -4
- package/dist/runtime/action-registry.js +0 -7
- package/dist/runtime/context.js +5 -11
- package/dist/runtime/events.js +3 -6
- package/dist/runtime/identity.d.ts +22 -0
- package/dist/runtime/identity.js +10 -0
- package/dist/runtime/index.d.ts +1 -2
- package/dist/runtime/index.js +1 -2
- package/dist/runtime/module-loader.d.ts +2 -34
- package/dist/runtime/module-loader.js +1 -16
- package/dist/runtime/process.d.ts +6 -24
- package/dist/runtime/process.js +1 -118
- package/dist/runtime/run-persistence.js +2 -5
- package/dist/runtime/runner.d.ts +33 -93
- package/dist/runtime/runner.js +132 -364
- package/dist/runtime/standalone.d.ts +23 -8
- package/dist/runtime/standalone.js +100 -147
- package/dist/server/body.d.ts +16 -8
- package/dist/server/body.js +33 -16
- package/dist/server/dispatcher.d.ts +6 -10
- package/dist/server/dispatcher.js +24 -32
- package/dist/server/http-server.d.ts +87 -0
- package/dist/server/http-server.js +304 -0
- package/dist/server/index.d.ts +11 -0
- package/dist/server/index.js +10 -0
- package/dist/server/mcp-endpoint.d.ts +61 -0
- package/dist/server/mcp-endpoint.js +118 -0
- package/dist/server/routes/actions.js +34 -28
- package/dist/server/routes/common.d.ts +10 -14
- package/dist/server/routes/common.js +32 -56
- package/dist/server/routes/config.js +54 -64
- package/dist/server/routes/doctor.d.ts +1 -1
- package/dist/server/routes/doctor.js +10 -18
- package/dist/server/routes/health.d.ts +1 -1
- package/dist/server/routes/health.js +2 -3
- package/dist/server/routes/info.d.ts +2 -2
- package/dist/server/routes/info.js +12 -13
- package/dist/server/routes/playbooks.js +16 -29
- package/dist/server/routes/runs.js +95 -117
- package/dist/server/routes/state.js +63 -62
- package/dist/server/server.d.ts +2 -2
- package/dist/server/server.js +43 -227
- package/dist/server/types.d.ts +8 -19
- package/dist/service/factory.d.ts +12 -0
- package/dist/service/factory.js +85 -0
- package/dist/{target → service}/index.d.ts +1 -1
- package/dist/{target → service}/index.js +1 -1
- package/dist/service/local.d.ts +27 -0
- package/dist/service/local.js +138 -0
- package/dist/{target → service}/remote-errors.d.ts +3 -6
- package/dist/service/remote-errors.js +38 -0
- package/dist/{target → service}/remote-polling.d.ts +2 -2
- package/dist/{target → service}/remote-polling.js +8 -8
- package/dist/service/remote.d.ts +34 -0
- package/dist/service/remote.js +634 -0
- package/dist/service/sse-stream.d.ts +24 -0
- package/dist/service/sse-stream.js +139 -0
- package/dist/service/types.d.ts +272 -0
- package/dist/service/types.js +32 -0
- package/dist/storage/data-dir-lock.js +10 -5
- package/dist/storage/driver.d.ts +2 -1
- package/dist/storage/driver.js +3 -52
- package/dist/storage/index.d.ts +1 -0
- package/dist/storage/index.js +1 -0
- package/dist/storage/lock-core.d.ts +2 -6
- package/dist/storage/lock-core.js +4 -17
- package/dist/storage/params.d.ts +2 -8
- package/dist/storage/params.js +2 -8
- package/dist/storage/sqlite-driver.d.ts +40 -0
- package/dist/storage/sqlite-driver.js +116 -0
- package/dist/storage/sqlite.js +21 -6
- package/dist/storage/types.d.ts +4 -4
- package/dist/storage/types.js +2 -2
- package/dist/utils/index.d.ts +53 -0
- package/dist/utils/index.js +99 -4
- package/dist/utils/process.d.ts +10 -0
- package/dist/utils/process.js +21 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +41 -3
- package/dist/app/index.d.ts +0 -2
- package/dist/app/index.js +0 -2
- package/dist/app/types.js +0 -1
- package/dist/catalog/action-index.d.ts +0 -9
- package/dist/catalog/action-index.js +0 -95
- package/dist/catalog/action-resolver.d.ts +0 -20
- package/dist/catalog/action-resolver.js +0 -95
- package/dist/catalog/location-registry.d.ts +0 -15
- package/dist/catalog/location-registry.js +0 -138
- package/dist/catalog/package-catalog.d.ts +0 -7
- package/dist/catalog/package-catalog.js +0 -77
- package/dist/catalog/types.d.ts +0 -68
- package/dist/catalog/types.js +0 -1
- package/dist/export/index.d.ts +0 -1
- package/dist/export/index.js +0 -1
- package/dist/export/templates.d.ts +0 -71
- package/dist/export/templates.js +0 -635
- package/dist/ipc/target.d.ts +0 -74
- package/dist/ipc/target.js +0 -365
- package/dist/platform/default.d.ts +0 -38
- package/dist/platform/default.js +0 -78
- package/dist/project/resolver.d.ts +0 -116
- package/dist/project/resolver.js +0 -376
- package/dist/registry/resolve.d.ts +0 -62
- package/dist/registry/resolve.js +0 -195
- package/dist/runtime/execution-manager.d.ts +0 -48
- package/dist/runtime/execution-manager.js +0 -66
- package/dist/runtime/package-runner-factory.d.ts +0 -82
- package/dist/runtime/package-runner-factory.js +0 -186
- package/dist/target/local.d.ts +0 -53
- package/dist/target/local.js +0 -349
- package/dist/target/remote-errors.js +0 -51
- package/dist/target/remote.d.ts +0 -86
- package/dist/target/remote.js +0 -818
- package/dist/target/target.d.ts +0 -6
- package/dist/target/target.js +0 -37
- package/dist/target/types.d.ts +0 -261
- package/dist/target/types.js +0 -35
- /package/dist/{app → package}/static-index.d.ts +0 -0
- /package/dist/{target → service}/sse-parser.d.ts +0 -0
- /package/dist/{target → service}/sse-parser.js +0 -0
package/dist/ipc/diagnostic.js
CHANGED
|
@@ -25,18 +25,6 @@ export class DiagnosticForwarder {
|
|
|
25
25
|
this.prefix = options.prefix ?? "";
|
|
26
26
|
this.onTruncated = options.onTruncated;
|
|
27
27
|
}
|
|
28
|
-
/**
|
|
29
|
-
* 是否已达到上限并处于截断状态。
|
|
30
|
-
*/
|
|
31
|
-
get isTruncated() {
|
|
32
|
-
return this.truncated;
|
|
33
|
-
}
|
|
34
|
-
/**
|
|
35
|
-
* 已成功转发的累计字节数。
|
|
36
|
-
*/
|
|
37
|
-
get bytesEmitted() {
|
|
38
|
-
return this.totalBytesEmitted;
|
|
39
|
-
}
|
|
40
28
|
/**
|
|
41
29
|
* 绑定并监听可读流(如 childProcess.stdout 或 childProcess.stderr)。
|
|
42
30
|
*
|
|
@@ -117,13 +105,4 @@ export class DiagnosticForwarder {
|
|
|
117
105
|
}
|
|
118
106
|
this.attachedStreams.clear();
|
|
119
107
|
}
|
|
120
|
-
/**
|
|
121
|
-
* 重置计数器与截断标志。
|
|
122
|
-
*/
|
|
123
|
-
reset() {
|
|
124
|
-
this.totalBytesEmitted = 0;
|
|
125
|
-
this.bytesInCurrentWindow = 0;
|
|
126
|
-
this.windowStartMs = Date.now();
|
|
127
|
-
this.truncated = false;
|
|
128
|
-
}
|
|
129
108
|
}
|
package/dist/ipc/host.d.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ActionDockService } from "../service/types.js";
|
|
2
2
|
/**
|
|
3
|
-
* 在 Host 子进程中启动 Node IPC 服务,向父进程暴露
|
|
3
|
+
* 在 Host 子进程中启动 Node IPC 服务,向父进程暴露 ActionDockService 门面能力。
|
|
4
4
|
*
|
|
5
5
|
* 跨进程取消链路:监督进程序列化执行选项时把 AbortSignal 替换为占位标记,
|
|
6
6
|
* 本侧反序列化时识别标记并重建 AbortController 传入目标调用,同时维护
|
|
7
7
|
* 调用 id 到控制器的映射;收到 abort 消息时触发对应控制器中止,
|
|
8
8
|
* 调用完成后清理映射,确保取消信号全链路透传。
|
|
9
9
|
*
|
|
10
|
-
* @param
|
|
10
|
+
* @param service 已构造好的 ActionDockService 实例
|
|
11
11
|
*/
|
|
12
|
-
export declare function serveParentIpc(
|
|
12
|
+
export declare function serveParentIpc(service: ActionDockService): Promise<void>;
|
package/dist/ipc/host.js
CHANGED
|
@@ -1,47 +1,49 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { hasIpcSignalMarker, IPC_SIGNAL_MARKER } from "./
|
|
1
|
+
import { ActionDockError, CAPABILITY_UNAVAILABLE, SERVICE_ERROR } from "../errors.js";
|
|
2
|
+
import { hasIpcSignalMarker, IPC_SIGNAL_MARKER } from "./service.js";
|
|
3
3
|
/**
|
|
4
|
-
* IPC
|
|
4
|
+
* IPC 通道方法分发表:方法名到目标调用的唯一事实源。
|
|
5
5
|
*
|
|
6
|
-
* 严格对齐
|
|
7
|
-
* 防止 IPC
|
|
6
|
+
* 严格对齐 ActionDockService 公共方法集合:表之外的方法一律拒绝,
|
|
7
|
+
* 防止 IPC 消息通道触达任意内部属性或危险方法。
|
|
8
|
+
* 白名单即分发表的键集合,杜绝两份清单重复维护漂移。
|
|
8
9
|
*/
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
]
|
|
10
|
+
const IPC_METHOD_HANDLERS = {
|
|
11
|
+
info: async (service) => service.info(),
|
|
12
|
+
listPackages: async (service) => service.discovery.listPackages(),
|
|
13
|
+
listActions: async (service, a) => service.discovery.listActions(a[0]),
|
|
14
|
+
describeAction: async (service, a) => service.discovery.describeAction(a[0]),
|
|
15
|
+
listPlaybooks: async (service, a) => service.discovery.listPlaybooks(a[0]),
|
|
16
|
+
describePlaybook: async (service, a) => service.discovery.describePlaybook(a[0]),
|
|
17
|
+
runAction: async (service, a) => service.execution.run(a[0], a[1], a[2]),
|
|
18
|
+
startAction: async (service, a) => service.execution.start(a[0], a[1], a[2]),
|
|
19
|
+
listRuns: async (service, a) => service.runs.list(a[0]),
|
|
20
|
+
getRun: async (service, a) => service.runs.get(a[0]),
|
|
21
|
+
cancelRun: async (service, a) => service.runs.cancel(a[0], a[1]),
|
|
22
|
+
clearRuns: async (service, a) => service.runs.clear ? await service.runs.clear(a[0]) : 0,
|
|
23
|
+
getConfig: async (service, a) => service.management?.config.get(a[0], a[1]),
|
|
24
|
+
setConfig: async (service, a) => service.management?.config.set(a[0], a[1], a[2]),
|
|
25
|
+
deleteConfig: async (service, a) => service.management?.config.delete(a[0], a[1]),
|
|
26
|
+
listConfig: async (service, a) => service.management?.config.list(a[0]),
|
|
27
|
+
getState: async (service, a) => service.management?.state.get(a[0], a[1], a[2], a[3]),
|
|
28
|
+
setState: async (service, a) => service.management?.state.set(a[0], a[1], a[2], a[3], a[4]),
|
|
29
|
+
deleteState: async (service, a) => service.management?.state.delete(a[0], a[1], a[2], a[3]),
|
|
30
|
+
listStateKeys: async (service, a) => service.management?.state.list(a[0], a[1], a[2]),
|
|
31
|
+
clearState: async (service, a) => service.management?.state.clear(a[0], a[1], a[2]),
|
|
32
|
+
listStateEntries: async (service, a) => service.management?.state.listEntries
|
|
33
|
+
? await service.management.state.listEntries(a[0], a[1])
|
|
34
|
+
: [],
|
|
35
|
+
};
|
|
34
36
|
/**
|
|
35
|
-
* 在 Host 子进程中启动 Node IPC 服务,向父进程暴露
|
|
37
|
+
* 在 Host 子进程中启动 Node IPC 服务,向父进程暴露 ActionDockService 门面能力。
|
|
36
38
|
*
|
|
37
39
|
* 跨进程取消链路:监督进程序列化执行选项时把 AbortSignal 替换为占位标记,
|
|
38
40
|
* 本侧反序列化时识别标记并重建 AbortController 传入目标调用,同时维护
|
|
39
41
|
* 调用 id 到控制器的映射;收到 abort 消息时触发对应控制器中止,
|
|
40
42
|
* 调用完成后清理映射,确保取消信号全链路透传。
|
|
41
43
|
*
|
|
42
|
-
* @param
|
|
44
|
+
* @param service 已构造好的 ActionDockService 实例
|
|
43
45
|
*/
|
|
44
|
-
export async function serveParentIpc(
|
|
46
|
+
export async function serveParentIpc(service) {
|
|
45
47
|
if (!process.send) {
|
|
46
48
|
// 若当前未以 IPC 模式运行,直接退出
|
|
47
49
|
return;
|
|
@@ -52,7 +54,7 @@ export async function serveParentIpc(target) {
|
|
|
52
54
|
return;
|
|
53
55
|
isClosing = true;
|
|
54
56
|
try {
|
|
55
|
-
await
|
|
57
|
+
await service.close();
|
|
56
58
|
}
|
|
57
59
|
catch {
|
|
58
60
|
// 忽略关闭异常
|
|
@@ -97,19 +99,14 @@ export async function serveParentIpc(target) {
|
|
|
97
99
|
const callMsg = msg;
|
|
98
100
|
const { id, method, args } = callMsg;
|
|
99
101
|
try {
|
|
100
|
-
//
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
throw err;
|
|
105
|
-
}
|
|
106
|
-
const fn = target[method];
|
|
107
|
-
if (typeof fn !== "function") {
|
|
108
|
-
throw new Error(`Target method '${method}' not found`);
|
|
102
|
+
// 白名单校验:仅允许分发表内方法,阻止任意方法反射调用
|
|
103
|
+
const handler = typeof method === "string" ? IPC_METHOD_HANDLERS[method] : undefined;
|
|
104
|
+
if (!handler) {
|
|
105
|
+
throw new ActionDockError(CAPABILITY_UNAVAILABLE, `Target method '${method}' is not allowed over IPC`);
|
|
109
106
|
}
|
|
110
107
|
// 反序列化执行选项:识别取消信号占位标记并重建控制器接入取消链路
|
|
111
|
-
const
|
|
112
|
-
const result = await
|
|
108
|
+
const a = (args || []).map((arg) => reviveIpcSignal(id, arg));
|
|
109
|
+
const result = await handler(service, a);
|
|
113
110
|
const response = {
|
|
114
111
|
id,
|
|
115
112
|
type: "response",
|
|
@@ -126,7 +123,7 @@ export async function serveParentIpc(target) {
|
|
|
126
123
|
type: "response",
|
|
127
124
|
ok: false,
|
|
128
125
|
error: {
|
|
129
|
-
code: err?.code ||
|
|
126
|
+
code: err?.code || SERVICE_ERROR,
|
|
130
127
|
message: err?.message || String(err),
|
|
131
128
|
stack: err?.stack,
|
|
132
129
|
details: err?.details,
|
|
@@ -162,7 +159,7 @@ export async function serveParentIpc(target) {
|
|
|
162
159
|
}
|
|
163
160
|
}
|
|
164
161
|
else {
|
|
165
|
-
//
|
|
162
|
+
// 防御性忽略未知消息类型并记录诊断,不中断通道
|
|
166
163
|
process.stderr.write(`[IPC Host] Ignoring unknown IPC message type: ${JSON.stringify(msg.type)}\n`);
|
|
167
164
|
}
|
|
168
165
|
});
|
package/dist/ipc/index.d.ts
CHANGED
package/dist/ipc/index.js
CHANGED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { type ChildProcess } from "node:child_process";
|
|
2
|
+
import type { PackageInfo } from "../package/types.js";
|
|
3
|
+
import type { ActionDockService, ConfigPort, DiscoveryPort, ExecutionPort, RunsPort, StatePort } from "../service/types.js";
|
|
4
|
+
import type { IpcServiceOptions } from "./types.js";
|
|
5
|
+
/**
|
|
6
|
+
* 跨进程取消信号占位标记字段名。
|
|
7
|
+
* AbortSignal 不可序列化,序列化 options 时把 signal 字段替换为该标记,
|
|
8
|
+
* 宿主侧识别后重建 AbortController 并接入 abort 消息通知链路。
|
|
9
|
+
*/
|
|
10
|
+
export declare const IPC_SIGNAL_MARKER = "__ipcSignal";
|
|
11
|
+
/**
|
|
12
|
+
* 判断执行选项中是否携带跨进程取消信号占位标记。
|
|
13
|
+
*/
|
|
14
|
+
export declare function hasIpcSignalMarker(value: unknown): value is Record<string, unknown>;
|
|
15
|
+
/**
|
|
16
|
+
* 基于 Node IPC 监督进程通信通道的 ActionDockService 实现。
|
|
17
|
+
* 遵循标准 Service Ports 体系,隔离子进程生命周期并暴露结构化服务端口。
|
|
18
|
+
*/
|
|
19
|
+
export declare class IpcActionDockService implements ActionDockService {
|
|
20
|
+
private child;
|
|
21
|
+
private forwarder;
|
|
22
|
+
private pendingCalls;
|
|
23
|
+
private isClosed;
|
|
24
|
+
private exitError?;
|
|
25
|
+
private readyPromise;
|
|
26
|
+
readonly discovery: DiscoveryPort;
|
|
27
|
+
readonly execution: ExecutionPort;
|
|
28
|
+
readonly runs: RunsPort;
|
|
29
|
+
readonly management?: {
|
|
30
|
+
config: ConfigPort;
|
|
31
|
+
state: StatePort;
|
|
32
|
+
};
|
|
33
|
+
constructor(options: IpcServiceOptions);
|
|
34
|
+
private callRemote;
|
|
35
|
+
private sendAbort;
|
|
36
|
+
info(): Promise<PackageInfo[]>;
|
|
37
|
+
get process(): ChildProcess;
|
|
38
|
+
waitReady(): Promise<void>;
|
|
39
|
+
close(): Promise<void>;
|
|
40
|
+
}
|
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { fork } from "node:child_process";
|
|
3
|
+
import { DiagnosticForwarder } from "./diagnostic.js";
|
|
4
|
+
import { ActionDockError, CAPABILITY_UNAVAILABLE, EXECUTION_ABORTED, HOST_PROCESS_EXITED, IPC_ERROR, SERVICE_CLOSED, } from "../errors.js";
|
|
5
|
+
/**
|
|
6
|
+
* 跨进程取消信号占位标记字段名。
|
|
7
|
+
* AbortSignal 不可序列化,序列化 options 时把 signal 字段替换为该标记,
|
|
8
|
+
* 宿主侧识别后重建 AbortController 并接入 abort 消息通知链路。
|
|
9
|
+
*/
|
|
10
|
+
export const IPC_SIGNAL_MARKER = "__ipcSignal";
|
|
11
|
+
/**
|
|
12
|
+
* 判断执行选项中是否携带跨进程取消信号占位标记。
|
|
13
|
+
*/
|
|
14
|
+
export function hasIpcSignalMarker(value) {
|
|
15
|
+
return (typeof value === "object" &&
|
|
16
|
+
value !== null &&
|
|
17
|
+
value[IPC_SIGNAL_MARKER] === true);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* 序列化执行选项:剥离不可序列化的 AbortSignal 并写入占位标记。
|
|
21
|
+
*/
|
|
22
|
+
function markIpcSignal(options) {
|
|
23
|
+
if (!options)
|
|
24
|
+
return undefined;
|
|
25
|
+
const clean = {};
|
|
26
|
+
if (options.timeoutMs !== undefined)
|
|
27
|
+
clean.timeoutMs = options.timeoutMs;
|
|
28
|
+
if (options.config !== undefined)
|
|
29
|
+
clean.config = options.config;
|
|
30
|
+
if (options.requestId !== undefined)
|
|
31
|
+
clean.requestId = options.requestId;
|
|
32
|
+
if (options.signal) {
|
|
33
|
+
clean[IPC_SIGNAL_MARKER] = true;
|
|
34
|
+
}
|
|
35
|
+
return clean;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* 基于 Node IPC 监督进程通信通道的 ActionDockService 实现。
|
|
39
|
+
* 遵循标准 Service Ports 体系,隔离子进程生命周期并暴露结构化服务端口。
|
|
40
|
+
*/
|
|
41
|
+
export class IpcActionDockService {
|
|
42
|
+
child;
|
|
43
|
+
forwarder;
|
|
44
|
+
pendingCalls = new Map();
|
|
45
|
+
isClosed = false;
|
|
46
|
+
exitError;
|
|
47
|
+
readyPromise;
|
|
48
|
+
discovery;
|
|
49
|
+
execution;
|
|
50
|
+
runs;
|
|
51
|
+
management;
|
|
52
|
+
constructor(options) {
|
|
53
|
+
this.forwarder = new DiagnosticForwarder({
|
|
54
|
+
maxBytes: options.maxDiagnosticBytes,
|
|
55
|
+
maxRateBytesPerSec: options.maxDiagnosticRate,
|
|
56
|
+
target: options.diagnosticTarget,
|
|
57
|
+
});
|
|
58
|
+
if (options.childProcess) {
|
|
59
|
+
this.child = options.childProcess;
|
|
60
|
+
}
|
|
61
|
+
else if (options.scriptPath) {
|
|
62
|
+
this.child = fork(options.scriptPath, options.scriptArgs || [], {
|
|
63
|
+
cwd: options.cwd || process.cwd(),
|
|
64
|
+
env: options.env || process.env,
|
|
65
|
+
stdio: ["pipe", "pipe", "pipe", "ipc"],
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
throw new Error("IpcActionDockService requires either 'childProcess' or 'scriptPath'");
|
|
70
|
+
}
|
|
71
|
+
if (this.child.stdout) {
|
|
72
|
+
this.forwarder.attach(this.child.stdout, "stdout");
|
|
73
|
+
}
|
|
74
|
+
if (this.child.stderr) {
|
|
75
|
+
this.forwarder.attach(this.child.stderr, "stderr");
|
|
76
|
+
}
|
|
77
|
+
let resolveReady;
|
|
78
|
+
this.readyPromise = new Promise((r) => {
|
|
79
|
+
resolveReady = r;
|
|
80
|
+
});
|
|
81
|
+
this.child.on("message", (msg) => {
|
|
82
|
+
if (!msg || typeof msg !== "object")
|
|
83
|
+
return;
|
|
84
|
+
if (msg.type === "ready") {
|
|
85
|
+
resolveReady();
|
|
86
|
+
}
|
|
87
|
+
else if (msg.type === "response") {
|
|
88
|
+
const resp = msg;
|
|
89
|
+
const pending = this.pendingCalls.get(resp.id);
|
|
90
|
+
if (pending) {
|
|
91
|
+
this.pendingCalls.delete(resp.id);
|
|
92
|
+
if (resp.ok) {
|
|
93
|
+
pending.resolve(resp.data);
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
const err = new ActionDockError(resp.error?.code || IPC_ERROR, resp.error?.message || "IPC Service call failed", resp.error?.details);
|
|
97
|
+
pending.reject(err);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
this.child.on("exit", (code, signal) => {
|
|
103
|
+
const exitMsg = `Host process exited prematurely with code ${code ?? signal ?? "unknown"}`;
|
|
104
|
+
const err = new ActionDockError(HOST_PROCESS_EXITED, exitMsg);
|
|
105
|
+
this.exitError = err;
|
|
106
|
+
this.isClosed = true;
|
|
107
|
+
// 快速失败所有待处理调用
|
|
108
|
+
for (const pending of this.pendingCalls.values()) {
|
|
109
|
+
if (pending.method === "runAction") {
|
|
110
|
+
pending.resolve({
|
|
111
|
+
ok: false,
|
|
112
|
+
runId: randomUUID(),
|
|
113
|
+
error: {
|
|
114
|
+
code: HOST_PROCESS_EXITED,
|
|
115
|
+
message: exitMsg,
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
pending.reject(err);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
this.pendingCalls.clear();
|
|
124
|
+
this.forwarder.detachAll();
|
|
125
|
+
resolveReady();
|
|
126
|
+
});
|
|
127
|
+
const self = this;
|
|
128
|
+
/**
|
|
129
|
+
* 生成方法级端口转发器:把端口方法调用映射为 callRemote 远端调用。
|
|
130
|
+
* 方法名与参数列表的对应关系即唯一事实源,杜绝手写转发样板膨胀。
|
|
131
|
+
*/
|
|
132
|
+
const forward = (method, args) => self.callRemote(method, args);
|
|
133
|
+
this.discovery = {
|
|
134
|
+
async listPackages() {
|
|
135
|
+
return forward("listPackages", []);
|
|
136
|
+
},
|
|
137
|
+
async listActions(opts) {
|
|
138
|
+
return forward("listActions", [opts]);
|
|
139
|
+
},
|
|
140
|
+
async describeAction(ref) {
|
|
141
|
+
return forward("describeAction", [ref]);
|
|
142
|
+
},
|
|
143
|
+
async listPlaybooks(opts) {
|
|
144
|
+
return forward("listPlaybooks", [opts]);
|
|
145
|
+
},
|
|
146
|
+
async describePlaybook(id) {
|
|
147
|
+
return forward("describePlaybook", [id]);
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
this.execution = {
|
|
151
|
+
async run(ref, input, opts) {
|
|
152
|
+
if (opts?.signal?.aborted) {
|
|
153
|
+
return {
|
|
154
|
+
ok: false,
|
|
155
|
+
runId: randomUUID(),
|
|
156
|
+
error: {
|
|
157
|
+
code: EXECUTION_ABORTED,
|
|
158
|
+
message: "Execution was aborted before starting",
|
|
159
|
+
},
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
const serializableOptions = markIpcSignal(opts);
|
|
163
|
+
return self.callRemote("runAction", [ref, input ?? {}, serializableOptions], opts?.signal);
|
|
164
|
+
},
|
|
165
|
+
async start(ref, input, opts) {
|
|
166
|
+
if (opts?.signal?.aborted) {
|
|
167
|
+
throw new ActionDockError(EXECUTION_ABORTED, "Execution was aborted before starting");
|
|
168
|
+
}
|
|
169
|
+
const serializableOptions = markIpcSignal(opts);
|
|
170
|
+
return self.callRemote("startAction", [ref, input ?? {}, serializableOptions], opts?.signal);
|
|
171
|
+
},
|
|
172
|
+
};
|
|
173
|
+
this.runs = {
|
|
174
|
+
async list(query) {
|
|
175
|
+
return forward("listRuns", [query]);
|
|
176
|
+
},
|
|
177
|
+
async get(runId) {
|
|
178
|
+
const res = await forward("getRun", [runId]);
|
|
179
|
+
return res ?? undefined;
|
|
180
|
+
},
|
|
181
|
+
async cancel(runId, reason) {
|
|
182
|
+
return forward("cancelRun", [runId, reason]);
|
|
183
|
+
},
|
|
184
|
+
async clear(opts) {
|
|
185
|
+
return forward("clearRuns", [opts]);
|
|
186
|
+
},
|
|
187
|
+
};
|
|
188
|
+
if (options.enableManagement !== false) {
|
|
189
|
+
this.management = {
|
|
190
|
+
config: {
|
|
191
|
+
async get(packageId, key) {
|
|
192
|
+
return forward("getConfig", [packageId, key]);
|
|
193
|
+
},
|
|
194
|
+
async set(packageId, key, value) {
|
|
195
|
+
return forward("setConfig", [packageId, key, value]);
|
|
196
|
+
},
|
|
197
|
+
async delete(packageId, key) {
|
|
198
|
+
return forward("deleteConfig", [packageId, key]);
|
|
199
|
+
},
|
|
200
|
+
async list(packageId) {
|
|
201
|
+
return forward("listConfig", [packageId]);
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
state: {
|
|
205
|
+
async get(packageId, actionId, key, opts) {
|
|
206
|
+
return forward("getState", [packageId, actionId, key, opts]);
|
|
207
|
+
},
|
|
208
|
+
async set(packageId, actionId, key, value, opts) {
|
|
209
|
+
return forward("setState", [packageId, actionId, key, value, opts]);
|
|
210
|
+
},
|
|
211
|
+
async delete(packageId, actionId, key, opts) {
|
|
212
|
+
return forward("deleteState", [packageId, actionId, key, opts]);
|
|
213
|
+
},
|
|
214
|
+
async list(packageId, actionId, opts) {
|
|
215
|
+
return forward("listStateKeys", [packageId, actionId, opts]);
|
|
216
|
+
},
|
|
217
|
+
async clear(packageId, actionId, opts) {
|
|
218
|
+
return forward("clearState", [packageId, actionId, opts]);
|
|
219
|
+
},
|
|
220
|
+
async listEntries(packageId, opts) {
|
|
221
|
+
return forward("listStateEntries", [packageId, opts]);
|
|
222
|
+
},
|
|
223
|
+
},
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
async callRemote(method, args, signal) {
|
|
228
|
+
if (this.isClosed || this.exitError) {
|
|
229
|
+
if (method === "runAction") {
|
|
230
|
+
return {
|
|
231
|
+
ok: false,
|
|
232
|
+
runId: randomUUID(),
|
|
233
|
+
error: {
|
|
234
|
+
code: HOST_PROCESS_EXITED,
|
|
235
|
+
message: this.exitError?.message || "Host process is already closed",
|
|
236
|
+
},
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
throw this.exitError || new ActionDockError(SERVICE_CLOSED, `IpcActionDockService is closed: cannot call '${method}'`);
|
|
240
|
+
}
|
|
241
|
+
await this.readyPromise;
|
|
242
|
+
if (signal?.aborted) {
|
|
243
|
+
const err = new Error(signal.reason ? String(signal.reason) : "Operation aborted");
|
|
244
|
+
err.name = "AbortError";
|
|
245
|
+
throw err;
|
|
246
|
+
}
|
|
247
|
+
const id = randomUUID();
|
|
248
|
+
const message = {
|
|
249
|
+
id,
|
|
250
|
+
type: "call",
|
|
251
|
+
method,
|
|
252
|
+
args,
|
|
253
|
+
};
|
|
254
|
+
let onAbort;
|
|
255
|
+
if (signal && !signal.aborted) {
|
|
256
|
+
onAbort = () => {
|
|
257
|
+
this.sendAbort(id);
|
|
258
|
+
};
|
|
259
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
260
|
+
}
|
|
261
|
+
return new Promise((resolve, reject) => {
|
|
262
|
+
this.pendingCalls.set(id, { resolve, reject, method });
|
|
263
|
+
try {
|
|
264
|
+
if (!this.child.send) {
|
|
265
|
+
throw new ActionDockError(CAPABILITY_UNAVAILABLE, "ChildProcess IPC channel is not available");
|
|
266
|
+
}
|
|
267
|
+
this.child.send(message, (err) => {
|
|
268
|
+
if (err) {
|
|
269
|
+
this.pendingCalls.delete(id);
|
|
270
|
+
reject(err);
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
catch (err) {
|
|
275
|
+
this.pendingCalls.delete(id);
|
|
276
|
+
reject(err);
|
|
277
|
+
}
|
|
278
|
+
}).finally(() => {
|
|
279
|
+
if (onAbort && signal) {
|
|
280
|
+
signal.removeEventListener("abort", onAbort);
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
sendAbort(id) {
|
|
285
|
+
if (this.isClosed)
|
|
286
|
+
return;
|
|
287
|
+
try {
|
|
288
|
+
const abortMsg = { id, type: "abort" };
|
|
289
|
+
if (this.child.send) {
|
|
290
|
+
this.child.send(abortMsg);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
catch (err) {
|
|
294
|
+
process.stderr.write(`[IPC Service] Failed to send abort for call '${id}': ${err instanceof Error ? err.message : String(err)}\n`);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
async info() {
|
|
298
|
+
return this.discovery.listPackages();
|
|
299
|
+
}
|
|
300
|
+
get process() {
|
|
301
|
+
return this.child;
|
|
302
|
+
}
|
|
303
|
+
async waitReady() {
|
|
304
|
+
return this.readyPromise;
|
|
305
|
+
}
|
|
306
|
+
async close() {
|
|
307
|
+
if (this.isClosed)
|
|
308
|
+
return;
|
|
309
|
+
if (this.child.exitCode !== null) {
|
|
310
|
+
this.forwarder.detachAll();
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
const exitPromise = new Promise((resolve) => {
|
|
314
|
+
this.child.once("exit", () => resolve());
|
|
315
|
+
});
|
|
316
|
+
try {
|
|
317
|
+
if (this.child.connected) {
|
|
318
|
+
this.child.send({ type: "close" });
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
catch {
|
|
322
|
+
// 忽略发送关闭消息异常
|
|
323
|
+
}
|
|
324
|
+
const termTimer = setTimeout(() => {
|
|
325
|
+
if (this.child.exitCode === null) {
|
|
326
|
+
try {
|
|
327
|
+
this.child.kill("SIGTERM");
|
|
328
|
+
}
|
|
329
|
+
catch { }
|
|
330
|
+
}
|
|
331
|
+
}, 1500);
|
|
332
|
+
const killTimer = setTimeout(() => {
|
|
333
|
+
if (this.child.exitCode === null) {
|
|
334
|
+
try {
|
|
335
|
+
this.child.kill("SIGKILL");
|
|
336
|
+
}
|
|
337
|
+
catch { }
|
|
338
|
+
}
|
|
339
|
+
}, 3000);
|
|
340
|
+
try {
|
|
341
|
+
await exitPromise;
|
|
342
|
+
}
|
|
343
|
+
finally {
|
|
344
|
+
clearTimeout(termTimer);
|
|
345
|
+
clearTimeout(killTimer);
|
|
346
|
+
this.forwarder.detachAll();
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
package/dist/ipc/types.d.ts
CHANGED
|
@@ -32,20 +32,26 @@ export interface IpcResponseMessage {
|
|
|
32
32
|
};
|
|
33
33
|
}
|
|
34
34
|
/**
|
|
35
|
-
*
|
|
35
|
+
* 监督进程 IPC 服务初始化选项。
|
|
36
36
|
*/
|
|
37
|
-
export interface
|
|
38
|
-
type
|
|
39
|
-
|
|
40
|
-
|
|
37
|
+
export interface IpcServiceOptions {
|
|
38
|
+
type?: "ipc";
|
|
39
|
+
/** 已启动的子进程实例(需具备 IPC 通道) */
|
|
40
|
+
childProcess?: any;
|
|
41
|
+
/** 待启动的脚本物理路径 */
|
|
42
|
+
scriptPath?: string;
|
|
43
|
+
/** 启动参数 */
|
|
44
|
+
scriptArgs?: string[];
|
|
45
|
+
/** 工作目录 */
|
|
46
|
+
cwd?: string;
|
|
47
|
+
/** 环境变量 */
|
|
48
|
+
env?: NodeJS.ProcessEnv;
|
|
49
|
+
/** 诊断流最大缓冲字节数 */
|
|
50
|
+
maxDiagnosticBytes?: number;
|
|
51
|
+
/** 诊断流每秒最大字节速率 */
|
|
52
|
+
maxDiagnosticRate?: number;
|
|
53
|
+
/** 诊断流目标输出 */
|
|
54
|
+
diagnosticTarget?: any;
|
|
55
|
+
/** 是否启用管理端口(默认 true) */
|
|
56
|
+
enableManagement?: boolean;
|
|
41
57
|
}
|
|
42
|
-
/**
|
|
43
|
-
* 宿主子进程初始化就绪通知。
|
|
44
|
-
*/
|
|
45
|
-
export interface IpcReadyMessage {
|
|
46
|
-
type: "ready";
|
|
47
|
-
}
|
|
48
|
-
/**
|
|
49
|
-
* 联合 IPC 消息类型。
|
|
50
|
-
*/
|
|
51
|
-
export type IpcMessage = IpcCallMessage | IpcResponseMessage | IpcAbortMessage | IpcEventMessage | IpcReadyMessage;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./module-loader.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./module-loader.js";
|