@stalfh233/omc-cli 0.4.74 → 0.4.76
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/coverage/m1-coverage-manifest-v1.json +9 -3
- package/dist/artifacts.js +2 -2
- package/dist/capability-overview.js +12 -3
- package/dist/cli.js +105 -39
- package/dist/command-transport.js +1 -1
- package/dist/compute.js +2 -1
- package/dist/discovery.js +18 -0
- package/dist/endpoints.js +24 -0
- package/dist/form-default.js +97 -10
- package/dist/form-layout.js +25 -2
- package/dist/form-support.js +361 -10
- package/dist/human-summary.js +4 -0
- package/dist/lanes/businessrule.js +7 -0
- package/dist/lanes/datarule.js +33 -9
- package/dist/lanes/form-contract.js +14 -4
- package/dist/lanes/form-coverage.js +2 -1
- package/dist/lanes/form.js +44 -1
- package/dist/lanes/organization.js +37 -3
- package/dist/lanes/rule-lifecycle-contract.js +17 -0
- package/dist/lanes/rule-lifecycle-coverage.js +2 -0
- package/dist/lanes/rule-lifecycle.js +103 -19
- package/dist/lanes/workflow-contract.js +57 -10
- package/dist/lanes/workflow-coverage.js +4 -0
- package/dist/lanes/workflow.js +458 -40
- package/dist/path-projection.js +129 -0
- package/dist/rollback-spec.js +32 -1
- package/dist/usage-log.js +11 -2
- package/dist/write.js +9 -3
- package/package.json +3 -3
- package/skills/omc-business-configuration.md +20 -2
- package/skills/omc-capabilities.md +8 -7
- package/skills/omc-write-hazards.md +4 -4
|
@@ -337,6 +337,7 @@
|
|
|
337
337
|
"workflow.update",
|
|
338
338
|
"workflow.publish",
|
|
339
339
|
"workflow.bindRule",
|
|
340
|
+
"workflow.setButtons",
|
|
340
341
|
"workflow.delete",
|
|
341
342
|
"configure_workflow",
|
|
342
343
|
"workflow.validate",
|
|
@@ -363,6 +364,7 @@
|
|
|
363
364
|
],
|
|
364
365
|
"scopes": [
|
|
365
366
|
"greenfield workflow authoring",
|
|
367
|
+
"workflow node form-button checkboxes",
|
|
366
368
|
"organization role member read (role/users)",
|
|
367
369
|
"organization role member management scope read (role/get_ouscope)",
|
|
368
370
|
"organization role group create (role/add_role_group)",
|
|
@@ -496,10 +498,12 @@
|
|
|
496
498
|
"rule.disable",
|
|
497
499
|
"rule.delete",
|
|
498
500
|
"rule.disableMany",
|
|
501
|
+
"rule.run",
|
|
499
502
|
"configure_rule"
|
|
500
503
|
],
|
|
501
504
|
"scopes": [
|
|
502
|
-
"enable invoke disable delete"
|
|
505
|
+
"enable invoke disable delete",
|
|
506
|
+
"manual rule execution (button trigger)"
|
|
503
507
|
]
|
|
504
508
|
},
|
|
505
509
|
{
|
|
@@ -856,11 +860,13 @@
|
|
|
856
860
|
"form.get",
|
|
857
861
|
"form.draft",
|
|
858
862
|
"form.publish",
|
|
859
|
-
"configure_form"
|
|
863
|
+
"configure_form",
|
|
864
|
+
"form.runButton"
|
|
860
865
|
],
|
|
861
866
|
"scopes": [
|
|
862
867
|
"publishedHtmlJson authoritative readback",
|
|
863
|
-
"draft publish publishedHtmlJson readback"
|
|
868
|
+
"draft publish publishedHtmlJson readback",
|
|
869
|
+
"form toolbar button execution"
|
|
864
870
|
]
|
|
865
871
|
},
|
|
866
872
|
{
|
package/dist/artifacts.js
CHANGED
|
@@ -83,7 +83,7 @@ export function isWithinRoot(root, candidate) {
|
|
|
83
83
|
export function assertSafeOutputPath(root, candidate, label) {
|
|
84
84
|
const normalized = resolve(root, candidate);
|
|
85
85
|
if (!isWithinRoot(root, normalized))
|
|
86
|
-
throw new Error(`${label} must be inside the configuration root
|
|
86
|
+
throw new Error(`${label} must be inside the configuration root(配置根 ${resolve(root)};本次解析为 ${normalized})`);
|
|
87
87
|
const parent = resolve(normalized, "..");
|
|
88
88
|
if (!existsSync(parent))
|
|
89
89
|
throw new Error(`${label} parent directory does not exist`);
|
|
@@ -101,7 +101,7 @@ export function assertSafeInputPath(root, candidate, label) {
|
|
|
101
101
|
const resolvedRoot = resolve(root);
|
|
102
102
|
const normalized = resolve(root, candidate);
|
|
103
103
|
if (!isWithinRoot(root, normalized))
|
|
104
|
-
throw new Error(`${label} must be inside the configuration root
|
|
104
|
+
throw new Error(`${label} must be inside the configuration root(配置根 ${resolvedRoot};本次解析为 ${normalized})`);
|
|
105
105
|
let current = normalized;
|
|
106
106
|
while (current !== resolvedRoot) {
|
|
107
107
|
try {
|
|
@@ -191,13 +191,22 @@ export const CAPABILITY_CUES = Object.freeze({
|
|
|
191
191
|
model: ["模型", "对象", "主数据", "物理删除", "汇总表", "统计表", "汇总对象", "月度汇总表", "汇总模型"],
|
|
192
192
|
field: ["字段", "属性", "子表", "明细", "下拉", "单选", "多选", "附件", "人员", "部门", "日期", "数值", "文本", "关联", "公式", "计算", "计算字段", "求和", "汇总", "合计", "回主表", "超链接", "地址", "表单层", "必填", "只读", "条件必填", "恒必填", "条件只读", "恒只读", "表单必填", "表单只读"],
|
|
193
193
|
form: ["表单", "页面", "布局", "控件", "只读", "显示"],
|
|
194
|
-
|
|
195
|
-
|
|
194
|
+
// r10 (2026-10-22, issue §1.4): an ask for a FORM toolbar button was
|
|
195
|
+
// answered "CLI 无按钮能力" — the capability exists (`listview.configure`
|
|
196
|
+
// `buttons` with useLocation FORM + bindAction SCRIPT). Cue the compound
|
|
197
|
+
// phrases so they outweigh businessrule's generic "按钮".
|
|
198
|
+
listview: ["列表", "视图", "查询列", "筛选", "表头", "甘特", "表单按钮", "工具栏按钮", "列表按钮", "行级按钮", "自定义按钮", "工具栏"],
|
|
199
|
+
// r10 (2026-10-22): "计划日期不能早于今天" routed to `field` (it matched
|
|
200
|
+
// 日期) because no cue named the VALIDATION intent, so the agent went to the
|
|
201
|
+
// rule graph and concluded "the platform has no today variable" — wrong, and
|
|
202
|
+
// the form designer's 提交校验 (datarule type1/checkType2) is the real face.
|
|
203
|
+
datarule: ["字段级规则", "控件规则", "校验规则", "计算拼接", "显示规则", "规则", "type1", "type2", "type3",
|
|
204
|
+
"不早于", "不能早于", "不晚于", "不能晚于", "早于今天", "晚于今天", "日期校验", "提交校验", "校验不通过", "必填校验", "正则校验", "重复校验", "长度校验"],
|
|
196
205
|
// NOTE: bare "自动" was a cue (round 10 bug: "自动识别"/"自动发邮件" matched
|
|
197
206
|
// businessrule and routed an UNSUPPORTED ask onto the write ladder). A business
|
|
198
207
|
// rule is named by 业务规则/触发器/规则图, not by "automatic".
|
|
199
208
|
businessrule: ["业务规则", "触发器", "规则图", "校验", "事件", "按钮", "定时任务", "定时触发", "定时器", "每天", "每日", "周期执行", "调度", "SCHEDULER", "TIMING_TASK"],
|
|
200
|
-
"rule-lifecycle": ["启用规则", "停用规则", "删除规则", "规则启停"],
|
|
209
|
+
"rule-lifecycle": ["启用规则", "停用规则", "删除规则", "规则启停", "触发规则", "执行规则", "跑规则", "按钮规则", "规则验收"],
|
|
201
210
|
workflow: ["流程", "工作流", "审批", "节点", "待办", "流转", "参与人", "审批人", "角色", "部门负责人", "审批角色"],
|
|
202
211
|
onlinejs: ["在线js", "脚本", "联动", "onload", "onvalidate"],
|
|
203
212
|
bizservice: ["集成", "接口", "服务", "回调", "对接", "外部系统", "datasql", "restful"],
|
package/dist/cli.js
CHANGED
|
@@ -17,7 +17,8 @@ import { configureOutput, humanSummary, stripOutputFlags, writeEnvelope } from "
|
|
|
17
17
|
import { beginUsage, completeUsage, recordUsageOutput, usageReport } from "./usage-log.js";
|
|
18
18
|
import { artifactFetchHint, artifactRoot, boundResult, emitInvalid, emitResult, emitTransportResult, outputBudget, readArtifact, withTransport, } from "./command-transport.js";
|
|
19
19
|
import { CLI_DEFAULT_OUTPUT_BYTES, assertSafeOutputPath, pruneArtifacts } from "./artifacts.js";
|
|
20
|
-
import {
|
|
20
|
+
import { projectJsonPath, projectResultExtract } from "./path-projection.js";
|
|
21
|
+
import { basename, join, resolve } from "node:path";
|
|
21
22
|
import { buildToolRegistry } from "./tools.js";
|
|
22
23
|
import { acquireWriteLock } from "./write-lock.js";
|
|
23
24
|
import { cmdCompute } from "./compute.js";
|
|
@@ -274,12 +275,19 @@ async function cmdCall(store, args) {
|
|
|
274
275
|
const positional = args.filter((token, index) => !token.startsWith("--") && (index === 0 || !args[index - 1]?.startsWith("--env") && !args[index - 1]?.startsWith("--environment") && !args[index - 1]?.startsWith("--args")));
|
|
275
276
|
const toolName = positional[0];
|
|
276
277
|
if (!toolName || toolName === "help" || args.includes("--help")) {
|
|
277
|
-
console.error("用法:omc call <tool> [--env <环境>] [--args '<json>' | --args - (stdin) | --args-file <path>]");
|
|
278
|
+
console.error("用法:omc call <tool> [--env <环境>] [--args '<json>' | --args - (stdin) | --args-file <配置根内路径>] [--extract <path>]... [--extract-keys]");
|
|
278
279
|
console.error("写入:由环境 writable 决定(只读环境拒写);生产环境的写必须在 args 里带 confirmProduction:true。");
|
|
279
280
|
return emit(toolName && toolName !== "help" ? "error" : "ok", {}, toolName && toolName !== "help" ? CLI_EXIT_CODES.invalidInput : 0);
|
|
280
281
|
}
|
|
281
282
|
const environment = flag("--env") ?? flag("--environment");
|
|
282
283
|
const requestValue = flag("--request-id");
|
|
284
|
+
// 1.2 of issue 2026-10-23: read faces answer {detailArtifactId, sha256} and the
|
|
285
|
+
// caller then needs a SECOND call to see one value (16 two-step chains in one
|
|
286
|
+
// session; 15 more were pure --keys probes). `--extract <path>` (repeatable)
|
|
287
|
+
// resolves the path inside the artifact of THIS result and returns only the
|
|
288
|
+
// value; `--extract-keys` lists the artifact's top-level keys with sizes.
|
|
289
|
+
const extractPaths = argvValues(args, "--extract");
|
|
290
|
+
const extractKeys = args.includes("--extract-keys");
|
|
283
291
|
const dryRun = args.includes("--dry-run");
|
|
284
292
|
const waitValue = flag("--wait");
|
|
285
293
|
let waitMs = 0;
|
|
@@ -327,8 +335,26 @@ async function cmdCall(store, args) {
|
|
|
327
335
|
validateAgentPaths(parsedArgs);
|
|
328
336
|
}
|
|
329
337
|
catch (error) {
|
|
330
|
-
|
|
331
|
-
|
|
338
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
339
|
+
// r10 F9: an out-of-root `--args-file` was reported under "--args 必须是
|
|
340
|
+
// 合法 JSON", which points the reader at the wrong flag. Name the real
|
|
341
|
+
// cause (and the configuration root) when the failure came from the file.
|
|
342
|
+
const configRoot = resolve(process.env.OMC_CONFIG_HOME ?? process.cwd());
|
|
343
|
+
// 1.1 of issue 2026-10-23: the old hint told the caller to write a TEMP file
|
|
344
|
+
// while the validator only accepts paths under the config root — the two
|
|
345
|
+
// instructions contradicted each other and neither named the root. Offer a
|
|
346
|
+
// single executable recipe: an absolute path inside the root.
|
|
347
|
+
const outOfRoot = /must be inside the configuration root/.test(detail);
|
|
348
|
+
const message = outOfRoot
|
|
349
|
+
? `--args-file 越界:${detail}。可执行做法:把参数文件放到 ${configRoot} 之下(如 ${join(configRoot, "args.json")}),再用 --args-file 引用;不要把参数文件放在系统临时目录(配置根之外一律拒绝)`
|
|
350
|
+
: `--args 必须是合法 JSON:${detail}。若问题在引号转义(Windows PowerShell),改用 --args-file <配置根内的路径>`;
|
|
351
|
+
console.error(message);
|
|
352
|
+
// The MACHINE envelope must carry the same actionable text: agents read
|
|
353
|
+
// stdout, and the old envelope only repeated the English detail.
|
|
354
|
+
// Keep the REJECTED path on stderr only: the machine envelope must not leak
|
|
355
|
+
// host layout (ticket 103), so it carries the root plus the bare file name.
|
|
356
|
+
const rejectedName = typeof argsFile === "string" ? basename(resolve(configRoot, argsFile)) : "";
|
|
357
|
+
writeEnvelope(envelope("error", {}, request, { code: "invalid-input", message: outOfRoot ? `--args-file 必须位于配置根内(配置根 ${configRoot}${rejectedName ? `;本次文件 ${rejectedName}` : ""})` : detail, retryable: false, ...(outOfRoot ? { configurationRoot: configRoot } : {}) }));
|
|
332
358
|
return CLI_EXIT_CODES.invalidInput;
|
|
333
359
|
}
|
|
334
360
|
const registry = buildToolRegistry();
|
|
@@ -397,9 +423,26 @@ async function cmdCall(store, args) {
|
|
|
397
423
|
await lock.release();
|
|
398
424
|
}
|
|
399
425
|
}
|
|
426
|
+
if (extractPaths.length > 0 || extractKeys) {
|
|
427
|
+
const projected = projectResultExtract(result, extractPaths, extractKeys);
|
|
428
|
+
if (projected !== undefined)
|
|
429
|
+
result = projected;
|
|
430
|
+
}
|
|
400
431
|
const classified = classifyResult(result);
|
|
401
432
|
return emit(classified.status, result, classified.exitCode);
|
|
402
433
|
}
|
|
434
|
+
/** Collect every value of a repeatable flag (`--extract a --extract b`). */
|
|
435
|
+
function argvValues(args, name) {
|
|
436
|
+
const values = [];
|
|
437
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
438
|
+
if (args[index] !== name)
|
|
439
|
+
continue;
|
|
440
|
+
const value = args[index + 1];
|
|
441
|
+
if (value !== undefined && !value.startsWith("--"))
|
|
442
|
+
values.push(value);
|
|
443
|
+
}
|
|
444
|
+
return values;
|
|
445
|
+
}
|
|
403
446
|
function resolvedDefaultEnvironment(store) {
|
|
404
447
|
try {
|
|
405
448
|
return store.resolveEnvironment(undefined).name;
|
|
@@ -408,36 +451,6 @@ function resolvedDefaultEnvironment(store) {
|
|
|
408
451
|
return "default";
|
|
409
452
|
}
|
|
410
453
|
}
|
|
411
|
-
function projectJsonPath(value, path) {
|
|
412
|
-
const normalized = path
|
|
413
|
-
.trim()
|
|
414
|
-
.replace(/^[.\/]+/, "")
|
|
415
|
-
.replace(/\[(\d+)\]/g, ".$1")
|
|
416
|
-
.replace(/\[(?:"([^"]+)"|'([^']+)')\]/g, (_all, dq, sq) => `.${dq || sq}`)
|
|
417
|
-
.replace(/\.{2,}/g, ".");
|
|
418
|
-
const segments = normalized.split(/[./]/).filter((segment) => segment.length > 0);
|
|
419
|
-
let current = value;
|
|
420
|
-
for (const segment of segments) {
|
|
421
|
-
if (Array.isArray(current)) {
|
|
422
|
-
const index = Number(segment);
|
|
423
|
-
if (!Number.isInteger(index) || index < 0 || index >= current.length)
|
|
424
|
-
return { missed: true, at: segment, available: `数组长度 ${current.length}(下标 0-${Math.max(current.length - 1, 0)})` };
|
|
425
|
-
current = current[index];
|
|
426
|
-
}
|
|
427
|
-
else if (current && typeof current === "object") {
|
|
428
|
-
const record = current;
|
|
429
|
-
if (!Object.prototype.hasOwnProperty.call(record, segment)) {
|
|
430
|
-
const keys = Object.keys(record);
|
|
431
|
-
return { missed: true, at: segment, available: keys.length > 0 ? keys.slice(0, 12) : "(该层是空对象)" };
|
|
432
|
-
}
|
|
433
|
-
current = record[segment];
|
|
434
|
-
}
|
|
435
|
-
else {
|
|
436
|
-
return { missed: true, at: segment, available: `路径在第 ${segments.indexOf(segment) + 1} 段之前已到标量/undefined(前段类型 ${current === null ? "null" : typeof current})` };
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
return { missed: false, value: current };
|
|
440
|
-
}
|
|
441
454
|
function parseDurationMs(text) {
|
|
442
455
|
const match = /^\s*(\d+)\s*(ms|s|m|h|d|w)?\s*$/u.exec(text);
|
|
443
456
|
if (!match)
|
|
@@ -943,11 +956,47 @@ function onboardingHelp() {
|
|
|
943
956
|
return [
|
|
944
957
|
"首次使用、或不确定从哪开始:omc help omc-start-here(随包权威 topic 路线);被拒或状态异常:omc help omc-refusals。",
|
|
945
958
|
"目标→工具一步定位:omc next \"<你要做什么>\"(离线、只读,给能力/说明/首批命令;不确定会明说并回退到 omc-requirement-analysis)。",
|
|
959
|
+
"大结果取值一步到位:读面回 detailArtifactId 时,用 omc call <tool> … --extract <path>(可重复)或 --extract-keys,省掉再跑一次 omc artifact get。",
|
|
946
960
|
"工具发现:omc commands --json(含 capability id/lane/recipes;加 --compact 只回工具行、无目录散文)· omc capabilities(能力 id/lane/aliases/tools/依赖顺序)· omc capability <id> --executable(单能力完整执行包)。",
|
|
947
961
|
"单工具/契约:omc describe <tool>(schema+recipe+能力路由)· omc contract <lane>(wire 契约+能力路由);环境就绪:omc call discover_environment --env <env> --args '{}'。",
|
|
948
962
|
"写入两道门:对话层草稿确认(omc help omc-config-draft-review)+ 环境 writable 开关;生产环境每笔写需在 args 里带 confirmProduction:true。",
|
|
949
963
|
].join("\n");
|
|
950
964
|
}
|
|
965
|
+
/** Last command-level failure, so the no-envelope fallback can name the cause. */
|
|
966
|
+
let lastFailureDetail;
|
|
967
|
+
function failureDetail() {
|
|
968
|
+
return process.env.OMC_LAST_FAILURE ?? lastFailureDetail;
|
|
969
|
+
}
|
|
970
|
+
/**
|
|
971
|
+
* 1.5 of issue 2026-10-23: a `--jq` expression containing spaces/pipes arrives as
|
|
972
|
+
* multiple argv entries when the caller's shell splits it. Commander then
|
|
973
|
+
* answers "too many arguments for 'get'" — an arity complaint, not a quoting
|
|
974
|
+
* hint. Re-join the value before parsing.
|
|
975
|
+
*/
|
|
976
|
+
function joinSplitFlagValues(raw, flags) {
|
|
977
|
+
const out = [];
|
|
978
|
+
for (let index = 0; index < raw.length; index += 1) {
|
|
979
|
+
const token = raw[index];
|
|
980
|
+
if (!flags.includes(token)) {
|
|
981
|
+
out.push(token);
|
|
982
|
+
continue;
|
|
983
|
+
}
|
|
984
|
+
const value = raw[index + 1];
|
|
985
|
+
if (value === undefined) {
|
|
986
|
+
out.push(token);
|
|
987
|
+
continue;
|
|
988
|
+
}
|
|
989
|
+
const parts = [value];
|
|
990
|
+
let cursor = index + 2;
|
|
991
|
+
while (cursor < raw.length && !raw[cursor].startsWith("--")) {
|
|
992
|
+
parts.push(raw[cursor]);
|
|
993
|
+
cursor += 1;
|
|
994
|
+
}
|
|
995
|
+
out.push(token, parts.join(" "));
|
|
996
|
+
index = cursor - 1;
|
|
997
|
+
}
|
|
998
|
+
return out;
|
|
999
|
+
}
|
|
951
1000
|
async function main() {
|
|
952
1001
|
let commandExitCode = 0;
|
|
953
1002
|
beginUsage(argv);
|
|
@@ -1031,8 +1080,8 @@ async function main() {
|
|
|
1031
1080
|
commandExitCode = CLI_EXIT_CODES.ok;
|
|
1032
1081
|
});
|
|
1033
1082
|
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); });
|
|
1034
|
-
command.command("call <tool>").option("--env <environment>").option("--environment <environment>").option("--args <json>").option("--args-file <path>").option("--max-output-bytes <bytes>").option("--request-id <requestId>").option("--timeout-ms <milliseconds>").option("--wait <milliseconds>").option("--dry-run").addHelpText("after", `\n${onboardingHelp()}\n`).action(async (tool, options) => {
|
|
1035
|
-
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.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"] : [])];
|
|
1083
|
+
command.command("call <tool>").option("--env <environment>").option("--environment <environment>").option("--args <json>").option("--args-file <path>").addOption(new Option("--extract <path>", "在本次结果的 artifact 内取一个路径的值(可重复;省掉一次 artifact get)").argParser((value, previous = []) => [...previous, value]).default([])).option("--extract-keys", "列出本次结果 artifact 的顶层键与体积(省掉一次 artifact get --keys)").option("--max-output-bytes <bytes>").option("--request-id <requestId>").option("--timeout-ms <milliseconds>").option("--wait <milliseconds>").option("--dry-run").addHelpText("after", `\n${onboardingHelp()}\n`).action(async (tool, options) => {
|
|
1084
|
+
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] : []), ...(Array.isArray(options.extract) ? options.extract.flatMap((path) => ["--extract", path]) : []), ...(options.extractKeys ? ["--extract-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"] : [])];
|
|
1036
1085
|
commandExitCode = await cmdCall(new ConfigStore(defaultPaths()), rest);
|
|
1037
1086
|
});
|
|
1038
1087
|
command.command("compute").option("--schema <schemaCode>").option("--schema-code <schemaCode>").option("--target <propertyCode>").option("--property <propertyCode>").option("--formula <formula>").option("--name <name>").option("--env <environment>").option("--environment <environment>").option("--apply").addHelpText("after", "\n字段计算规则拼装器:公式 {字段} 引用 → datarule type2 完整形状(editorText/editorMark 自动生成)。默认 dry-run,--apply 写入。复杂场景(条件分支/跨模型)走 omc next 的 recipe 路径。\n").action(async (options) => {
|
|
@@ -1154,7 +1203,8 @@ async function main() {
|
|
|
1154
1203
|
intentCommand.command("readback <file>").option("--env <environment>").option("--max-output-bytes <bytes>").action(async (file, options) => {
|
|
1155
1204
|
commandExitCode = await withTransport(options, request, (transport) => cmdIntentReadback(file, options.env, transport));
|
|
1156
1205
|
});
|
|
1157
|
-
|
|
1206
|
+
// `--jq` expressions the shell split arrive as several tokens; re-join them.
|
|
1207
|
+
await command.parseAsync(joinSplitFlagValues(stripOutputFlags(argv), ["--jq"]));
|
|
1158
1208
|
return commandExitCode;
|
|
1159
1209
|
}
|
|
1160
1210
|
/**
|
|
@@ -1174,12 +1224,28 @@ const request = requestId();
|
|
|
1174
1224
|
main().then((code) => {
|
|
1175
1225
|
// Commands that own their stdout never grow a trailing envelope. `--version`/
|
|
1176
1226
|
// `-V` are here because their short-circuit already wrote the bare version.
|
|
1177
|
-
|
|
1227
|
+
// Composers (`form-default`, `compute`, `rule-when`, `approval-flow`) own their
|
|
1228
|
+
// stdout exactly like `call` does. Without them in this list every composer
|
|
1229
|
+
// printed its real envelope AND a trailing `{"ok":true,"result":{}}` — r10 read
|
|
1230
|
+
// that as a second, empty answer. `rollback` / `env` emit their own
|
|
1231
|
+
// envelope (or, for `env --json`, their own JSON) and were printing a second
|
|
1232
|
+
// empty envelope after it (live 2026-10-23: `omc rollback apply` answered two
|
|
1233
|
+
// lines, violating the one-envelope stdout contract).
|
|
1234
|
+
const ownsStdout = ["call", "script", "artifact", "commands", "describe", "contract", "capabilities", "capability", "next", "usage", "intent", "help", "form-default", "compute", "rule-when", "approval-flow", "rollback", "env", "--version", "-V"];
|
|
1235
|
+
if (ownsStdout.includes(argv[2] ?? "")) {
|
|
1178
1236
|
completeUsage(code);
|
|
1179
1237
|
finish(code);
|
|
1180
1238
|
return;
|
|
1181
1239
|
}
|
|
1182
|
-
|
|
1240
|
+
// 1.6 of issue 2026-10-23: a bare "command failed" gives no stage and no cause.
|
|
1241
|
+
writeEnvelope(envelope(code === 0 ? "ok" : "error", {}, request, code === 0
|
|
1242
|
+
? null
|
|
1243
|
+
: {
|
|
1244
|
+
code: "internal",
|
|
1245
|
+
message: `${argv[2] ?? "omc"} 以退出码 ${code} 结束但没有写出结果信封${failureDetail() ? `:${failureDetail()}` : "(没有捕获到具体异常;加 --verbose 复现,或用相同参数再跑一次看 stderr)"}`,
|
|
1246
|
+
retryable: false,
|
|
1247
|
+
...(failureDetail() ? { stage: "post-command" } : {}),
|
|
1248
|
+
}));
|
|
1183
1249
|
completeUsage(code);
|
|
1184
1250
|
finish(code);
|
|
1185
1251
|
}, (error) => {
|
|
@@ -82,7 +82,7 @@ export function artifactFetchHint(artifactId) {
|
|
|
82
82
|
// raw. `--summary` shows the key/size map so they fetch only what they need.
|
|
83
83
|
// Kept terse on purpose: this line rides EVERY write and every spilled read
|
|
84
84
|
// (2026-09-25 cost pass — the parenthetical prose was 80 B of the 133 B).
|
|
85
|
-
return `omc artifact get ${artifactId} --summary(再 --keys/--jq
|
|
85
|
+
return `omc artifact get ${artifactId} --summary(再 --keys/--jq);或原调用加 --extract <path> / --extract-keys 一次拿到`;
|
|
86
86
|
}
|
|
87
87
|
/** Replace local paths with a redaction marker (never leak the host layout). */
|
|
88
88
|
function sanitizePaths(value) {
|
package/dist/compute.js
CHANGED
|
@@ -74,9 +74,10 @@ export async function cmdCompute(args) {
|
|
|
74
74
|
}
|
|
75
75
|
catch (error) {
|
|
76
76
|
const message = error instanceof Error ? error.message : String(error);
|
|
77
|
+
process.env.OMC_LAST_FAILURE = `compute → datarule.create: ${message}`;
|
|
77
78
|
writeEnvelope(envelope("error", {}, request, {
|
|
78
79
|
code: "compute-write-failed",
|
|
79
|
-
message: `compute 调用 datarule.create
|
|
80
|
+
message: `compute 调用 datarule.create 失败(stage=datarule.create):${message}\n` +
|
|
80
81
|
"若 --schema 是**子表 code**(子表列计算),改用可跑样例直接发:omc describe datarule.create --example field-compute-child-column," +
|
|
81
82
|
"或 omc call datarule.create --args '<该样例 args>'(含 resultType/formulaType/expression 的嵌套 JSON)。",
|
|
82
83
|
retryable: false,
|
package/dist/discovery.js
CHANGED
|
@@ -200,11 +200,29 @@ export function describeTool(registry, name) {
|
|
|
200
200
|
// of a 133 KB task even though a matching recipe existed.
|
|
201
201
|
...(hasRecipe ? { exampleHint: `可运行参数用 --example <id>(~1–2KB);单参数用 --param <name>;仅当二者都缺形状才 --full` } : {}),
|
|
202
202
|
...(!hasRecipe ? { exampleHint: `无静态 recipe:required + params 已够常规调用;单参数说明用 --param <name>;仅当需要全部参数注释/拒绝形状才 --full(5–20KB)` } : {}),
|
|
203
|
+
// 1.7 of issue 2026-10-23: the parameter DOC for `subFields` was only
|
|
204
|
+
// reachable through --param/--full, i.e. discovering a param needed its
|
|
205
|
+
// name first. Name the documented params inline (cheap: names only) so one
|
|
206
|
+
// `describe` answers "does this tool take X, and what is X called".
|
|
207
|
+
...(documentedParamNames(tool).length > 0
|
|
208
|
+
? { paramDocs: { available: documentedParamNames(tool), read: `omc describe ${tool.name} --param <name>(单个参数注释,几百字节)` } }
|
|
209
|
+
: {}),
|
|
203
210
|
// Keep the cost salient at the decision point: agents reached for --full even
|
|
204
211
|
// when --example/--param existed (sandbox22: 8 --full calls, one 19 KB).
|
|
205
212
|
full: `omc describe ${tool.name} --full(5–20KB;先试 --example/--param)`,
|
|
206
213
|
};
|
|
207
214
|
}
|
|
215
|
+
/** Param names whose schema entry carries a `.describe()` (documented params). */
|
|
216
|
+
function documentedParamNames(tool) {
|
|
217
|
+
const shape = (tool.inputSchema ?? {});
|
|
218
|
+
const names = [];
|
|
219
|
+
for (const [key, value] of Object.entries(shape)) {
|
|
220
|
+
const description = value?.description;
|
|
221
|
+
if (typeof description === "string" && description.trim() !== "")
|
|
222
|
+
names.push(key);
|
|
223
|
+
}
|
|
224
|
+
return names.sort();
|
|
225
|
+
}
|
|
208
226
|
/** `omc describe <tool> --example <id>`: one recipe in full, by id. */
|
|
209
227
|
export function describeExample(registry, name, exampleId) {
|
|
210
228
|
const tool = registry.find((entry) => entry.name === name);
|
package/dist/endpoints.js
CHANGED
|
@@ -58,6 +58,30 @@ export const ENDPOINTS = {
|
|
|
58
58
|
businessRuleNodeRunMap: "/api/api/app/businessrule/node_run_map",
|
|
59
59
|
businessRuleEnable: "/api/api/app/businessrule/enable",
|
|
60
60
|
businessRuleDelete: "/api/api/app/businessrule/delete",
|
|
61
|
+
/**
|
|
62
|
+
* Manual execution of a CUSTOM-trigger rule (the list/form button path).
|
|
63
|
+
* Live 2026-10-23 (AI-Platform 1.1.25, Playwright capture of a
|
|
64
|
+
* `listview.configure` BUSINESS_RULE row button):
|
|
65
|
+
* POST /api/api/launcher/domain_service_runtime/<schemaCode>/<ruleCode>
|
|
66
|
+
* body {schemaCode, ids:[bizObjectId...], ruleCode, consolidation:false}
|
|
67
|
+
* → {errcode:0, errmsg:"Execution Completed", data:[<pre-execution bizObjects>], traceId}
|
|
68
|
+
* The response objects are the PRE-execution snapshot (the frontend sends
|
|
69
|
+
* reacquireDataFromDb:false), so effects must be read back separately.
|
|
70
|
+
*/
|
|
71
|
+
ruleRuntimeExecuteBase: "/api/api/launcher/domain_service_runtime",
|
|
72
|
+
/**
|
|
73
|
+
* Form-toolbar button execution — a DIFFERENT face from the list-button one.
|
|
74
|
+
* Live 2026-10-23 (1.1.25, Playwright capture of a `triggerType=FORM` button
|
|
75
|
+
* rendered on /form/detail):
|
|
76
|
+
* POST /api/api/runtime/form/executeCustomButton
|
|
77
|
+
* {bizButtonModel:<the h_biz_button row>, bizObjectList:[<full bizObject>],
|
|
78
|
+
* reacquireDataFromDb:true}
|
|
79
|
+
* The list button posts ids to the launcher path instead; the two are not
|
|
80
|
+
* interchangeable.
|
|
81
|
+
*/
|
|
82
|
+
runtimeFormExecuteCustomButton: "/api/api/runtime/form/executeCustomButton",
|
|
83
|
+
/** Designer's form-button inventory: form trigger infos + their BizButtonModels. */
|
|
84
|
+
systemGetCustomButtons: "/api/api/system/getCustomButtons",
|
|
61
85
|
dataRuleCreate: "/api/api/app/bizdatarule/create",
|
|
62
86
|
dataRuleList: "/api/api/app/bizdatarule/list",
|
|
63
87
|
dataRuleUpdate: "/api/api/app/bizdatarule/update",
|
package/dist/form-default.js
CHANGED
|
@@ -14,9 +14,14 @@
|
|
|
14
14
|
* 将要做的修改。
|
|
15
15
|
*/
|
|
16
16
|
import { runToolCall } from "./call.js";
|
|
17
|
+
import { readFileSync } from "node:fs";
|
|
18
|
+
import { readArtifact } from "./artifacts.js";
|
|
19
|
+
import { artifactRoot } from "./command-transport.js";
|
|
17
20
|
import { envelope, requestId, classifyResult, CLI_EXIT_CODES } from "./cli-contract.js";
|
|
18
21
|
import { writeEnvelope } from "./cli-output.js";
|
|
19
22
|
import { argvValue } from "./args.js";
|
|
23
|
+
/** Sub-calls must not print their own envelopes (the composer owns stdout). */
|
|
24
|
+
const silent = () => { };
|
|
20
25
|
/** 从 form.get 结果提取控件数组(draft 优先,published 兜底)。 */
|
|
21
26
|
function toControlList(value) {
|
|
22
27
|
if (Array.isArray(value))
|
|
@@ -35,6 +40,64 @@ function extractControls(formGet) {
|
|
|
35
40
|
return { controls: published, face: "published" };
|
|
36
41
|
return undefined;
|
|
37
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* `form.get` summarizes large JSON as `{length, sha256}` (token diet), and with
|
|
45
|
+
* `controls:true` even the control LISTS come back as `{keys, sha256}` — treating
|
|
46
|
+
* those as arrays yields "2 controls". The raw faces ride the detail artifact.
|
|
47
|
+
*
|
|
48
|
+
* r10 (2026-10-22) F1: `form-default --apply` cloned that摘要 as the draft, wrote
|
|
49
|
+
* a 2-control form, and `form.publish`'s degrade guard then refused its own
|
|
50
|
+
* output (`form-publish-degrade`, 7 identical failures).
|
|
51
|
+
*/
|
|
52
|
+
function readDetailForm(result) {
|
|
53
|
+
// In-process composers see `detailFile` (the tool's own artifact path); the CLI
|
|
54
|
+
// transport rewrites it to `detailArtifactId` before printing. Handle both.
|
|
55
|
+
const raw = (() => {
|
|
56
|
+
if (typeof result?.detailArtifactId === "string") {
|
|
57
|
+
try {
|
|
58
|
+
return readArtifact(artifactRoot(), result.detailArtifactId).content.toString("utf8");
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (typeof result?.detailFile === "string") {
|
|
65
|
+
try {
|
|
66
|
+
return readFileSync(result.detailFile, "utf8");
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return undefined;
|
|
73
|
+
})();
|
|
74
|
+
if (raw === undefined)
|
|
75
|
+
return undefined;
|
|
76
|
+
try {
|
|
77
|
+
const parsed = JSON.parse(raw);
|
|
78
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : undefined;
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
return undefined;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
function parseJsonColumn(value) {
|
|
85
|
+
if (typeof value !== "string")
|
|
86
|
+
return value;
|
|
87
|
+
try {
|
|
88
|
+
return JSON.parse(value);
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
return undefined;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/** The published control map `{ <controlKey>: {key,type,options} }`, raw. */
|
|
95
|
+
function controlMap(value) {
|
|
96
|
+
const parsed = parseJsonColumn(value);
|
|
97
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
98
|
+
return undefined;
|
|
99
|
+
return parsed;
|
|
100
|
+
}
|
|
38
101
|
export async function cmdFormDefault(args) {
|
|
39
102
|
const request = requestId();
|
|
40
103
|
const flag = (name) => argvValue(args, name);
|
|
@@ -51,7 +114,7 @@ export async function cmdFormDefault(args) {
|
|
|
51
114
|
if (!schema || !field || value === undefined)
|
|
52
115
|
return invalid("--schema/--field/--default 均必填");
|
|
53
116
|
// 1) 读控件(单控件投影,拿全对象含 options)
|
|
54
|
-
const getOutcome = await runToolCall("form.get", { args: { schemaCode: schema, controls: [field], ...(environment ? { environment } : {}) }, requestId: request, environment });
|
|
117
|
+
const getOutcome = await runToolCall("form.get", { args: { schemaCode: schema, controls: [field], ...(environment ? { environment } : {}) }, requestId: request, environment, print: silent });
|
|
55
118
|
if (getOutcome.exitCode !== 0) {
|
|
56
119
|
const classified = classifyResult(getOutcome.result);
|
|
57
120
|
writeEnvelope(envelope(classified.status, getOutcome.result, request, classified.error));
|
|
@@ -73,34 +136,58 @@ export async function cmdFormDefault(args) {
|
|
|
73
136
|
writeEnvelope(envelope("ok", { schemaCode: schema, field, from: options.defaultValue === value ? undefined : control, to: nextControl, dryRun: true, note: "--apply 执行 form.draft→form.publish" }, request));
|
|
74
137
|
return 0;
|
|
75
138
|
}
|
|
76
|
-
// 2) form.draft:需要完整 draftAttributesJson
|
|
77
|
-
|
|
139
|
+
// 2) form.draft:需要完整 draftAttributesJson——单控件投影不够,取发布面的全量控件
|
|
140
|
+
// 图。`controls:true` / `full:true` 的大字段在 CLI 面被摘要成 {length,sha256}
|
|
141
|
+
// 或 {keys,sha256},必须从 detail artifact 取原始 JSON。
|
|
142
|
+
const getAll = await runToolCall("form.get", { args: { schemaCode: schema, full: true, ...(environment ? { environment } : {}) }, requestId: request, environment, print: silent });
|
|
78
143
|
if (getAll.exitCode !== 0) {
|
|
79
144
|
const classified = classifyResult(getAll.result);
|
|
80
145
|
writeEnvelope(envelope(classified.status, getAll.result, request, classified.error));
|
|
81
146
|
return getAll.exitCode;
|
|
82
147
|
}
|
|
83
|
-
const
|
|
84
|
-
|
|
148
|
+
const detail = readDetailForm(getAll.result) ?? (getAll.result ?? {});
|
|
149
|
+
const publishedAttrs = controlMap(detail.publishedAttributesJson);
|
|
150
|
+
const draftAttrs = controlMap(detail.draftAttributesJson);
|
|
151
|
+
const publishedCount = publishedAttrs ? Object.keys(publishedAttrs).length : 0;
|
|
152
|
+
const draftCount = draftAttrs ? Object.keys(draftAttrs).length : 0;
|
|
153
|
+
// Clone the COMPLETE face: a partial draft (or a 摘要) would shrink the form and
|
|
154
|
+
// trip the platform's own degrade guard at publish time.
|
|
155
|
+
const base = draftAttrs && draftCount >= publishedCount && draftCount > 0 ? draftAttrs : publishedAttrs;
|
|
156
|
+
if (!base || Object.keys(base).length === 0)
|
|
85
157
|
return invalid("form.get 未取到任何控件(表单为空?先发布默认表单)");
|
|
86
|
-
const
|
|
158
|
+
const controlKey = Object.keys(base).find((key) => key === field)
|
|
159
|
+
?? Object.keys(base).find((key) => String(base[key].key ?? "") === field);
|
|
160
|
+
if (!controlKey)
|
|
161
|
+
return invalid(`发布面里没有字段 "${field}" 的控件——先 field.publish 并 form.publish`);
|
|
162
|
+
const cloned = JSON.parse(JSON.stringify(base));
|
|
163
|
+
const targetControl = cloned[controlKey];
|
|
164
|
+
const targetOptions = (targetControl.options ?? {});
|
|
165
|
+
targetOptions.defaultValue = Array.isArray(targetOptions.defaultValue) ? [value] : value;
|
|
166
|
+
targetControl.options = targetOptions;
|
|
167
|
+
const draftViewRaw = detail.publishedViewJson ?? detail.draftViewJson;
|
|
168
|
+
const draftView = parseJsonColumn(draftViewRaw);
|
|
87
169
|
const draftOutcome = await runToolCall("form.draft", {
|
|
88
|
-
args: {
|
|
89
|
-
|
|
170
|
+
args: {
|
|
171
|
+
schemaCode: schema,
|
|
172
|
+
draftAttributesJson: JSON.stringify(cloned),
|
|
173
|
+
...(draftView !== undefined ? { draftViewJson: JSON.stringify(draftView) } : {}),
|
|
174
|
+
...(environment ? { environment } : {}),
|
|
175
|
+
},
|
|
176
|
+
requestId: request, environment, print: silent,
|
|
90
177
|
});
|
|
91
178
|
if (draftOutcome.exitCode !== 0) {
|
|
92
179
|
const classified = classifyResult(draftOutcome.result);
|
|
93
180
|
writeEnvelope(envelope(classified.status, draftOutcome.result, request, classified.error));
|
|
94
181
|
return draftOutcome.exitCode;
|
|
95
182
|
}
|
|
96
|
-
const publishOutcome = await runToolCall("form.publish", { args: { schemaCode: schema, ...(environment ? { environment } : {}) }, requestId: request, environment });
|
|
183
|
+
const publishOutcome = await runToolCall("form.publish", { args: { schemaCode: schema, ...(environment ? { environment } : {}) }, requestId: request, environment, print: silent });
|
|
97
184
|
if (publishOutcome.exitCode !== 0) {
|
|
98
185
|
const classified = classifyResult(publishOutcome.result);
|
|
99
186
|
writeEnvelope(envelope(classified.status, publishOutcome.result, request, classified.error));
|
|
100
187
|
return publishOutcome.exitCode;
|
|
101
188
|
}
|
|
102
189
|
// 3) 权威回读
|
|
103
|
-
const verify = await runToolCall("form.get", { args: { schemaCode: schema, controls: [field], ...(environment ? { environment } : {}) }, requestId: request, environment });
|
|
190
|
+
const verify = await runToolCall("form.get", { args: { schemaCode: schema, controls: [field], ...(environment ? { environment } : {}) }, requestId: request, environment, print: silent });
|
|
104
191
|
const verifyResult = verify.result ?? {};
|
|
105
192
|
const verifyControls = extractControls(verifyResult)?.controls ?? [];
|
|
106
193
|
const verifyControl = verifyControls.find((c) => String(c.key ?? c.dataItemName ?? "") === field);
|
package/dist/form-layout.js
CHANGED
|
@@ -14,6 +14,23 @@
|
|
|
14
14
|
*/
|
|
15
15
|
export const ALLOWED_SPANS = new Set([6, 8, 12, 16, 18, 24]);
|
|
16
16
|
export const FULL_SPAN = 24;
|
|
17
|
+
/**
|
|
18
|
+
* 1.3 of issue 2026-10-23: these three classifications describe shapes the
|
|
19
|
+
* PLATFORM ITSELF stores — its default scaffold puts creater/createdTime/
|
|
20
|
+
* sequenceNo on one row (each defaulting to span 24 → "overflow"), and some
|
|
21
|
+
* published layouts list child columns as rows. Refusing them makes a
|
|
22
|
+
* read→write round-trip impossible, so they are warnings, not blockers.
|
|
23
|
+
* Coverage/span/options/type violations stay hard.
|
|
24
|
+
*/
|
|
25
|
+
export const SOFT_LAYOUT_CLASSIFICATIONS = new Set();
|
|
26
|
+
// No classification is unconditionally soft: a caller-authored bad layout must
|
|
27
|
+
// still refuse with ZERO wire. The platform's own readback is tolerated by
|
|
28
|
+
// `form.draft`'s `fromReadback` path instead (issue 2026-10-23 §1.3), where the
|
|
29
|
+
// supplied faces can be compared against the stored sheet. The default-width
|
|
30
|
+
// row rule below still keeps the platform's scaffold rows from tripping.
|
|
31
|
+
export function hardLayoutViolations(violations) {
|
|
32
|
+
return violations.filter((violation) => !SOFT_LAYOUT_CLASSIFICATIONS.has(violation.classification));
|
|
33
|
+
}
|
|
17
34
|
/** Child-table (Sheet 201) column keys, which must live on `columns`, never in rows. */
|
|
18
35
|
function childColumnKeys(attrs) {
|
|
19
36
|
const keys = new Set();
|
|
@@ -99,10 +116,16 @@ export function validateFormLayout(rows, attributes) {
|
|
|
99
116
|
const codes = row.map((code) => String(code));
|
|
100
117
|
const widths = codes.map((code) => spanOf(attrs, code, violations));
|
|
101
118
|
const total = widths.reduce((sum, width) => sum + width, 0);
|
|
102
|
-
|
|
119
|
+
// The platform's OWN scaffold writes rows like creater+createdTime+
|
|
120
|
+
// sequenceNo with NO explicit span (each defaults to 24) — rows made only of
|
|
121
|
+
// default-width system controls are the platform's shape, not a caller
|
|
122
|
+
// error (issue 2026-10-23 §1.3). Only a row that sets a span explicitly is
|
|
123
|
+
// judged by the width rules.
|
|
124
|
+
const hasExplicitSpan = codes.some((code) => attrs[code]?.options !== undefined && (attrs[code]?.options).span !== undefined);
|
|
125
|
+
if (hasExplicitSpan && total > FULL_SPAN) {
|
|
103
126
|
violations.push({ classification: "layout_row_overflow", row: index + 1, message: `第 ${index + 1} 行 span 合计 ${total},超过 ${FULL_SPAN}(${codes.join(" + ")})` });
|
|
104
127
|
}
|
|
105
|
-
if (codes.length !== 1 && widths.some((width) => width === FULL_SPAN)) {
|
|
128
|
+
if (hasExplicitSpan && codes.length !== 1 && widths.some((width) => width === FULL_SPAN)) {
|
|
106
129
|
violations.push({ classification: "full_row_control_conflict", row: index + 1, message: `第 ${index + 1} 行含整行控件(span=24),不能与其他控件同行(${codes.join(" + ")})` });
|
|
107
130
|
}
|
|
108
131
|
});
|