@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/server/body.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ActionDockError } from "../errors.js";
|
|
1
2
|
/**
|
|
2
3
|
* 默认单次 HTTP 请求体最大字节上限(1 MiB)。
|
|
3
4
|
*/
|
|
@@ -5,38 +6,36 @@ export const DEFAULT_MAX_BODY_BYTES = 1024 * 1024; // 1 MiB
|
|
|
5
6
|
/**
|
|
6
7
|
* 请求体超出最大允许体积时抛出的异常(对应 HTTP 413 Payload Too Large)。
|
|
7
8
|
*/
|
|
8
|
-
export class RequestTooLargeError extends
|
|
9
|
-
code = "REQUEST_TOO_LARGE";
|
|
9
|
+
export class RequestTooLargeError extends ActionDockError {
|
|
10
10
|
constructor(message = "Request body exceeds maximum allowed size") {
|
|
11
|
-
super(message);
|
|
11
|
+
super("REQUEST_TOO_LARGE", message);
|
|
12
12
|
this.name = "RequestTooLargeError";
|
|
13
|
+
Object.setPrototypeOf(this, RequestTooLargeError.prototype);
|
|
13
14
|
}
|
|
14
15
|
}
|
|
15
16
|
/**
|
|
16
17
|
* 请求体非合法 JSON 格式时抛出的异常(对应 HTTP 400 Bad Request)。
|
|
17
18
|
*/
|
|
18
|
-
export class InvalidJsonError extends
|
|
19
|
-
code = "INVALID_JSON";
|
|
19
|
+
export class InvalidJsonError extends ActionDockError {
|
|
20
20
|
constructor(message = "Failed to parse request body as JSON") {
|
|
21
|
-
super(message);
|
|
21
|
+
super("INVALID_JSON", message);
|
|
22
22
|
this.name = "InvalidJsonError";
|
|
23
|
+
Object.setPrototypeOf(this, InvalidJsonError.prototype);
|
|
23
24
|
}
|
|
24
25
|
}
|
|
25
26
|
/**
|
|
26
|
-
* 流式安全地读取 HTTP
|
|
27
|
+
* 流式安全地读取 HTTP 请求体为原始字节数组(有界读取单一事实源)。
|
|
27
28
|
*
|
|
28
29
|
* 防护机制:
|
|
29
|
-
* 1. 快速拒绝(Fast-path):若请求头中存在
|
|
30
|
-
* 2. 流式计数器(Chunk Streaming
|
|
31
|
-
* 3. 安全解码:处理空请求体与 UTF-8 字符集解码。
|
|
30
|
+
* 1. 快速拒绝(Fast-path):若请求头中存在 Content-Length 且超过 maxBytes,立即抛出 RequestTooLargeError,不进行内存分配。
|
|
31
|
+
* 2. 流式计数器(Chunk Streaming):读取 ReadableStream 过程中实时累计字节数,中途超出上限即刻截断并释放流锁,防止大文件 DoS 与内存耗尽(OOM)。
|
|
32
32
|
*
|
|
33
33
|
* @param req 传入的 Request 对象
|
|
34
34
|
* @param options 读取配置选项
|
|
35
|
-
* @returns
|
|
35
|
+
* @returns 原始字节数组(空请求体返回空数组)
|
|
36
36
|
* @throws {RequestTooLargeError} 若请求体体积超限
|
|
37
|
-
* @throws {InvalidJsonError} 若请求体非合法 JSON
|
|
38
37
|
*/
|
|
39
|
-
export async function
|
|
38
|
+
export async function readBodyWithLimit(req, options = {}) {
|
|
40
39
|
const maxBytes = options.maxBytes ?? DEFAULT_MAX_BODY_BYTES;
|
|
41
40
|
// 1. 快速检查 Content-Length 头部
|
|
42
41
|
const contentLengthHeader = req.headers.get("content-length");
|
|
@@ -48,7 +47,7 @@ export async function readJsonBody(req, options = {}) {
|
|
|
48
47
|
}
|
|
49
48
|
// 2. 流式读取并实时统计字节数
|
|
50
49
|
if (!req.body) {
|
|
51
|
-
return
|
|
50
|
+
return new Uint8Array(0);
|
|
52
51
|
}
|
|
53
52
|
const reader = req.body.getReader();
|
|
54
53
|
const chunks = [];
|
|
@@ -61,6 +60,7 @@ export async function readJsonBody(req, options = {}) {
|
|
|
61
60
|
if (value) {
|
|
62
61
|
totalBytes += value.byteLength;
|
|
63
62
|
if (totalBytes > maxBytes) {
|
|
63
|
+
await reader.cancel();
|
|
64
64
|
throw new RequestTooLargeError();
|
|
65
65
|
}
|
|
66
66
|
chunks.push(value);
|
|
@@ -71,15 +71,32 @@ export async function readJsonBody(req, options = {}) {
|
|
|
71
71
|
reader.releaseLock();
|
|
72
72
|
}
|
|
73
73
|
if (chunks.length === 0 || totalBytes === 0) {
|
|
74
|
-
return
|
|
74
|
+
return new Uint8Array(0);
|
|
75
75
|
}
|
|
76
|
-
// 3.
|
|
76
|
+
// 3. 拼接字节数组
|
|
77
77
|
const merged = new Uint8Array(totalBytes);
|
|
78
78
|
let offset = 0;
|
|
79
79
|
for (const chunk of chunks) {
|
|
80
80
|
merged.set(chunk, offset);
|
|
81
81
|
offset += chunk.byteLength;
|
|
82
82
|
}
|
|
83
|
+
return merged;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* 流式安全地读取 HTTP 请求体并解析为 JSON 对象。
|
|
87
|
+
* 防护机制见 readBodyWithLimit;此外处理空请求体与 UTF-8 字符集解码。
|
|
88
|
+
*
|
|
89
|
+
* @param req 传入的 Request 对象
|
|
90
|
+
* @param options 读取配置选项
|
|
91
|
+
* @returns 解析后的 JSON 对象
|
|
92
|
+
* @throws {RequestTooLargeError} 若请求体体积超限
|
|
93
|
+
* @throws {InvalidJsonError} 若请求体非合法 JSON
|
|
94
|
+
*/
|
|
95
|
+
export async function readJsonBody(req, options = {}) {
|
|
96
|
+
const merged = await readBodyWithLimit(req, options);
|
|
97
|
+
if (merged.byteLength === 0) {
|
|
98
|
+
return {};
|
|
99
|
+
}
|
|
83
100
|
const text = new TextDecoder("utf-8").decode(merged);
|
|
84
101
|
if (!text.trim()) {
|
|
85
102
|
return {};
|
|
@@ -4,8 +4,6 @@
|
|
|
4
4
|
export type FetchDispatcherProvider = () => unknown;
|
|
5
5
|
/**
|
|
6
6
|
* 注册全局用于忽略服务端证书校验的调度器提供者。
|
|
7
|
-
* 唯一注册通道:平台适配层(如 @actiondock/runtime-node 的
|
|
8
|
-
* installInsecureDispatcher)显式调用完成注册,不再读取全局符号注入。
|
|
9
7
|
*/
|
|
10
8
|
export declare function setInsecureDispatcherProvider(provider?: FetchDispatcherProvider): void;
|
|
11
9
|
/**
|
|
@@ -13,17 +11,15 @@ export declare function setInsecureDispatcherProvider(provider?: FetchDispatcher
|
|
|
13
11
|
*/
|
|
14
12
|
export declare function getInsecureDispatcherProvider(): FetchDispatcherProvider | undefined;
|
|
15
13
|
/**
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* 1. 优先调用显式注册的提供者(如由 @actiondock/runtime-node 显式安装);
|
|
19
|
-
* 2. 若在 Node 环境下且无注册提供者,动态加载 @actiondock/runtime-node
|
|
20
|
-
* (其入口聚合点会显式完成安装),绝不让 core 包产生静态硬编译依赖;
|
|
21
|
-
* 3. 仍不可得时返回 undefined,由调用方回退默认 fetch,绝不在 core 内
|
|
22
|
-
* 直接探测加载 undici,避免与平台适配层形成层次倒置与隐藏依赖环。
|
|
14
|
+
* 获取用于忽略服务端证书校验的全局单例 Undici Agent 调度器。
|
|
15
|
+
* 显式配置 bodyTimeout: 0 以防止 SSE 长连接与流式事件读取出现 UND_ERR_BODY_TIMEOUT 空闲超时断流。
|
|
23
16
|
*/
|
|
24
17
|
export declare function getInsecureDispatcher(): unknown;
|
|
18
|
+
/**
|
|
19
|
+
* 显式安装平台调度器:向 core 注册本模块的提供者函数。
|
|
20
|
+
*/
|
|
21
|
+
export declare function installInsecureDispatcher(): void;
|
|
25
22
|
/**
|
|
26
23
|
* 显式强制重置并销毁调度器连接池(仅用于单元测试重置或进程退出)。
|
|
27
|
-
* core 自身不再持有任何兜底连接池,关闭语义完全委托已注册的平台提供者宿主。
|
|
28
24
|
*/
|
|
29
25
|
export declare function closeInsecureDispatcher(): Promise<void>;
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Agent } from "undici";
|
|
2
2
|
let insecureDispatcherProvider;
|
|
3
|
+
let globalInsecureAgent;
|
|
3
4
|
/**
|
|
4
5
|
* 注册全局用于忽略服务端证书校验的调度器提供者。
|
|
5
|
-
* 唯一注册通道:平台适配层(如 @actiondock/runtime-node 的
|
|
6
|
-
* installInsecureDispatcher)显式调用完成注册,不再读取全局符号注入。
|
|
7
6
|
*/
|
|
8
7
|
export function setInsecureDispatcherProvider(provider) {
|
|
9
8
|
insecureDispatcherProvider = provider;
|
|
@@ -15,48 +14,41 @@ export function getInsecureDispatcherProvider() {
|
|
|
15
14
|
return insecureDispatcherProvider;
|
|
16
15
|
}
|
|
17
16
|
/**
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* 1. 优先调用显式注册的提供者(如由 @actiondock/runtime-node 显式安装);
|
|
21
|
-
* 2. 若在 Node 环境下且无注册提供者,动态加载 @actiondock/runtime-node
|
|
22
|
-
* (其入口聚合点会显式完成安装),绝不让 core 包产生静态硬编译依赖;
|
|
23
|
-
* 3. 仍不可得时返回 undefined,由调用方回退默认 fetch,绝不在 core 内
|
|
24
|
-
* 直接探测加载 undici,避免与平台适配层形成层次倒置与隐藏依赖环。
|
|
17
|
+
* 获取用于忽略服务端证书校验的全局单例 Undici Agent 调度器。
|
|
18
|
+
* 显式配置 bodyTimeout: 0 以防止 SSE 长连接与流式事件读取出现 UND_ERR_BODY_TIMEOUT 空闲超时断流。
|
|
25
19
|
*/
|
|
26
20
|
export function getInsecureDispatcher() {
|
|
27
21
|
if (insecureDispatcherProvider) {
|
|
28
22
|
return insecureDispatcherProvider();
|
|
29
23
|
}
|
|
30
|
-
if (
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}
|
|
38
|
-
catch {
|
|
39
|
-
// 忽略未安装 @actiondock/runtime-node 的情况
|
|
40
|
-
}
|
|
24
|
+
if (!globalInsecureAgent || globalInsecureAgent.closed || globalInsecureAgent.destroyed) {
|
|
25
|
+
globalInsecureAgent = new Agent({
|
|
26
|
+
connect: {
|
|
27
|
+
rejectUnauthorized: false,
|
|
28
|
+
},
|
|
29
|
+
bodyTimeout: 0,
|
|
30
|
+
});
|
|
41
31
|
}
|
|
42
|
-
return
|
|
32
|
+
return globalInsecureAgent;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* 显式安装平台调度器:向 core 注册本模块的提供者函数。
|
|
36
|
+
*/
|
|
37
|
+
export function installInsecureDispatcher() {
|
|
38
|
+
setInsecureDispatcherProvider(getInsecureDispatcher);
|
|
43
39
|
}
|
|
44
40
|
/**
|
|
45
41
|
* 显式强制重置并销毁调度器连接池(仅用于单元测试重置或进程退出)。
|
|
46
|
-
* core 自身不再持有任何兜底连接池,关闭语义完全委托已注册的平台提供者宿主。
|
|
47
42
|
*/
|
|
48
43
|
export async function closeInsecureDispatcher() {
|
|
49
|
-
if (
|
|
50
|
-
|
|
44
|
+
if (globalInsecureAgent) {
|
|
45
|
+
const agent = globalInsecureAgent;
|
|
46
|
+
globalInsecureAgent = undefined;
|
|
51
47
|
try {
|
|
52
|
-
|
|
53
|
-
const runtimeNode = req("@actiondock/runtime-node");
|
|
54
|
-
if (typeof runtimeNode?.closeInsecureDispatcher === "function") {
|
|
55
|
-
await runtimeNode.closeInsecureDispatcher();
|
|
56
|
-
}
|
|
48
|
+
await agent.close();
|
|
57
49
|
}
|
|
58
|
-
catch {
|
|
59
|
-
|
|
50
|
+
catch (err) {
|
|
51
|
+
console.warn("[actiondock] closeInsecureDispatcher failed to close agent:", err);
|
|
60
52
|
}
|
|
61
53
|
}
|
|
62
54
|
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { type IncomingMessage, type Server, type ServerResponse } from "node:http";
|
|
2
|
+
import { type Server as HttpsServer } from "node:https";
|
|
3
|
+
import type { ServerTlsOptions } from "./types.js";
|
|
4
|
+
/**
|
|
5
|
+
* Web 标准 Request 处理函数。
|
|
6
|
+
*/
|
|
7
|
+
export type WebRequestHandler = (request: Request) => Promise<Response> | Response;
|
|
8
|
+
/**
|
|
9
|
+
* Node HTTP 服务端配置选项。
|
|
10
|
+
*/
|
|
11
|
+
export interface NodeHttpServerOptions {
|
|
12
|
+
port?: number;
|
|
13
|
+
host?: string;
|
|
14
|
+
fetch: WebRequestHandler;
|
|
15
|
+
baseOrigin?: string;
|
|
16
|
+
tls?: ServerTlsOptions;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* 将 Node.js IncomingMessage 请求转化为标准的 Web Request 对象。
|
|
20
|
+
*/
|
|
21
|
+
export declare function createWebRequest(req: IncomingMessage, options?: {
|
|
22
|
+
baseOrigin?: string;
|
|
23
|
+
res?: ServerResponse;
|
|
24
|
+
} | ServerResponse): Request;
|
|
25
|
+
/**
|
|
26
|
+
* 将标准的 Web Response 响应流式写回 Node.js ServerResponse 客户端输出。
|
|
27
|
+
*/
|
|
28
|
+
export declare function sendWebResponse(webResponse: Response, res: ServerResponse): Promise<void>;
|
|
29
|
+
/**
|
|
30
|
+
* 创建适用于 Node.js 原生 http.createServer 的请求监听函数。
|
|
31
|
+
*/
|
|
32
|
+
export declare function createRequestListener(handler: WebRequestHandler, options?: {
|
|
33
|
+
baseOrigin?: string;
|
|
34
|
+
}): (req: IncomingMessage, res: ServerResponse) => void;
|
|
35
|
+
/**
|
|
36
|
+
* 基于 Node.js node:http 的轻量级 HTTP 服务端封装。
|
|
37
|
+
* 接收 Node HTTP 请求并转化为标准 Web Request,支持 Web Response 流式输出。
|
|
38
|
+
*/
|
|
39
|
+
export declare class NodeHttpServer {
|
|
40
|
+
private server;
|
|
41
|
+
private listening;
|
|
42
|
+
private portNumber;
|
|
43
|
+
private hostAddress;
|
|
44
|
+
private tlsOptions?;
|
|
45
|
+
private persistentErrorHandler?;
|
|
46
|
+
constructor(optionsOrHandler: WebRequestHandler | NodeHttpServerOptions);
|
|
47
|
+
/**
|
|
48
|
+
* 底层原生 Node.js http.Server / https.Server 实例。
|
|
49
|
+
*/
|
|
50
|
+
get rawServer(): Server | HttpsServer;
|
|
51
|
+
/**
|
|
52
|
+
* 绑定的监听端口号。
|
|
53
|
+
*/
|
|
54
|
+
get port(): number;
|
|
55
|
+
/**
|
|
56
|
+
* 绑定的主机地址。
|
|
57
|
+
*/
|
|
58
|
+
get host(): string;
|
|
59
|
+
/**
|
|
60
|
+
* 完整的 HTTP 访问基础路径。
|
|
61
|
+
*/
|
|
62
|
+
get url(): string;
|
|
63
|
+
/**
|
|
64
|
+
* 服务端当前是否正在监听连接。
|
|
65
|
+
*/
|
|
66
|
+
get isListening(): boolean;
|
|
67
|
+
/**
|
|
68
|
+
* 启动监听。若未显式传入端口,则默认使用实例配置或随机可用端口(0)。
|
|
69
|
+
*/
|
|
70
|
+
listen(port?: number, host?: string): Promise<{
|
|
71
|
+
port: number;
|
|
72
|
+
host: string;
|
|
73
|
+
url: string;
|
|
74
|
+
}>;
|
|
75
|
+
/**
|
|
76
|
+
* 优雅关闭服务端并释放端口与活动连接。
|
|
77
|
+
*/
|
|
78
|
+
close(): Promise<void>;
|
|
79
|
+
/**
|
|
80
|
+
* 停止服务端监听(兼容 stop 契约)。
|
|
81
|
+
*/
|
|
82
|
+
stop(): Promise<void>;
|
|
83
|
+
/**
|
|
84
|
+
* 便捷工厂方法:创建并启动监听。
|
|
85
|
+
*/
|
|
86
|
+
static start(optionsOrHandler: WebRequestHandler | NodeHttpServerOptions, port?: number, host?: string): Promise<NodeHttpServer>;
|
|
87
|
+
}
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
import { createServer } from "node:http";
|
|
2
|
+
import { createServer as createHttpsServer } from "node:https";
|
|
3
|
+
import { readFileSync } from "node:fs";
|
|
4
|
+
import { Readable } from "node:stream";
|
|
5
|
+
import { pipeline } from "node:stream/promises";
|
|
6
|
+
import { BAD_REQUEST, SERVER_ERROR } from "../errors.js";
|
|
7
|
+
import { formatHostForUrl } from "./server.js";
|
|
8
|
+
/**
|
|
9
|
+
* 将 Node.js IncomingMessage 请求转化为标准的 Web Request 对象。
|
|
10
|
+
*/
|
|
11
|
+
export function createWebRequest(req, options) {
|
|
12
|
+
const protocol = req.socket?.encrypted ? "https" : "http";
|
|
13
|
+
const host = req.headers.host || "127.0.0.1";
|
|
14
|
+
let baseOrigin;
|
|
15
|
+
let res;
|
|
16
|
+
if (options) {
|
|
17
|
+
if ("writableFinished" in options || typeof options.writeHead === "function") {
|
|
18
|
+
res = options;
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
baseOrigin = options.baseOrigin;
|
|
22
|
+
res = options.res;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
if (!res && req.res) {
|
|
26
|
+
res = req.res;
|
|
27
|
+
}
|
|
28
|
+
baseOrigin = baseOrigin || `${protocol}://${host}`;
|
|
29
|
+
const url = new URL(req.url || "/", baseOrigin).href;
|
|
30
|
+
const headers = new Headers();
|
|
31
|
+
for (const [key, val] of Object.entries(req.headers)) {
|
|
32
|
+
if (val === undefined)
|
|
33
|
+
continue;
|
|
34
|
+
if (Array.isArray(val)) {
|
|
35
|
+
for (const item of val) {
|
|
36
|
+
headers.append(key, item);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
headers.set(key, val);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const method = (req.method || "GET").toUpperCase();
|
|
44
|
+
const hasBody = method !== "GET" && method !== "HEAD";
|
|
45
|
+
const ac = new AbortController();
|
|
46
|
+
req.on("close", () => {
|
|
47
|
+
if (!req.complete) {
|
|
48
|
+
ac.abort(new Error("Request aborted by client"));
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
if (res) {
|
|
52
|
+
res.on("close", () => {
|
|
53
|
+
if (!res.writableFinished) {
|
|
54
|
+
ac.abort(new Error("Request aborted by client"));
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
const init = {
|
|
59
|
+
method,
|
|
60
|
+
headers,
|
|
61
|
+
signal: ac.signal,
|
|
62
|
+
};
|
|
63
|
+
if (hasBody) {
|
|
64
|
+
const webStream = Readable.toWeb(req);
|
|
65
|
+
init.body = webStream;
|
|
66
|
+
init.duplex = "half";
|
|
67
|
+
}
|
|
68
|
+
return new Request(url, init);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* 将标准的 Web Response 响应流式写回 Node.js ServerResponse 客户端输出。
|
|
72
|
+
*/
|
|
73
|
+
export async function sendWebResponse(webResponse, res) {
|
|
74
|
+
res.statusCode = webResponse.status;
|
|
75
|
+
if (webResponse.statusText) {
|
|
76
|
+
res.statusMessage = webResponse.statusText;
|
|
77
|
+
}
|
|
78
|
+
webResponse.headers.forEach((val, key) => {
|
|
79
|
+
if (key.toLowerCase() === "set-cookie") {
|
|
80
|
+
if (typeof webResponse.headers.getSetCookie === "function") {
|
|
81
|
+
res.setHeader("set-cookie", webResponse.headers.getSetCookie());
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
res.appendHeader("set-cookie", val);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
res.setHeader(key, val);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
if (!webResponse.body) {
|
|
92
|
+
res.end();
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
const nodeStream = Readable.fromWeb(webResponse.body);
|
|
96
|
+
try {
|
|
97
|
+
await pipeline(nodeStream, res);
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
if (err?.code !== "ERR_STREAM_PREMATURE_CLOSE") {
|
|
101
|
+
throw err;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* 创建适用于 Node.js 原生 http.createServer 的请求监听函数。
|
|
107
|
+
*/
|
|
108
|
+
export function createRequestListener(handler, options) {
|
|
109
|
+
return (req, res) => {
|
|
110
|
+
let webReq;
|
|
111
|
+
try {
|
|
112
|
+
webReq = createWebRequest(req, { ...options, res });
|
|
113
|
+
}
|
|
114
|
+
catch (err) {
|
|
115
|
+
if (!res.headersSent) {
|
|
116
|
+
res.statusCode = 400;
|
|
117
|
+
res.setHeader("Content-Type", "application/json");
|
|
118
|
+
res.end(JSON.stringify({
|
|
119
|
+
ok: false,
|
|
120
|
+
error: {
|
|
121
|
+
code: BAD_REQUEST,
|
|
122
|
+
message: err?.message || String(err),
|
|
123
|
+
},
|
|
124
|
+
}));
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
res.destroy(err);
|
|
128
|
+
}
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
Promise.resolve()
|
|
132
|
+
.then(() => handler(webReq))
|
|
133
|
+
.then((webRes) => sendWebResponse(webRes, res))
|
|
134
|
+
.catch((err) => {
|
|
135
|
+
if (!res.headersSent) {
|
|
136
|
+
res.statusCode = 500;
|
|
137
|
+
res.setHeader("Content-Type", "application/json");
|
|
138
|
+
res.end(JSON.stringify({
|
|
139
|
+
ok: false,
|
|
140
|
+
error: {
|
|
141
|
+
code: SERVER_ERROR,
|
|
142
|
+
message: err?.message || String(err),
|
|
143
|
+
},
|
|
144
|
+
}));
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
res.destroy(err);
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* 基于 Node.js node:http 的轻量级 HTTP 服务端封装。
|
|
154
|
+
* 接收 Node HTTP 请求并转化为标准 Web Request,支持 Web Response 流式输出。
|
|
155
|
+
*/
|
|
156
|
+
export class NodeHttpServer {
|
|
157
|
+
server;
|
|
158
|
+
listening = false;
|
|
159
|
+
portNumber = 0;
|
|
160
|
+
hostAddress = "127.0.0.1";
|
|
161
|
+
tlsOptions;
|
|
162
|
+
persistentErrorHandler;
|
|
163
|
+
constructor(optionsOrHandler) {
|
|
164
|
+
const handler = typeof optionsOrHandler === "function"
|
|
165
|
+
? optionsOrHandler
|
|
166
|
+
: optionsOrHandler.fetch;
|
|
167
|
+
const baseOrigin = typeof optionsOrHandler === "object"
|
|
168
|
+
? optionsOrHandler.baseOrigin
|
|
169
|
+
: undefined;
|
|
170
|
+
const tls = typeof optionsOrHandler === "object"
|
|
171
|
+
? optionsOrHandler.tls
|
|
172
|
+
: undefined;
|
|
173
|
+
this.tlsOptions = tls;
|
|
174
|
+
const requestListener = createRequestListener(handler, { baseOrigin });
|
|
175
|
+
if (tls) {
|
|
176
|
+
let cert = tls.cert;
|
|
177
|
+
if (!cert && tls.certPath) {
|
|
178
|
+
cert = readFileSync(tls.certPath);
|
|
179
|
+
}
|
|
180
|
+
let key = tls.key;
|
|
181
|
+
if (!key && tls.keyPath) {
|
|
182
|
+
key = readFileSync(tls.keyPath);
|
|
183
|
+
}
|
|
184
|
+
const httpsOptions = { cert, key };
|
|
185
|
+
if (tls.ca)
|
|
186
|
+
httpsOptions.ca = tls.ca;
|
|
187
|
+
if (tls.passphrase)
|
|
188
|
+
httpsOptions.passphrase = tls.passphrase;
|
|
189
|
+
this.server = createHttpsServer(httpsOptions, requestListener);
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
this.server = createServer(requestListener);
|
|
193
|
+
}
|
|
194
|
+
if (typeof optionsOrHandler === "object") {
|
|
195
|
+
if (optionsOrHandler.port !== undefined) {
|
|
196
|
+
this.portNumber = optionsOrHandler.port;
|
|
197
|
+
}
|
|
198
|
+
if (optionsOrHandler.host !== undefined) {
|
|
199
|
+
this.hostAddress = optionsOrHandler.host;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* 底层原生 Node.js http.Server / https.Server 实例。
|
|
205
|
+
*/
|
|
206
|
+
get rawServer() {
|
|
207
|
+
return this.server;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* 绑定的监听端口号。
|
|
211
|
+
*/
|
|
212
|
+
get port() {
|
|
213
|
+
return this.portNumber;
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* 绑定的主机地址。
|
|
217
|
+
*/
|
|
218
|
+
get host() {
|
|
219
|
+
return this.hostAddress;
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* 完整的 HTTP 访问基础路径。
|
|
223
|
+
*/
|
|
224
|
+
get url() {
|
|
225
|
+
const protocol = this.tlsOptions ? "https" : "http";
|
|
226
|
+
return `${protocol}://${formatHostForUrl(this.hostAddress)}:${this.portNumber}`;
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* 服务端当前是否正在监听连接。
|
|
230
|
+
*/
|
|
231
|
+
get isListening() {
|
|
232
|
+
return this.listening;
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* 启动监听。若未显式传入端口,则默认使用实例配置或随机可用端口(0)。
|
|
236
|
+
*/
|
|
237
|
+
async listen(port, host) {
|
|
238
|
+
const targetPort = port ?? this.portNumber ?? 0;
|
|
239
|
+
const targetHost = host ?? this.hostAddress ?? "127.0.0.1";
|
|
240
|
+
if (!this.persistentErrorHandler) {
|
|
241
|
+
this.persistentErrorHandler = (err) => {
|
|
242
|
+
console.error("[NodeHttpServer] unexpected server error:", err);
|
|
243
|
+
};
|
|
244
|
+
this.server.on("error", this.persistentErrorHandler);
|
|
245
|
+
}
|
|
246
|
+
return new Promise((resolve, reject) => {
|
|
247
|
+
const onListenError = (err) => {
|
|
248
|
+
this.server.removeListener("error", onListenError);
|
|
249
|
+
reject(err);
|
|
250
|
+
};
|
|
251
|
+
this.server.once("error", onListenError);
|
|
252
|
+
this.server.listen(targetPort, targetHost, () => {
|
|
253
|
+
this.server.removeListener("error", onListenError);
|
|
254
|
+
const addr = this.server.address();
|
|
255
|
+
if (addr && typeof addr === "object") {
|
|
256
|
+
this.portNumber = addr.port;
|
|
257
|
+
this.hostAddress = addr.address === "::" ? "127.0.0.1" : addr.address;
|
|
258
|
+
}
|
|
259
|
+
this.listening = true;
|
|
260
|
+
resolve({
|
|
261
|
+
port: this.portNumber,
|
|
262
|
+
host: this.hostAddress,
|
|
263
|
+
url: this.url,
|
|
264
|
+
});
|
|
265
|
+
});
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* 优雅关闭服务端并释放端口与活动连接。
|
|
270
|
+
*/
|
|
271
|
+
async close() {
|
|
272
|
+
if (!this.listening) {
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
if (this.persistentErrorHandler) {
|
|
276
|
+
this.server.removeListener("error", this.persistentErrorHandler);
|
|
277
|
+
this.persistentErrorHandler = undefined;
|
|
278
|
+
}
|
|
279
|
+
return new Promise((resolve, reject) => {
|
|
280
|
+
this.server.close((err) => {
|
|
281
|
+
this.listening = false;
|
|
282
|
+
if (err)
|
|
283
|
+
reject(err);
|
|
284
|
+
else
|
|
285
|
+
resolve();
|
|
286
|
+
});
|
|
287
|
+
this.server.closeAllConnections?.();
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* 停止服务端监听(兼容 stop 契约)。
|
|
292
|
+
*/
|
|
293
|
+
async stop() {
|
|
294
|
+
return this.close();
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* 便捷工厂方法:创建并启动监听。
|
|
298
|
+
*/
|
|
299
|
+
static async start(optionsOrHandler, port, host) {
|
|
300
|
+
const server = new NodeHttpServer(optionsOrHandler);
|
|
301
|
+
await server.listen(port, host);
|
|
302
|
+
return server;
|
|
303
|
+
}
|
|
304
|
+
}
|
package/dist/server/index.d.ts
CHANGED
|
@@ -3,4 +3,15 @@ export * from "./security.js";
|
|
|
3
3
|
export * from "./body.js";
|
|
4
4
|
export * from "./routes/index.js";
|
|
5
5
|
export * from "./server.js";
|
|
6
|
+
export * from "./mcp-endpoint.js";
|
|
6
7
|
export * from "./dispatcher.js";
|
|
8
|
+
export * from "./http-server.js";
|
|
9
|
+
export { serveParentIpc } from "../ipc/host.js";
|
|
10
|
+
export { IpcActionDockService } from "../ipc/service.js";
|
|
11
|
+
export type { IpcServiceOptions } from "../ipc/types.js";
|
|
12
|
+
export { parseDuration } from "../utils/index.js";
|
|
13
|
+
export type { ActionDockHost, ActionDockHostOptions, } from "../host/types.js";
|
|
14
|
+
export { createActionDockHost, DefaultActionDockHost, } from "../host/host.js";
|
|
15
|
+
export { LocalActionDockService, } from "../service/local.js";
|
|
16
|
+
export { RemoteActionDockService, } from "../service/remote.js";
|
|
17
|
+
export { ExitCode, StandaloneDispatcher, type StandaloneDispatcherOptions, type InvocationControl, runStandaloneProcess, } from "../runtime/standalone.js";
|
package/dist/server/index.js
CHANGED
|
@@ -3,4 +3,14 @@ export * from "./security.js";
|
|
|
3
3
|
export * from "./body.js";
|
|
4
4
|
export * from "./routes/index.js";
|
|
5
5
|
export * from "./server.js";
|
|
6
|
+
export * from "./mcp-endpoint.js";
|
|
6
7
|
export * from "./dispatcher.js";
|
|
8
|
+
export * from "./http-server.js";
|
|
9
|
+
export { serveParentIpc } from "../ipc/host.js";
|
|
10
|
+
export { IpcActionDockService } from "../ipc/service.js";
|
|
11
|
+
export { parseDuration } from "../utils/index.js";
|
|
12
|
+
export { createActionDockHost, DefaultActionDockHost, } from "../host/host.js";
|
|
13
|
+
export { LocalActionDockService, } from "../service/local.js";
|
|
14
|
+
export { RemoteActionDockService, } from "../service/remote.js";
|
|
15
|
+
// 独立进程分发器与退出控制
|
|
16
|
+
export { ExitCode, StandaloneDispatcher, runStandaloneProcess, } from "../runtime/standalone.js";
|