@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.
Files changed (236) hide show
  1. package/README.md +61 -68
  2. package/dist/catalog/action-catalog.d.ts +56 -0
  3. package/dist/catalog/action-catalog.js +202 -0
  4. package/dist/catalog/discovery.d.ts +60 -0
  5. package/dist/catalog/discovery.js +119 -0
  6. package/dist/catalog/graph.d.ts +190 -0
  7. package/dist/catalog/graph.js +589 -0
  8. package/dist/catalog/index.d.ts +5 -5
  9. package/dist/catalog/index.js +5 -5
  10. package/dist/catalog/resolve-action.d.ts +43 -0
  11. package/dist/catalog/resolve-action.js +119 -0
  12. package/dist/catalog/resolve-playbook.d.ts +26 -0
  13. package/dist/catalog/resolve-playbook.js +114 -0
  14. package/dist/doctor/checks/project.js +27 -8
  15. package/dist/doctor/checks/registry.js +2 -2
  16. package/dist/doctor/checks/runtime.js +34 -1
  17. package/dist/errors.d.ts +102 -10
  18. package/dist/errors.js +103 -10
  19. package/dist/execution/service.d.ts +18 -16
  20. package/dist/execution/service.js +183 -252
  21. package/dist/execution/types.d.ts +25 -69
  22. package/dist/execution/types.js +2 -1
  23. package/dist/filter/intent.d.ts +8 -9
  24. package/dist/filter/intent.js +49 -10
  25. package/dist/graph/index.d.ts +4 -0
  26. package/dist/graph/index.js +4 -0
  27. package/dist/host/host.d.ts +74 -32
  28. package/dist/host/host.js +585 -336
  29. package/dist/host/routing.d.ts +15 -48
  30. package/dist/host/routing.js +90 -153
  31. package/dist/host/types.d.ts +50 -17
  32. package/dist/index.d.ts +21 -24
  33. package/dist/index.js +19 -23
  34. package/dist/input/advice.d.ts +5 -1
  35. package/dist/input/advice.js +163 -214
  36. package/dist/input/describe.d.ts +1 -1
  37. package/dist/input/flat-errors.d.ts +5 -6
  38. package/dist/input/flat-errors.js +6 -6
  39. package/dist/input/flat-materializer.js +42 -44
  40. package/dist/invocation/index.d.ts +2 -0
  41. package/dist/invocation/index.js +2 -0
  42. package/dist/invocation/policy.d.ts +157 -0
  43. package/dist/invocation/policy.js +231 -0
  44. package/dist/invocation/types.d.ts +172 -0
  45. package/dist/invocation/types.js +97 -0
  46. package/dist/ipc/diagnostic.d.ts +0 -12
  47. package/dist/ipc/diagnostic.js +0 -21
  48. package/dist/ipc/host.d.ts +4 -4
  49. package/dist/ipc/host.js +44 -47
  50. package/dist/ipc/index.d.ts +1 -1
  51. package/dist/ipc/index.js +1 -1
  52. package/dist/ipc/service.d.ts +40 -0
  53. package/dist/ipc/service.js +349 -0
  54. package/dist/ipc/types.d.ts +21 -15
  55. package/dist/node/index.d.ts +1 -0
  56. package/dist/node/index.js +1 -0
  57. package/dist/node/module-loader.d.ts +63 -0
  58. package/dist/node/module-loader.js +112 -0
  59. package/dist/package/index.d.ts +23 -0
  60. package/dist/package/index.js +18 -0
  61. package/dist/{app/app.d.ts → package/runtime.d.ts} +41 -20
  62. package/dist/{app/app.js → package/runtime.js} +140 -95
  63. package/dist/{app → package}/static-index.js +58 -41
  64. package/dist/package/storage-view.d.ts +13 -0
  65. package/dist/package/storage-view.js +22 -0
  66. package/dist/{app → package}/types.d.ts +76 -57
  67. package/dist/package/types.js +22 -0
  68. package/dist/platform/index.d.ts +1 -1
  69. package/dist/platform/index.js +1 -1
  70. package/dist/platform/node.d.ts +48 -0
  71. package/dist/platform/node.js +96 -0
  72. package/dist/platform/types.d.ts +0 -9
  73. package/dist/process/control-arbiter.d.ts +2 -1
  74. package/dist/process/control-arbiter.js +1 -2
  75. package/dist/process/driver.d.ts +9 -8
  76. package/dist/process/driver.js +11 -22
  77. package/dist/process/index.d.ts +2 -0
  78. package/dist/process/index.js +2 -0
  79. package/dist/process/managed-record.d.ts +2 -2
  80. package/dist/process/process-driver.d.ts +109 -0
  81. package/dist/process/process-driver.js +650 -0
  82. package/dist/process/process-executor.d.ts +47 -0
  83. package/dist/process/process-executor.js +296 -0
  84. package/dist/process/process-manager.js +48 -26
  85. package/dist/process/run-executor.js +12 -12
  86. package/dist/profile/client-actions.js +2 -2
  87. package/dist/profile/client-health.js +2 -2
  88. package/dist/profile/client-query.js +5 -6
  89. package/dist/profile/client-transport.d.ts +19 -17
  90. package/dist/profile/client-transport.js +47 -80
  91. package/dist/profile/client.d.ts +2 -2
  92. package/dist/profile/client.js +1 -1
  93. package/dist/profile/index.d.ts +1 -0
  94. package/dist/profile/index.js +1 -0
  95. package/dist/profile/manager.d.ts +5 -0
  96. package/dist/profile/manager.js +41 -6
  97. package/dist/project/closure.js +2 -2
  98. package/dist/project/index.d.ts +8 -1
  99. package/dist/project/index.js +10 -1
  100. package/dist/project/init.js +2 -2
  101. package/dist/project/loader.d.ts +1 -1
  102. package/dist/project/loader.js +26 -29
  103. package/dist/project/transactions.js +5 -10
  104. package/dist/registry/index.d.ts +3 -1
  105. package/dist/registry/index.js +2 -1
  106. package/dist/registry/lock.d.ts +28 -7
  107. package/dist/registry/lock.js +136 -16
  108. package/dist/registry/registry.d.ts +1 -5
  109. package/dist/registry/registry.js +1 -53
  110. package/dist/registry/scan.d.ts +0 -15
  111. package/dist/registry/scan.js +0 -59
  112. package/dist/registry/store.d.ts +42 -0
  113. package/dist/registry/store.js +43 -0
  114. package/dist/registry/types.d.ts +0 -24
  115. package/dist/runtime/action-collection.d.ts +1 -1
  116. package/dist/runtime/action-registry.d.ts +0 -4
  117. package/dist/runtime/action-registry.js +0 -7
  118. package/dist/runtime/context.js +5 -11
  119. package/dist/runtime/events.js +3 -6
  120. package/dist/runtime/identity.d.ts +22 -0
  121. package/dist/runtime/identity.js +10 -0
  122. package/dist/runtime/index.d.ts +1 -2
  123. package/dist/runtime/index.js +1 -2
  124. package/dist/runtime/module-loader.d.ts +2 -34
  125. package/dist/runtime/module-loader.js +1 -16
  126. package/dist/runtime/process.d.ts +6 -24
  127. package/dist/runtime/process.js +1 -118
  128. package/dist/runtime/run-persistence.js +2 -5
  129. package/dist/runtime/runner.d.ts +33 -93
  130. package/dist/runtime/runner.js +132 -364
  131. package/dist/runtime/standalone.d.ts +23 -8
  132. package/dist/runtime/standalone.js +100 -147
  133. package/dist/server/body.d.ts +16 -8
  134. package/dist/server/body.js +33 -16
  135. package/dist/server/dispatcher.d.ts +6 -10
  136. package/dist/server/dispatcher.js +24 -32
  137. package/dist/server/http-server.d.ts +87 -0
  138. package/dist/server/http-server.js +304 -0
  139. package/dist/server/index.d.ts +11 -0
  140. package/dist/server/index.js +10 -0
  141. package/dist/server/mcp-endpoint.d.ts +61 -0
  142. package/dist/server/mcp-endpoint.js +118 -0
  143. package/dist/server/routes/actions.js +34 -28
  144. package/dist/server/routes/common.d.ts +10 -14
  145. package/dist/server/routes/common.js +32 -56
  146. package/dist/server/routes/config.js +54 -64
  147. package/dist/server/routes/doctor.d.ts +1 -1
  148. package/dist/server/routes/doctor.js +10 -18
  149. package/dist/server/routes/health.d.ts +1 -1
  150. package/dist/server/routes/health.js +2 -3
  151. package/dist/server/routes/info.d.ts +2 -2
  152. package/dist/server/routes/info.js +12 -13
  153. package/dist/server/routes/playbooks.js +16 -29
  154. package/dist/server/routes/runs.js +95 -117
  155. package/dist/server/routes/state.js +63 -62
  156. package/dist/server/server.d.ts +2 -2
  157. package/dist/server/server.js +43 -227
  158. package/dist/server/types.d.ts +8 -19
  159. package/dist/service/factory.d.ts +12 -0
  160. package/dist/service/factory.js +85 -0
  161. package/dist/{target → service}/index.d.ts +1 -1
  162. package/dist/{target → service}/index.js +1 -1
  163. package/dist/service/local.d.ts +27 -0
  164. package/dist/service/local.js +138 -0
  165. package/dist/{target → service}/remote-errors.d.ts +3 -6
  166. package/dist/service/remote-errors.js +38 -0
  167. package/dist/{target → service}/remote-polling.d.ts +2 -2
  168. package/dist/{target → service}/remote-polling.js +8 -8
  169. package/dist/service/remote.d.ts +34 -0
  170. package/dist/service/remote.js +634 -0
  171. package/dist/service/sse-stream.d.ts +24 -0
  172. package/dist/service/sse-stream.js +139 -0
  173. package/dist/service/types.d.ts +272 -0
  174. package/dist/service/types.js +32 -0
  175. package/dist/storage/data-dir-lock.js +10 -5
  176. package/dist/storage/driver.d.ts +2 -1
  177. package/dist/storage/driver.js +3 -52
  178. package/dist/storage/index.d.ts +1 -0
  179. package/dist/storage/index.js +1 -0
  180. package/dist/storage/lock-core.d.ts +2 -6
  181. package/dist/storage/lock-core.js +4 -17
  182. package/dist/storage/params.d.ts +2 -8
  183. package/dist/storage/params.js +2 -8
  184. package/dist/storage/sqlite-driver.d.ts +40 -0
  185. package/dist/storage/sqlite-driver.js +116 -0
  186. package/dist/storage/sqlite.js +21 -6
  187. package/dist/storage/types.d.ts +4 -4
  188. package/dist/storage/types.js +2 -2
  189. package/dist/utils/index.d.ts +53 -0
  190. package/dist/utils/index.js +99 -4
  191. package/dist/utils/process.d.ts +10 -0
  192. package/dist/utils/process.js +21 -0
  193. package/dist/version.d.ts +1 -1
  194. package/dist/version.js +1 -1
  195. package/package.json +41 -3
  196. package/dist/app/index.d.ts +0 -2
  197. package/dist/app/index.js +0 -2
  198. package/dist/app/types.js +0 -1
  199. package/dist/catalog/action-index.d.ts +0 -9
  200. package/dist/catalog/action-index.js +0 -95
  201. package/dist/catalog/action-resolver.d.ts +0 -20
  202. package/dist/catalog/action-resolver.js +0 -95
  203. package/dist/catalog/location-registry.d.ts +0 -15
  204. package/dist/catalog/location-registry.js +0 -138
  205. package/dist/catalog/package-catalog.d.ts +0 -7
  206. package/dist/catalog/package-catalog.js +0 -77
  207. package/dist/catalog/types.d.ts +0 -68
  208. package/dist/catalog/types.js +0 -1
  209. package/dist/export/index.d.ts +0 -1
  210. package/dist/export/index.js +0 -1
  211. package/dist/export/templates.d.ts +0 -71
  212. package/dist/export/templates.js +0 -635
  213. package/dist/ipc/target.d.ts +0 -74
  214. package/dist/ipc/target.js +0 -365
  215. package/dist/platform/default.d.ts +0 -38
  216. package/dist/platform/default.js +0 -78
  217. package/dist/project/resolver.d.ts +0 -116
  218. package/dist/project/resolver.js +0 -376
  219. package/dist/registry/resolve.d.ts +0 -62
  220. package/dist/registry/resolve.js +0 -195
  221. package/dist/runtime/execution-manager.d.ts +0 -48
  222. package/dist/runtime/execution-manager.js +0 -66
  223. package/dist/runtime/package-runner-factory.d.ts +0 -82
  224. package/dist/runtime/package-runner-factory.js +0 -186
  225. package/dist/target/local.d.ts +0 -53
  226. package/dist/target/local.js +0 -349
  227. package/dist/target/remote-errors.js +0 -51
  228. package/dist/target/remote.d.ts +0 -86
  229. package/dist/target/remote.js +0 -818
  230. package/dist/target/target.d.ts +0 -6
  231. package/dist/target/target.js +0 -37
  232. package/dist/target/types.d.ts +0 -261
  233. package/dist/target/types.js +0 -35
  234. /package/dist/{app → package}/static-index.d.ts +0 -0
  235. /package/dist/{target → service}/sse-parser.d.ts +0 -0
  236. /package/dist/{target → service}/sse-parser.js +0 -0
@@ -1,4 +1,4 @@
1
- import { isFlatPathPropertyName, isForbiddenActionInputPropertyName, } from "./flat-predicates.js";
1
+ import { isFlatSafePropertyName, isFlatPathPropertyName, isForbiddenActionInputPropertyName, } from "./flat-predicates.js";
2
2
  const RECOGNIZED_SCHEMA_TYPES = new Set([
3
3
  "object",
4
4
  "array",
@@ -54,6 +54,93 @@ function createMalformedAdvice(message) {
54
54
  fields: [],
55
55
  };
56
56
  }
57
+ /**
58
+ * 构造无字段可用、推荐 full-json 的 ok 状态报告骨架(absent / any 语义共用单一事实源)。
59
+ * 字段顺序与既有契约保持一致,禁止调整键序。
60
+ */
61
+ function createNonFlatOkAdvice(schemaState) {
62
+ return {
63
+ version: 1,
64
+ analysisMode: "declared-properties-only",
65
+ analysisStatus: "ok",
66
+ schemaState,
67
+ inputFeasibility: "possible-or-unknown",
68
+ flatAvailable: false,
69
+ requiredSatisfiable: null,
70
+ flatCandidate: false,
71
+ schemaRecommendedMode: "full-json",
72
+ fields: [],
73
+ };
74
+ }
75
+ /**
76
+ * 校验 schema 节点的 type 关键字结构合法性(根级与属性级重复逻辑的单一事实源)。
77
+ *
78
+ * 判定规则:
79
+ * - 字符串型 type 必须属于 RECOGNIZED_SCHEMA_TYPES;
80
+ * - 数组型 type 不得为空、元素必须是受认可字符串且不得重复;
81
+ * - 其余形态(数字、对象等)非法。
82
+ *
83
+ * @param type 待校验的 type 关键字值
84
+ * @returns 非法时返回结构化原因,合法时返回 undefined
85
+ */
86
+ function validateTypeKeyword(type) {
87
+ if (typeof type === "string") {
88
+ if (!RECOGNIZED_SCHEMA_TYPES.has(type)) {
89
+ return { code: "UNRECOGNIZED_TYPE", value: type };
90
+ }
91
+ return undefined;
92
+ }
93
+ if (Array.isArray(type)) {
94
+ if (type.length === 0) {
95
+ return { code: "EMPTY_TYPE_ARRAY" };
96
+ }
97
+ const seen = new Set();
98
+ for (const t of type) {
99
+ if (typeof t !== "string" || !RECOGNIZED_SCHEMA_TYPES.has(t)) {
100
+ return { code: "UNRECOGNIZED_TYPE_IN_ARRAY", value: String(t) };
101
+ }
102
+ if (seen.has(t)) {
103
+ return { code: "DUPLICATE_TYPE", value: t };
104
+ }
105
+ seen.add(t);
106
+ }
107
+ return undefined;
108
+ }
109
+ return { code: "NOT_STRING_OR_ARRAY" };
110
+ }
111
+ /**
112
+ * 依 TypeKeywordIssue 构造根级 type 关键字 malformed 消息(措辞与历史实现逐字节一致)。
113
+ */
114
+ function formatRootTypeKeywordMessage(issue) {
115
+ switch (issue.code) {
116
+ case "UNRECOGNIZED_TYPE":
117
+ return `Unrecognized type keyword: '${issue.value}'`;
118
+ case "UNRECOGNIZED_TYPE_IN_ARRAY":
119
+ return `Unrecognized type in type array: '${issue.value}'`;
120
+ case "EMPTY_TYPE_ARRAY":
121
+ return "Type array must not be empty";
122
+ case "DUPLICATE_TYPE":
123
+ return `Duplicate type in type array: '${issue.value}'`;
124
+ case "NOT_STRING_OR_ARRAY":
125
+ return "Type keyword must be a string or array of strings";
126
+ }
127
+ }
128
+ /**
129
+ * 依 TypeKeywordIssue 构造属性级 type 关键字 malformed 消息(措辞与历史实现逐字节一致)。
130
+ */
131
+ function formatPropertyTypeKeywordMessage(issue, propKey) {
132
+ switch (issue.code) {
133
+ case "UNRECOGNIZED_TYPE":
134
+ case "UNRECOGNIZED_TYPE_IN_ARRAY":
135
+ return `Unrecognized type '${issue.value}' in property '${propKey}'`;
136
+ case "EMPTY_TYPE_ARRAY":
137
+ return `Type array in property '${propKey}' must not be empty`;
138
+ case "DUPLICATE_TYPE":
139
+ return `Duplicate type '${issue.value}' in property '${propKey}'`;
140
+ case "NOT_STRING_OR_ARRAY":
141
+ return `Type in property '${propKey}' must be string or array of strings`;
142
+ }
143
+ }
57
144
  /**
58
145
  * 依据 inputSchema 构造 v1 版本的 Action 输入编码顾问报告(机器契约)。
59
146
  *
@@ -64,18 +151,7 @@ export function buildCliInputAdviceV1(schema) {
64
151
  // 1. Schema Sanity 阶段(Section 44)
65
152
  // 1.1 根形状检查(Section 44.1)
66
153
  if (schema === undefined) {
67
- return {
68
- version: 1,
69
- analysisMode: "declared-properties-only",
70
- analysisStatus: "ok",
71
- schemaState: "absent",
72
- inputFeasibility: "possible-or-unknown",
73
- flatAvailable: false,
74
- requiredSatisfiable: null,
75
- flatCandidate: false,
76
- schemaRecommendedMode: "full-json",
77
- fields: [],
78
- };
154
+ return createNonFlatOkAdvice("absent");
79
155
  }
80
156
  if (typeof schema === "boolean") {
81
157
  if (schema === false) {
@@ -93,18 +169,7 @@ export function buildCliInputAdviceV1(schema) {
93
169
  fields: [],
94
170
  };
95
171
  }
96
- return {
97
- version: 1,
98
- analysisMode: "declared-properties-only",
99
- analysisStatus: "ok",
100
- schemaState: "any",
101
- inputFeasibility: "possible-or-unknown",
102
- flatAvailable: false,
103
- requiredSatisfiable: null,
104
- flatCandidate: false,
105
- schemaRecommendedMode: "full-json",
106
- fields: [],
107
- };
172
+ return createNonFlatOkAdvice("any");
108
173
  }
109
174
  if (!isPlainObject(schema)) {
110
175
  return {
@@ -124,30 +189,11 @@ export function buildCliInputAdviceV1(schema) {
124
189
  }
125
190
  const s = schema;
126
191
  // 1.2 关键字形状检查(Section 44.2)
127
- // 检查 type
192
+ // 检查 type(结构校验单一事实源,根级消息模板独立持有)
128
193
  if (s.type !== undefined) {
129
- if (typeof s.type === "string") {
130
- if (!RECOGNIZED_SCHEMA_TYPES.has(s.type)) {
131
- return createMalformedAdvice(`Unrecognized type keyword: '${s.type}'`);
132
- }
133
- }
134
- else if (Array.isArray(s.type)) {
135
- if (s.type.length === 0) {
136
- return createMalformedAdvice("Type array must not be empty");
137
- }
138
- const seen = new Set();
139
- for (const t of s.type) {
140
- if (typeof t !== "string" || !RECOGNIZED_SCHEMA_TYPES.has(t)) {
141
- return createMalformedAdvice(`Unrecognized type in type array: '${String(t)}'`);
142
- }
143
- if (seen.has(t)) {
144
- return createMalformedAdvice(`Duplicate type in type array: '${t}'`);
145
- }
146
- seen.add(t);
147
- }
148
- }
149
- else {
150
- return createMalformedAdvice("Type keyword must be a string or array of strings");
194
+ const issue = validateTypeKeyword(s.type);
195
+ if (issue) {
196
+ return createMalformedAdvice(formatRootTypeKeywordMessage(issue));
151
197
  }
152
198
  }
153
199
  // 检查 properties
@@ -161,28 +207,9 @@ export function buildCliInputAdviceV1(schema) {
161
207
  }
162
208
  if (isPlainObject(propVal)) {
163
209
  if (propVal.type !== undefined) {
164
- if (typeof propVal.type === "string") {
165
- if (!RECOGNIZED_SCHEMA_TYPES.has(propVal.type)) {
166
- return createMalformedAdvice(`Unrecognized type '${propVal.type}' in property '${propKey}'`);
167
- }
168
- }
169
- else if (Array.isArray(propVal.type)) {
170
- if (propVal.type.length === 0) {
171
- return createMalformedAdvice(`Type array in property '${propKey}' must not be empty`);
172
- }
173
- const seen = new Set();
174
- for (const t of propVal.type) {
175
- if (typeof t !== "string" || !RECOGNIZED_SCHEMA_TYPES.has(t)) {
176
- return createMalformedAdvice(`Unrecognized type '${String(t)}' in property '${propKey}'`);
177
- }
178
- if (seen.has(t)) {
179
- return createMalformedAdvice(`Duplicate type '${t}' in property '${propKey}'`);
180
- }
181
- seen.add(t);
182
- }
183
- }
184
- else {
185
- return createMalformedAdvice(`Type in property '${propKey}' must be string or array of strings`);
210
+ const issue = validateTypeKeyword(propVal.type);
211
+ if (issue) {
212
+ return createMalformedAdvice(formatPropertyTypeKeywordMessage(issue, propKey));
186
213
  }
187
214
  }
188
215
  if (propVal.enum !== undefined) {
@@ -280,18 +307,7 @@ export function buildCliInputAdviceV1(schema) {
280
307
  }
281
308
  // 2. 语义分类阶段(Section 45)
282
309
  if (Object.keys(s).length === 0) {
283
- return {
284
- version: 1,
285
- analysisMode: "declared-properties-only",
286
- analysisStatus: "ok",
287
- schemaState: "any",
288
- inputFeasibility: "possible-or-unknown",
289
- flatAvailable: false,
290
- requiredSatisfiable: null,
291
- flatCandidate: false,
292
- schemaRecommendedMode: "full-json",
293
- fields: [],
294
- };
310
+ return createNonFlatOkAdvice("any");
295
311
  }
296
312
  const hasComplexKeyword = isTupleItems ||
297
313
  COMPLEX_KEYWORDS.some((kw) => s[kw] !== undefined);
@@ -413,83 +429,37 @@ export function buildCliInputAdviceV1(schema) {
413
429
  // enum/const 绝不改变 operator 选择,仅显式单一 schema.type 决定 operator。
414
430
  // 无显式 type 时不推断类型,推荐 full-json,flatSafe: false。
415
431
  if (typeof propType === "string") {
416
- if (propType === "string") {
417
- fields.push({
418
- path: key,
419
- inputAllowed: true,
420
- flatSafe: true,
421
- required: isReq,
422
- type: "string",
423
- operator: "=",
424
- encoding: "string",
425
- assignmentTemplate: `${key}=TEXT`,
426
- ...(desc ? { description: desc } : {}),
427
- });
428
- }
429
- else if (propType === "number" || propType === "integer") {
430
- fields.push({
431
- path: key,
432
- inputAllowed: true,
433
- flatSafe: true,
434
- required: isReq,
435
- type: propType,
436
- operator: ":=",
437
- encoding: "json-number",
438
- assignmentTemplate: `${key}:=NUMBER`,
439
- ...(desc ? { description: desc } : {}),
440
- });
441
- }
442
- else if (propType === "boolean") {
443
- fields.push({
444
- path: key,
445
- inputAllowed: true,
446
- flatSafe: true,
447
- required: isReq,
448
- type: "boolean",
449
- operator: ":=",
450
- encoding: "json-boolean",
451
- assignmentTemplate: `${key}:=BOOLEAN`,
452
- ...(desc ? { description: desc } : {}),
453
- });
454
- }
455
- else if (propType === "null") {
456
- fields.push({
457
- path: key,
458
- inputAllowed: true,
459
- flatSafe: true,
460
- required: isReq,
461
- type: "null",
462
- operator: ":=",
463
- encoding: "json-null",
464
- assignmentTemplate: `${key}:=null`,
465
- ...(desc ? { description: desc } : {}),
466
- });
467
- }
468
- else if (propType === "array") {
469
- fields.push({
470
- path: key,
471
- inputAllowed: true,
472
- flatSafe: true,
473
- required: isReq,
474
- type: "array",
475
- operator: ":=",
432
+ // Flat-safe 显式单一类型的编码参数表(string/number/integer/boolean/null/array/object)。
433
+ // 各分支仅在 type、encoding、assignmentTemplate 与 hint 上差异,结构完全一致。
434
+ const flatSafeTypeTable = {
435
+ string: { encoding: "string", assignmentTemplate: `${key}=TEXT` },
436
+ number: { encoding: "json-number", assignmentTemplate: `${key}:=NUMBER` },
437
+ integer: { encoding: "json-number", assignmentTemplate: `${key}:=NUMBER` },
438
+ boolean: { encoding: "json-boolean", assignmentTemplate: `${key}:=BOOLEAN` },
439
+ null: { encoding: "json-null", assignmentTemplate: `${key}:=null` },
440
+ array: {
476
441
  encoding: "json-array",
477
442
  assignmentTemplate: `${key}:=JSON`,
478
443
  hint: "建议使用 --input-file 或标准输入传递数组结构",
479
- ...(desc ? { description: desc } : {}),
480
- });
481
- }
482
- else if (propType === "object") {
444
+ },
445
+ object: {
446
+ encoding: "json-object",
447
+ assignmentTemplate: `${key}:=JSON`,
448
+ hint: "大型结构建议使用 --input-file",
449
+ },
450
+ };
451
+ const flatSafeEntry = flatSafeTypeTable[propType];
452
+ if (flatSafeEntry) {
483
453
  fields.push({
484
454
  path: key,
485
455
  inputAllowed: true,
486
456
  flatSafe: true,
487
457
  required: isReq,
488
- type: "object",
489
- operator: ":=",
490
- encoding: "json-object",
491
- assignmentTemplate: `${key}:=JSON`,
492
- hint: "大型结构建议使用 --input-file",
458
+ type: propType,
459
+ operator: propType === "string" ? "=" : ":=",
460
+ encoding: flatSafeEntry.encoding,
461
+ assignmentTemplate: flatSafeEntry.assignmentTemplate,
462
+ ...(flatSafeEntry.hint ? { hint: flatSafeEntry.hint } : {}),
493
463
  ...(desc ? { description: desc } : {}),
494
464
  });
495
465
  }
@@ -599,13 +569,31 @@ export function buildCliInputAdviceV1(schema) {
599
569
  fields,
600
570
  };
601
571
  }
602
- export const FLAT_SAFE_KEY_REGEX = /^[A-Za-z_][A-Za-z0-9_-]*$/;
572
+ /**
573
+ * 扁平编码通用指引文案。
574
+ * 属性名安全判定统一引用 flat-predicates 的单一事实源(含禁止属性排除),
575
+ * 禁止在本文件另建独立正则分叉。
576
+ */
603
577
  export const FLAT_ENCODING_GUIDELINES = [
604
578
  "字符串: path=TEXT",
605
579
  "JSON 标量与结构: path:=JSON (例如 count:=1, enabled:=true)",
606
580
  "数组元素: path.INDEX=... (例如 items.0=first)",
607
581
  "提示: 复杂嵌套或大段文本建议使用 --input 或 --input-file",
608
582
  ];
583
+ /**
584
+ * 构造无扁平能力、无字段明细的空结果建议报告(各降级早退分支共用单一事实源)。
585
+ * 键序与既有契约保持一致,禁止调整。
586
+ */
587
+ function createEmptyFlatAdvice(notes) {
588
+ return {
589
+ flatSupported: false,
590
+ hasFlatFields: false,
591
+ fields: [],
592
+ requiredTemplates: [],
593
+ optionalTemplates: [],
594
+ notes,
595
+ };
596
+ }
609
597
  /**
610
598
  * 依据 inputSchema 构造统一的 Action 输入编码建议。
611
599
  *
@@ -615,62 +603,27 @@ export const FLAT_ENCODING_GUIDELINES = [
615
603
  export function buildActionInputAdvice(schema) {
616
604
  if (typeof schema === "boolean") {
617
605
  if (schema === false) {
618
- return {
619
- flatSupported: false,
620
- hasFlatFields: false,
621
- fields: [],
622
- requiredTemplates: [],
623
- optionalTemplates: [],
624
- notes: ["布尔模式 false:拒绝所有输入,任何调用参数均判定为非法"],
625
- };
606
+ return createEmptyFlatAdvice(["布尔模式 false:拒绝所有输入,任何调用参数均判定为非法"]);
626
607
  }
627
- return {
628
- flatSupported: false,
629
- hasFlatFields: false,
630
- fields: [],
631
- requiredTemplates: [],
632
- optionalTemplates: [],
633
- notes: [
634
- "布尔模式 true:接受任意合法 JSON 输入;调用时无需指定必填参数,非对象根输入请使用 --input-file 或 --input",
635
- ],
636
- };
608
+ return createEmptyFlatAdvice([
609
+ "布尔模式 true:接受任意合法 JSON 输入;调用时无需指定必填参数,非对象根输入请使用 --input-file 或 --input",
610
+ ]);
637
611
  }
638
612
  if (!schema || typeof schema !== "object") {
639
- return {
640
- flatSupported: false,
641
- hasFlatFields: false,
642
- fields: [],
643
- requiredTemplates: [],
644
- optionalTemplates: [],
645
- notes: ["根模式未声明输入字段,直接调用或使用 --input-file / --input '{}'"],
646
- };
613
+ return createEmptyFlatAdvice(["根模式未声明输入字段,直接调用或使用 --input-file / --input '{}'"]);
647
614
  }
648
615
  const s = schema;
649
616
  // 校验根模式是否为 object 类型
650
617
  if (s.type !== undefined && s.type !== "object") {
651
- return {
652
- flatSupported: false,
653
- hasFlatFields: false,
654
- fields: [],
655
- requiredTemplates: [],
656
- optionalTemplates: [],
657
- notes: [
658
- `根模式类型为 '${s.type}',不支持扁平赋值,建议使用 --input-file 或 --input`,
659
- ],
660
- };
618
+ return createEmptyFlatAdvice([
619
+ `根模式类型为 '${s.type}',不支持扁平赋值,建议使用 --input-file 或 --input`,
620
+ ]);
661
621
  }
662
622
  const properties = (s.properties || {});
663
623
  const required = Array.isArray(s.required) ? s.required : [];
664
624
  const propKeys = Object.keys(properties);
665
625
  if (propKeys.length === 0) {
666
- return {
667
- flatSupported: false,
668
- hasFlatFields: false,
669
- fields: [],
670
- requiredTemplates: [],
671
- optionalTemplates: [],
672
- notes: ["根模式无声明属性,不支持扁平赋值,建议使用 --input-file 或 --input '{}'"],
673
- };
626
+ return createEmptyFlatAdvice(["根模式无声明属性,不支持扁平赋值,建议使用 --input-file 或 --input '{}'"]);
674
627
  }
675
628
  const fields = [];
676
629
  const requiredTemplates = [];
@@ -680,10 +633,21 @@ export function buildActionInputAdvice(schema) {
680
633
  const prop = properties[key] || {};
681
634
  const typeStr = prop.type ? String(prop.type) : "any";
682
635
  const isReq = required.includes(key);
683
- const isFlatSafe = FLAT_SAFE_KEY_REGEX.test(key);
636
+ const isFlatSafe = isFlatSafePropertyName(key);
684
637
  let assignmentTemplate;
685
638
  let hint;
686
639
  if (isFlatSafe) {
640
+ // 数组元素标量类型到扁平元素赋值模板的参数表(共用同一守卫与拼接规则)。
641
+ // 命中时模板形态为 `${key}.0<operator><占位符>`,未命中回退数组整体 JSON 模板。
642
+ const itemsTypeTemplates = {
643
+ string: `${key}.0=TEXT`,
644
+ number: `${key}.0:=NUMBER`,
645
+ integer: `${key}.0:=NUMBER`,
646
+ boolean: `${key}.0:=BOOLEAN`,
647
+ };
648
+ const itemsTemplate = prop.type === "array" && prop.items && typeof prop.items === "object"
649
+ ? itemsTypeTemplates[prop.items.type]
650
+ : undefined;
687
651
  if (prop.type === "string") {
688
652
  assignmentTemplate = `${key}=TEXT`;
689
653
  }
@@ -693,23 +657,8 @@ export function buildActionInputAdvice(schema) {
693
657
  else if (prop.type === "boolean") {
694
658
  assignmentTemplate = `${key}:=BOOLEAN`;
695
659
  }
696
- else if (prop.type === "array" &&
697
- prop.items &&
698
- typeof prop.items === "object" &&
699
- prop.items.type === "string") {
700
- assignmentTemplate = `${key}.0=TEXT`;
701
- }
702
- else if (prop.type === "array" &&
703
- prop.items &&
704
- typeof prop.items === "object" &&
705
- (prop.items.type === "number" || prop.items.type === "integer")) {
706
- assignmentTemplate = `${key}.0:=NUMBER`;
707
- }
708
- else if (prop.type === "array" &&
709
- prop.items &&
710
- typeof prop.items === "object" &&
711
- prop.items.type === "boolean") {
712
- assignmentTemplate = `${key}.0:=BOOLEAN`;
660
+ else if (itemsTemplate) {
661
+ assignmentTemplate = itemsTemplate;
713
662
  }
714
663
  else if (prop.type === "array") {
715
664
  assignmentTemplate = `${key}:=JSON`;
@@ -1,4 +1,4 @@
1
- import type { ActionSpec } from "../app/types.js";
1
+ import type { ActionSpec } from "../package/types.js";
2
2
  /**
3
3
  * 字段级异常明细。
4
4
  */
@@ -1,5 +1,5 @@
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, };
1
+ import { ActionDockError, type ErrorCode } 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, } from "../errors.js";
3
3
  /** 输入大小或深度超出限制的根因类型 */
4
4
  export type InputLimitExceededReason = "MAX_INPUT_BYTES" | "MAX_JSON_DEPTH" | string;
5
5
  /** 扁平入参安全阈值超限的根因类型 */
@@ -19,16 +19,15 @@ export type InputValidationSource = "flat-json-literal" | "flat-materialized" |
19
19
  /**
20
20
  * 输入领域通用结构化异常类。
21
21
  */
22
- export declare class InputError extends Error {
23
- readonly code: string;
22
+ export declare class InputError extends ActionDockError {
24
23
  readonly details?: Record<string, unknown> | string[];
25
- constructor(code: string, message: string, details?: Record<string, unknown> | string[]);
24
+ constructor(code: ErrorCode, message: string, details?: Record<string, unknown> | string[]);
26
25
  }
27
26
  /**
28
27
  * 扁平输入结构化异常类(继承 InputError 保持既有捕获兼容)。
29
28
  */
30
29
  export declare class FlatInputError extends InputError {
31
- constructor(code: string, message: string, details?: Record<string, unknown>);
30
+ constructor(code: ErrorCode, message: string, details?: Record<string, unknown>);
32
31
  }
33
32
  /**
34
33
  * 构造 INVALID_JSON 异常(用于完整 JSON 文档解析失败)。
@@ -1,15 +1,15 @@
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, };
1
+ import { ActionDockError, 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, } from "../errors.js";
2
+ // 错误码常量对外转引单一事实源:仅保留工厂函数实际引用的导入绑定,
3
+ // 对外输出统一经由本条 export-from 语句完成,不再另列本地重导出清单。
4
+ 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, } from "../errors.js";
3
5
  /**
4
6
  * 输入领域通用结构化异常类。
5
7
  */
6
- export class InputError extends Error {
7
- code;
8
+ export class InputError extends ActionDockError {
8
9
  details;
9
10
  constructor(code, message, details) {
10
- super(message);
11
+ super(code, message, details);
11
12
  this.name = "InputError";
12
- this.code = code;
13
13
  this.details = details;
14
14
  Object.setPrototypeOf(this, InputError.prototype);
15
15
  }
@@ -64,6 +64,44 @@ function materializeNode(node) {
64
64
  */
65
65
  export function materializeFlatInput(assignments, options) {
66
66
  const root = createNode("", NodeState.OBJECT);
67
+ /**
68
+ * 叶子赋值四态推进(字符串段与数字段共用单一事实源)。
69
+ * - UNSET:置为 VALUE 并写入值;
70
+ * - VALUE:重复赋值,抛 DUPLICATE_ASSIGNMENT;
71
+ * - OBJECT/ARRAY:值写入既有容器,抛 LEAF_CONTAINER_CONFLICT。
72
+ */
73
+ const assignLeaf = (child, currentPathStr, operator, value, valueLength) => {
74
+ if (child.state === NodeState.UNSET) {
75
+ child.state = NodeState.VALUE;
76
+ child.value = value;
77
+ }
78
+ else if (child.state === NodeState.VALUE) {
79
+ throw inputPathConflict(`Duplicate assignment to leaf path "${currentPathStr}"`, { path: currentPathStr, operator, valueLength, reason: "DUPLICATE_ASSIGNMENT" });
80
+ }
81
+ else {
82
+ throw inputPathConflict(`Path conflict at "${currentPathStr}": cannot assign value to existing container (${child.state.toLowerCase()})`, { path: currentPathStr, operator, valueLength, reason: "LEAF_CONTAINER_CONFLICT" });
83
+ }
84
+ };
85
+ /**
86
+ * 容器下钻三态推进(字符串段与数字段共用单一事实源)。
87
+ * - UNSET:按下一路径段形态初始化为 OBJECT 或 ARRAY;
88
+ * - VALUE:穿透既有值节点,抛 LEAF_CONTAINER_CONFLICT;
89
+ * - 容器形态与期望不符:抛 OBJECT_ARRAY_CONFLICT。
90
+ * 错误消息中下一访问段的措辞由 accessNoun 提供(字符串段为 property,数字段为 index/property)。
91
+ */
92
+ const descend = (child, currentPathStr, nextSeg, operator, valueLength, accessNoun) => {
93
+ const nextExpectedState = typeof nextSeg === "number" ? NodeState.ARRAY : NodeState.OBJECT;
94
+ if (child.state === NodeState.UNSET) {
95
+ child.state = nextExpectedState;
96
+ }
97
+ else if (child.state === NodeState.VALUE) {
98
+ throw inputPathConflict(`Path conflict at "${currentPathStr}": cannot access ${accessNoun} "${nextSeg}" on existing value`, { path: currentPathStr, operator, valueLength, reason: "LEAF_CONTAINER_CONFLICT" });
99
+ }
100
+ else if (child.state !== nextExpectedState) {
101
+ throw inputPathConflict(`Path conflict at "${currentPathStr}": expected ${nextExpectedState.toLowerCase()} but found ${child.state.toLowerCase()}`, { path: currentPathStr, operator, valueLength, reason: "OBJECT_ARRAY_CONFLICT" });
102
+ }
103
+ return child;
104
+ };
67
105
  for (const assignment of assignments) {
68
106
  const { path, value, operator, rawValue } = assignment;
69
107
  const valueLength = Buffer.byteLength(rawValue, "utf8");
@@ -90,30 +128,10 @@ export function materializeFlatInput(assignments, options) {
90
128
  current.objectChildren[seg] = child;
91
129
  }
92
130
  if (isLast) {
93
- if (child.state === NodeState.UNSET) {
94
- child.state = NodeState.VALUE;
95
- child.value = value;
96
- }
97
- else if (child.state === NodeState.VALUE) {
98
- throw inputPathConflict(`Duplicate assignment to leaf path "${currentPathStr}"`, { path: currentPathStr, operator, valueLength, reason: "DUPLICATE_ASSIGNMENT" });
99
- }
100
- else {
101
- throw inputPathConflict(`Path conflict at "${currentPathStr}": cannot assign value to existing container (${child.state.toLowerCase()})`, { path: currentPathStr, operator, valueLength, reason: "LEAF_CONTAINER_CONFLICT" });
102
- }
131
+ assignLeaf(child, currentPathStr, operator, value, valueLength);
103
132
  }
104
133
  else {
105
- const nextSeg = path[i + 1];
106
- const nextExpectedState = typeof nextSeg === "number" ? NodeState.ARRAY : NodeState.OBJECT;
107
- if (child.state === NodeState.UNSET) {
108
- child.state = nextExpectedState;
109
- }
110
- else if (child.state === NodeState.VALUE) {
111
- throw inputPathConflict(`Path conflict at "${currentPathStr}": cannot access property "${nextSeg}" on existing value`, { path: currentPathStr, operator, valueLength, reason: "LEAF_CONTAINER_CONFLICT" });
112
- }
113
- else if (child.state !== nextExpectedState) {
114
- throw inputPathConflict(`Path conflict at "${currentPathStr}": expected ${nextExpectedState.toLowerCase()} but found ${child.state.toLowerCase()}`, { path: currentPathStr, operator, valueLength, reason: "OBJECT_ARRAY_CONFLICT" });
115
- }
116
- current = child;
134
+ current = descend(child, currentPathStr, path[i + 1], operator, valueLength, "property");
117
135
  }
118
136
  }
119
137
  else {
@@ -132,30 +150,10 @@ export function materializeFlatInput(assignments, options) {
132
150
  current.arrayChildren.set(seg, child);
133
151
  }
134
152
  if (isLast) {
135
- if (child.state === NodeState.UNSET) {
136
- child.state = NodeState.VALUE;
137
- child.value = value;
138
- }
139
- else if (child.state === NodeState.VALUE) {
140
- throw inputPathConflict(`Duplicate assignment to leaf path "${currentPathStr}"`, { path: currentPathStr, operator, valueLength, reason: "DUPLICATE_ASSIGNMENT" });
141
- }
142
- else {
143
- throw inputPathConflict(`Path conflict at "${currentPathStr}": cannot assign value to existing container (${child.state.toLowerCase()})`, { path: currentPathStr, operator, valueLength, reason: "LEAF_CONTAINER_CONFLICT" });
144
- }
153
+ assignLeaf(child, currentPathStr, operator, value, valueLength);
145
154
  }
146
155
  else {
147
- const nextSeg = path[i + 1];
148
- const nextExpectedState = typeof nextSeg === "number" ? NodeState.ARRAY : NodeState.OBJECT;
149
- if (child.state === NodeState.UNSET) {
150
- child.state = nextExpectedState;
151
- }
152
- else if (child.state === NodeState.VALUE) {
153
- throw inputPathConflict(`Path conflict at "${currentPathStr}": cannot access index/property "${nextSeg}" on existing value`, { path: currentPathStr, operator, valueLength, reason: "LEAF_CONTAINER_CONFLICT" });
154
- }
155
- else if (child.state !== nextExpectedState) {
156
- throw inputPathConflict(`Path conflict at "${currentPathStr}": expected ${nextExpectedState.toLowerCase()} but found ${child.state.toLowerCase()}`, { path: currentPathStr, operator, valueLength, reason: "OBJECT_ARRAY_CONFLICT" });
157
- }
158
- current = child;
156
+ current = descend(child, currentPathStr, path[i + 1], operator, valueLength, "index/property");
159
157
  }
160
158
  }
161
159
  }
@@ -0,0 +1,2 @@
1
+ export * from "./types.js";
2
+ export * from "./policy.js";
@@ -0,0 +1,2 @@
1
+ export * from "./types.js";
2
+ export * from "./policy.js";