@actiondock/core 2.6.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 +122 -10
- package/dist/errors.js +123 -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 -22
- package/dist/index.js +19 -21
- package/dist/input/advice.d.ts +104 -0
- package/dist/input/advice.js +716 -0
- package/dist/input/describe.d.ts +106 -0
- package/dist/input/describe.js +176 -0
- package/dist/input/file-input.d.ts +54 -0
- package/dist/input/file-input.js +124 -0
- package/dist/input/flat-decode.d.ts +11 -0
- package/dist/input/flat-decode.js +13 -0
- package/dist/input/flat-errors.d.ts +71 -0
- package/dist/input/flat-errors.js +87 -0
- package/dist/input/flat-materializer.d.ts +29 -0
- package/dist/input/flat-materializer.js +177 -0
- package/dist/input/flat-parser.d.ts +50 -0
- package/dist/input/flat-parser.js +169 -0
- package/dist/input/flat-predicates.d.ts +34 -0
- package/dist/input/flat-predicates.js +50 -0
- package/dist/input/index.d.ts +14 -0
- package/dist/input/index.js +14 -0
- package/dist/input/input-resolver.d.ts +63 -0
- package/dist/input/input-resolver.js +254 -0
- package/dist/input/json-depth-scanner.d.ts +17 -0
- package/dist/input/json-depth-scanner.js +49 -0
- package/dist/input/metadata.d.ts +99 -0
- package/dist/input/metadata.js +99 -0
- package/dist/input/stdin-input.d.ts +26 -0
- package/dist/input/stdin-input.js +125 -0
- package/dist/input/utf8.d.ts +19 -0
- package/dist/input/utf8.js +36 -0
- package/dist/input/validation-mapper.d.ts +27 -0
- package/dist/input/validation-mapper.js +144 -0
- 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/json/index.d.ts +1 -0
- package/dist/json/index.js +1 -0
- package/dist/json/value-validator.d.ts +88 -0
- package/dist/json/value-validator.js +330 -0
- 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 +3 -2
- package/dist/profile/client-query.js +5 -6
- package/dist/profile/client-transport.d.ts +19 -17
- package/dist/profile/client-transport.js +48 -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 -103
- package/dist/runtime/runner.js +142 -404
- package/dist/runtime/standalone.d.ts +37 -9
- package/dist/runtime/standalone.js +195 -136
- package/dist/schema/index.d.ts +1 -0
- package/dist/schema/index.js +1 -0
- package/dist/schema/validator.d.ts +10 -0
- package/dist/schema/validator.js +48 -54
- 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 -623
- 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
|
@@ -0,0 +1,716 @@
|
|
|
1
|
+
import { isFlatSafePropertyName, isFlatPathPropertyName, isForbiddenActionInputPropertyName, } from "./flat-predicates.js";
|
|
2
|
+
const RECOGNIZED_SCHEMA_TYPES = new Set([
|
|
3
|
+
"object",
|
|
4
|
+
"array",
|
|
5
|
+
"string",
|
|
6
|
+
"number",
|
|
7
|
+
"integer",
|
|
8
|
+
"boolean",
|
|
9
|
+
"null",
|
|
10
|
+
]);
|
|
11
|
+
const COMPLEX_KEYWORDS = [
|
|
12
|
+
"oneOf",
|
|
13
|
+
"anyOf",
|
|
14
|
+
"allOf",
|
|
15
|
+
"$ref",
|
|
16
|
+
"not",
|
|
17
|
+
"if",
|
|
18
|
+
"then",
|
|
19
|
+
"else",
|
|
20
|
+
"patternProperties",
|
|
21
|
+
"dependentSchemas",
|
|
22
|
+
"dependentRequired",
|
|
23
|
+
"propertyNames",
|
|
24
|
+
"unevaluatedProperties",
|
|
25
|
+
];
|
|
26
|
+
function isPlainObject(val) {
|
|
27
|
+
return typeof val === "object" && val !== null && !Array.isArray(val);
|
|
28
|
+
}
|
|
29
|
+
function isJsonValueLike(val) {
|
|
30
|
+
if (typeof val === "function" ||
|
|
31
|
+
typeof val === "symbol" ||
|
|
32
|
+
typeof val === "undefined" ||
|
|
33
|
+
typeof val === "bigint") {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
if (typeof val === "number" && (!Number.isFinite(val) || Number.isNaN(val))) {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
function createMalformedAdvice(message) {
|
|
42
|
+
return {
|
|
43
|
+
version: 1,
|
|
44
|
+
analysisMode: "declared-properties-only",
|
|
45
|
+
analysisStatus: "malformed",
|
|
46
|
+
analysisCode: "MALFORMED_SCHEMA",
|
|
47
|
+
analysisMessage: message,
|
|
48
|
+
schemaState: "complex",
|
|
49
|
+
inputFeasibility: "possible-or-unknown",
|
|
50
|
+
flatAvailable: false,
|
|
51
|
+
requiredSatisfiable: null,
|
|
52
|
+
flatCandidate: false,
|
|
53
|
+
schemaRecommendedMode: "full-json",
|
|
54
|
+
fields: [],
|
|
55
|
+
};
|
|
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
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* 依据 inputSchema 构造 v1 版本的 Action 输入编码顾问报告(机器契约)。
|
|
146
|
+
*
|
|
147
|
+
* @param schema Action 的 inputSchema 定义
|
|
148
|
+
* @returns 遵循技术设计文档规范的结构化输入建议
|
|
149
|
+
*/
|
|
150
|
+
export function buildCliInputAdviceV1(schema) {
|
|
151
|
+
// 1. Schema Sanity 阶段(Section 44)
|
|
152
|
+
// 1.1 根形状检查(Section 44.1)
|
|
153
|
+
if (schema === undefined) {
|
|
154
|
+
return createNonFlatOkAdvice("absent");
|
|
155
|
+
}
|
|
156
|
+
if (typeof schema === "boolean") {
|
|
157
|
+
if (schema === false) {
|
|
158
|
+
return {
|
|
159
|
+
version: 1,
|
|
160
|
+
analysisMode: "declared-properties-only",
|
|
161
|
+
analysisStatus: "ok",
|
|
162
|
+
schemaState: "reject-all",
|
|
163
|
+
inputFeasibility: "known-impossible",
|
|
164
|
+
feasibilityCode: "SCHEMA_REJECTS_ALL",
|
|
165
|
+
flatAvailable: false,
|
|
166
|
+
requiredSatisfiable: false,
|
|
167
|
+
flatCandidate: false,
|
|
168
|
+
schemaRecommendedMode: "none",
|
|
169
|
+
fields: [],
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
return createNonFlatOkAdvice("any");
|
|
173
|
+
}
|
|
174
|
+
if (!isPlainObject(schema)) {
|
|
175
|
+
return {
|
|
176
|
+
version: 1,
|
|
177
|
+
analysisMode: "declared-properties-only",
|
|
178
|
+
analysisStatus: "malformed",
|
|
179
|
+
analysisCode: "MALFORMED_SCHEMA",
|
|
180
|
+
analysisMessage: "Schema root must be undefined, boolean, or a plain object",
|
|
181
|
+
schemaState: "complex",
|
|
182
|
+
inputFeasibility: "possible-or-unknown",
|
|
183
|
+
flatAvailable: false,
|
|
184
|
+
requiredSatisfiable: null,
|
|
185
|
+
flatCandidate: false,
|
|
186
|
+
schemaRecommendedMode: "full-json",
|
|
187
|
+
fields: [],
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
const s = schema;
|
|
191
|
+
// 1.2 关键字形状检查(Section 44.2)
|
|
192
|
+
// 检查 type(结构校验单一事实源,根级消息模板独立持有)
|
|
193
|
+
if (s.type !== undefined) {
|
|
194
|
+
const issue = validateTypeKeyword(s.type);
|
|
195
|
+
if (issue) {
|
|
196
|
+
return createMalformedAdvice(formatRootTypeKeywordMessage(issue));
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
// 检查 properties
|
|
200
|
+
if (s.properties !== undefined) {
|
|
201
|
+
if (!isPlainObject(s.properties)) {
|
|
202
|
+
return createMalformedAdvice("Properties keyword must be a plain object");
|
|
203
|
+
}
|
|
204
|
+
for (const [propKey, propVal] of Object.entries(s.properties)) {
|
|
205
|
+
if (typeof propVal !== "boolean" && !isPlainObject(propVal)) {
|
|
206
|
+
return createMalformedAdvice(`Property '${propKey}' schema must be a boolean or plain object`);
|
|
207
|
+
}
|
|
208
|
+
if (isPlainObject(propVal)) {
|
|
209
|
+
if (propVal.type !== undefined) {
|
|
210
|
+
const issue = validateTypeKeyword(propVal.type);
|
|
211
|
+
if (issue) {
|
|
212
|
+
return createMalformedAdvice(formatPropertyTypeKeywordMessage(issue, propKey));
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
if (propVal.enum !== undefined) {
|
|
216
|
+
if (!Array.isArray(propVal.enum) || propVal.enum.length === 0) {
|
|
217
|
+
return createMalformedAdvice(`Enum in property '${propKey}' must be a non-empty array`);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
if (propVal.const !== undefined) {
|
|
221
|
+
if (!isJsonValueLike(propVal.const)) {
|
|
222
|
+
return createMalformedAdvice(`Const in property '${propKey}' must be a valid JSON value literal`);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
if (propVal.items !== undefined) {
|
|
226
|
+
if (typeof propVal.items !== "boolean" &&
|
|
227
|
+
!isPlainObject(propVal.items) &&
|
|
228
|
+
!Array.isArray(propVal.items)) {
|
|
229
|
+
return createMalformedAdvice(`Items in property '${propKey}' must be boolean, plain object, or array`);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
// 检查 required
|
|
236
|
+
if (s.required !== undefined) {
|
|
237
|
+
if (!Array.isArray(s.required)) {
|
|
238
|
+
return createMalformedAdvice("Required keyword must be an array of strings");
|
|
239
|
+
}
|
|
240
|
+
const seen = new Set();
|
|
241
|
+
for (const item of s.required) {
|
|
242
|
+
if (typeof item !== "string") {
|
|
243
|
+
return createMalformedAdvice("Every item in required array must be a string");
|
|
244
|
+
}
|
|
245
|
+
if (seen.has(item)) {
|
|
246
|
+
return createMalformedAdvice(`Duplicate required property: '${item}'`);
|
|
247
|
+
}
|
|
248
|
+
seen.add(item);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
// 检查 additionalProperties
|
|
252
|
+
if (s.additionalProperties !== undefined) {
|
|
253
|
+
if (typeof s.additionalProperties !== "boolean" &&
|
|
254
|
+
!isPlainObject(s.additionalProperties)) {
|
|
255
|
+
return createMalformedAdvice("AdditionalProperties keyword must be a boolean or plain object");
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
// 检查 items
|
|
259
|
+
let isTupleItems = false;
|
|
260
|
+
if (s.items !== undefined) {
|
|
261
|
+
if (typeof s.items === "boolean" || isPlainObject(s.items)) {
|
|
262
|
+
// 合法
|
|
263
|
+
}
|
|
264
|
+
else if (Array.isArray(s.items)) {
|
|
265
|
+
isTupleItems = true;
|
|
266
|
+
}
|
|
267
|
+
else {
|
|
268
|
+
return createMalformedAdvice("Items keyword must be boolean, plain object, or array");
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
// 检查 enum
|
|
272
|
+
if (s.enum !== undefined) {
|
|
273
|
+
if (!Array.isArray(s.enum) || s.enum.length === 0) {
|
|
274
|
+
return createMalformedAdvice("Enum keyword must be a non-empty array");
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
// 检查 const
|
|
278
|
+
if (s.const !== undefined) {
|
|
279
|
+
if (!isJsonValueLike(s.const)) {
|
|
280
|
+
return createMalformedAdvice("Const keyword must be a valid JSON value literal");
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
// 检查 applicators
|
|
284
|
+
if (s.oneOf !== undefined && !Array.isArray(s.oneOf)) {
|
|
285
|
+
return createMalformedAdvice("OneOf keyword must be an array");
|
|
286
|
+
}
|
|
287
|
+
if (s.anyOf !== undefined && !Array.isArray(s.anyOf)) {
|
|
288
|
+
return createMalformedAdvice("AnyOf keyword must be an array");
|
|
289
|
+
}
|
|
290
|
+
if (s.allOf !== undefined && !Array.isArray(s.allOf)) {
|
|
291
|
+
return createMalformedAdvice("AllOf keyword must be an array");
|
|
292
|
+
}
|
|
293
|
+
if (s.$ref !== undefined && typeof s.$ref !== "string") {
|
|
294
|
+
return createMalformedAdvice("$ref keyword must be a string");
|
|
295
|
+
}
|
|
296
|
+
if (s.not !== undefined && typeof s.not !== "boolean" && !isPlainObject(s.not)) {
|
|
297
|
+
return createMalformedAdvice("Not keyword must be a boolean or plain object");
|
|
298
|
+
}
|
|
299
|
+
if (s.if !== undefined && typeof s.if !== "boolean" && !isPlainObject(s.if)) {
|
|
300
|
+
return createMalformedAdvice("If keyword must be a boolean or plain object");
|
|
301
|
+
}
|
|
302
|
+
if (s.then !== undefined && typeof s.then !== "boolean" && !isPlainObject(s.then)) {
|
|
303
|
+
return createMalformedAdvice("Then keyword must be a boolean or plain object");
|
|
304
|
+
}
|
|
305
|
+
if (s.else !== undefined && typeof s.else !== "boolean" && !isPlainObject(s.else)) {
|
|
306
|
+
return createMalformedAdvice("Else keyword must be a boolean or plain object");
|
|
307
|
+
}
|
|
308
|
+
// 2. 语义分类阶段(Section 45)
|
|
309
|
+
if (Object.keys(s).length === 0) {
|
|
310
|
+
return createNonFlatOkAdvice("any");
|
|
311
|
+
}
|
|
312
|
+
const hasComplexKeyword = isTupleItems ||
|
|
313
|
+
COMPLEX_KEYWORDS.some((kw) => s[kw] !== undefined);
|
|
314
|
+
let schemaState;
|
|
315
|
+
if (hasComplexKeyword) {
|
|
316
|
+
schemaState = "complex";
|
|
317
|
+
}
|
|
318
|
+
else if (s.type === "object") {
|
|
319
|
+
schemaState = "object";
|
|
320
|
+
}
|
|
321
|
+
else if (typeof s.type === "string" && s.type !== "object") {
|
|
322
|
+
schemaState = "json-only";
|
|
323
|
+
}
|
|
324
|
+
else if (Array.isArray(s.type)) {
|
|
325
|
+
schemaState = "complex";
|
|
326
|
+
}
|
|
327
|
+
else if (s.type === undefined &&
|
|
328
|
+
(s.properties !== undefined || s.required !== undefined || s.additionalProperties !== undefined)) {
|
|
329
|
+
schemaState = "complex";
|
|
330
|
+
}
|
|
331
|
+
else {
|
|
332
|
+
schemaState = "complex";
|
|
333
|
+
}
|
|
334
|
+
const analysisStatus = schemaState === "complex" ? "unsupported" : "ok";
|
|
335
|
+
const analysisCode = schemaState === "complex"
|
|
336
|
+
? isTupleItems
|
|
337
|
+
? "UNSUPPORTED_SCHEMA_SHAPE"
|
|
338
|
+
: "COMPLEX_SCHEMA"
|
|
339
|
+
: undefined;
|
|
340
|
+
const analysisMessage = schemaState === "complex"
|
|
341
|
+
? "Complex schema structure is not supported for flat encoding"
|
|
342
|
+
: undefined;
|
|
343
|
+
// 3. 输入可行性判定(Section 47)
|
|
344
|
+
const requiredList = Array.isArray(s.required) ? s.required : [];
|
|
345
|
+
const properties = isPlainObject(s.properties) ? s.properties : {};
|
|
346
|
+
let inputFeasibility = "possible-or-unknown";
|
|
347
|
+
let feasibilityCode;
|
|
348
|
+
if (requiredList.some(isForbiddenActionInputPropertyName)) {
|
|
349
|
+
inputFeasibility = "known-impossible";
|
|
350
|
+
feasibilityCode = "REQUIRED_FIELD_FORBIDDEN";
|
|
351
|
+
}
|
|
352
|
+
else if (requiredList.some((k) => properties[k] === false)) {
|
|
353
|
+
inputFeasibility = "known-impossible";
|
|
354
|
+
feasibilityCode = "REQUIRED_FIELD_REJECTS_ALL";
|
|
355
|
+
}
|
|
356
|
+
else if (s.additionalProperties === false &&
|
|
357
|
+
requiredList.some((k) => !(k in properties))) {
|
|
358
|
+
inputFeasibility = "known-impossible";
|
|
359
|
+
feasibilityCode = "REQUIRED_FIELD_DISALLOWED_BY_ADDITIONAL_PROPERTIES";
|
|
360
|
+
}
|
|
361
|
+
// 4. 字段建议生成(Section 50)
|
|
362
|
+
const fields = [];
|
|
363
|
+
for (const [key, propSchema] of Object.entries(properties)) {
|
|
364
|
+
const isReq = requiredList.includes(key);
|
|
365
|
+
if (isForbiddenActionInputPropertyName(key)) {
|
|
366
|
+
fields.push({
|
|
367
|
+
path: key,
|
|
368
|
+
inputAllowed: false,
|
|
369
|
+
flatSafe: false,
|
|
370
|
+
required: isReq,
|
|
371
|
+
reason: "FORBIDDEN_PROPERTY",
|
|
372
|
+
fallback: null,
|
|
373
|
+
...(isPlainObject(propSchema) && propSchema.description
|
|
374
|
+
? { description: propSchema.description }
|
|
375
|
+
: {}),
|
|
376
|
+
hint: "Globally forbidden property",
|
|
377
|
+
});
|
|
378
|
+
continue;
|
|
379
|
+
}
|
|
380
|
+
if (propSchema === false) {
|
|
381
|
+
fields.push({
|
|
382
|
+
path: key,
|
|
383
|
+
inputAllowed: false,
|
|
384
|
+
flatSafe: false,
|
|
385
|
+
required: isReq,
|
|
386
|
+
reason: "PROPERTY_SCHEMA_FALSE",
|
|
387
|
+
fallback: null,
|
|
388
|
+
hint: "Property schema rejects all values",
|
|
389
|
+
});
|
|
390
|
+
continue;
|
|
391
|
+
}
|
|
392
|
+
if (!isFlatPathPropertyName(key)) {
|
|
393
|
+
const typeStr = isPlainObject(propSchema) && propSchema.type
|
|
394
|
+
? String(propSchema.type)
|
|
395
|
+
: "any";
|
|
396
|
+
fields.push({
|
|
397
|
+
path: key,
|
|
398
|
+
inputAllowed: true,
|
|
399
|
+
flatSafe: false,
|
|
400
|
+
required: isReq,
|
|
401
|
+
type: typeStr,
|
|
402
|
+
reason: "UNSAFE_FLAT_PROPERTY",
|
|
403
|
+
fallback: "stdin-json",
|
|
404
|
+
...(isPlainObject(propSchema) && propSchema.description
|
|
405
|
+
? { description: propSchema.description }
|
|
406
|
+
: {}),
|
|
407
|
+
hint: "Property name contains unsafe characters for flat encoding",
|
|
408
|
+
});
|
|
409
|
+
continue;
|
|
410
|
+
}
|
|
411
|
+
// Flat-safe property
|
|
412
|
+
if (propSchema === true) {
|
|
413
|
+
fields.push({
|
|
414
|
+
path: key,
|
|
415
|
+
inputAllowed: true,
|
|
416
|
+
flatSafe: true,
|
|
417
|
+
required: isReq,
|
|
418
|
+
type: "any",
|
|
419
|
+
operator: ":=",
|
|
420
|
+
encoding: "json",
|
|
421
|
+
assignmentTemplate: `${key}:=JSON`,
|
|
422
|
+
});
|
|
423
|
+
continue;
|
|
424
|
+
}
|
|
425
|
+
const propObj = propSchema;
|
|
426
|
+
const propType = propObj.type;
|
|
427
|
+
const desc = propObj.description;
|
|
428
|
+
// Section 46: enum/const 规则:
|
|
429
|
+
// enum/const 绝不改变 operator 选择,仅显式单一 schema.type 决定 operator。
|
|
430
|
+
// 无显式 type 时不推断类型,推荐 full-json,flatSafe: false。
|
|
431
|
+
if (typeof propType === "string") {
|
|
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: {
|
|
441
|
+
encoding: "json-array",
|
|
442
|
+
assignmentTemplate: `${key}:=JSON`,
|
|
443
|
+
hint: "建议使用 --input-file 或标准输入传递数组结构",
|
|
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) {
|
|
453
|
+
fields.push({
|
|
454
|
+
path: key,
|
|
455
|
+
inputAllowed: true,
|
|
456
|
+
flatSafe: true,
|
|
457
|
+
required: isReq,
|
|
458
|
+
type: propType,
|
|
459
|
+
operator: propType === "string" ? "=" : ":=",
|
|
460
|
+
encoding: flatSafeEntry.encoding,
|
|
461
|
+
assignmentTemplate: flatSafeEntry.assignmentTemplate,
|
|
462
|
+
...(flatSafeEntry.hint ? { hint: flatSafeEntry.hint } : {}),
|
|
463
|
+
...(desc ? { description: desc } : {}),
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
else {
|
|
467
|
+
fields.push({
|
|
468
|
+
path: key,
|
|
469
|
+
inputAllowed: true,
|
|
470
|
+
flatSafe: false,
|
|
471
|
+
required: isReq,
|
|
472
|
+
type: propType,
|
|
473
|
+
reason: "UNSAFE_FLAT_PROPERTY",
|
|
474
|
+
fallback: "stdin-json",
|
|
475
|
+
...(desc ? { description: desc } : {}),
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
else {
|
|
480
|
+
// 无显式单一类型(如 union type 或无 type 的 enum/const)
|
|
481
|
+
fields.push({
|
|
482
|
+
path: key,
|
|
483
|
+
inputAllowed: true,
|
|
484
|
+
flatSafe: false,
|
|
485
|
+
required: isReq,
|
|
486
|
+
type: Array.isArray(propType) ? propType.join(" | ") : "any",
|
|
487
|
+
reason: "UNSAFE_FLAT_PROPERTY",
|
|
488
|
+
fallback: "stdin-json",
|
|
489
|
+
...(desc ? { description: desc } : {}),
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
// 5. requiredSatisfiable 计算(Section 48)
|
|
494
|
+
let requiredSatisfiable = null;
|
|
495
|
+
if (schemaState === "json-only") {
|
|
496
|
+
requiredSatisfiable = false;
|
|
497
|
+
}
|
|
498
|
+
else if (schemaState === "complex") {
|
|
499
|
+
requiredSatisfiable = null;
|
|
500
|
+
}
|
|
501
|
+
else if (schemaState === "object") {
|
|
502
|
+
if (requiredList.length === 0) {
|
|
503
|
+
requiredSatisfiable = true;
|
|
504
|
+
}
|
|
505
|
+
else {
|
|
506
|
+
let hasNull = false;
|
|
507
|
+
let allSafe = true;
|
|
508
|
+
for (const reqKey of requiredList) {
|
|
509
|
+
if (isForbiddenActionInputPropertyName(reqKey)) {
|
|
510
|
+
requiredSatisfiable = false;
|
|
511
|
+
allSafe = false;
|
|
512
|
+
break;
|
|
513
|
+
}
|
|
514
|
+
const field = fields.find((f) => f.path === reqKey);
|
|
515
|
+
if (!field) {
|
|
516
|
+
if (s.additionalProperties === false) {
|
|
517
|
+
requiredSatisfiable = false;
|
|
518
|
+
allSafe = false;
|
|
519
|
+
break;
|
|
520
|
+
}
|
|
521
|
+
else {
|
|
522
|
+
hasNull = true;
|
|
523
|
+
continue;
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
if (!field.inputAllowed ||
|
|
527
|
+
field.flatSafe === false ||
|
|
528
|
+
!field.operator ||
|
|
529
|
+
!field.encoding) {
|
|
530
|
+
requiredSatisfiable = false;
|
|
531
|
+
allSafe = false;
|
|
532
|
+
break;
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
if (allSafe) {
|
|
536
|
+
requiredSatisfiable = hasNull ? null : true;
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
// 6. flatAvailable, flatCandidate, schemaRecommendedMode(Section 49)
|
|
541
|
+
const flatAvailable = fields.some((f) => f.flatSafe && f.operator !== undefined);
|
|
542
|
+
const flatCandidate = schemaState === "object" && requiredSatisfiable === true && flatAvailable;
|
|
543
|
+
let schemaRecommendedMode;
|
|
544
|
+
if (inputFeasibility === "known-impossible") {
|
|
545
|
+
schemaRecommendedMode = "none";
|
|
546
|
+
}
|
|
547
|
+
else if (schemaState === "json-only" || schemaState === "complex") {
|
|
548
|
+
schemaRecommendedMode = "full-json";
|
|
549
|
+
}
|
|
550
|
+
else if (schemaState === "object" && flatCandidate) {
|
|
551
|
+
schemaRecommendedMode = "flat";
|
|
552
|
+
}
|
|
553
|
+
else {
|
|
554
|
+
schemaRecommendedMode = "full-json";
|
|
555
|
+
}
|
|
556
|
+
return {
|
|
557
|
+
version: 1,
|
|
558
|
+
analysisMode: "declared-properties-only",
|
|
559
|
+
analysisStatus,
|
|
560
|
+
...(analysisCode ? { analysisCode } : {}),
|
|
561
|
+
...(analysisMessage ? { analysisMessage } : {}),
|
|
562
|
+
schemaState,
|
|
563
|
+
inputFeasibility,
|
|
564
|
+
flatAvailable,
|
|
565
|
+
requiredSatisfiable,
|
|
566
|
+
flatCandidate,
|
|
567
|
+
schemaRecommendedMode,
|
|
568
|
+
...(feasibilityCode ? { feasibilityCode } : {}),
|
|
569
|
+
fields,
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
/**
|
|
573
|
+
* 扁平编码通用指引文案。
|
|
574
|
+
* 属性名安全判定统一引用 flat-predicates 的单一事实源(含禁止属性排除),
|
|
575
|
+
* 禁止在本文件另建独立正则分叉。
|
|
576
|
+
*/
|
|
577
|
+
export const FLAT_ENCODING_GUIDELINES = [
|
|
578
|
+
"字符串: path=TEXT",
|
|
579
|
+
"JSON 标量与结构: path:=JSON (例如 count:=1, enabled:=true)",
|
|
580
|
+
"数组元素: path.INDEX=... (例如 items.0=first)",
|
|
581
|
+
"提示: 复杂嵌套或大段文本建议使用 --input 或 --input-file",
|
|
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
|
+
}
|
|
597
|
+
/**
|
|
598
|
+
* 依据 inputSchema 构造统一的 Action 输入编码建议。
|
|
599
|
+
*
|
|
600
|
+
* @param schema Action 的 inputSchema 定义
|
|
601
|
+
* @returns 结构化编码建议报告
|
|
602
|
+
*/
|
|
603
|
+
export function buildActionInputAdvice(schema) {
|
|
604
|
+
if (typeof schema === "boolean") {
|
|
605
|
+
if (schema === false) {
|
|
606
|
+
return createEmptyFlatAdvice(["布尔模式 false:拒绝所有输入,任何调用参数均判定为非法"]);
|
|
607
|
+
}
|
|
608
|
+
return createEmptyFlatAdvice([
|
|
609
|
+
"布尔模式 true:接受任意合法 JSON 输入;调用时无需指定必填参数,非对象根输入请使用 --input-file 或 --input",
|
|
610
|
+
]);
|
|
611
|
+
}
|
|
612
|
+
if (!schema || typeof schema !== "object") {
|
|
613
|
+
return createEmptyFlatAdvice(["根模式未声明输入字段,直接调用或使用 --input-file / --input '{}'"]);
|
|
614
|
+
}
|
|
615
|
+
const s = schema;
|
|
616
|
+
// 校验根模式是否为 object 类型
|
|
617
|
+
if (s.type !== undefined && s.type !== "object") {
|
|
618
|
+
return createEmptyFlatAdvice([
|
|
619
|
+
`根模式类型为 '${s.type}',不支持扁平赋值,建议使用 --input-file 或 --input`,
|
|
620
|
+
]);
|
|
621
|
+
}
|
|
622
|
+
const properties = (s.properties || {});
|
|
623
|
+
const required = Array.isArray(s.required) ? s.required : [];
|
|
624
|
+
const propKeys = Object.keys(properties);
|
|
625
|
+
if (propKeys.length === 0) {
|
|
626
|
+
return createEmptyFlatAdvice(["根模式无声明属性,不支持扁平赋值,建议使用 --input-file 或 --input '{}'"]);
|
|
627
|
+
}
|
|
628
|
+
const fields = [];
|
|
629
|
+
const requiredTemplates = [];
|
|
630
|
+
const optionalTemplates = [];
|
|
631
|
+
const notes = [];
|
|
632
|
+
for (const key of propKeys) {
|
|
633
|
+
const prop = properties[key] || {};
|
|
634
|
+
const typeStr = prop.type ? String(prop.type) : "any";
|
|
635
|
+
const isReq = required.includes(key);
|
|
636
|
+
const isFlatSafe = isFlatSafePropertyName(key);
|
|
637
|
+
let assignmentTemplate;
|
|
638
|
+
let hint;
|
|
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;
|
|
651
|
+
if (prop.type === "string") {
|
|
652
|
+
assignmentTemplate = `${key}=TEXT`;
|
|
653
|
+
}
|
|
654
|
+
else if (prop.type === "number" || prop.type === "integer") {
|
|
655
|
+
assignmentTemplate = `${key}:=NUMBER`;
|
|
656
|
+
}
|
|
657
|
+
else if (prop.type === "boolean") {
|
|
658
|
+
assignmentTemplate = `${key}:=BOOLEAN`;
|
|
659
|
+
}
|
|
660
|
+
else if (itemsTemplate) {
|
|
661
|
+
assignmentTemplate = itemsTemplate;
|
|
662
|
+
}
|
|
663
|
+
else if (prop.type === "array") {
|
|
664
|
+
assignmentTemplate = `${key}:=JSON`;
|
|
665
|
+
hint = "数组结构建议使用 --input-file";
|
|
666
|
+
}
|
|
667
|
+
else if (prop.type === "object") {
|
|
668
|
+
assignmentTemplate = `${key}:=JSON`;
|
|
669
|
+
hint = "大型结构建议使用 --input-file";
|
|
670
|
+
}
|
|
671
|
+
else {
|
|
672
|
+
assignmentTemplate = `${key}:=JSON`;
|
|
673
|
+
hint = "复杂或未知类型建议使用 --input-file";
|
|
674
|
+
}
|
|
675
|
+
if (isReq) {
|
|
676
|
+
if (assignmentTemplate) {
|
|
677
|
+
requiredTemplates.push(assignmentTemplate);
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
else {
|
|
681
|
+
if (assignmentTemplate) {
|
|
682
|
+
optionalTemplates.push(assignmentTemplate);
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
else {
|
|
687
|
+
hint = "包含非安全字符,建议使用 --input-file";
|
|
688
|
+
notes.push(`属性 '${key}' 包含非安全字符,不支持扁平赋值,建议使用 --input-file`);
|
|
689
|
+
}
|
|
690
|
+
fields.push({
|
|
691
|
+
path: key,
|
|
692
|
+
type: typeStr,
|
|
693
|
+
required: isReq,
|
|
694
|
+
flatSafe: isFlatSafe,
|
|
695
|
+
assignmentTemplate,
|
|
696
|
+
hint,
|
|
697
|
+
description: prop.description,
|
|
698
|
+
});
|
|
699
|
+
}
|
|
700
|
+
const hasFlatFields = fields.some((f) => f.flatSafe && f.assignmentTemplate !== undefined);
|
|
701
|
+
// 仅当根模式为对象且所有 required 字段均具备 flatSafe 赋值方案时为 true
|
|
702
|
+
const allRequiredFlatSafe = required.every((reqKey) => {
|
|
703
|
+
const f = fields.find((field) => field.path === reqKey);
|
|
704
|
+
return f !== undefined && f.flatSafe && f.assignmentTemplate !== undefined;
|
|
705
|
+
});
|
|
706
|
+
const flatSupported = hasFlatFields && allRequiredFlatSafe;
|
|
707
|
+
return {
|
|
708
|
+
flatSupported,
|
|
709
|
+
hasFlatFields,
|
|
710
|
+
fields,
|
|
711
|
+
requiredTemplates,
|
|
712
|
+
optionalTemplates,
|
|
713
|
+
notes,
|
|
714
|
+
};
|
|
715
|
+
}
|
|
716
|
+
export { formatActionDetail } from "./describe.js";
|