@actiondock/core 2.5.1 → 2.7.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.
Files changed (175) hide show
  1. package/dist/app/app.d.ts +14 -1
  2. package/dist/app/app.js +88 -222
  3. package/dist/app/static-index.d.ts +39 -0
  4. package/dist/app/static-index.js +164 -0
  5. package/dist/app/types.d.ts +5 -0
  6. package/dist/catalog/action-index.js +6 -4
  7. package/dist/catalog/package-catalog.js +2 -1
  8. package/dist/doctor/checks/index.d.ts +5 -0
  9. package/dist/doctor/checks/index.js +5 -0
  10. package/dist/doctor/checks/project-locate.d.ts +6 -0
  11. package/dist/doctor/checks/project-locate.js +36 -0
  12. package/dist/doctor/checks/project.d.ts +6 -0
  13. package/dist/doctor/checks/project.js +383 -0
  14. package/dist/doctor/checks/registry.d.ts +13 -0
  15. package/dist/doctor/checks/registry.js +207 -0
  16. package/dist/doctor/checks/runtime.d.ts +13 -0
  17. package/dist/doctor/checks/runtime.js +90 -0
  18. package/dist/doctor/checks/storage.d.ts +5 -0
  19. package/dist/doctor/checks/storage.js +34 -0
  20. package/dist/doctor/context.d.ts +35 -0
  21. package/dist/doctor/context.js +11 -0
  22. package/dist/doctor/doctor.d.ts +5 -0
  23. package/dist/doctor/doctor.js +33 -602
  24. package/dist/errors.d.ts +22 -0
  25. package/dist/errors.js +22 -0
  26. package/dist/execution/service.js +14 -9
  27. package/dist/export/templates.js +42 -30
  28. package/dist/host/host.d.ts +33 -0
  29. package/dist/host/host.js +222 -362
  30. package/dist/host/routing.d.ts +92 -0
  31. package/dist/host/routing.js +238 -0
  32. package/dist/host/types.d.ts +8 -0
  33. package/dist/index.d.ts +3 -1
  34. package/dist/index.js +3 -1
  35. package/dist/input/advice.d.ts +100 -0
  36. package/dist/input/advice.js +767 -0
  37. package/dist/input/describe.d.ts +106 -0
  38. package/dist/input/describe.js +176 -0
  39. package/dist/input/file-input.d.ts +54 -0
  40. package/dist/input/file-input.js +124 -0
  41. package/dist/input/flat-decode.d.ts +11 -0
  42. package/dist/input/flat-decode.js +13 -0
  43. package/dist/input/flat-errors.d.ts +72 -0
  44. package/dist/input/flat-errors.js +87 -0
  45. package/dist/input/flat-materializer.d.ts +29 -0
  46. package/dist/input/flat-materializer.js +179 -0
  47. package/dist/input/flat-parser.d.ts +50 -0
  48. package/dist/input/flat-parser.js +169 -0
  49. package/dist/input/flat-predicates.d.ts +34 -0
  50. package/dist/input/flat-predicates.js +50 -0
  51. package/dist/input/index.d.ts +14 -0
  52. package/dist/input/index.js +14 -0
  53. package/dist/input/input-resolver.d.ts +63 -0
  54. package/dist/input/input-resolver.js +254 -0
  55. package/dist/input/json-depth-scanner.d.ts +17 -0
  56. package/dist/input/json-depth-scanner.js +49 -0
  57. package/dist/input/metadata.d.ts +99 -0
  58. package/dist/input/metadata.js +99 -0
  59. package/dist/input/stdin-input.d.ts +26 -0
  60. package/dist/input/stdin-input.js +125 -0
  61. package/dist/input/utf8.d.ts +19 -0
  62. package/dist/input/utf8.js +36 -0
  63. package/dist/input/validation-mapper.d.ts +27 -0
  64. package/dist/input/validation-mapper.js +144 -0
  65. package/dist/ipc/host.js +38 -0
  66. package/dist/json/index.d.ts +1 -0
  67. package/dist/json/index.js +1 -0
  68. package/dist/json/value-validator.d.ts +88 -0
  69. package/dist/json/value-validator.js +330 -0
  70. package/dist/platform/types.d.ts +7 -0
  71. package/dist/process/control-arbiter.d.ts +107 -0
  72. package/dist/process/control-arbiter.js +251 -0
  73. package/dist/process/diagnostics-sink.d.ts +26 -0
  74. package/dist/process/diagnostics-sink.js +35 -0
  75. package/dist/process/index.d.ts +7 -0
  76. package/dist/process/index.js +7 -0
  77. package/dist/process/input-dispatcher.d.ts +88 -0
  78. package/dist/process/input-dispatcher.js +240 -0
  79. package/dist/process/managed-record.d.ts +78 -0
  80. package/dist/process/managed-record.js +1 -0
  81. package/dist/process/process-manager.d.ts +15 -56
  82. package/dist/process/process-manager.js +119 -707
  83. package/dist/process/reservation-table.d.ts +39 -0
  84. package/dist/process/reservation-table.js +80 -0
  85. package/dist/process/run-executor.d.ts +16 -0
  86. package/dist/process/run-executor.js +155 -0
  87. package/dist/process/terminal-output-cache.d.ts +66 -0
  88. package/dist/process/terminal-output-cache.js +132 -0
  89. package/dist/profile/client-actions.d.ts +56 -0
  90. package/dist/profile/client-actions.js +215 -0
  91. package/dist/profile/client-config.d.ts +20 -0
  92. package/dist/profile/client-config.js +51 -0
  93. package/dist/profile/client-health.d.ts +14 -0
  94. package/dist/profile/client-health.js +69 -0
  95. package/dist/profile/client-playbooks.d.ts +19 -0
  96. package/dist/profile/client-playbooks.js +36 -0
  97. package/dist/profile/client-query.d.ts +30 -0
  98. package/dist/profile/client-query.js +70 -0
  99. package/dist/profile/client-runs.d.ts +42 -0
  100. package/dist/profile/client-runs.js +58 -0
  101. package/dist/profile/client-state.d.ts +56 -0
  102. package/dist/profile/client-state.js +83 -0
  103. package/dist/profile/client-transport.d.ts +61 -0
  104. package/dist/profile/client-transport.js +156 -0
  105. package/dist/profile/client.d.ts +23 -142
  106. package/dist/profile/client.js +28 -588
  107. package/dist/profile/manager.js +1 -1
  108. package/dist/project/closure.d.ts +4 -4
  109. package/dist/project/closure.js +11 -5
  110. package/dist/project/init.js +2 -2
  111. package/dist/project/loader.d.ts +0 -25
  112. package/dist/project/loader.js +1 -154
  113. package/dist/project/transactions.d.ts +12 -14
  114. package/dist/project/transactions.js +60 -836
  115. package/dist/registry/registry.js +6 -4
  116. package/dist/registry/resolve.js +1 -1
  117. package/dist/runtime/action-registry.d.ts +45 -0
  118. package/dist/runtime/action-registry.js +108 -0
  119. package/dist/runtime/clock.d.ts +5 -17
  120. package/dist/runtime/clock.js +5 -15
  121. package/dist/runtime/context.d.ts +3 -0
  122. package/dist/runtime/context.js +17 -56
  123. package/dist/runtime/index.d.ts +3 -0
  124. package/dist/runtime/index.js +3 -0
  125. package/dist/runtime/package-runner-factory.d.ts +82 -0
  126. package/dist/runtime/package-runner-factory.js +186 -0
  127. package/dist/runtime/run-persistence.d.ts +85 -0
  128. package/dist/runtime/run-persistence.js +111 -0
  129. package/dist/runtime/runner.d.ts +25 -45
  130. package/dist/runtime/runner.js +130 -354
  131. package/dist/runtime/standalone.d.ts +25 -2
  132. package/dist/runtime/standalone.js +305 -87
  133. package/dist/schema/index.d.ts +1 -0
  134. package/dist/schema/index.js +1 -0
  135. package/dist/schema/validator.d.ts +10 -0
  136. package/dist/schema/validator.js +48 -54
  137. package/dist/server/dispatcher.d.ts +8 -3
  138. package/dist/server/dispatcher.js +16 -40
  139. package/dist/server/routes/runs.js +6 -4
  140. package/dist/server/security.d.ts +1 -7
  141. package/dist/server/security.js +2 -13
  142. package/dist/storage/clock.d.ts +25 -0
  143. package/dist/storage/clock.js +23 -0
  144. package/dist/storage/data-dir-lock.d.ts +3 -36
  145. package/dist/storage/data-dir-lock.js +48 -881
  146. package/dist/storage/index.d.ts +8 -1
  147. package/dist/storage/index.js +8 -2
  148. package/dist/storage/lazy.js +1 -0
  149. package/dist/storage/lock-core.d.ts +162 -0
  150. package/dist/storage/lock-core.js +885 -0
  151. package/dist/storage/sqlite.d.ts +19 -6
  152. package/dist/storage/sqlite.js +81 -89
  153. package/dist/storage/types.d.ts +27 -5
  154. package/dist/storage/types.js +21 -0
  155. package/dist/storage/utils.d.ts +22 -0
  156. package/dist/storage/utils.js +34 -0
  157. package/dist/target/local.d.ts +1 -0
  158. package/dist/target/local.js +23 -17
  159. package/dist/target/remote-errors.d.ts +21 -0
  160. package/dist/target/remote-errors.js +51 -0
  161. package/dist/target/remote-polling.d.ts +35 -0
  162. package/dist/target/remote-polling.js +104 -0
  163. package/dist/target/remote.d.ts +4 -0
  164. package/dist/target/remote.js +108 -301
  165. package/dist/target/sse-parser.d.ts +35 -0
  166. package/dist/target/sse-parser.js +163 -0
  167. package/dist/target/target.js +2 -0
  168. package/dist/target/types.d.ts +6 -0
  169. package/dist/utils/index.d.ts +1 -0
  170. package/dist/utils/index.js +1 -0
  171. package/dist/utils/net.d.ts +10 -0
  172. package/dist/utils/net.js +16 -0
  173. package/dist/version.d.ts +1 -1
  174. package/dist/version.js +1 -1
  175. package/package.json +2 -2
@@ -0,0 +1,106 @@
1
+ import type { ActionSpec } from "../app/types.js";
2
+ /**
3
+ * 字段级异常明细。
4
+ */
5
+ export interface ActionDescribeIssue {
6
+ /** 字段路径 */
7
+ path: string;
8
+ /** 异常代码 */
9
+ code: string;
10
+ }
11
+ /**
12
+ * Action 输入模式建议契约。
13
+ */
14
+ export interface ActionDescribeAdvice {
15
+ /** 建议契约版本号 */
16
+ version: 1;
17
+ /** 推荐的输入模式 */
18
+ recommendedMode: "flat" | "full-json" | "none";
19
+ /** 扁平入参赋值操作符字典(属性名 -> "=" 或 ":=") */
20
+ assignments?: Record<string, "=" | ":=">;
21
+ /** 推荐原因代码 */
22
+ reason?: string;
23
+ /** 字段级异常与警告清单 */
24
+ issues?: ActionDescribeIssue[];
25
+ }
26
+ /**
27
+ * 统一 Action 描述载荷契约。
28
+ */
29
+ export interface ActionDescribePayload {
30
+ /** Action 唯一标识 */
31
+ id: string;
32
+ /** 所属包唯一标识 */
33
+ packageId?: string;
34
+ /** Action 描述信息 */
35
+ description?: string;
36
+ /** 输入模式规范 */
37
+ inputSchema?: unknown;
38
+ /** 输出模式规范 */
39
+ outputSchema?: unknown;
40
+ /** 标签列表 */
41
+ tags?: string[];
42
+ /** 协议注解元数据 */
43
+ annotations?: Record<string, unknown>;
44
+ /** 依赖的 Action 列表 */
45
+ uses?: string[];
46
+ /** 入口相对路径 */
47
+ entry?: string;
48
+ /** 统一输入模式建议 */
49
+ inputAdvice: ActionDescribeAdvice;
50
+ }
51
+ /**
52
+ * 构造 Action 描述载荷的可选选项。
53
+ */
54
+ export interface BuildActionDescribePayloadOptions {
55
+ /** 所属包唯一标识(当 spec 中未携带时作为回退) */
56
+ packageId?: string;
57
+ }
58
+ /**
59
+ * 计算 Action 输入建议。
60
+ *
61
+ * @param schema Action 的 inputSchema 定义
62
+ * @returns 统一输入建议结构
63
+ */
64
+ export declare function computeActionDescribeAdvice(schema: unknown): ActionDescribeAdvice;
65
+ /**
66
+ * 构造统一 Action 描述载荷数据。
67
+ * 普通 CLI 与 Standalone 必须通过该函数生成统一数据。
68
+ *
69
+ * @param spec Action 规范信息
70
+ * @param options 可选配置选项
71
+ * @returns 统一 Action 描述载荷
72
+ */
73
+ export declare function buildActionDescribePayload(spec: {
74
+ id: string;
75
+ packageId?: string;
76
+ description?: string;
77
+ inputSchema?: unknown;
78
+ outputSchema?: unknown;
79
+ tags?: string[];
80
+ annotations?: Record<string, unknown>;
81
+ uses?: string[];
82
+ entry?: string;
83
+ [key: string]: any;
84
+ }, options?: BuildActionDescribePayloadOptions): ActionDescribePayload;
85
+ /**
86
+ * 格式化渲染 Action 详情人类可读文本。
87
+ * 普通 CLI 与 Standalone 统一复用该函数,避免长期排版漂移。
88
+ *
89
+ * @param input Action 描述载荷或 Action 规范
90
+ * @returns 格式化后的说明文本
91
+ */
92
+ export declare function formatActionDetail(input: ActionDescribePayload | (Partial<ActionSpec> & {
93
+ id: string;
94
+ }) | {
95
+ id: string;
96
+ packageId?: string;
97
+ projectRoot?: string;
98
+ description?: string;
99
+ inputSchema?: unknown;
100
+ outputSchema?: unknown;
101
+ tags?: string[];
102
+ annotations?: Record<string, unknown>;
103
+ uses?: string[];
104
+ entry?: string;
105
+ [key: string]: any;
106
+ }): string;
@@ -0,0 +1,176 @@
1
+ import { buildCliInputAdviceV1 } from "./advice.js";
2
+ import { isForbiddenActionInputPropertyName } from "./flat-predicates.js";
3
+ /**
4
+ * 计算 Action 输入建议。
5
+ *
6
+ * @param schema Action 的 inputSchema 定义
7
+ * @returns 统一输入建议结构
8
+ */
9
+ export function computeActionDescribeAdvice(schema) {
10
+ const v1Advice = buildCliInputAdviceV1(schema);
11
+ const issues = [];
12
+ for (const field of v1Advice.fields) {
13
+ if (field.reason) {
14
+ issues.push({ path: field.path, code: field.reason });
15
+ }
16
+ }
17
+ // 检查 required 列表中未被 fields 覆盖的禁止属性
18
+ if (typeof schema === "object" && schema !== null) {
19
+ const s = schema;
20
+ if (Array.isArray(s.required)) {
21
+ for (const reqKey of s.required) {
22
+ if (typeof reqKey === "string" && isForbiddenActionInputPropertyName(reqKey)) {
23
+ if (!issues.some((i) => i.path === reqKey)) {
24
+ issues.push({ path: reqKey, code: "FORBIDDEN_PROPERTY" });
25
+ }
26
+ }
27
+ }
28
+ }
29
+ }
30
+ if (v1Advice.schemaRecommendedMode === "none") {
31
+ return {
32
+ version: 1,
33
+ recommendedMode: "none",
34
+ reason: v1Advice.feasibilityCode || "SCHEMA_REJECTS_ALL",
35
+ ...(issues.length > 0 ? { issues } : {}),
36
+ };
37
+ }
38
+ // 空对象模式判定:根模式为 object,无声明属性,且必填已满足(即无必填字段)
39
+ const isEmptyObjectSchema = v1Advice.schemaState === "object" &&
40
+ v1Advice.requiredSatisfiable === true &&
41
+ v1Advice.fields.length === 0;
42
+ if (v1Advice.schemaRecommendedMode === "flat" || isEmptyObjectSchema) {
43
+ const assignments = {};
44
+ for (const field of v1Advice.fields) {
45
+ if (field.flatSafe && field.operator) {
46
+ assignments[field.path] = field.operator;
47
+ }
48
+ }
49
+ return {
50
+ version: 1,
51
+ recommendedMode: "flat",
52
+ assignments,
53
+ ...(issues.length > 0 ? { issues } : {}),
54
+ };
55
+ }
56
+ // full-json 模式原因推导
57
+ let reason = "COMPLEX_SCHEMA";
58
+ if (v1Advice.analysisCode) {
59
+ reason = v1Advice.analysisCode;
60
+ }
61
+ else if (v1Advice.schemaState === "json-only") {
62
+ reason = "NON_OBJECT_SCHEMA";
63
+ }
64
+ else if (v1Advice.schemaState === "object") {
65
+ if (v1Advice.requiredSatisfiable === false) {
66
+ reason = "REQUIRED_FIELD_NOT_FLAT_SAFE";
67
+ }
68
+ else if (!v1Advice.flatAvailable) {
69
+ reason = "NO_FLAT_FIELDS";
70
+ }
71
+ else if (v1Advice.requiredSatisfiable === null) {
72
+ reason = "UNDECLARED_REQUIRED_FIELDS";
73
+ }
74
+ }
75
+ else if (v1Advice.schemaState === "absent") {
76
+ reason = "NO_SCHEMA";
77
+ }
78
+ else if (v1Advice.schemaState === "any") {
79
+ reason = "ARBITRARY_SCHEMA";
80
+ }
81
+ return {
82
+ version: 1,
83
+ recommendedMode: "full-json",
84
+ reason,
85
+ ...(issues.length > 0 ? { issues } : {}),
86
+ };
87
+ }
88
+ /**
89
+ * 构造统一 Action 描述载荷数据。
90
+ * 普通 CLI 与 Standalone 必须通过该函数生成统一数据。
91
+ *
92
+ * @param spec Action 规范信息
93
+ * @param options 可选配置选项
94
+ * @returns 统一 Action 描述载荷
95
+ */
96
+ export function buildActionDescribePayload(spec, options) {
97
+ const packageId = spec.packageId ?? options?.packageId;
98
+ const payload = {
99
+ id: spec.id,
100
+ ...(packageId !== undefined ? { packageId } : {}),
101
+ ...(spec.description !== undefined ? { description: spec.description } : {}),
102
+ ...(spec.inputSchema !== undefined ? { inputSchema: spec.inputSchema } : {}),
103
+ ...(spec.outputSchema !== undefined ? { outputSchema: spec.outputSchema } : {}),
104
+ ...(spec.tags && spec.tags.length > 0 ? { tags: spec.tags } : {}),
105
+ ...(spec.annotations && Object.keys(spec.annotations).length > 0
106
+ ? { annotations: spec.annotations }
107
+ : {}),
108
+ ...(spec.uses && spec.uses.length > 0 ? { uses: spec.uses } : {}),
109
+ ...(spec.entry !== undefined ? { entry: spec.entry } : {}),
110
+ inputAdvice: computeActionDescribeAdvice(spec.inputSchema),
111
+ };
112
+ return payload;
113
+ }
114
+ /**
115
+ * 格式化渲染 Action 详情人类可读文本。
116
+ * 普通 CLI 与 Standalone 统一复用该函数,避免长期排版漂移。
117
+ *
118
+ * @param input Action 描述载荷或 Action 规范
119
+ * @returns 格式化后的说明文本
120
+ */
121
+ export function formatActionDetail(input) {
122
+ const payload = "inputAdvice" in input && input.inputAdvice
123
+ ? input
124
+ : buildActionDescribePayload(input);
125
+ const lines = [];
126
+ lines.push(`Action: ${payload.id}`);
127
+ if (payload.packageId) {
128
+ lines.push(`Package: ${payload.packageId}`);
129
+ }
130
+ if (payload.description) {
131
+ lines.push(`Description: ${payload.description}`);
132
+ }
133
+ if (payload.tags && payload.tags.length > 0) {
134
+ lines.push(`Tags: ${payload.tags.join(", ")}`);
135
+ }
136
+ if (payload.uses && payload.uses.length > 0) {
137
+ lines.push(`Uses: ${payload.uses.join(", ")}`);
138
+ }
139
+ if (payload.entry) {
140
+ lines.push(`Entry: ${payload.entry}`);
141
+ }
142
+ if (payload.inputSchema !== undefined) {
143
+ lines.push("");
144
+ lines.push("Input Schema:");
145
+ lines.push(typeof payload.inputSchema === "string"
146
+ ? payload.inputSchema
147
+ : JSON.stringify(payload.inputSchema, null, 2));
148
+ }
149
+ if (payload.outputSchema !== undefined) {
150
+ lines.push("");
151
+ lines.push("Output Schema:");
152
+ lines.push(typeof payload.outputSchema === "string"
153
+ ? payload.outputSchema
154
+ : JSON.stringify(payload.outputSchema, null, 2));
155
+ }
156
+ lines.push("");
157
+ lines.push(`Recommended Input: ${payload.inputAdvice.recommendedMode}`);
158
+ if (payload.inputAdvice.reason) {
159
+ lines.push(`Reason: ${payload.inputAdvice.reason}`);
160
+ }
161
+ if (payload.inputAdvice.assignments && Object.keys(payload.inputAdvice.assignments).length > 0) {
162
+ lines.push("");
163
+ lines.push("Assignments:");
164
+ for (const [key, op] of Object.entries(payload.inputAdvice.assignments)) {
165
+ lines.push(` ${key}${op}`);
166
+ }
167
+ }
168
+ if (payload.inputAdvice.issues && payload.inputAdvice.issues.length > 0) {
169
+ lines.push("");
170
+ lines.push("Issues:");
171
+ for (const issue of payload.inputAdvice.issues) {
172
+ lines.push(` - ${issue.path}: ${issue.code}`);
173
+ }
174
+ }
175
+ return lines.join("\n");
176
+ }
@@ -0,0 +1,54 @@
1
+ import { type FileHandle } from "node:fs/promises";
2
+ import type { Stats } from "node:fs";
3
+ /** 默认最大输入字节数限制(10MB) */
4
+ export declare const DEFAULT_MAX_INPUT_BYTES: number;
5
+ /**
6
+ * 安全打开的常规文件描述句柄包装。
7
+ */
8
+ export interface OpenedRegularFile {
9
+ /** 底层 Node.js 文件句柄 */
10
+ readonly handle: FileHandle;
11
+ /** 文件路径 */
12
+ readonly path: string;
13
+ /** 文件元信息 */
14
+ readonly stat: Stats;
15
+ /** 文件字节大小 */
16
+ readonly size: number;
17
+ /**
18
+ * 在指定字节上限约束下读取文件完整内容。
19
+ *
20
+ * @param maxInputBytes 最大允许读取字节数
21
+ * @param signal 可选的中断信号
22
+ * @returns 读取的文件内容 Buffer
23
+ */
24
+ readBounded(maxInputBytes: number, signal?: AbortSignal): Promise<Buffer>;
25
+ /**
26
+ * 关闭文件句柄,释放操作系统文件描述符。
27
+ */
28
+ close(): Promise<void>;
29
+ }
30
+ /**
31
+ * 安全打开常规输入文件。
32
+ *
33
+ * 安全契约:
34
+ * - 最终打开目标必须为 regular file(允许普通文件及指向普通文件的符号链接 symlink)。
35
+ * - 严厉拒绝目录、FIFO 管道、socket、字符设备与块设备等,抛出 INPUT_FILE_READ_FAILED(reason: "UNSUPPORTED_FILE_TYPE")。
36
+ * - 完美支持 AbortSignal 取消。若信号在 open 完成前触发,执行逻辑取消;若 open 滞后成功,立即执行 handle.close() 关闭句柄,严防文件描述符泄漏。
37
+ * - 大小预检与有界读取:当文件大小超出 maxInputBytes 时抛出 INPUT_LIMIT_EXCEEDED(reason: "MAX_INPUT_BYTES")。
38
+ *
39
+ * @param filePath 输入文件路径
40
+ * @param signal 可选的取消信号
41
+ * @returns 包装后的常规文件句柄对象
42
+ */
43
+ export declare function openRegularInputFile(filePath: string, signal?: AbortSignal): Promise<OpenedRegularFile>;
44
+ /**
45
+ * 以有界大小限制安全读取常规文件的全部内容并自动关闭句柄。
46
+ *
47
+ * @param filePath 输入文件路径
48
+ * @param options 读取配置选项
49
+ * @returns 文件的原始字节 Buffer
50
+ */
51
+ export declare function readRegularFileBounded(filePath: string, options?: {
52
+ maxInputBytes?: number;
53
+ signal?: AbortSignal;
54
+ }): Promise<Buffer>;
@@ -0,0 +1,124 @@
1
+ import { open } from "node:fs/promises";
2
+ import { inputFileNotFound, inputFileReadFailed, inputLimitExceeded, } from "./flat-errors.js";
3
+ /** 默认最大输入字节数限制(10MB) */
4
+ export const DEFAULT_MAX_INPUT_BYTES = 10 * 1024 * 1024;
5
+ /**
6
+ * 安全打开常规输入文件。
7
+ *
8
+ * 安全契约:
9
+ * - 最终打开目标必须为 regular file(允许普通文件及指向普通文件的符号链接 symlink)。
10
+ * - 严厉拒绝目录、FIFO 管道、socket、字符设备与块设备等,抛出 INPUT_FILE_READ_FAILED(reason: "UNSUPPORTED_FILE_TYPE")。
11
+ * - 完美支持 AbortSignal 取消。若信号在 open 完成前触发,执行逻辑取消;若 open 滞后成功,立即执行 handle.close() 关闭句柄,严防文件描述符泄漏。
12
+ * - 大小预检与有界读取:当文件大小超出 maxInputBytes 时抛出 INPUT_LIMIT_EXCEEDED(reason: "MAX_INPUT_BYTES")。
13
+ *
14
+ * @param filePath 输入文件路径
15
+ * @param signal 可选的取消信号
16
+ * @returns 包装后的常规文件句柄对象
17
+ */
18
+ export async function openRegularInputFile(filePath, signal) {
19
+ if (signal?.aborted) {
20
+ throw signal.reason ?? new DOMException("The operation was aborted", "AbortError");
21
+ }
22
+ let handle;
23
+ let aborted = false;
24
+ const onAbort = () => {
25
+ aborted = true;
26
+ if (handle) {
27
+ handle.close().catch(() => { });
28
+ }
29
+ };
30
+ if (signal) {
31
+ signal.addEventListener("abort", onAbort, { once: true });
32
+ }
33
+ try {
34
+ try {
35
+ handle = await open(filePath, "r");
36
+ }
37
+ catch (err) {
38
+ if (err && err.code === "ENOENT") {
39
+ throw inputFileNotFound(filePath);
40
+ }
41
+ throw inputFileReadFailed(filePath, err);
42
+ }
43
+ // 若在 open 等待期间触发 abort,立即关闭滞后打开的句柄并中止
44
+ if (aborted || signal?.aborted) {
45
+ await handle.close().catch(() => { });
46
+ throw signal?.reason ?? new DOMException("The operation was aborted", "AbortError");
47
+ }
48
+ let stat;
49
+ try {
50
+ stat = await handle.stat();
51
+ }
52
+ catch (err) {
53
+ await handle.close().catch(() => { });
54
+ throw inputFileReadFailed(filePath, err);
55
+ }
56
+ if (aborted || signal?.aborted) {
57
+ await handle.close().catch(() => { });
58
+ throw signal?.reason ?? new DOMException("The operation was aborted", "AbortError");
59
+ }
60
+ // 严厉校验目标是否为常规文件(排除目录、FIFO、socket、设备等)
61
+ if (!stat.isFile()) {
62
+ await handle.close().catch(() => { });
63
+ throw inputFileReadFailed(filePath, new Error("Unsupported file type"), {
64
+ reason: "UNSUPPORTED_FILE_TYPE",
65
+ });
66
+ }
67
+ let isClosed = false;
68
+ const openedFile = {
69
+ handle,
70
+ path: filePath,
71
+ stat,
72
+ size: stat.size,
73
+ close: async () => {
74
+ if (!isClosed) {
75
+ isClosed = true;
76
+ await handle.close().catch(() => { });
77
+ }
78
+ },
79
+ readBounded: async (maxInputBytes, readSignal) => {
80
+ const effectiveSignal = readSignal ?? signal;
81
+ if (effectiveSignal?.aborted) {
82
+ throw effectiveSignal.reason ?? new DOMException("The operation was aborted", "AbortError");
83
+ }
84
+ // 大小预检:若 stat.size 明确超过上限,直接拦截
85
+ if (stat.size > maxInputBytes) {
86
+ throw inputLimitExceeded(`File size (${stat.size} bytes) exceeds limit of ${maxInputBytes} bytes`, { reason: "MAX_INPUT_BYTES" });
87
+ }
88
+ // 分配有界缓冲区(多读 1 字节以探测并发写入扩大的竞态)
89
+ const buffer = Buffer.alloc(Math.min(stat.size, maxInputBytes) + 1);
90
+ const { bytesRead } = await handle.read(buffer, 0, buffer.length, 0);
91
+ if (effectiveSignal?.aborted) {
92
+ throw effectiveSignal.reason ?? new DOMException("The operation was aborted", "AbortError");
93
+ }
94
+ if (bytesRead > maxInputBytes) {
95
+ throw inputLimitExceeded(`File read size (${bytesRead} bytes) exceeds limit of ${maxInputBytes} bytes`, { reason: "MAX_INPUT_BYTES" });
96
+ }
97
+ return buffer.subarray(0, bytesRead);
98
+ },
99
+ };
100
+ return openedFile;
101
+ }
102
+ finally {
103
+ if (signal) {
104
+ signal.removeEventListener("abort", onAbort);
105
+ }
106
+ }
107
+ }
108
+ /**
109
+ * 以有界大小限制安全读取常规文件的全部内容并自动关闭句柄。
110
+ *
111
+ * @param filePath 输入文件路径
112
+ * @param options 读取配置选项
113
+ * @returns 文件的原始字节 Buffer
114
+ */
115
+ export async function readRegularFileBounded(filePath, options) {
116
+ const maxBytes = options?.maxInputBytes ?? DEFAULT_MAX_INPUT_BYTES;
117
+ const opened = await openRegularInputFile(filePath, options?.signal);
118
+ try {
119
+ return await opened.readBounded(maxBytes, options?.signal);
120
+ }
121
+ finally {
122
+ await opened.close();
123
+ }
124
+ }
@@ -0,0 +1,11 @@
1
+ import type { JsonValue } from "@actiondock/sdk";
2
+ import { type FlatParserOptions } from "./flat-parser.js";
3
+ import { type FlatMaterializerOptions } from "./flat-materializer.js";
4
+ /**
5
+ * 将扁平入参字符串列表解码并物化为标准 JSON 值对象。
6
+ *
7
+ * @param tokens 扁平入参字符串列表
8
+ * @param options 解析与物化安全阈值选项
9
+ * @returns 物化后的 JSON 值对象
10
+ */
11
+ export declare function decodeFlatInput(tokens: string[], options?: FlatParserOptions & FlatMaterializerOptions): JsonValue;
@@ -0,0 +1,13 @@
1
+ import { parseFlatAssignments } from "./flat-parser.js";
2
+ import { materializeFlatInput, } from "./flat-materializer.js";
3
+ /**
4
+ * 将扁平入参字符串列表解码并物化为标准 JSON 值对象。
5
+ *
6
+ * @param tokens 扁平入参字符串列表
7
+ * @param options 解析与物化安全阈值选项
8
+ * @returns 物化后的 JSON 值对象
9
+ */
10
+ export function decodeFlatInput(tokens, options) {
11
+ const assignments = parseFlatAssignments(tokens, options);
12
+ return materializeFlatInput(assignments, options);
13
+ }
@@ -0,0 +1,72 @@
1
+ import { INVALID_JSON, INVALID_JSON_LITERAL, INVALID_FLAT_ARGUMENT, INPUT_PATH_CONFLICT, FLAT_INPUT_LIMIT_EXCEEDED, INPUT_LIMIT_EXCEEDED, INPUT_POLICY_VIOLATION, INPUT_CONFLICT, INPUT_FILE_NOT_FOUND, INPUT_FILE_READ_FAILED, INPUT_NOT_JSON, INPUT_VALIDATION_FAILED } from "../errors.js";
2
+ export { INVALID_JSON, INVALID_JSON_LITERAL, INVALID_FLAT_ARGUMENT, INPUT_PATH_CONFLICT, FLAT_INPUT_LIMIT_EXCEEDED, INPUT_LIMIT_EXCEEDED, INPUT_POLICY_VIOLATION, INPUT_CONFLICT, INPUT_FILE_NOT_FOUND, INPUT_FILE_READ_FAILED, INPUT_NOT_JSON, INPUT_VALIDATION_FAILED, };
3
+ /** 输入大小或深度超出限制的根因类型 */
4
+ export type InputLimitExceededReason = "MAX_INPUT_BYTES" | "MAX_JSON_DEPTH" | string;
5
+ /** 扁平入参安全阈值超限的根因类型 */
6
+ export type FlatInputLimitExceededReason = "MAX_ASSIGNMENTS" | "MAX_PATH_DEPTH" | "MAX_PATH_BYTES" | "MAX_PROPERTY_KEY_BYTES" | "MAX_RAW_VALUE_BYTES" | "MAX_JSON_LITERAL_BYTES" | "MAX_TOTAL_RAW_BYTES" | "MAX_ARRAY_INDEX" | "MAX_MATERIALIZED_BYTES" | "MAX_MATERIALIZED_JSON_DEPTH" | string;
7
+ /** 输入策略违规的根因类型 */
8
+ export type InputPolicyViolationReason = "FORBIDDEN_PROPERTY" | string;
9
+ /** 非法扁平参数的根因类型 */
10
+ export type InvalidFlatArgumentReason = "MISSING_OPERATOR" | "EMPTY_PATH" | "INVALID_DOT_NOTATION" | "INVALID_SEGMENT" | "FORBIDDEN_PROPERTY" | string;
11
+ /** 输入路径冲突的根因类型 */
12
+ export type InputPathConflictReason = "ROOT_INDEX_NOT_ALLOWED" | "DUPLICATE_ASSIGNMENT" | "LEAF_CONTAINER_CONFLICT" | "OBJECT_ARRAY_CONFLICT" | "SPARSE_ARRAY" | "INCOMPLETE_CONTAINER" | string;
13
+ /** 非法完整 JSON 的根因类型 */
14
+ export type InvalidJsonReason = "SYNTAX_ERROR" | "INVALID_UTF8" | "NON_FINITE_NUMBER" | "MAX_JSON_DEPTH" | "INVALID_JSON_VALUE" | string;
15
+ /** 非法 JSON 字面量的根因类型 */
16
+ export type InvalidJsonLiteralReason = "SYNTAX_ERROR" | "NON_FINITE_NUMBER" | "MAX_JSON_DEPTH" | "INVALID_JSON_VALUE" | string;
17
+ /** 输入来源类型 */
18
+ export type InputValidationSource = "flat-json-literal" | "flat-materialized" | "full-json-inline" | "full-json-file" | "full-json-stdin" | "cli-pre-target" | "runtime";
19
+ /**
20
+ * 输入领域通用结构化异常类。
21
+ */
22
+ export declare class InputError extends Error {
23
+ readonly code: string;
24
+ readonly details?: Record<string, unknown> | string[];
25
+ constructor(code: string, message: string, details?: Record<string, unknown> | string[]);
26
+ }
27
+ /**
28
+ * 扁平输入结构化异常类(继承 InputError 保持既有捕获兼容)。
29
+ */
30
+ export declare class FlatInputError extends InputError {
31
+ constructor(code: string, message: string, details?: Record<string, unknown>);
32
+ }
33
+ /**
34
+ * 构造 INVALID_JSON 异常(用于完整 JSON 文档解析失败)。
35
+ */
36
+ export declare function invalidJson(message: string, details?: Record<string, unknown>): InputError;
37
+ /**
38
+ * 构造 INVALID_JSON_LITERAL 异常(仅用于 path:=json 字面量解析失败)。
39
+ */
40
+ export declare function invalidJsonLiteral(message: string, details?: Record<string, unknown>): FlatInputError;
41
+ /**
42
+ * 构造 INVALID_FLAT_ARGUMENT 异常。
43
+ */
44
+ export declare function invalidFlatArgument(message: string, details?: Record<string, unknown>): FlatInputError;
45
+ /**
46
+ * 构造 INPUT_PATH_CONFLICT 异常。
47
+ */
48
+ export declare function inputPathConflict(message: string, details?: Record<string, unknown>): FlatInputError;
49
+ /**
50
+ * 构造 FLAT_INPUT_LIMIT_EXCEEDED 异常。
51
+ */
52
+ export declare function flatInputLimitExceeded(message: string, details?: Record<string, unknown>): FlatInputError;
53
+ /**
54
+ * 构造 INPUT_CONFLICT 异常。
55
+ */
56
+ export declare function inputConflict(message: string, details?: Record<string, unknown>): InputError;
57
+ /**
58
+ * 构造 INPUT_FILE_NOT_FOUND 异常。
59
+ */
60
+ export declare function inputFileNotFound(filePath: string, details?: Record<string, unknown>): InputError;
61
+ /**
62
+ * 构造 INPUT_LIMIT_EXCEEDED 异常。
63
+ */
64
+ export declare function inputLimitExceeded(message: string, details?: Record<string, unknown>): InputError;
65
+ /**
66
+ * 构造 INPUT_POLICY_VIOLATION 异常。
67
+ */
68
+ export declare function inputPolicyViolation(message: string, details?: Record<string, unknown>): InputError;
69
+ /**
70
+ * 构造 INPUT_FILE_READ_FAILED 异常。
71
+ */
72
+ export declare function inputFileReadFailed(source: string, cause: unknown, details?: Record<string, unknown>): InputError;
@@ -0,0 +1,87 @@
1
+ import { INVALID_JSON, INVALID_JSON_LITERAL, INVALID_FLAT_ARGUMENT, INPUT_PATH_CONFLICT, FLAT_INPUT_LIMIT_EXCEEDED, INPUT_LIMIT_EXCEEDED, INPUT_POLICY_VIOLATION, INPUT_CONFLICT, INPUT_FILE_NOT_FOUND, INPUT_FILE_READ_FAILED, INPUT_NOT_JSON, INPUT_VALIDATION_FAILED, } from "../errors.js";
2
+ export { INVALID_JSON, INVALID_JSON_LITERAL, INVALID_FLAT_ARGUMENT, INPUT_PATH_CONFLICT, FLAT_INPUT_LIMIT_EXCEEDED, INPUT_LIMIT_EXCEEDED, INPUT_POLICY_VIOLATION, INPUT_CONFLICT, INPUT_FILE_NOT_FOUND, INPUT_FILE_READ_FAILED, INPUT_NOT_JSON, INPUT_VALIDATION_FAILED, };
3
+ /**
4
+ * 输入领域通用结构化异常类。
5
+ */
6
+ export class InputError extends Error {
7
+ code;
8
+ details;
9
+ constructor(code, message, details) {
10
+ super(message);
11
+ this.name = "InputError";
12
+ this.code = code;
13
+ this.details = details;
14
+ Object.setPrototypeOf(this, InputError.prototype);
15
+ }
16
+ }
17
+ /**
18
+ * 扁平输入结构化异常类(继承 InputError 保持既有捕获兼容)。
19
+ */
20
+ export class FlatInputError extends InputError {
21
+ constructor(code, message, details) {
22
+ super(code, message, details);
23
+ this.name = "FlatInputError";
24
+ Object.setPrototypeOf(this, FlatInputError.prototype);
25
+ }
26
+ }
27
+ /**
28
+ * 构造 INVALID_JSON 异常(用于完整 JSON 文档解析失败)。
29
+ */
30
+ export function invalidJson(message, details) {
31
+ return new InputError(INVALID_JSON, message, details);
32
+ }
33
+ /**
34
+ * 构造 INVALID_JSON_LITERAL 异常(仅用于 path:=json 字面量解析失败)。
35
+ */
36
+ export function invalidJsonLiteral(message, details) {
37
+ return new FlatInputError(INVALID_JSON_LITERAL, message, details);
38
+ }
39
+ /**
40
+ * 构造 INVALID_FLAT_ARGUMENT 异常。
41
+ */
42
+ export function invalidFlatArgument(message, details) {
43
+ return new FlatInputError(INVALID_FLAT_ARGUMENT, message, details);
44
+ }
45
+ /**
46
+ * 构造 INPUT_PATH_CONFLICT 异常。
47
+ */
48
+ export function inputPathConflict(message, details) {
49
+ return new FlatInputError(INPUT_PATH_CONFLICT, message, details);
50
+ }
51
+ /**
52
+ * 构造 FLAT_INPUT_LIMIT_EXCEEDED 异常。
53
+ */
54
+ export function flatInputLimitExceeded(message, details) {
55
+ return new FlatInputError(FLAT_INPUT_LIMIT_EXCEEDED, message, details);
56
+ }
57
+ /**
58
+ * 构造 INPUT_CONFLICT 异常。
59
+ */
60
+ export function inputConflict(message, details) {
61
+ return new InputError(INPUT_CONFLICT, message, details);
62
+ }
63
+ /**
64
+ * 构造 INPUT_FILE_NOT_FOUND 异常。
65
+ */
66
+ export function inputFileNotFound(filePath, details) {
67
+ return new InputError(INPUT_FILE_NOT_FOUND, `Input file not found: ${filePath}`, { filePath, ...details });
68
+ }
69
+ /**
70
+ * 构造 INPUT_LIMIT_EXCEEDED 异常。
71
+ */
72
+ export function inputLimitExceeded(message, details) {
73
+ return new InputError(INPUT_LIMIT_EXCEEDED, message, details);
74
+ }
75
+ /**
76
+ * 构造 INPUT_POLICY_VIOLATION 异常。
77
+ */
78
+ export function inputPolicyViolation(message, details) {
79
+ return new InputError(INPUT_POLICY_VIOLATION, message, details);
80
+ }
81
+ /**
82
+ * 构造 INPUT_FILE_READ_FAILED 异常。
83
+ */
84
+ export function inputFileReadFailed(source, cause, details) {
85
+ const reason = cause instanceof Error ? cause.message : String(cause);
86
+ return new InputError(INPUT_FILE_READ_FAILED, `Failed to read input from ${source}: ${reason}`, { source, ...details });
87
+ }
@@ -0,0 +1,29 @@
1
+ import type { JsonValue } from "@actiondock/sdk";
2
+ import type { FlatAssignment } from "./flat-parser.js";
3
+ /**
4
+ * 节点状态枚举(四态节点模型)。
5
+ */
6
+ export declare const NodeState: {
7
+ readonly UNSET: "UNSET";
8
+ readonly OBJECT: "OBJECT";
9
+ readonly ARRAY: "ARRAY";
10
+ readonly VALUE: "VALUE";
11
+ };
12
+ export type NodeState = (typeof NodeState)[keyof typeof NodeState];
13
+ /**
14
+ * 物化配置选项。
15
+ */
16
+ export interface FlatMaterializerOptions {
17
+ maxMaterializedSizeBytes?: number;
18
+ }
19
+ /**
20
+ * 将扁平赋值表达式列表物化为标准 JSON 值对象。
21
+ *
22
+ * 安全规范:
23
+ * - 错误信息与 details 中严禁回显原始 raw、rawValue 或完整 token,仅保留 path、operator、valueLength。
24
+ *
25
+ * @param assignments 扁平赋值表达式列表
26
+ * @param options 物化配置选项
27
+ * @returns 物化后的 JSON 对象
28
+ */
29
+ export declare function materializeFlatInput(assignments: FlatAssignment[], options?: FlatMaterializerOptions): JsonValue;