@stalfh233/omc-cli 0.6.16 → 0.6.18
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/dist/batch-call.js +39 -5
- package/dist/capability-overview.js +2 -2
- package/dist/cli.js +25 -13
- package/dist/compute.js +1 -1
- package/dist/field-change.js +7 -1
- package/dist/field-families.js +102 -1
- package/dist/graph.js +142 -24
- package/dist/lanes/app.js +17 -4
- package/dist/lanes/field.js +118 -5
- package/dist/lanes/model-contract.js +1 -1
- package/dist/lanes/model.js +57 -30
- package/dist/lanes/online-js-contract.js +1 -1
- package/dist/lanes/online-js.js +41 -9
- package/dist/lanes/organization.js +9 -2
- package/dist/lanes/programming.js +67 -14
- package/dist/lanes/runtime-data.js +177 -20
- package/dist/lanes/workflow-contract.js +5 -1
- package/dist/lanes/workflow.js +104 -3
- package/dist/mcp-graph.js +45 -15
- package/dist/programming-workflow.js +16 -3
- package/dist/read-shapes.js +3 -3
- package/dist/rollback.js +54 -19
- package/dist/rule-when.js +42 -12
- package/dist/skills.js +1 -1
- package/dist/workflow-support.js +2 -1
- package/dist/write.js +6 -2
- package/package.json +3 -3
package/dist/batch-call.js
CHANGED
|
@@ -19,6 +19,8 @@
|
|
|
19
19
|
* per-tool preflight apply exactly as they do for a single call.
|
|
20
20
|
*/
|
|
21
21
|
import { refusalText } from "./cli-contract.js";
|
|
22
|
+
import { createArtifact } from "./artifacts.js";
|
|
23
|
+
import { artifactFetchHint, artifactRoot } from "./command-transport.js";
|
|
22
24
|
import { runScript } from "./script.js";
|
|
23
25
|
function inputBusinessKey(args) {
|
|
24
26
|
const keys = ["code", "name", "bizObjectId", "id"];
|
|
@@ -44,7 +46,12 @@ function failureDetails(entry, args) {
|
|
|
44
46
|
}
|
|
45
47
|
return { hint: "平台回了无明细 50000,常见成因见 help omc-refusals;请用该行输入与已发布字段逐项核对" };
|
|
46
48
|
}
|
|
47
|
-
export function batchWarnings(argsList) {
|
|
49
|
+
export function batchWarnings(argsList, tool) {
|
|
50
|
+
// Runtime data validation belongs to the runtime-data lane, which has the
|
|
51
|
+
// published field metadata. The old generic `code` requirement fabricated
|
|
52
|
+
// ghost warnings for data.save (models commonly have no code field).
|
|
53
|
+
if (tool === "data.save")
|
|
54
|
+
return [];
|
|
48
55
|
const warnings = [];
|
|
49
56
|
const seenCodes = new Map();
|
|
50
57
|
argsList.forEach((args, index) => {
|
|
@@ -141,6 +148,11 @@ export async function runBatchCall(options) {
|
|
|
141
148
|
? String(readback.authoritative.id)
|
|
142
149
|
: undefined;
|
|
143
150
|
const skippedMarker = performed?.skipped;
|
|
151
|
+
const applicationCode = typeof performed?.applicationCode === "string"
|
|
152
|
+
? String(performed.applicationCode)
|
|
153
|
+
: typeof entry.result.readback?.applicationCode === "string"
|
|
154
|
+
? String(entry.result.readback.applicationCode)
|
|
155
|
+
: undefined;
|
|
144
156
|
return {
|
|
145
157
|
index: entry.index,
|
|
146
158
|
row: entry.index + 1,
|
|
@@ -148,11 +160,25 @@ export async function runBatchCall(options) {
|
|
|
148
160
|
ok: entry.status === "ok",
|
|
149
161
|
...inputBusinessKey(options.argsList[entry.index] ?? {}),
|
|
150
162
|
...(bizObjectId !== undefined ? { bizObjectId } : {}),
|
|
163
|
+
...(applicationCode !== undefined ? { applicationCode } : {}),
|
|
151
164
|
...(entry.status === "ok" ? {} : { status: String(entry.result.status ?? "failed"), blocker: entry.result.blocker ?? null }),
|
|
152
165
|
...(skippedMarker !== undefined ? { skipped: String(skippedMarker) } : {}),
|
|
153
166
|
};
|
|
154
167
|
});
|
|
155
168
|
const entriesInline = entriesSummary.slice(0, ENTRIES_INLINE_LIMIT);
|
|
169
|
+
const laneWarnings = entries.flatMap((entry) => {
|
|
170
|
+
const warnings = entry.result.readback?.warnings;
|
|
171
|
+
return Array.isArray(warnings)
|
|
172
|
+
? warnings.map((warning) => ({ ...warning, row: entry.index + 1 }))
|
|
173
|
+
: [];
|
|
174
|
+
});
|
|
175
|
+
const warnings = options.tool === "data.save" ? laneWarnings : batchWarnings(options.argsList, options.tool);
|
|
176
|
+
const applicationCode = entries.map((entry) => entry.result.performed?.applicationCode).find((value) => typeof value === "string");
|
|
177
|
+
const warningsInline = warnings.slice(0, ENTRIES_INLINE_LIMIT);
|
|
178
|
+
const detailOmitted = entriesSummary.length > entriesInline.length || warnings.length > warningsInline.length;
|
|
179
|
+
const detailArtifact = detailOmitted
|
|
180
|
+
? createArtifact(artifactRoot(), JSON.stringify({ entries: entriesSummary, warnings }, null, 2) + "\n")
|
|
181
|
+
: undefined;
|
|
156
182
|
return {
|
|
157
183
|
exitCode: failed > 0 ? 1 : 0,
|
|
158
184
|
result: {
|
|
@@ -162,14 +188,22 @@ export async function runBatchCall(options) {
|
|
|
162
188
|
executed,
|
|
163
189
|
succeeded,
|
|
164
190
|
failed,
|
|
191
|
+
...(applicationCode !== undefined ? { applicationCode } : {}),
|
|
165
192
|
...(skipped > 0 ? { notExecuted: skipped } : {}),
|
|
166
193
|
// 每条一行摘要(index/ok/bizObjectId)——不是完整信封,但足以回答
|
|
167
194
|
// 「第 3 条建成了吗、它的 id 是什么」。失败项另有 failures 带完整原因。
|
|
168
195
|
entries: entriesInline,
|
|
169
|
-
...(
|
|
170
|
-
|
|
171
|
-
? {
|
|
172
|
-
|
|
196
|
+
...(warnings.length > 0 ? {
|
|
197
|
+
warnings: warningsInline,
|
|
198
|
+
...(warnings.length > warningsInline.length ? { warningsOmitted: warnings.length - warningsInline.length } : {}),
|
|
199
|
+
} : {}),
|
|
200
|
+
...(entriesSummary.length > entriesInline.length ? { entriesOmitted: entriesSummary.length - entriesInline.length } : {}),
|
|
201
|
+
...(detailArtifact !== undefined ? {
|
|
202
|
+
detailArtifactId: detailArtifact.artifactId,
|
|
203
|
+
sha256: detailArtifact.sha256,
|
|
204
|
+
fetch: artifactFetchHint(detailArtifact.artifactId),
|
|
205
|
+
entriesHint: `条目或预检警告超过 ${ENTRIES_INLINE_LIMIT} 条,只内联前 ${ENTRIES_INLINE_LIMIT} 条;完整结果见 detailArtifactId`,
|
|
206
|
+
} : {}),
|
|
173
207
|
// Only failures carry full per-entry detail: echoing 8 full success
|
|
174
208
|
// envelopes would rebuild exactly the wall of text this command exists
|
|
175
209
|
// to remove.
|
|
@@ -123,7 +123,7 @@ const SURFACES = [
|
|
|
123
123
|
},
|
|
124
124
|
{
|
|
125
125
|
id: "workflow",
|
|
126
|
-
title: "流程模板",
|
|
126
|
+
title: "流程模板 + 组织与权限",
|
|
127
127
|
lane: LANES.workflow,
|
|
128
128
|
description: "流程模板生命周期:get_draft/get_published、create、update、publish、bindRule、delete、workitem.list。**含组织/部门/角色/权限范围(org.* 工具)**:部门树、角色组/角色、用户搜索、成员与管理范围(userScope)——流程参与人/可见性的候选来源。",
|
|
129
129
|
operations: ["create", "update", "publish", "bindRule", "delete"],
|
|
@@ -193,7 +193,7 @@ const CAPABILITY_ALIASES = Object.freeze({
|
|
|
193
193
|
businessrule: ["business-rules", "B1-businessrule"],
|
|
194
194
|
"rule-lifecycle": ["rule-lifecycles", "S25-rule-lifecycle"],
|
|
195
195
|
// r28h F1/F7: org.* lives under workflow; aliases let "我要管部门/组织" hit it.
|
|
196
|
-
workflow: ["workflows", "W7-S29-greenfield", "org", "organization", "
|
|
196
|
+
workflow: ["workflows", "W7-S29-greenfield", "org", "organization", "permission", "role", "权限", "角色", "组织"],
|
|
197
197
|
onlinejs: ["online-js"],
|
|
198
198
|
programming: ["backend-online-coding", "programming-online-dev", "在线开发", "在线编码"],
|
|
199
199
|
bizservice: ["bizservices", "B2-registration"],
|
package/dist/cli.js
CHANGED
|
@@ -368,7 +368,7 @@ function parseDoctorStep(line) {
|
|
|
368
368
|
function doctorAnchor() {
|
|
369
369
|
const topics = listSkills().map((entry) => entry.name).join("、");
|
|
370
370
|
return [
|
|
371
|
-
"最小路径 omc next \"<目标>\"(目标→工具一步定位,不确定会明说)→ omc help omc-start-here → omc call discover_environment --env <env> --args '{}'(<env> 名以 doctor 的「环境=」或项目 .omc-env
|
|
371
|
+
"最小路径 omc next \"<目标>\"(目标→工具一步定位,不确定会明说)→ omc help omc-start-here → omc call discover_environment --env <env> --args '{}'(<env> 名以 doctor 的「环境=」或项目 .omc-env 为准,勿猜;项目环境白名单按 cwd 解析,须在含 .omc-env 的目录内运行)→ omc capabilities / omc describe <tool>",
|
|
372
372
|
`方法论 omc help <topic>(或 --topic <name>)--sections / --section "<标题>"(按节读,随包 skills/);topic:${topics}`,
|
|
373
373
|
"大输出 超预算结果只回 artifactId/sha256/fetch;用 fetch 里的 omc artifact get <id>(可加 --jq <path>)取回,勿全量读入",
|
|
374
374
|
// r29f F9:只读面不碰远端,但仍会本地落盘——写清,避免「零写入」核对误判。
|
|
@@ -534,7 +534,7 @@ async function cmdCall(store, args) {
|
|
|
534
534
|
return emitTransportResult({ status: "ok", ...described, helpNote: "来自 omc describe;完整面用 omc describe <tool> [--full|--param|--example]" }, { request, maxOutputBytes });
|
|
535
535
|
}
|
|
536
536
|
}
|
|
537
|
-
console.error("用法:omc call <tool> [--env <环境>] [--args '<json>' | --args - (stdin) | --args-file
|
|
537
|
+
console.error("用法:omc call <tool> [--env <环境>] [--args '<json>' | --args - (stdin) | --args-file <配置根内路径(必须位于配置根内;数组批量请改用 --args-list;跨目录临时文件用 --args -(stdin))>] [--extract <path>]... [--jq [path]] [--extract-keys] [--args-list <json数组>(最多 200 条;超出请分批或用 omc script 读文件) [--preflight]]");
|
|
538
538
|
console.error("输出预算:--max-output-bytes 默认 65536(只约束最终 stdout 信封;读面 diet ~2048 独立外置 detailArtifactId);artifact get 内联上限 8192");
|
|
539
539
|
console.error("写入:由环境 writable 决定(只读环境拒写);生产环境的写必须在 args 里带 confirmProduction:true。");
|
|
540
540
|
return emit(toolName && toolName !== "help" ? "error" : "ok", {}, toolName && toolName !== "help" ? CLI_EXIT_CODES.invalidInput : 0);
|
|
@@ -632,18 +632,20 @@ async function cmdCall(store, args) {
|
|
|
632
632
|
// 原来只在末尾附一句「若问题在引号转义…」,容易被当成补充说明;这里把它
|
|
633
633
|
// 提成主建议,并给出 PS 可粘贴的两条命令。
|
|
634
634
|
const looksLikeShellSplitting = /Unexpected (token|end of JSON)|Expected property name|position \d+/i.test(detail);
|
|
635
|
-
const message =
|
|
636
|
-
?
|
|
637
|
-
:
|
|
638
|
-
? `--args
|
|
639
|
-
:
|
|
635
|
+
const message = argsFile !== undefined && /must be a JSON object/.test(detail)
|
|
636
|
+
? `${detail};--args-file 只收单个 JSON 对象,批量数组请改用 --args-list(最多 200 条,可分批执行)`
|
|
637
|
+
: outOfRoot
|
|
638
|
+
? `--args-file 越界:${detail}。可执行做法:把参数文件放到 ${configRoot} 之下(如 ${join(configRoot, "args.json")}),再用 --args-file 引用;不要把参数文件放在系统临时目录(配置根之外一律拒绝)`
|
|
639
|
+
: looksLikeShellSplitting
|
|
640
|
+
? `--args 不像合法 JSON(${detail})——这个形态通常是 shell 把内联 JSON 拆坏了(Windows PowerShell 的引号/转义规则),不是你写错了字段。改法二选一:① 把 JSON 写进 ${join(configRoot, "args.json")} 再用 --args-file;② \`omc call <tool> --args - \` 从 stdin 读(PS: \`'{"k":"v"}' | omc call <tool> --args -\`)。POSIX 文档里的单引号写法不能照抄到 PS`
|
|
641
|
+
: `--args 必须是合法 JSON:${detail}。若问题在引号转义(Windows PowerShell),改用 --args-file <配置根内的路径> 或 --args -(stdin)`;
|
|
640
642
|
console.error(message);
|
|
641
643
|
// The MACHINE envelope must carry the same actionable text: agents read
|
|
642
644
|
// stdout, and the old envelope only repeated the English detail.
|
|
643
645
|
// Keep the REJECTED path on stderr only: the machine envelope must not leak
|
|
644
646
|
// host layout (ticket 103), so it carries the root plus the bare file name.
|
|
645
647
|
const rejectedName = typeof argsFile === "string" ? basename(resolve(configRoot, argsFile)) : "";
|
|
646
|
-
writeEnvelope(envelope("error", {}, request, { code: "invalid-input", message: outOfRoot ? `--args-file 必须位于配置根内(配置根 ${configRoot}${rejectedName ? `;本次文件 ${rejectedName}` : ""})。免落盘替代:--args - 从 stdin 读 JSON(echo '<json>' | omc call <tool> --args -),或把文件放进配置根后再引用;限制是为了防参数文件携带凭据被收进会话产物` : detail, retryable: false, ...(outOfRoot ? { configurationRoot: configRoot } : {}) }));
|
|
648
|
+
writeEnvelope(envelope("error", {}, request, { code: "invalid-input", message: outOfRoot ? `--args-file 必须位于配置根内(配置根 ${configRoot}${rejectedName ? `;本次文件 ${rejectedName}` : ""})。免落盘替代:--args - 从 stdin 读 JSON(echo '<json>' | omc call <tool> --args -),或把文件放进配置根后再引用;限制是为了防参数文件携带凭据被收进会话产物` : argsFile !== undefined && /must be a JSON object/.test(detail) ? `${detail};批量数组请改用 --args-list(最多 200 条,可分批执行)` : detail, retryable: false, ...(outOfRoot ? { configurationRoot: configRoot } : {}) }));
|
|
647
649
|
return CLI_EXIT_CODES.invalidInput;
|
|
648
650
|
}
|
|
649
651
|
const registry = buildToolRegistry();
|
|
@@ -1624,6 +1626,7 @@ async function cmdGraphPull(environment, concurrency, transport) {
|
|
|
1624
1626
|
identityFingerprint: data.meta.identityFingerprint,
|
|
1625
1627
|
pulledAtIso: data.meta.pulledAtIso,
|
|
1626
1628
|
scope: data.meta.scope,
|
|
1629
|
+
coverage: { applicationCodes: [data.meta.applicationCode] },
|
|
1627
1630
|
freshness: data.gaps.length === 0 ? "complete" : "partial",
|
|
1628
1631
|
scanned: { models: data.scanned.models, rules: data.scanned.rules, datarules: data.scanned.datarules, views: data.scanned.views, ...(data.scanned.parseFailures > 0 ? { parseFailures: data.scanned.parseFailures } : {}) },
|
|
1629
1632
|
edges: edgesByKind,
|
|
@@ -1670,7 +1673,10 @@ function cmdGraphReferences(kind, code, transport, requestedEnvironment) {
|
|
|
1670
1673
|
function cmdGraphStatus(transport) {
|
|
1671
1674
|
try {
|
|
1672
1675
|
const status = readGraphStatus();
|
|
1673
|
-
|
|
1676
|
+
// r31a-F16:这里是**第四个**白名单式重建(前三次:compact() 投影、references
|
|
1677
|
+
// 覆盖声明、mcp-graph 的 explore)。白名单每漏一个键就等于没修,且不会被任何
|
|
1678
|
+
// 单测发现——所以改成展开,新增字段自动透传。
|
|
1679
|
+
return emitResult({ status: "ok", ...status }, transport);
|
|
1674
1680
|
}
|
|
1675
1681
|
catch (error) {
|
|
1676
1682
|
if (error instanceof GraphIndexAbsentError)
|
|
@@ -2159,7 +2165,7 @@ async function main() {
|
|
|
2159
2165
|
commandExitCode = CLI_EXIT_CODES.ok;
|
|
2160
2166
|
});
|
|
2161
2167
|
command.command("doctor").option("--environment <environment>").option("--env <environment>").option("--service <serviceCode>", "可选:体检单个业务集成服务的鉴权参数形状(掩码/占位/全 0 检出,WARN 非致命)").action(async (options) => { commandExitCode = await cmdDoctor(new ConfigStore(defaultPaths()), options.environment ?? options.env, options.service); });
|
|
2162
|
-
command.command("call <tool>").option("--env <environment>").option("--environment <environment>").option("--args <json>").option("--args-file <path>", "从文件读参数 JSON
|
|
2168
|
+
command.command("call <tool>").option("--env <environment>", "环境名;项目白名单按当前 cwd 解析,须在含 .omc-env 的目录内运行").option("--environment <environment>").option("--args <json>").option("--args-file <path>", "从文件读参数 JSON。**必须位于配置根内**(数组批量请改用 --args-list;跨目录/系统临时文件会被拒);免落盘用 `--args -`(stdin)").option("--args-list <json-array>", '同一工具跑 N 次(JSON 数组,每条一个 args 对象,最多 200 条);超过上限请分批,或用 omc script 读文件。逐条执行、默认不中断,最后回 succeeded/failed 汇总。适合批量建字段等').option("--preflight", "与 --args-list 一起用:只跑参数校验 + 写前语义闸(零 wire、零写入),一次报全所有形状问题").option("--keys", "与 artifact get 的 --keys 同义(= --extract-keys):列出本次结果 artifact 的顶层键与体积").addOption(new Option("--extract <path>", "在本次结果的 artifact 内取一个路径的值(可重复;省掉一次 artifact get)").argParser((value, previous = []) => [...previous, value]).default([])).addOption(new Option("--jq [path]", "同 --extract,并支持 [] 映射(如 items[].fields.planNo);裸 --jq 列出结果的顶层键").argParser((value, previous = []) => [...previous, value]).default([])).option("--extract-keys", "列出本次结果 artifact 的顶层键与体积(省掉一次 artifact get --keys)").option("--max-output-bytes <bytes>", "最终 stdout 信封预算;默认 65536(投影/--jq 之后再判);读面 diet(~2048) 独立外置 detailArtifactId,抬高本参数不会把已 diet 的大表重新内联;artifact get 内联上限 8192").option("--request-id <requestId>").option("--timeout-ms <milliseconds>").option("--wait <milliseconds>").option("--dry-run").addHelpText("after", `\n${onboardingHelp()}\n`).action(async (tool, options) => {
|
|
2163
2169
|
const rest = [tool, ...(options.env ? ["--env", options.env] : []), ...(options.environment ? ["--environment", options.environment] : []), ...(options.args !== undefined ? ["--args", options.args] : []), ...(options.argsFile !== undefined ? ["--args-file", options.argsFile] : []), ...(options.argsList !== undefined ? ["--args-list", options.argsList] : []), ...(options.preflight ? ["--preflight"] : []), ...(Array.isArray(options.extract) ? options.extract.flatMap((path) => ["--extract", path]) : []), ...(Array.isArray(options.jq) ? options.jq.flatMap((path) => path === true ? ["--jq"] : ["--jq", path]) : []), ...(options.extractKeys ? ["--extract-keys"] : []),
|
|
2164
2170
|
...(options.keys ? ["--keys"] : []), ...(options.maxOutputBytes !== undefined ? ["--max-output-bytes", options.maxOutputBytes] : []), ...(options.requestId ? ["--request-id", options.requestId] : []), ...(options.timeoutMs !== undefined ? ["--timeout-ms", options.timeoutMs] : []), ...(options.wait !== undefined ? ["--wait", options.wait] : []), ...(options.dryRun ? ["--dry-run"] : [])];
|
|
2165
2171
|
commandExitCode = await cmdCall(new ConfigStore(defaultPaths()), rest);
|
|
@@ -2405,15 +2411,21 @@ async function main() {
|
|
|
2405
2411
|
script: { lines: lines.length, skipped: parsed.skipped.length },
|
|
2406
2412
|
}, 0);
|
|
2407
2413
|
});
|
|
2408
|
-
const rollbackCommand = command.command("rollback").description("配置回滚(git-revert 语义):list 列写时间线 / plan 预览恢复(diff+后续写覆盖警告)/ apply 执行恢复——恢复走完整写闸门并记 journal
|
|
2414
|
+
const rollbackCommand = command.command("rollback").description("配置回滚(git-revert 语义):list 列写时间线 / plan 预览恢复(diff+后续写覆盖警告)/ apply 执行恢复——恢复走完整写闸门并记 journal(回滚可再回滚)。apply 支持 --wait <毫秒> 有界排队;list/plan 为只读面不接写锁。接入全部配置写面(workflow/规则/数据规则/视图/表单草稿/在线JS/字典/字段/模型/应用/报表);publish 类生命周期推进不可逆(plan 会说明);journal 与 before 快照为本机边界(不跨机器)");
|
|
2409
2415
|
rollbackCommand.command("list").option("--env <environment>").option("--environment <environment>").option("--target <substr>").option("--tool <name>").option("--limit <n>").action(async (options) => {
|
|
2410
2416
|
commandExitCode = await withTransport(options, "rollback list", (transport) => cmdRollbackList({ environment: options.env ?? options.environment, target: options.target, tool: options.tool, limit: options.limit }, transport));
|
|
2411
2417
|
});
|
|
2412
2418
|
rollbackCommand.command("plan <ref>").option("--env <environment>").option("--environment <environment>").action(async (ref, options) => {
|
|
2413
2419
|
commandExitCode = await withTransport(options, "rollback plan", (transport) => cmdRollbackPlan(ref, { environment: options.env ?? options.environment }, transport));
|
|
2414
2420
|
});
|
|
2415
|
-
rollbackCommand.command("apply <ref>").option("--env <environment>").option("--environment <environment>").option("--confirm-production").action(async (ref, options) => {
|
|
2416
|
-
|
|
2421
|
+
rollbackCommand.command("apply <ref>").option("--env <environment>").option("--environment <environment>").option("--confirm-production").option("--wait <milliseconds>", "写锁冲突时的有界排队毫秒数(默认 0=立刻拒绝)").action(async (ref, options) => {
|
|
2422
|
+
const waitMs = options.wait === undefined ? 0 : Number(options.wait);
|
|
2423
|
+
if (!Number.isSafeInteger(waitMs) || waitMs < 0 || waitMs > 600_000) {
|
|
2424
|
+
console.error("--wait must be an integer number of milliseconds from 0 to 600000");
|
|
2425
|
+
commandExitCode = CLI_EXIT_CODES.invalidInput;
|
|
2426
|
+
return;
|
|
2427
|
+
}
|
|
2428
|
+
commandExitCode = await withTransport(options, "rollback apply", (transport) => cmdRollbackApply(ref, { environment: options.env ?? options.environment, confirmProduction: options.confirmProduction === true, waitMs }, transport));
|
|
2417
2429
|
});
|
|
2418
2430
|
const artifactCommand = command.command("artifact");
|
|
2419
2431
|
artifactCommand.command("prune").option("--older-than <duration>", "删除超过该时长的读回产物(如 7d/12h/30m)").option("--max-bytes <bytes>", "产物目录预算;超出时按最旧优先删除").option("--json").action((options) => {
|
package/dist/compute.js
CHANGED
|
@@ -189,7 +189,7 @@ export async function cmdCompute(args) {
|
|
|
189
189
|
let result;
|
|
190
190
|
let exitCode;
|
|
191
191
|
try {
|
|
192
|
-
({ result, exitCode } = await runToolCall("datarule.create", { args: createArgs, requestId: request, environment }));
|
|
192
|
+
({ result, exitCode } = await runToolCall("datarule.create", { args: createArgs, requestId: request, environment, print: () => undefined }));
|
|
193
193
|
}
|
|
194
194
|
catch (error) {
|
|
195
195
|
const message = error instanceof Error ? error.message : String(error);
|
package/dist/field-change.js
CHANGED
|
@@ -59,7 +59,12 @@ export function prepareFieldChange(input) {
|
|
|
59
59
|
assertFormConditions(raw);
|
|
60
60
|
const options = ensureRelationDisplayFieldOptions(propertyType, raw);
|
|
61
61
|
const displayCode = relationDisplayFieldCode(propertyType, options);
|
|
62
|
-
|
|
62
|
+
const optionRelationTarget = typeof options.schemaCode === "string" ? options.schemaCode.trim() : "";
|
|
63
|
+
const normalizedRelation = (propertyType === FIELD_PROPERTY_TYPES.WORK_SHEET || propertyType === FIELD_PROPERTY_TYPES.MULT_WORK_SHEET) &&
|
|
64
|
+
optionRelationTarget.length > 0 && !(typeof input.relativeCode === "string" && input.relativeCode.trim().length > 0)
|
|
65
|
+
? { relationTarget: "options.schemaCode→relativeCode" }
|
|
66
|
+
: undefined;
|
|
67
|
+
return { propertyType, isChildTable, options, ...(displayCode ? { displayCode } : {}), ...(normalizedRelation ? { normalized: normalizedRelation } : {}) };
|
|
63
68
|
}
|
|
64
69
|
/** The single platform wire body for a field create. */
|
|
65
70
|
export function fieldChangeWireBody(input, prepared) {
|
|
@@ -74,6 +79,7 @@ export function fieldChangeWireBody(input, prepared) {
|
|
|
74
79
|
? { defaultValue: null, subSchema: prepared.subSchema }
|
|
75
80
|
: { defaultValue: "", ...(prepared.displayCode ? { relativePropertyCode: prepared.displayCode } : {}), options: JSON.stringify(prepared.options) }),
|
|
76
81
|
...(input.relativeCode ? { relativeCode: input.relativeCode } : {}),
|
|
82
|
+
...(!input.relativeCode && prepared.normalized ? { relativeCode: String(prepared.options.schemaCode) } : {}),
|
|
77
83
|
};
|
|
78
84
|
}
|
|
79
85
|
/**
|
package/dist/field-families.js
CHANGED
|
@@ -64,7 +64,7 @@ export function relationDisplayFieldCode(type, options) {
|
|
|
64
64
|
const displayField = options.displayField;
|
|
65
65
|
return typeof displayField === "string" && displayField.trim() ? displayField.trim() : undefined;
|
|
66
66
|
}
|
|
67
|
-
export const choicesArg = z.array(z.string()).optional().describe('allowed values for text-choice families
|
|
67
|
+
export const choicesArg = z.array(z.string()).optional().describe('allowed values for text-choice families, e.g. ["正常","异常","待复检"]。注意:SELECTION(5) 是人员/部门混合选择器,不接受 choices;文本单选族随版本不同,拒绝信息会列当前 live 集');
|
|
68
68
|
function jsonString() {
|
|
69
69
|
return z.string().refine((value) => {
|
|
70
70
|
if (value === "")
|
|
@@ -270,6 +270,13 @@ export const FIELD_TYPE_SYNONYMS = Object.freeze({
|
|
|
270
270
|
});
|
|
271
271
|
/** A "did you mean" hint for a family name that is actually a Chinese concept. */
|
|
272
272
|
export function fieldTypeHint(type) {
|
|
273
|
+
const commonAliases = {
|
|
274
|
+
SINGLE_TEXT: "SHORT_TEXT",
|
|
275
|
+
NUMERIC: "NUMERICAL",
|
|
276
|
+
};
|
|
277
|
+
const alias = commonAliases[type.toUpperCase()];
|
|
278
|
+
if (alias)
|
|
279
|
+
return `「${type}」不是族名;你要找的可能是 ${alias}`;
|
|
273
280
|
for (const [concept, family] of Object.entries(FIELD_TYPE_SYNONYMS))
|
|
274
281
|
if (type.includes(concept) || concept.includes(type))
|
|
275
282
|
return `「${type}」看起来是概念名而非族名;最接近的是 ${family}`;
|
|
@@ -281,6 +288,63 @@ export function fieldTypeHint(type) {
|
|
|
281
288
|
return `「${type}」不是族名;你要找的可能是 ${closest}`;
|
|
282
289
|
return undefined;
|
|
283
290
|
}
|
|
291
|
+
/**
|
|
292
|
+
* Validate a model-relation target before bizproperty/create. The platform
|
|
293
|
+
* accepts the row and only fails at model publish (300010), which leaves the
|
|
294
|
+
* whole model poisoned. Callers supply the already-read model list so this
|
|
295
|
+
* helper remains usable by both field entry points without owning transport.
|
|
296
|
+
*/
|
|
297
|
+
export function assertRelationTargetExists(args, candidates) {
|
|
298
|
+
const raw = args.propertyType;
|
|
299
|
+
const type = typeof raw === "number" && Number.isInteger(raw)
|
|
300
|
+
? raw
|
|
301
|
+
: typeof raw === "string" && raw in FIELD_PROPERTY_TYPES ? FIELD_PROPERTY_TYPES[raw] : undefined;
|
|
302
|
+
if (type !== FIELD_PROPERTY_TYPES.WORK_SHEET && type !== FIELD_PROPERTY_TYPES.MULT_WORK_SHEET)
|
|
303
|
+
return;
|
|
304
|
+
const optionsCode = typeof args.options?.schemaCode === "string" ? args.options.schemaCode.trim() : "";
|
|
305
|
+
const relativeCode = typeof args.relativeCode === "string" ? args.relativeCode.trim() : "";
|
|
306
|
+
const target = optionsCode || relativeCode;
|
|
307
|
+
// Some offline/fake transports do not expose a model-list face. An empty
|
|
308
|
+
// candidate set is therefore "unknown", not proof of absence; live model
|
|
309
|
+
// lists are non-empty and get the strict pre-write gate.
|
|
310
|
+
if (!target || candidates.length === 0 || candidates.includes(target))
|
|
311
|
+
return;
|
|
312
|
+
const visibleCandidates = [...candidates].sort().slice(0, 5);
|
|
313
|
+
throw new ToolRefusalError({
|
|
314
|
+
status: "refused",
|
|
315
|
+
blocker: "relation-target-not-found",
|
|
316
|
+
relationTarget: target,
|
|
317
|
+
candidates: visibleCandidates,
|
|
318
|
+
message: `关联字段 ${String(args.code ?? "(未命名)")} 的关联目标 ${target} 不存在;已在写入前拒绝。可选模型:${visibleCandidates.length > 0 ? visibleCandidates.join("、") : "(无)"}`,
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
/** Construct the field-specific explanation when an older platform leaks 300010. */
|
|
322
|
+
export function relationPublishFailureMessage(fields, candidates) {
|
|
323
|
+
for (const field of fields) {
|
|
324
|
+
let options;
|
|
325
|
+
if (field.options && typeof field.options === "object" && !Array.isArray(field.options))
|
|
326
|
+
options = field.options;
|
|
327
|
+
else if (typeof field.options === "string") {
|
|
328
|
+
try {
|
|
329
|
+
const parsed = JSON.parse(field.options);
|
|
330
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed))
|
|
331
|
+
options = parsed;
|
|
332
|
+
}
|
|
333
|
+
catch { /* malformed options will be reported by the platform */ }
|
|
334
|
+
}
|
|
335
|
+
const raw = field.propertyType ?? field.type;
|
|
336
|
+
const type = typeof raw === "number" ? raw : typeof raw === "string" && raw in FIELD_PROPERTY_TYPES ? FIELD_PROPERTY_TYPES[raw] : undefined;
|
|
337
|
+
if (type !== FIELD_PROPERTY_TYPES.WORK_SHEET && type !== FIELD_PROPERTY_TYPES.MULT_WORK_SHEET)
|
|
338
|
+
continue;
|
|
339
|
+
const target = typeof options?.schemaCode === "string" ? options.schemaCode.trim() : typeof field.relativeCode === "string" ? field.relativeCode.trim() : "";
|
|
340
|
+
if (typeof options?.schemaCode === "string" && options.schemaCode.trim() && (!field.relativeCode || String(field.relativeCode).trim() === "")) {
|
|
341
|
+
return `字段 ${String(field.code ?? "(未知)")} 的关联目标 ${options.schemaCode} 仅写在 options.schemaCode;发布面不解析该位置(平台原始 errcode 300010「数据模型编码不能为空!」)。请重建/归一化为顶层 relativeCode=${options.schemaCode}`;
|
|
342
|
+
}
|
|
343
|
+
if (target && candidates.length > 0 && !candidates.includes(target))
|
|
344
|
+
return `字段 ${String(field.code ?? "(未知)")} 的关联目标 ${target} 不存在(平台原始 errcode 300010「数据模型编码不能为空!」);可选模型:${candidates.slice(0, 5).join("、")}`;
|
|
345
|
+
}
|
|
346
|
+
return undefined;
|
|
347
|
+
}
|
|
284
348
|
/** Case/underscore-insensitive closest match over the family list (shared helper, see closest-match.ts). */
|
|
285
349
|
function closestFamilyName(type) {
|
|
286
350
|
return closestMatch(type, Object.keys(FIELD_PROPERTY_TYPES));
|
|
@@ -830,6 +894,43 @@ export function assertFieldFamilyAllowed(args) {
|
|
|
830
894
|
// works if the preflight gate is the same gate the write uses.
|
|
831
895
|
assertNumericalScale(normalized, options);
|
|
832
896
|
}
|
|
897
|
+
/**
|
|
898
|
+
* Reject the platform's accept-then-drop org-selector+choices shapes before write.
|
|
899
|
+
*
|
|
900
|
+
* 三个组织选择器族同语义(SELECTION 混合 / STAFF_SELECTOR 人员 / DEPARTMENT_SELECTOR
|
|
901
|
+
* 部门):choices 都会被平台静默丢弃,字段建成后写任何 choices 值都 50000 无明细。
|
|
902
|
+
* r37(2026-11-07)之前 SELECTION 走的是 execute 路径的 capability-out-of-coverage
|
|
903
|
+
* (citation.reason 才说不接受 choices),STAFF/DEPARTMENT 走 live 集拒绝——同一语义
|
|
904
|
+
* 三个族两种 blocker。统一收敛到这里:同语义 = 同 blocker(含 options.choices 形态,
|
|
905
|
+
* 那是 F14 真机撞出来的:字段落库、field.list 还回显 choices、运行面才炸)。
|
|
906
|
+
*/
|
|
907
|
+
const ORG_SELECTOR_TYPES = new Set([
|
|
908
|
+
FIELD_PROPERTY_TYPES.SELECTION,
|
|
909
|
+
FIELD_PROPERTY_TYPES.STAFF_SELECTOR,
|
|
910
|
+
FIELD_PROPERTY_TYPES.DEPARTMENT_SELECTOR,
|
|
911
|
+
]);
|
|
912
|
+
export function assertSelectionChoicesAllowed(args) {
|
|
913
|
+
const options = args.options;
|
|
914
|
+
const hasTopLevelChoices = args.choices !== undefined;
|
|
915
|
+
const hasOptionsChoices = options !== null && typeof options === "object" && !Array.isArray(options)
|
|
916
|
+
&& Object.prototype.hasOwnProperty.call(options, "choices");
|
|
917
|
+
if (!hasTopLevelChoices && !hasOptionsChoices)
|
|
918
|
+
return;
|
|
919
|
+
const raw = args.propertyType;
|
|
920
|
+
const type = typeof raw === "number" && Number.isInteger(raw)
|
|
921
|
+
? raw
|
|
922
|
+
: typeof raw === "string" && raw in FIELD_PROPERTY_TYPES
|
|
923
|
+
? FIELD_PROPERTY_TYPES[raw]
|
|
924
|
+
: undefined;
|
|
925
|
+
if (type === undefined || !ORG_SELECTOR_TYPES.has(type))
|
|
926
|
+
return;
|
|
927
|
+
const family = typeof raw === "string" ? raw : String(type);
|
|
928
|
+
throw new ToolRefusalError({
|
|
929
|
+
status: "refused",
|
|
930
|
+
blocker: "field-choices-family-mismatch",
|
|
931
|
+
message: `${family} 是组织选择器(人员/部门),choices 会被平台静默丢弃;字段虽会建成,但写入任何 choices 值都会返回 50000 且无明细。选人/部门请使用不带 choices 的本族字段(值走引用);文本单选族随版本不同,请先看 omc describe field.create --param propertyType,拒绝信息会列当前 live 集。`,
|
|
932
|
+
});
|
|
933
|
+
}
|
|
833
934
|
function refuseFieldOptions(reason, message) {
|
|
834
935
|
const citation = {
|
|
835
936
|
code: "capability-out-of-coverage",
|