@stalfh233/omc-cli 0.6.11 → 0.6.13
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 +2 -0
- package/dist/approval-flow.js +84 -3
- package/dist/batch-call.js +32 -2
- package/dist/bench/call-face-eval.js +2 -2
- package/dist/call.js +35 -2
- package/dist/capability-overview.js +21 -7
- package/dist/cli-contract.js +34 -4
- package/dist/cli-output.js +6 -2
- package/dist/cli.js +303 -69
- package/dist/describe-cache.js +8 -4
- package/dist/environment-policy.js +20 -0
- package/dist/field-families.js +62 -4
- package/dist/form-support.js +293 -6
- package/dist/goal-routes.js +137 -22
- package/dist/identity.js +63 -11
- package/dist/lanes/app-contract.js +4 -4
- package/dist/lanes/app.js +19 -1
- package/dist/lanes/dictionary-contract.js +16 -5
- package/dist/lanes/dictionary-coverage.js +1 -0
- package/dist/lanes/dictionary.js +200 -8
- package/dist/lanes/environment-contract.js +18 -6
- package/dist/lanes/environment.js +129 -16
- package/dist/lanes/field-contract.js +2 -1
- package/dist/lanes/field.js +707 -59
- package/dist/lanes/form.js +52 -6
- package/dist/lanes/list-view-contract.js +2 -2
- package/dist/lanes/list-view.js +98 -2
- package/dist/lanes/meta.js +36 -10
- package/dist/lanes/model-contract.js +7 -6
- package/dist/lanes/model.js +154 -10
- package/dist/lanes/organization.js +67 -7
- package/dist/lanes/runtime-data-contract.js +4 -2
- package/dist/lanes/runtime-data.js +395 -41
- package/dist/lanes/workflow-contract.js +2 -2
- package/dist/lanes/workflow.js +381 -11
- package/dist/model-scaffold.js +11 -3
- package/dist/path-projection.js +192 -32
- package/dist/read-shapes.js +31 -11
- package/dist/recipes/field.js +16 -3
- package/dist/recipes/form.js +2 -1
- package/dist/recipes/runtime-data.js +58 -8
- package/dist/recipes/workflow.js +86 -1
- package/dist/refusal-guidance.js +18 -6
- package/dist/rollback-spec.js +41 -2
- package/dist/rollback.js +49 -11
- package/dist/script.js +72 -12
- package/dist/skills.js +91 -3
- package/dist/tools.js +113 -1
- package/dist/wire.js +13 -1
- package/dist/write-lock.js +92 -4
- package/dist/write.js +129 -16
- package/package.json +5 -3
- package/skills/omc-business-configuration.md +1 -1
- package/skills/omc-capabilities.md +5 -4
- package/skills/omc-playbook.md +41 -0
- package/skills/omc-refusals.md +58 -6
- package/skills/omc-tool-selection.md +2 -0
- package/skills/omc-write-hazards.md +3 -2
package/dist/lanes/form.js
CHANGED
|
@@ -290,9 +290,13 @@ export function registerFormLane(tools) {
|
|
|
290
290
|
draftViewJson: z.string().optional().describe("行分组:JSON 二维字符串数组 `[[\"codeA\",\"codeB\"],[\"codeC\"]]`——**一行几个 code 就是几列**(不是改 span 来改列数);末行只放一个 code 即整宽。每个 code 必须同时出现在 draftAttributesJson 里。现成样例:`omc describe form.draft --example two-column-layout`"),
|
|
291
291
|
draftAttributesJson: z.string().optional().describe("控件表:JSON 对象 `{ \"codeA\": { key:\"codeA\", type: <控件类型号>, options:{ name:\"显示名\", span: 12 } } }`。`span` 是 24 栅格宽度:两列各 span:12、整宽 span:24(必须与 draftViewJson 的行分组一致,否则 layout_coverage_mismatch)。现成样例:`omc describe form.draft --example two-column-layout`"),
|
|
292
292
|
draftHtmlJson: z.string().optional(),
|
|
293
|
-
fieldCodes: z.array(z.string().min(1)).optional().describe("脚手架绑定的已发布字段(按此顺序);由 configure_form/intent
|
|
293
|
+
fieldCodes: z.array(z.string().min(1)).optional().describe("脚手架绑定的已发布字段(按此顺序);由 configure_form/intent 显式传入。子表列写「父字段.子列」等价于 childColumns"),
|
|
294
|
+
childColumns: z
|
|
295
|
+
.array(z.object({ parent: z.string().min(1), fieldCodes: z.array(z.string().min(1)) }))
|
|
296
|
+
.optional()
|
|
297
|
+
.describe("把子表列绑进**已有** Sheet201 控件的 columns(issue #24):[{parent:\"items\",fieldCodes:[\"category\"]}]。只追加列,不动其它控件/布局行;无已存布局时退回 scaffold"),
|
|
294
298
|
rebuild: z.boolean().optional().describe("true 时丢弃现有草稿布局,按已发布字段重新生成(新增字段/子表要用它;默认保留现有草稿)"),
|
|
295
|
-
fromReadback: z.boolean().optional().describe("true = 本次 draftViewJson/draftAttributesJson 来自 form.get
|
|
299
|
+
fromReadback: z.boolean().optional().describe("true = 本次 draftViewJson/draftAttributesJson 来自 form.get 的**逐字未改动**原文(平台自身布局可能含子表列行/系统行超宽,逐字回写放行)。语义是「回写」不是「我读过」:读过之后改过一个字符就不再是回读,会被拒(去掉本参数按普通写面校验)。只加子表列请用 configure_form 的 childColumns"),
|
|
296
300
|
allowEmptyLayout: z.boolean().optional().describe("显式放行空布局(\"[]\"/\"{}\")写入;默认拒绝(form-empty-layout),防设计器 Save 清空后照写丢控件"),
|
|
297
301
|
buttons: z
|
|
298
302
|
.array(z.record(z.unknown()))
|
|
@@ -365,14 +369,18 @@ export function registerFormLane(tools) {
|
|
|
365
369
|
fieldCodes: z
|
|
366
370
|
.array(z.string().min(1))
|
|
367
371
|
.optional()
|
|
368
|
-
.describe("
|
|
369
|
-
|
|
372
|
+
.describe("表单要绑定的已发布字段(按此顺序);省略则脚手架绑定全部已发布字段;子表列写「父字段.子列」(如 items.category),等价于 childColumns;未知 code 拒绝(form-fieldcodes-unknown)"),
|
|
373
|
+
childColumns: z
|
|
374
|
+
.array(z.object({ parent: z.string().min(1), fieldCodes: z.array(z.string().min(1)) }))
|
|
375
|
+
.optional()
|
|
376
|
+
.describe("把子表列绑进已有 Sheet201 控件的 columns(不动其它控件/布局):[{parent:\"items\",fieldCodes:[\"category\"]}]。给已有表单加子表列时用它——rebuild:true 会丢弃手工布局与控件表达式"),
|
|
377
|
+
rebuild: z.boolean().optional().describe("true 时丢弃现有草稿布局,按已发布字段重新生成(新增字段/子表要用它;默认保留现有草稿,只增量处理 fieldCodes/childColumns)"),
|
|
370
378
|
draftHtmlJson: z.string().optional(),
|
|
371
379
|
},
|
|
372
380
|
plan: (args) => ({
|
|
373
381
|
summary: writeSummary("configure_form", args, undefined, "plan"),
|
|
374
382
|
actions: [
|
|
375
|
-
{ tool: "form.draft", args: { schemaCode: args.schemaCode, sheetCode: resolveSheetCode(args.schemaCode, args.sheetCode), ...(args.fieldCodes ? { fieldCodes: args.fieldCodes } : {}), ...(args.rebuild === true ? { rebuild: true } : {}), ...(typeof args.draftHtmlJson === "string" ? { draftHtmlJson: args.draftHtmlJson } : {}) } },
|
|
383
|
+
{ tool: "form.draft", args: { schemaCode: args.schemaCode, sheetCode: resolveSheetCode(args.schemaCode, args.sheetCode), ...(args.fieldCodes ? { fieldCodes: args.fieldCodes } : {}), ...(args.childColumns ? { childColumns: args.childColumns } : {}), ...(args.rebuild === true ? { rebuild: true } : {}), ...(typeof args.draftHtmlJson === "string" ? { draftHtmlJson: args.draftHtmlJson } : {}) } },
|
|
376
384
|
{ tool: "form.publish", args: { schemaCode: args.schemaCode, sheetCode: resolveSheetCode(args.schemaCode, args.sheetCode) } },
|
|
377
385
|
],
|
|
378
386
|
}),
|
|
@@ -386,6 +394,7 @@ export function registerFormLane(tools) {
|
|
|
386
394
|
schemaCode: args.schemaCode,
|
|
387
395
|
sheetCode,
|
|
388
396
|
...(args.fieldCodes ? { fieldCodes: args.fieldCodes } : {}),
|
|
397
|
+
...(args.childColumns ? { childColumns: args.childColumns } : {}),
|
|
389
398
|
...(args.rebuild === true ? { rebuild: true } : {}),
|
|
390
399
|
...(typeof args.draftHtmlJson === "string" ? { draftHtmlJson: args.draftHtmlJson } : {}),
|
|
391
400
|
});
|
|
@@ -401,13 +410,50 @@ export function registerFormLane(tools) {
|
|
|
401
410
|
// r40 F4:published=true ≠ 请求的字段进了布局——模型已有手工布局时,非 rebuild
|
|
402
411
|
// 草稿保留原控件,新字段不会出现,而 formPublished 仍是 true(假成功)。
|
|
403
412
|
// readback 必须核「请求面是否真的落进 published 控件」,缺了就点名+给重建路。
|
|
404
|
-
const missing = (args.fieldCodes ?? []).filter((code) => !publishedControlCodes.includes(code));
|
|
413
|
+
const missing = (args.fieldCodes ?? []).filter((code) => !code.includes(".") && !publishedControlCodes.includes(code));
|
|
414
|
+
// issue #24:子表列不进 viewJson / 顶层 control codes——它们的权威面是父
|
|
415
|
+
// Sheet201 控件的 columns。请求了却没落进 columns 就是「页面仍不可见」,
|
|
416
|
+
// 必须逐条点名,不能报 formPublished:true 了事。
|
|
417
|
+
const requestedChild = [
|
|
418
|
+
...(args.childColumns ?? []),
|
|
419
|
+
...(args.fieldCodes ?? []).filter((code) => code.includes(".")).map((entry) => {
|
|
420
|
+
const [parent, ...rest] = entry.split(".");
|
|
421
|
+
return { parent, fieldCodes: [rest.join(".")] };
|
|
422
|
+
}),
|
|
423
|
+
];
|
|
424
|
+
let childColumnReadback = {};
|
|
425
|
+
if (requestedChild.length > 0) {
|
|
426
|
+
const publishedAttributes = (() => {
|
|
427
|
+
try {
|
|
428
|
+
return JSON.parse(String(pub.publishedAttributesJson ?? "{}"));
|
|
429
|
+
}
|
|
430
|
+
catch {
|
|
431
|
+
return {};
|
|
432
|
+
}
|
|
433
|
+
})();
|
|
434
|
+
const missingChildColumns = [];
|
|
435
|
+
for (const request of requestedChild) {
|
|
436
|
+
const columns = publishedAttributes[request.parent]?.columns;
|
|
437
|
+
const bound = new Set(Array.isArray(columns) ? columns.map((column) => String(column.key ?? "")) : []);
|
|
438
|
+
for (const code of request.fieldCodes)
|
|
439
|
+
if (!bound.has(code))
|
|
440
|
+
missingChildColumns.push({ parent: request.parent, code });
|
|
441
|
+
}
|
|
442
|
+
childColumnReadback = {
|
|
443
|
+
childColumnsRequested: requestedChild,
|
|
444
|
+
childColumnsPresent: missingChildColumns.length === 0,
|
|
445
|
+
...(missingChildColumns.length > 0
|
|
446
|
+
? { missingChildColumns, warning: `子表列 ${missingChildColumns.map((entry) => `${entry.parent}.${entry.code}`).join("、")} 未落进已发布表单的 Sheet201 columns——页面仍看不到;用 childColumns 绑定后重跑 form.publish` }
|
|
447
|
+
: {}),
|
|
448
|
+
};
|
|
449
|
+
}
|
|
405
450
|
return {
|
|
406
451
|
steps: results,
|
|
407
452
|
readback: {
|
|
408
453
|
formPublished: pub.published === true,
|
|
409
454
|
publishedHtmlJsonPresent: typeof pub.publishedHtmlJson === "string",
|
|
410
455
|
publishedControlCodes,
|
|
456
|
+
...childColumnReadback,
|
|
411
457
|
...(missing.length > 0
|
|
412
458
|
? { missingFieldCodes: missing, warning: `表单已发布,但请求的 ${missing.join("、")} 不在 published 控件里(既有手工布局未带上新字段)——重跑 configure_form 带 rebuild:true(或 form.draft {rebuild:true} → form.publish)后重验本字段` }
|
|
413
459
|
: {}),
|
|
@@ -122,7 +122,7 @@ export const LIST_VIEW_LANE_CONTRACT = Object.freeze({
|
|
|
122
122
|
}),
|
|
123
123
|
"listview.create": Object.freeze({
|
|
124
124
|
scope: LIST_VIEW_SCOPE,
|
|
125
|
-
description: "创建自定义列表视图(query/create,强制 LIST/showOnPc/unpublished
|
|
125
|
+
description: "创建自定义列表视图(query/create,强制 LIST/showOnPc/unpublished 默认)。本工具只收 schemaCode/name/code——**列配置不在这里**:建完后用 `listview.configure` 的 fieldCodes 配列(不要把 fieldCodes 传给 create)。**业务展示的默认入口就是自定义视图**:平台在 model.create 时自动生成的默认视图(code = schemaCode,系统自带)**不作为业务展示用**——列别在默认视图上堆、也别带系统字段;按需求建自定义视图(listview.create 命名业务视图),列**只用业务字段**,筛选条件**按需求选少数几个**(不要照搬全部字段/默认条件)。readback 见 get_header。",
|
|
126
126
|
}),
|
|
127
127
|
"listview.configure": Object.freeze({
|
|
128
128
|
scope: LIST_VIEW_SCOPE,
|
|
@@ -166,7 +166,7 @@ export function assertListViewContract() {
|
|
|
166
166
|
throw new Error("list-view contract must pin queryLayer");
|
|
167
167
|
if (!LIST_VIEW_WIRE.deleteBody.includes("query/delete"))
|
|
168
168
|
throw new Error("list-view contract must pin the delete endpoint");
|
|
169
|
-
assertDescriptionCarries(LIST_VIEW_LANE_CONTRACT, "listview.create", ["query/create", "LIST", "get_header"]);
|
|
169
|
+
assertDescriptionCarries(LIST_VIEW_LANE_CONTRACT, "listview.create", ["query/create", "LIST", "get_header", "listview.configure"]);
|
|
170
170
|
// Button scripts are stored in h_biz_button.actionConfig.scriptData (a JSON
|
|
171
171
|
// STRING); listview.get/--buttons and form.get/--buttonScripts surface them
|
|
172
172
|
// without a round trip through the artifact.
|
package/dist/lanes/list-view.js
CHANGED
|
@@ -5,7 +5,7 @@ import { capabilityArg, environmentArg } from "../args.js";
|
|
|
5
5
|
import { governedRead } from "../read.js";
|
|
6
6
|
import { writeTool } from "../write.js";
|
|
7
7
|
import { encodeJsonColumn } from "../json-column.js";
|
|
8
|
-
import { defineTool, ToolRefusalError } from "../tool-types.js";
|
|
8
|
+
import { defineTool, ToolInputError, ToolRefusalError } from "../tool-types.js";
|
|
9
9
|
import { wireCall, wireRows, WireError } from "../wire.js";
|
|
10
10
|
import { QUERY_ACTION_TYPE } from "omc-core/list-view-wire";
|
|
11
11
|
import { resolveModelTreeNode } from "./model.js";
|
|
@@ -454,13 +454,44 @@ export function registerListViewLane(tools) {
|
|
|
454
454
|
lane: LIST_VIEW_LANE,
|
|
455
455
|
scope: LIST_VIEW_TOOLS["listview.create"].scope,
|
|
456
456
|
description: LIST_VIEW_TOOLS["listview.create"].description,
|
|
457
|
-
inputSchema: {
|
|
457
|
+
inputSchema: {
|
|
458
|
+
schemaCode: z.string().min(1),
|
|
459
|
+
name: z.string().min(1),
|
|
460
|
+
code: z.string().optional().describe("视图 code;省略时**默认取 schemaCode**(与 listview.get / listview.list / rollback 的默认视图口径一致)。平台侧 code 必填,本工具会替你填,不要再依赖不回执的猜测(r26 真机:不传直接被 errcode 10005「编码不能为空」拒绝,白跑一轮线上往返)"),
|
|
461
|
+
// r29b F5a: create 没有列参数。接受 fieldCodes 只是为了给可执行 nextCommand,
|
|
462
|
+
// 避免未知键算法给出「fieldCodes→code」这种南辕北辙的建议。
|
|
463
|
+
fieldCodes: z.array(z.string()).optional().describe("本工具不配列——若传了会拒绝并指引 listview.configure"),
|
|
464
|
+
},
|
|
465
|
+
// dry-run 不跑 execute——fieldCodes 必须在 preflight 拦,否则 dry-run 仍会放行。
|
|
466
|
+
preflight: (args) => {
|
|
467
|
+
if (Array.isArray(args.fieldCodes) && args.fieldCodes.length > 0) {
|
|
468
|
+
const code = args.code ?? args.schemaCode;
|
|
469
|
+
throw new ToolInputError({
|
|
470
|
+
status: "error",
|
|
471
|
+
blocker: "tool-input-invalid",
|
|
472
|
+
tool: "listview.create",
|
|
473
|
+
message: "listview.create 不接受列参数:先 create(schemaCode/name/code),再 listview.configure 配 fieldCodes。" +
|
|
474
|
+
"不要把 fieldCodes 当成 create 的拼写错误去改成 code。",
|
|
475
|
+
nextCommand: `omc call listview.configure --args '${JSON.stringify({ schemaCode: args.schemaCode, code, fieldCodes: args.fieldCodes })}'`,
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
},
|
|
458
479
|
execute: async (session, args) => {
|
|
459
480
|
// r26 真机:`code` 在 schema 里可选,但三处读面/rollback 都用
|
|
460
481
|
// `args.code ?? schemaCode`,唯独 create 把 undefined 原样发给平台 →
|
|
461
482
|
// `errcode 10005 编码不能为空`,探针白跑一轮。默认视图 code 就是
|
|
462
483
|
// schemaCode(listview.list 的 hint 也这么写),这里对齐。
|
|
463
484
|
const code = args.code ?? args.schemaCode;
|
|
485
|
+
if (Array.isArray(args.fieldCodes) && args.fieldCodes.length > 0) {
|
|
486
|
+
throw new ToolInputError({
|
|
487
|
+
status: "error",
|
|
488
|
+
blocker: "tool-input-invalid",
|
|
489
|
+
tool: "listview.create",
|
|
490
|
+
message: "listview.create 不接受列参数:先 create(schemaCode/name/code),再 listview.configure 配 fieldCodes。" +
|
|
491
|
+
"不要把 fieldCodes 当成 create 的拼写错误去改成 code。",
|
|
492
|
+
nextCommand: `omc call listview.configure --args '${JSON.stringify({ schemaCode: args.schemaCode, code, fieldCodes: args.fieldCodes })}'`,
|
|
493
|
+
});
|
|
494
|
+
}
|
|
464
495
|
// Ticket 81: read the header FIRST — a same-code retry short-circuits
|
|
465
496
|
// with the existing view and sends ZERO create wires (the live
|
|
466
497
|
// post-timeout retry otherwise answers 303002 编码重复).
|
|
@@ -545,6 +576,37 @@ export function registerListViewLane(tools) {
|
|
|
545
576
|
unknown: unknownRefs,
|
|
546
577
|
message: `fieldCodes 引用了模型 ${args.schemaCode} 发布面上不存在的字段(可给 propertyCode 或字段名):${unknownRefs.join("、")};先 field.create + field.publish,或修正引用`,
|
|
547
578
|
});
|
|
579
|
+
// r29b F5b: 列表视图不支持整列展示子表——平台会 50000「服务异常」,dry-run
|
|
580
|
+
// 以前也不拦。写前按 propertyType=CHILD_TABLE(8) 拒绝并给替代。
|
|
581
|
+
const childTableRefs = args.fieldCodes
|
|
582
|
+
.map((ref) => {
|
|
583
|
+
const resolved = resolveFieldRef(fieldRows, ref);
|
|
584
|
+
if (!resolved)
|
|
585
|
+
return null;
|
|
586
|
+
const field = fieldRows.find((row) => (row.code ?? row.propertyCode) === resolved.code);
|
|
587
|
+
const type = Number(field.type ?? field.propertyType);
|
|
588
|
+
return type === 8 ? resolved.code : null;
|
|
589
|
+
})
|
|
590
|
+
.filter((code) => typeof code === "string");
|
|
591
|
+
if (childTableRefs.length > 0) {
|
|
592
|
+
const alternatives = args.fieldCodes.filter((ref) => {
|
|
593
|
+
const resolved = resolveFieldRef(fieldRows, ref);
|
|
594
|
+
if (!resolved)
|
|
595
|
+
return false;
|
|
596
|
+
const field = fieldRows.find((row) => (row.code ?? row.propertyCode) === resolved.code);
|
|
597
|
+
return Number(field.type ?? field.propertyType) !== 8;
|
|
598
|
+
});
|
|
599
|
+
throw new ToolInputError({
|
|
600
|
+
status: "error",
|
|
601
|
+
blocker: "tool-input-invalid",
|
|
602
|
+
tool: "listview.configure",
|
|
603
|
+
message: `列表视图不支持整列展示子表(propertyType=CHILD_TABLE):${childTableRefs.join("、")}。` +
|
|
604
|
+
"平台会回 50000 服务异常。请改用子表首列/展开列,或在详情页(form)看子表;列表只放主表标量列。",
|
|
605
|
+
nextCommand: alternatives.length > 0
|
|
606
|
+
? `omc call listview.configure --args '${JSON.stringify({ schemaCode: args.schemaCode, code, fieldCodes: alternatives })}'`
|
|
607
|
+
: `omc call field.list --args '${JSON.stringify({ schemaCode: args.schemaCode, published: true })}'`,
|
|
608
|
+
});
|
|
609
|
+
}
|
|
548
610
|
// replaceColumns: the caller's fieldCodes ARE the view. Start from an
|
|
549
611
|
// empty set so system columns fall away unless listed — a system field
|
|
550
612
|
// can still be shown by naming its code in fieldCodes (createdTime etc.
|
|
@@ -818,6 +880,40 @@ export function registerListViewLane(tools) {
|
|
|
818
880
|
return { performed: { errcode: performed.errcode }, readback };
|
|
819
881
|
},
|
|
820
882
|
preflight: assertListViewConfigureShape,
|
|
883
|
+
// r29b F5b: dry-run 也必须拦 CHILD_TABLE 列,否则「计划能过、真写 50000」。
|
|
884
|
+
dryRunPayload: async (session, args) => {
|
|
885
|
+
const code = args.code ?? args.schemaCode;
|
|
886
|
+
const fieldRows = wireRows((await wireCall(session, { path: ENDPOINTS.fieldList, query: { schemaCode: args.schemaCode, isPublish: "true" } })).data);
|
|
887
|
+
const childTableRefs = args.fieldCodes
|
|
888
|
+
.map((ref) => {
|
|
889
|
+
const resolved = resolveFieldRef(fieldRows, ref);
|
|
890
|
+
if (!resolved)
|
|
891
|
+
return null;
|
|
892
|
+
const field = fieldRows.find((row) => (row.code ?? row.propertyCode) === resolved.code);
|
|
893
|
+
return Number(field.type ?? field.propertyType) === 8 ? resolved.code : null;
|
|
894
|
+
})
|
|
895
|
+
.filter((value) => typeof value === "string");
|
|
896
|
+
if (childTableRefs.length > 0) {
|
|
897
|
+
const alternatives = args.fieldCodes.filter((ref) => {
|
|
898
|
+
const resolved = resolveFieldRef(fieldRows, ref);
|
|
899
|
+
if (!resolved)
|
|
900
|
+
return false;
|
|
901
|
+
const field = fieldRows.find((row) => (row.code ?? row.propertyCode) === resolved.code);
|
|
902
|
+
return Number(field.type ?? field.propertyType) !== 8;
|
|
903
|
+
});
|
|
904
|
+
throw new ToolInputError({
|
|
905
|
+
status: "error",
|
|
906
|
+
blocker: "tool-input-invalid",
|
|
907
|
+
tool: "listview.configure",
|
|
908
|
+
message: `列表视图不支持整列展示子表(propertyType=CHILD_TABLE):${childTableRefs.join("、")}。` +
|
|
909
|
+
"平台会回 50000 服务异常。请改用子表首列/展开列,或在详情页(form)看子表;列表只放主表标量列。",
|
|
910
|
+
nextCommand: alternatives.length > 0
|
|
911
|
+
? `omc call listview.configure --args '${JSON.stringify({ schemaCode: args.schemaCode, code, fieldCodes: alternatives })}'`
|
|
912
|
+
: `omc call field.list --args '${JSON.stringify({ schemaCode: args.schemaCode, published: true })}'`,
|
|
913
|
+
});
|
|
914
|
+
}
|
|
915
|
+
return { wouldChange: true, summary: `即将配置列表视图 ${args.schemaCode}/${code} 的列` };
|
|
916
|
+
},
|
|
821
917
|
rollback: listviewRollbackSpec("listview.configure"),
|
|
822
918
|
});
|
|
823
919
|
writeTool({
|
package/dist/lanes/meta.js
CHANGED
|
@@ -12,27 +12,47 @@ export function registerMetaLane(tools) {
|
|
|
12
12
|
layer: "meta",
|
|
13
13
|
kind: "meta",
|
|
14
14
|
// r28k F5:manifest 是 lane 粒度,不是 capability id(放首句,避免 summary 截断吞掉)。
|
|
15
|
-
|
|
15
|
+
// r30f F6:capability 参数同时接受能力描述名与 lane id(归一);按能力 id 详查仍用 omc capability <id>(如 listview)。
|
|
16
|
+
description: `Indexed by lane — capability arg accepts lane id (W1-list-view); use omc capability listview for "can X?". ${META_TOOLS["coverage.manifest"].description}`,
|
|
16
17
|
inputSchema: {
|
|
17
18
|
full: z.boolean().optional().describe("返回完整覆盖投影;默认返回紧凑摘要"),
|
|
18
19
|
// r28k F6(真机 2026-11-05):探针传 `{"capability":"report"}` 想按能力过滤,
|
|
19
20
|
// schema 只有 full,参数被**静默丢弃**,拿到 46 项还以为已过滤——多读 6.3KB。
|
|
20
21
|
// 与其拒绝,不如真的过滤:manifest 行本身就有 capability 字段(免费)。
|
|
21
|
-
|
|
22
|
+
// r30f F6:同时接受 lane id(W1-list-view)——旧提示「按 id 查用 omc capability」对 lane id 是错的。
|
|
23
|
+
capability: z.string().optional().describe("能力描述名或 lane id(子串匹配 capability/lane);详查用 omc capability listview"),
|
|
22
24
|
lane: z.string().optional().describe("按 lane 名过滤(子串匹配)"),
|
|
23
25
|
},
|
|
24
26
|
handler: async (ctx, args) => {
|
|
25
27
|
const full = args.full === true;
|
|
26
28
|
const capabilityFilter = typeof args.capability === "string" && args.capability.trim() !== "" ? args.capability.trim().toLowerCase() : undefined;
|
|
27
29
|
const laneFilter = typeof args.lane === "string" && args.lane.trim() !== "" ? args.lane.trim().toLowerCase() : undefined;
|
|
28
|
-
//
|
|
29
|
-
const matched = ctx.manifest.items.filter((item) =>
|
|
30
|
-
|
|
30
|
+
// r30f F6:capability 参数归一匹配 capability 描述名 **或** lane id。
|
|
31
|
+
const matched = ctx.manifest.items.filter((item) => {
|
|
32
|
+
const lane = String(item.lane ?? "").toLowerCase();
|
|
33
|
+
const capability = String(item.capability ?? "").toLowerCase();
|
|
34
|
+
const capabilityOk = capabilityFilter === undefined
|
|
35
|
+
|| capability.includes(capabilityFilter)
|
|
36
|
+
|| lane.includes(capabilityFilter);
|
|
37
|
+
const laneOk = laneFilter === undefined || lane.includes(laneFilter);
|
|
38
|
+
return capabilityOk && laneOk;
|
|
39
|
+
});
|
|
40
|
+
const totalBeforeFilter = ctx.manifest.items.length;
|
|
31
41
|
const filterNote = capabilityFilter === undefined && laneFilter === undefined
|
|
32
42
|
? undefined
|
|
33
43
|
: matched.length === 0
|
|
34
|
-
? {
|
|
35
|
-
|
|
44
|
+
? {
|
|
45
|
+
filtered: { capability: capabilityFilter ?? null, lane: laneFilter ?? null },
|
|
46
|
+
matched: 0,
|
|
47
|
+
totalBeforeFilter,
|
|
48
|
+
hint: `没有 capability/lane 命中「${capabilityFilter ?? laneFilter}」。capability 参数可传能力描述名(如 Report)或 lane id(如 W1-list-view);按能力 id 详查用 omc capability listview / omc capabilities`,
|
|
49
|
+
}
|
|
50
|
+
: {
|
|
51
|
+
filtered: { capability: capabilityFilter ?? null, lane: laneFilter ?? null },
|
|
52
|
+
matched: matched.length,
|
|
53
|
+
totalBeforeFilter,
|
|
54
|
+
totalScope: "filtered",
|
|
55
|
+
};
|
|
36
56
|
const items = matched.map((item) => full
|
|
37
57
|
? {
|
|
38
58
|
lane: item.lane,
|
|
@@ -59,7 +79,8 @@ export function registerMetaLane(tools) {
|
|
|
59
79
|
},
|
|
60
80
|
};
|
|
61
81
|
}
|
|
62
|
-
|
|
82
|
+
// r30f F6:byStatus 与 total 同口径(过滤后);另给 totalBeforeFilter,避免 total:0 与 byStatus 46 并列。
|
|
83
|
+
const byStatus = matched.reduce((acc, item) => {
|
|
63
84
|
acc[item.status] = (acc[item.status] ?? 0) + 1;
|
|
64
85
|
return acc;
|
|
65
86
|
}, {});
|
|
@@ -68,10 +89,15 @@ export function registerMetaLane(tools) {
|
|
|
68
89
|
manifest: {
|
|
69
90
|
version_family: ctx.manifest.version_family,
|
|
70
91
|
anchor_version: ctx.manifest.anchor_version,
|
|
71
|
-
counts: {
|
|
92
|
+
counts: {
|
|
93
|
+
total: items.length,
|
|
94
|
+
totalScope: filterNote !== undefined ? "filtered" : "all",
|
|
95
|
+
...(filterNote !== undefined ? { totalBeforeFilter } : {}),
|
|
96
|
+
byStatus,
|
|
97
|
+
},
|
|
72
98
|
items,
|
|
73
99
|
...(filterNote !== undefined ? { filter: filterNote } : {}),
|
|
74
|
-
hint: "manifest 按 lane 索引(同一 lane 可有多行细项,status
|
|
100
|
+
hint: "manifest 按 lane 索引(同一 lane 可有多行细项,status 不统一)。capability 参数可传能力描述名或 lane id(如 W1-list-view);答「X 能力能不能用」请用 omc capability listview / omc capabilities。full:true 取完整投影(scope/reason/tools);工具清单用 commands --json --compact",
|
|
75
101
|
},
|
|
76
102
|
};
|
|
77
103
|
},
|
|
@@ -100,7 +100,7 @@ export const MODEL_LANE_CONTRACT = Object.freeze({
|
|
|
100
100
|
tools: Object.freeze({
|
|
101
101
|
"model.list": Object.freeze({
|
|
102
102
|
scope: MODEL_SCOPE,
|
|
103
|
-
description: "List the models in an application (apppackage/trees, type==='BizModel') as a
|
|
103
|
+
description: "List the models in an application (apppackage/trees, type==='BizModel') as a stable `{code,schemaCode,name[,legacyString]}` inventory (never a bare `code|name` string — diet 也保对象形). Fills the gap where enumerating models otherwise required discover_environment + an artifact read. `applicationCode` optional (configured/default app; unknown → app-not-found); `filter`/`search`/`query` are interchangeable substring matches on code/name."
|
|
104
104
|
}),
|
|
105
105
|
"model.inspect": Object.freeze({
|
|
106
106
|
scope: MODEL_SCOPE,
|
|
@@ -112,7 +112,7 @@ export const MODEL_LANE_CONTRACT = Object.freeze({
|
|
|
112
112
|
}),
|
|
113
113
|
"model.delete": Object.freeze({
|
|
114
114
|
scope: MODEL_SCOPE,
|
|
115
|
-
description: "
|
|
115
|
+
description: "物理删除模型(apppackage/physically_delete [{id,code,type:BizModel}]);exact-absence 读回在 `readback.absent`/`readback.treeNodePending`(顶层无这两键——用 `--jq readback.absent`)。id 必须是应用树 AppFunction id(业务模型记录 id → 601003)。无 `applicationCode` 时跨应用解析节点,记录在但树节点缺 → `model-application-unresolved`。同应用入站引用预检 → `model-referenced-by`(dependency-inverted:先删引用方再删被引用方)。列级联 40–60s:`absent:false`+`treeNodePending:true` 等约 60s 再跑(幂等)。可显式传 `applicationCode`。",
|
|
116
116
|
}),
|
|
117
117
|
configure_model: Object.freeze({
|
|
118
118
|
scope: MODEL_SCOPE,
|
|
@@ -120,15 +120,15 @@ export const MODEL_LANE_CONTRACT = Object.freeze({
|
|
|
120
120
|
}),
|
|
121
121
|
"model.deleteMany": Object.freeze({
|
|
122
122
|
scope: MODEL_SCOPE,
|
|
123
|
-
description: "
|
|
123
|
+
description: "批量物理删除:apppackage/physically_delete 收 List<AppDeleteParamVO>,schemaCodes[] 一次发 [{id,code,type:BizModel}]。id=跨应用解析的 AppFunction 节点;记录在树节点缺 → 整批 `model-application-unresolved`。入站引用预检:命中则整批拒并给 blockedTargets(零副作用;引用方需一并列入)。已缺席短路;缺席轮询同 model.delete(wait-and-recheck)。",
|
|
124
124
|
}),
|
|
125
125
|
"model.setSummary": Object.freeze({
|
|
126
126
|
scope: MODEL_SCOPE,
|
|
127
|
-
description: "
|
|
127
|
+
description: "设数据标题(POST bizmodels/update_summary {schemaCode,summary})。平台存 Summary[]:{isDataItem:1|2,code,param}。工具归一:合法 Summary[] 校验重序列化;纯 `{field}` 模板 → [{isDataItem:1,code}];其余拒 `model-summary-invalid`(原文模板会毒化模型,后续 data.save/submit 裸 50000)。读回 GET get_summary;空 summary → `model-summary-empty`;缺 schema → 300014。",
|
|
128
128
|
}),
|
|
129
129
|
"model.move": Object.freeze({
|
|
130
130
|
scope: "move a model's app-tree node to another folder (bizmodels/update parentId)",
|
|
131
|
-
description: "
|
|
131
|
+
description: "把模型在**同一应用内**移动到指定目录(分组):复用 `POST bizmodels/update`,只把 `parentId` 换成目标目录节点 id(AppFunctionController 补丁语义:bizSchemaCode/id/name/parentId 全量必填,工具从应用树解析)。`folderId` 必须是 `folder.tree` 里 type=Folder 的 **id**,给目录名会 `folder-not-found` 并列出可用目录。**模型不能跨应用移动**(`folder-other-application`):要在目标应用得到同名模型,请 `model.create --applicationCode <目标>`(换 code,或先 `model.delete` 源再同 code 重建)。移动只改菜单节点归属:schema/字段/表单/视图/发布状态不动。回读用应用树按 `parentId` 核对(`moved:true`)。",
|
|
132
132
|
}),
|
|
133
133
|
"model.update": Object.freeze({
|
|
134
134
|
scope: MODEL_SCOPE,
|
|
@@ -149,7 +149,8 @@ export function assertModelContract() {
|
|
|
149
149
|
throw new Error("model contract must require per-step authoritative readbacks");
|
|
150
150
|
assertDescriptionCarries(MODEL_LANE_CONTRACT, "model.deleteMany", ["physically_delete", "List<AppDeleteParamVO>", "blockedTargets", "wait-and-recheck"]);
|
|
151
151
|
assertDescriptionCarries(MODEL_LANE_CONTRACT, "model.setSummary", ["update_summary", "get_summary", "数据标题", "300014", "model-summary-empty", "model-summary-invalid", "Summary[]"]);
|
|
152
|
-
assertDescriptionCarries(MODEL_LANE_CONTRACT, "model.move", ["bizmodels/update", "parentId", "folder.tree", "folder-not-found", "moved"]);
|
|
152
|
+
assertDescriptionCarries(MODEL_LANE_CONTRACT, "model.move", ["bizmodels/update", "parentId", "folder.tree", "folder-not-found", "moved", "不能跨应用", "model.create"]);
|
|
153
|
+
assertDescriptionCarries(MODEL_LANE_CONTRACT, "model.list", ["code", "name", "filter", "query", "legacyString"]);
|
|
153
154
|
assertDescriptionCarries(MODEL_LANE_CONTRACT, "model.update", ["bizmodels/update", "get_all", "model-type-immutable", "model-update-target-absent", "model-update-empty-patch"]);
|
|
154
155
|
if (!MODEL_WIRE.summaryWire.includes("validateNotEmpty"))
|
|
155
156
|
throw new Error("model contract must carry the update_summary validateNotEmpty fact");
|