@stalfh233/omc-cli 0.6.7 → 0.6.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/README.md +2 -1
  2. package/dist/approval-flow.js +5 -3
  3. package/dist/bizservice-config.js +26 -10
  4. package/dist/call.js +47 -0
  5. package/dist/capability-overview.js +47 -24
  6. package/dist/cli-contract.js +73 -26
  7. package/dist/cli-output.js +41 -2
  8. package/dist/cli-stdout.js +74 -0
  9. package/dist/cli.js +135 -52
  10. package/dist/command-transport.js +8 -1
  11. package/dist/config-store.js +17 -2
  12. package/dist/discovery.js +62 -3
  13. package/dist/field-families.js +35 -6
  14. package/dist/goal-routes.js +118 -11
  15. package/dist/human-summary.js +11 -0
  16. package/dist/lanes/app-contract.js +12 -5
  17. package/dist/lanes/app.js +221 -134
  18. package/dist/lanes/apply-changes.js +2 -1
  19. package/dist/lanes/b2-registration-contract.js +3 -3
  20. package/dist/lanes/b2-registration.js +890 -693
  21. package/dist/lanes/businessrule.js +75 -51
  22. package/dist/lanes/custom-page.js +166 -115
  23. package/dist/lanes/datarule-contract.js +2 -2
  24. package/dist/lanes/datarule.js +238 -154
  25. package/dist/lanes/dictionary.js +253 -110
  26. package/dist/lanes/environment.js +57 -9
  27. package/dist/lanes/field.js +160 -107
  28. package/dist/lanes/folder-contract.js +7 -3
  29. package/dist/lanes/folder.js +249 -118
  30. package/dist/lanes/form-contract.js +1 -1
  31. package/dist/lanes/form.js +188 -64
  32. package/dist/lanes/list-view.js +405 -369
  33. package/dist/lanes/meta.js +31 -6
  34. package/dist/lanes/model-contract.js +1 -1
  35. package/dist/lanes/model.js +610 -512
  36. package/dist/lanes/online-js-contract.js +1 -1
  37. package/dist/lanes/online-js.js +160 -146
  38. package/dist/lanes/organization.js +905 -640
  39. package/dist/lanes/programming.js +274 -136
  40. package/dist/lanes/report-contract.js +2 -2
  41. package/dist/lanes/report.js +354 -198
  42. package/dist/lanes/rule-lifecycle.js +294 -140
  43. package/dist/lanes/runtime-data-contract.js +4 -4
  44. package/dist/lanes/runtime-data.js +869 -745
  45. package/dist/lanes/workflow-contract.js +3 -3
  46. package/dist/lanes/workflow.js +732 -605
  47. package/dist/model-scaffold.js +26 -5
  48. package/dist/path-projection.js +196 -14
  49. package/dist/plan.js +25 -0
  50. package/dist/programming-workflow.js +55 -3
  51. package/dist/read-shapes.js +146 -0
  52. package/dist/read.js +40 -9
  53. package/dist/recipes/businessrule.js +74 -24
  54. package/dist/recipes/dictionary.js +7 -4
  55. package/dist/recipes/form.js +46 -0
  56. package/dist/recipes/runtime-data.js +3 -2
  57. package/dist/refusal-guidance.js +29 -5
  58. package/dist/rule-support.js +14 -1
  59. package/dist/rule-when.js +2 -2
  60. package/dist/tool-types.js +9 -3
  61. package/dist/version-family-gaps.js +101 -0
  62. package/dist/write.js +2 -1
  63. package/package.json +6 -4
  64. package/skills/omc-glossary.md +13 -0
  65. package/skills/omc-refusals.md +13 -5
  66. package/skills/omc-tool-selection.md +2 -1
  67. package/skills/omc-write-hazards.md +5 -2
@@ -9,6 +9,7 @@ import { executeRuleSaveWrite, ruleNodeGraphArg } from "../rule-support.js";
9
9
  import { defineTool, ToolInputError, ToolRefusalError } from "../tool-types.js";
10
10
  import { resolveRuleRowId } from "../identity.js";
11
11
  import { wireCall, wireCallWith404Fallback, wireRows } from "../wire.js";
12
+ import { readModelRecord } from "../model-record.js";
12
13
  import { BUSINESSRULE_LANE_CONTRACT } from "./businessrule-contract.js";
13
14
  const RULE_LANE = BUSINESSRULE_LANE_CONTRACT.lane;
14
15
  const RULE_TOOLS = BUSINESSRULE_LANE_CONTRACT.tools;
@@ -109,6 +110,20 @@ export function registerBusinessRuleLane(tools) {
109
110
  capability: capabilityArg,
110
111
  },
111
112
  handler: async (ctx, args, self) => governedRead(ctx, self, args, async (session) => {
113
+ // r28g F35/F36:list_by_schemacode 对任意 schemaCode 会补建 7 条预置并
114
+ // publish。从未存在的模型一旦被 list,就留下 API 删不掉的预置行。
115
+ // 写前 gate(assertRuleSchemaExists)已拦;读面也必须先探,不存在则
116
+ // 直接回 modelPresent:false + rules:[],**不发 list 请求**。
117
+ const modelRecord = await readModelRecord(session, args.schemaCode);
118
+ if (modelRecord === null) {
119
+ return {
120
+ schemaCode: args.schemaCode,
121
+ modelPresent: false,
122
+ rules: [],
123
+ rulesCount: 0,
124
+ hint: "模型不存在(bizmodels/get_all 空)——未发 rule.list 请求(避免补建 7 条不可删预置);判定模型面用 model.inspect",
125
+ };
126
+ }
112
127
  // 版本族备选(2026-11-03 issue A1):8.6 锚点走 runtime 面;8.1 族
113
128
  // runtime 端点缺席(404)时下探 app 面 `businessrule/list_by_schemacode`。
114
129
  // 仅 404 下探,401/500/业务码拒绝原样上抛。rule.save / rule.enable 的
@@ -126,23 +141,14 @@ export function registerBusinessRuleLane(tools) {
126
141
  // key (schemaCode+ruleCode), so the row id is rarely needed.
127
142
  const fullRows = args.full === true;
128
143
  const visible = args.onlyCustom === true ? rows.filter((row) => row.defaultRule !== true) : rows;
129
- // r40 维护者自踩:schemaCode 不存在时平台静默回空数组——空规则被读成
130
- // 『模型在、无规则』走错分支(mcc_contract 从不存在却答 0 规则)。
131
- // 空结果时用 field.list 探一次真身(300004=不存在),把 modelPresent 标进结果。
132
- let modelPresent = true;
133
- if (visible.length === 0) {
134
- try {
135
- await wireCall(session, { path: ENDPOINTS.fieldList, query: { schemaCode: args.schemaCode, isPublish: "true" } });
136
- }
137
- catch (error) {
138
- if (String(error.message ?? error).includes("errcode 300004"))
139
- modelPresent = false;
140
- }
141
- }
142
144
  return {
143
145
  schemaCode: args.schemaCode,
144
- ...(modelPresent ? {} : { modelPresent: false, hint: "模型不存在(field.list 300004)——空规则是假象;先 model.create / configure_model,判定模型面用 model.inspect" }),
146
+ modelPresent: true,
147
+ // r28g F18:list 投影的是 PUBLISH 行;运行时可执行性以 rule.get / rule.enable.verified 为准。
148
+ source: "published-list",
149
+ executableHint: "rules[].enabled/status 来自 PUBLISH 投影;运行时判定读 DRAFT(rule.get 的 enabled+ruleStatus,或 rule.enable 回执的 executable/verified)。不要仅凭本面判定可 rule.run。",
145
150
  ...(args.onlyCustom === true ? { filtered: { onlyCustom: true, total: rows.length, returned: visible.length } } : {}),
151
+ ...(endpointFamily ? { endpointFamily } : {}),
146
152
  rules: visible.map((row) => ({
147
153
  code: row.code,
148
154
  name: row.name,
@@ -439,48 +445,66 @@ export function registerBusinessRuleLane(tools) {
439
445
  return payload;
440
446
  }, { full: args.full === true, compact: ruleCompactProjection, target: `rule.get:${args.id ?? `${args.schemaCode}/${args.ruleCode}`}` }),
441
447
  }));
442
- writeTool(tools, "rule.save", RULE_LANE, RULE_TOOLS["rule.save"].scope, RULE_TOOLS["rule.save"].description, {
443
- schemaCode: z.string().min(1),
444
- ruleCode: z.string().min(1),
445
- name: z.string().min(1),
446
- nodeGraph: ruleNodeGraphArg,
447
- publish: z.boolean().optional().describe("also publish the new version in this call (businessrule/publish) so the designer sees it; enabling stays with rule.enable — default false keeps the legacy draft-only save"),
448
- customTrigger: z.boolean().optional().describe("默认开:自定义 ruleCode + bizRuleType 0(非平台触发码 Create/Update/Delete/GetList/Load/Available/Cancel)即按钮/流程规则,工具在 START 后合成平台标准 CUSTOM_NODE「自定义按钮/流程触发」系统触发节点(nodeCode=ruleCode,live 布局 x558/y233);调用方已写 CUSTOM_NODE 则原样保留;显式 false 退回"),
449
- }, async (session, args) => executeRuleSaveWrite(session, args), undefined, undefined, businessRuleRollbackSpec("rule.save"));
448
+ writeTool({
449
+ tools,
450
+ name: "rule.save",
451
+ lane: RULE_LANE,
452
+ scope: RULE_TOOLS["rule.save"].scope,
453
+ description: RULE_TOOLS["rule.save"].description,
454
+ inputSchema: {
455
+ schemaCode: z.string().min(1),
456
+ ruleCode: z.string().min(1),
457
+ name: z.string().min(1),
458
+ nodeGraph: ruleNodeGraphArg,
459
+ publish: z.boolean().optional().describe("also publish the new version in this call (businessrule/publish) so the designer sees it; enabling stays with rule.enable — default false keeps the legacy draft-only save"),
460
+ customTrigger: z.boolean().optional().describe("默认开:自定义 ruleCode + bizRuleType 0(非平台触发码 Create/Update/Delete/GetList/Load/Available/Cancel)即按钮/流程规则,工具在 START 后合成平台标准 CUSTOM_NODE「自定义按钮/流程触发」系统触发节点(nodeCode=ruleCode,live 布局 x558/y233);调用方已写 CUSTOM_NODE 则原样保留;显式 false 退回"),
461
+ },
462
+ execute: async (session, args) => executeRuleSaveWrite(session, args),
463
+ rollback: businessRuleRollbackSpec("rule.save"),
464
+ });
450
465
  // Round 10 (issue 04) — precise modify. `rule.save` replaces the WHOLE nodeGraph,
451
466
  // so changing one branch threshold meant read the whole graph, edit, resend. This
452
467
  // tool reads the current graph, patches ONE node, and reuses the same save seam
453
468
  // (so every graph gate + readback is identical). Platform constraints honoured:
454
469
  // `nodeCode`/`nodeType` are immutable (routes reference nodeCode; the platform
455
470
  // cannot retype a node), so the patch may not change them.
456
- writeTool(tools, "rule.node.update", RULE_LANE, RULE_TOOLS["rule.node.update"].scope, RULE_TOOLS["rule.node.update"].description, {
457
- schemaCode: z.string().min(1),
458
- ruleCode: z.string().min(1),
459
- nodeCode: z.string().min(1).describe("要改的节点 nodeCode(在现有图里定位,不是新建)"),
460
- patch: z.record(z.string(), z.unknown()).describe("浅合并到该节点的字段(如 exitConditionGroups/filterCondition/dataActions/nodeName);不允许改 nodeCode/nodeType"),
461
- publish: z.boolean().optional().describe("改完同样顺带 publish(默认 false,仅存草稿;生效仍需 rule.enable)"),
462
- customTrigger: z.boolean().optional(),
463
- }, async (session, args) => {
464
- const id = await resolveRuleRowId(session, args.schemaCode, args.ruleCode);
465
- if (!id)
466
- throw new ToolRefusalError({ status: "refused", blocker: "rule-absent", message: `规则 ${args.schemaCode}.${args.ruleCode} 不存在;拒绝 node patch` });
467
- const record = (await wireCall(session, { path: ENDPOINTS.businessRuleGet, query: { id } })).data;
468
- if (!record)
469
- throw new ToolRefusalError({ status: "refused", blocker: "rule-absent", message: `规则 ${args.schemaCode}.${args.ruleCode}(id ${id})读回为空` });
470
- for (const key of ["nodeCode", "nodeType"])
471
- if (key in args.patch)
472
- throw new ToolRefusalError({ status: "refused", blocker: "rule-node-immutable-key", message: `node patch 不允许改 ${key}(平台按 nodeCode 连接路由、节点类型不可变);要改名/改型请整图 rule.save` });
473
- // node/route are SEPARATE JSON-string columns (see rule.get): `node` is the
474
- // nodes array, `route` the routes array — not one {nodes,routes} object.
475
- const nodes = decodeJsonColumn(record.node, JSON_COLUMN_NULL_COALESCE);
476
- const routes = decodeJsonColumn(record.route, JSON_COLUMN_NULL_COALESCE);
477
- const nodeList = Array.isArray(nodes) ? nodes : [];
478
- const target = nodeList.find((node) => node.nodeCode === args.nodeCode);
479
- if (!target)
480
- throw new ToolRefusalError({ status: "refused", blocker: "rule-node-absent", message: `现有图中没有 nodeCode=${args.nodeCode};用 rule.get schemaCode+ruleCode 查看 nodeCode` });
481
- const mergedNode = { ...target, ...args.patch };
482
- const mergedGraph = { nodes: nodeList.map((node) => (node.nodeCode === args.nodeCode ? mergedNode : node)), ...(Array.isArray(routes) ? { routes } : {}) };
483
- return executeRuleSaveWrite(session, { schemaCode: args.schemaCode, ruleCode: args.ruleCode, name: String(record.name ?? args.ruleCode), nodeGraph: mergedGraph, ...(args.publish === true ? { publish: true } : {}), ...(args.customTrigger === true ? { customTrigger: true } : {}) });
484
- }, undefined, undefined, businessRuleRollbackSpec("rule.node.update"));
471
+ writeTool({
472
+ tools,
473
+ name: "rule.node.update",
474
+ lane: RULE_LANE,
475
+ scope: RULE_TOOLS["rule.node.update"].scope,
476
+ description: RULE_TOOLS["rule.node.update"].description,
477
+ inputSchema: {
478
+ schemaCode: z.string().min(1),
479
+ ruleCode: z.string().min(1),
480
+ nodeCode: z.string().min(1).describe("要改的节点 nodeCode(在现有图里定位,不是新建)"),
481
+ patch: z.record(z.string(), z.unknown()).describe("浅合并到该节点的字段(如 exitConditionGroups/filterCondition/dataActions/nodeName);不允许改 nodeCode/nodeType"),
482
+ publish: z.boolean().optional().describe("改完同样顺带 publish(默认 false,仅存草稿;生效仍需 rule.enable)"),
483
+ customTrigger: z.boolean().optional(),
484
+ },
485
+ execute: async (session, args) => {
486
+ const id = await resolveRuleRowId(session, args.schemaCode, args.ruleCode);
487
+ if (!id)
488
+ throw new ToolRefusalError({ status: "refused", blocker: "rule-absent", message: `规则 ${args.schemaCode}.${args.ruleCode} 不存在;拒绝 node patch` });
489
+ const record = (await wireCall(session, { path: ENDPOINTS.businessRuleGet, query: { id } })).data;
490
+ if (!record)
491
+ throw new ToolRefusalError({ status: "refused", blocker: "rule-absent", message: `规则 ${args.schemaCode}.${args.ruleCode}(id ${id})读回为空` });
492
+ for (const key of ["nodeCode", "nodeType"])
493
+ if (key in args.patch)
494
+ throw new ToolRefusalError({ status: "refused", blocker: "rule-node-immutable-key", message: `node patch 不允许改 ${key}(平台按 nodeCode 连接路由、节点类型不可变);要改名/改型请整图 rule.save` });
495
+ // node/route are SEPARATE JSON-string columns (see rule.get): `node` is the
496
+ // nodes array, `route` the routes array — not one {nodes,routes} object.
497
+ const nodes = decodeJsonColumn(record.node, JSON_COLUMN_NULL_COALESCE);
498
+ const routes = decodeJsonColumn(record.route, JSON_COLUMN_NULL_COALESCE);
499
+ const nodeList = Array.isArray(nodes) ? nodes : [];
500
+ const target = nodeList.find((node) => node.nodeCode === args.nodeCode);
501
+ if (!target)
502
+ throw new ToolRefusalError({ status: "refused", blocker: "rule-node-absent", message: `现有图中没有 nodeCode=${args.nodeCode};用 rule.get schemaCode+ruleCode 查看 nodeCode` });
503
+ const mergedNode = { ...target, ...args.patch };
504
+ const mergedGraph = { nodes: nodeList.map((node) => (node.nodeCode === args.nodeCode ? mergedNode : node)), ...(Array.isArray(routes) ? { routes } : {}) };
505
+ return executeRuleSaveWrite(session, { schemaCode: args.schemaCode, ruleCode: args.ruleCode, name: String(record.name ?? args.ruleCode), nodeGraph: mergedGraph, ...(args.publish === true ? { publish: true } : {}), ...(args.customTrigger === true ? { customTrigger: true } : {}) });
506
+ },
507
+ rollback: businessRuleRollbackSpec("rule.node.update"),
508
+ });
485
509
  }
486
510
  //# sourceMappingURL=businessrule.js.map
@@ -127,63 +127,90 @@ export function registerCustomPageTools(tools) {
127
127
  };
128
128
  }),
129
129
  }));
130
- writeTool(tools, "custompage.create", LANE, T["custompage.create"].scope, T["custompage.create"].description, {
131
- name: z.string().min(1),
132
- code: z.string().min(1).describe("页面 code(≤28 字符,平台 validateCode28)"),
133
- parentId: z.string().min(1).describe("父节点 id(应用根或目录);用 folder.tree 取"),
134
- applicationCode: z.string().optional().describe("应用 code;省略用配置/默认应用"),
135
- menuPointType: z.enum(MENU_POINT_TYPES).optional().describe("菜单类型;自定义页固定 CUSTOM_PAGE(省略即用 CUSTOM_PAGE)"),
136
- icon: z.string().optional(),
137
- pcAble: z.boolean().optional().describe("PC 端可见(默认 true)"),
138
- mobileAble: z.boolean().optional().describe("移动端可见(默认 true)"),
139
- openMode: z.string().optional().describe("打开方式;默认 VUE_MODE(平台建 CustomPageModel 时也用这个)"),
140
- }, async (session, args) => {
141
- const appCode = await resolveApplicationCode(session, args.applicationCode);
142
- const body = {
143
- name: args.name,
144
- name_i18n: JSON.stringify({ zh: args.name }),
145
- code: args.code,
146
- parentId: args.parentId,
147
- appCode,
148
- menuPointType: args.menuPointType ?? "CUSTOM_PAGE",
149
- pcAble: args.pcAble !== false,
150
- mobileAble: args.mobileAble !== false,
151
- openMode: args.openMode ?? "VUE_MODE",
152
- ...(args.icon !== undefined ? { icon: args.icon } : {}),
153
- };
154
- const performed = await wireCall(session, { path: ENDPOINTS.customPageCreate, method: "POST", body });
155
- const readback = await readPageOrNull(session, args.code);
156
- return {
157
- performed: { errcode: performed.errcode },
158
- readback: { code: args.code, present: readback !== null, page: readback ? pickPage(readback) : null, next: `写入页面正文:omc call custompage.content.update --args '{"code":"${args.code}","content":"<JS/HTML>"}'` },
159
- };
130
+ writeTool({
131
+ tools,
132
+ name: "custompage.create",
133
+ lane: LANE,
134
+ scope: T["custompage.create"].scope,
135
+ description: T["custompage.create"].description,
136
+ inputSchema: {
137
+ name: z.string().min(1),
138
+ code: z.string().min(1).describe("页面 code(≤28 字符,平台 validateCode28)"),
139
+ parentId: z.string().min(1).describe("父节点 id(应用根或目录);用 folder.tree 取。平台 create/get 都不回迁移后的 parentId——create 回执会给 parentIdNote,请用 folder.tree 按 code 核对落点"),
140
+ applicationCode: z.string().optional().describe("应用 code;省略用配置/默认应用"),
141
+ menuPointType: z.enum(MENU_POINT_TYPES).optional().describe("菜单类型;自定义页固定 CUSTOM_PAGE(省略即用 CUSTOM_PAGE)"),
142
+ icon: z.string().optional(),
143
+ pcAble: z.boolean().optional().describe("PC 端可见(默认 true)"),
144
+ mobileAble: z.boolean().optional().describe("移动端可见(默认 true)"),
145
+ openMode: z.string().optional().describe("打开方式;默认 VUE_MODE(平台建 CustomPageModel 时也用这个)"),
146
+ },
147
+ execute: async (session, args) => {
148
+ const appCode = await resolveApplicationCode(session, args.applicationCode);
149
+ const body = {
150
+ name: args.name,
151
+ name_i18n: JSON.stringify({ zh: args.name }),
152
+ code: args.code,
153
+ parentId: args.parentId,
154
+ appCode,
155
+ menuPointType: args.menuPointType ?? "CUSTOM_PAGE",
156
+ pcAble: args.pcAble !== false,
157
+ mobileAble: args.mobileAble !== false,
158
+ openMode: args.openMode ?? "VUE_MODE",
159
+ ...(args.icon !== undefined ? { icon: args.icon } : {}),
160
+ };
161
+ const performed = await wireCall(session, { path: ENDPOINTS.customPageCreate, method: "POST", body });
162
+ const readback = await readPageOrNull(session, args.code);
163
+ const page = readback ? pickPage(readback) : null;
164
+ // r28i F5/F6: /custom/get does not return the AppFunction parentId. Leaving
165
+ // page.parentId:null on create looked like "hung at root / failed to attach".
166
+ const parentIdNote = "平台 create/get 不回迁移后的 parentId(它属于应用树 AppFunction 行);null 不代表没挂上。用 custompage.get 或 folder.tree 按 code 核对实际落点;要改父节点请 custompage.update 显式传 parentId";
167
+ return {
168
+ performed: { errcode: performed.errcode },
169
+ readback: {
170
+ code: args.code,
171
+ present: readback !== null,
172
+ page,
173
+ parentIdNote,
174
+ requestedParentId: args.parentId,
175
+ next: `写入页面正文:omc call custompage.content.update --args '{"code":"${args.code}","content":"<JS/HTML>"}';父节点请用 folder.tree 按 code 校验`,
176
+ },
177
+ };
178
+ },
160
179
  });
161
- writeTool(tools, "custompage.update", LANE, T["custompage.update"].scope, T["custompage.update"].description, {
162
- name: z.string().optional(),
163
- code: z.string().min(1),
164
- parentId: z.string().min(1).describe("父节点 id(平台 update 强制必填,省略会 50000)"),
165
- menuPointType: z.enum(MENU_POINT_TYPES).optional(),
166
- icon: z.string().optional(),
167
- pcAble: z.boolean().optional(),
168
- mobileAble: z.boolean().optional(),
169
- openMode: z.string().optional(),
170
- }, async (session, args) => {
171
- const current = await readPageOrNull(session, args.code);
172
- if (current === null)
173
- throw new ToolRefusalError({ status: "refused", blocker: "custompage-absent", code: args.code, message: `自定义页 ${args.code} 不存在;先 custompage.create` });
174
- const body = {
175
- ...current,
176
- name: args.name ?? current.name,
177
- parentId: args.parentId,
178
- menuPointType: args.menuPointType ?? current.menuPointType ?? "CUSTOM_PAGE",
179
- pcAble: args.pcAble ?? current.pcAble ?? true,
180
- mobileAble: args.mobileAble ?? current.mobileAble ?? true,
181
- ...(args.openMode !== undefined ? { openMode: args.openMode } : {}),
182
- ...(args.icon !== undefined ? { icon: args.icon } : {}),
183
- };
184
- const performed = await wireCall(session, { path: ENDPOINTS.customPageUpdate, method: "POST", body });
185
- const readback = await readPageOrNull(session, args.code);
186
- return { performed: { errcode: performed.errcode }, readback: { code: args.code, page: readback ? pickPage(readback) : null } };
180
+ writeTool({
181
+ tools,
182
+ name: "custompage.update",
183
+ lane: LANE,
184
+ scope: T["custompage.update"].scope,
185
+ description: T["custompage.update"].description,
186
+ inputSchema: {
187
+ name: z.string().optional(),
188
+ code: z.string().min(1),
189
+ parentId: z.string().min(1).describe("父节点 id(平台 update 强制必填,省略会 50000)"),
190
+ menuPointType: z.enum(MENU_POINT_TYPES).optional(),
191
+ icon: z.string().optional(),
192
+ pcAble: z.boolean().optional(),
193
+ mobileAble: z.boolean().optional(),
194
+ openMode: z.string().optional(),
195
+ },
196
+ execute: async (session, args) => {
197
+ const current = await readPageOrNull(session, args.code);
198
+ if (current === null)
199
+ throw new ToolRefusalError({ status: "refused", blocker: "custompage-absent", code: args.code, message: `自定义页 ${args.code} 不存在;先 custompage.create` });
200
+ const body = {
201
+ ...current,
202
+ name: args.name ?? current.name,
203
+ parentId: args.parentId,
204
+ menuPointType: args.menuPointType ?? current.menuPointType ?? "CUSTOM_PAGE",
205
+ pcAble: args.pcAble ?? current.pcAble ?? true,
206
+ mobileAble: args.mobileAble ?? current.mobileAble ?? true,
207
+ ...(args.openMode !== undefined ? { openMode: args.openMode } : {}),
208
+ ...(args.icon !== undefined ? { icon: args.icon } : {}),
209
+ };
210
+ const performed = await wireCall(session, { path: ENDPOINTS.customPageUpdate, method: "POST", body });
211
+ const readback = await readPageOrNull(session, args.code);
212
+ return { performed: { errcode: performed.errcode }, readback: { code: args.code, page: readback ? pickPage(readback) : null } };
213
+ },
187
214
  });
188
215
  tools.push(defineTool({
189
216
  name: "custompage.content.get",
@@ -218,70 +245,94 @@ export function registerCustomPageTools(tools) {
218
245
  };
219
246
  }),
220
247
  }));
221
- writeTool(tools, "custompage.content.update", LANE, T["custompage.content.update"].scope, T["custompage.content.update"].description, {
222
- code: z.string().min(1),
223
- content: z.string().describe("页面正文(JS/HTML)。大体积请用 --args-file 传整个 args 对象,避免命令行引号/长度限制"),
224
- publish: z.boolean().optional().describe("true = 同一调用里再走 /page/content/publish(草稿 + 发布都写),默认 false 只存草稿"),
225
- }, async (session, args) => {
226
- const performed = await wireCall(session, {
227
- path: ENDPOINTS.customPageContentUpdate,
228
- method: "POST",
229
- body: { code: args.code, draftContent: args.content, publishedContent: args.content },
230
- });
231
- let publishErrcode = null;
232
- if (args.publish === true) {
233
- const published = await wireCall(session, {
234
- path: ENDPOINTS.customPageContentPublish,
248
+ writeTool({
249
+ tools,
250
+ name: "custompage.content.update",
251
+ lane: LANE,
252
+ scope: T["custompage.content.update"].scope,
253
+ description: T["custompage.content.update"].description,
254
+ inputSchema: {
255
+ code: z.string().min(1),
256
+ content: z.string().describe("页面正文(JS/HTML)。大体积请用 --args-file 传整个 args 对象(路径必须在配置根 OMC_CONFIG_HOME/默认 cwd 内;临时文件请放 ./.scratch/),或用 --args - 从 stdin 读,避免命令行引号/长度限制"),
257
+ publish: z.boolean().optional().describe("true = 同一调用里再走 /page/content/publish(草稿 + 发布都写),默认 false 只存草稿"),
258
+ },
259
+ execute: async (session, args) => {
260
+ const performed = await wireCall(session, {
261
+ path: ENDPOINTS.customPageContentUpdate,
235
262
  method: "POST",
236
263
  body: { code: args.code, draftContent: args.content, publishedContent: args.content },
237
264
  });
238
- publishErrcode = published.errcode;
239
- }
240
- const readback = (await wireCall(session, { path: ENDPOINTS.customPageContentGet, query: { code: args.code } })).data;
241
- const draft = typeof readback?.draftContent === "string" ? readback.draftContent : null;
242
- const publishedContent = typeof readback?.publishedContent === "string" ? readback.publishedContent : null;
243
- return {
244
- performed: { errcode: performed.errcode, ...(publishErrcode === null ? {} : { publishErrcode }) },
245
- readback: {
246
- code: args.code,
247
- existDraft: readback?.existDraft ?? null,
248
- published: readback?.published ?? null,
249
- draftMatches: draft === args.content,
250
- publishedMatches: publishedContent === args.content,
251
- ...(publishErrcode === null ? { publishHint: "只写了草稿:门户读 publishedContent。要上线再 custompage.content.publish(或本工具 publish:true)" } : {}),
252
- },
253
- };
265
+ let publishErrcode = null;
266
+ if (args.publish === true) {
267
+ const published = await wireCall(session, {
268
+ path: ENDPOINTS.customPageContentPublish,
269
+ method: "POST",
270
+ body: { code: args.code, draftContent: args.content, publishedContent: args.content },
271
+ });
272
+ publishErrcode = published.errcode;
273
+ }
274
+ const readback = (await wireCall(session, { path: ENDPOINTS.customPageContentGet, query: { code: args.code } })).data;
275
+ const draft = typeof readback?.draftContent === "string" ? readback.draftContent : null;
276
+ const publishedContent = typeof readback?.publishedContent === "string" ? readback.publishedContent : null;
277
+ return {
278
+ performed: { errcode: performed.errcode, ...(publishErrcode === null ? {} : { publishErrcode }) },
279
+ readback: {
280
+ code: args.code,
281
+ existDraft: readback?.existDraft ?? null,
282
+ published: readback?.published ?? null,
283
+ draftMatches: draft === args.content,
284
+ publishedMatches: publishedContent === args.content,
285
+ ...(publishErrcode === null ? { publishHint: "只写了草稿:门户读 publishedContent。要上线再 custompage.content.publish(或本工具 publish:true)" } : {}),
286
+ },
287
+ };
288
+ },
254
289
  });
255
- writeTool(tools, "custompage.content.publish", LANE, T["custompage.content.publish"].scope, T["custompage.content.publish"].description, {
256
- code: z.string().min(1),
257
- content: z.string().optional().describe("要发布的正文;省略则把当前 draftContent 发布出去"),
258
- }, async (session, args) => {
259
- const current = (await wireCall(session, { path: ENDPOINTS.customPageContentGet, query: { code: args.code } })).data;
260
- const draft = typeof current?.draftContent === "string" ? current.draftContent : null;
261
- const content = args.content ?? draft;
262
- if (content === null)
263
- throw new ToolRefusalError({ status: "refused", blocker: "custompage-content-absent", code: args.code, message: `自定义页 ${args.code} 没有草稿正文,且本次未传 content;先 custompage.content.update` });
264
- const performed = await wireCall(session, {
265
- path: ENDPOINTS.customPageContentPublish,
266
- method: "POST",
267
- body: { code: args.code, draftContent: draft ?? content, publishedContent: content },
268
- });
269
- const readback = (await wireCall(session, { path: ENDPOINTS.customPageContentGet, query: { code: args.code } })).data;
270
- const publishedContent = typeof readback?.publishedContent === "string" ? readback.publishedContent : null;
271
- return {
272
- performed: { errcode: performed.errcode },
273
- readback: { code: args.code, published: readback?.published ?? null, publishedMatches: publishedContent === content, publishedBytes: publishedContent === null ? 0 : Buffer.byteLength(publishedContent, "utf8") },
274
- };
290
+ writeTool({
291
+ tools,
292
+ name: "custompage.content.publish",
293
+ lane: LANE,
294
+ scope: T["custompage.content.publish"].scope,
295
+ description: T["custompage.content.publish"].description,
296
+ inputSchema: {
297
+ code: z.string().min(1),
298
+ content: z.string().optional().describe("要发布的正文;省略则把当前 draftContent 发布出去"),
299
+ },
300
+ execute: async (session, args) => {
301
+ const current = (await wireCall(session, { path: ENDPOINTS.customPageContentGet, query: { code: args.code } })).data;
302
+ const draft = typeof current?.draftContent === "string" ? current.draftContent : null;
303
+ const content = args.content ?? draft;
304
+ if (content === null)
305
+ throw new ToolRefusalError({ status: "refused", blocker: "custompage-content-absent", code: args.code, message: `自定义页 ${args.code} 没有草稿正文,且本次未传 content;先 custompage.content.update` });
306
+ const performed = await wireCall(session, {
307
+ path: ENDPOINTS.customPageContentPublish,
308
+ method: "POST",
309
+ body: { code: args.code, draftContent: draft ?? content, publishedContent: content },
310
+ });
311
+ const readback = (await wireCall(session, { path: ENDPOINTS.customPageContentGet, query: { code: args.code } })).data;
312
+ const publishedContent = typeof readback?.publishedContent === "string" ? readback.publishedContent : null;
313
+ return {
314
+ performed: { errcode: performed.errcode },
315
+ readback: { code: args.code, published: readback?.published ?? null, publishedMatches: publishedContent === content, publishedBytes: publishedContent === null ? 0 : Buffer.byteLength(publishedContent, "utf8") },
316
+ };
317
+ },
275
318
  });
276
- writeTool(tools, "custompage.delete", LANE, T["custompage.delete"].scope, T["custompage.delete"].description, {
277
- code: z.string().min(1),
278
- }, async (session, args) => {
279
- const current = await readPageOrNull(session, args.code);
280
- if (current === null)
281
- return { performed: { skipped: "already-absent" }, readback: { code: args.code, absent: true } };
282
- const performed = await wireCall(session, { path: ENDPOINTS.customPageDelete, query: { code: args.code } });
283
- const readback = await readPageOrNull(session, args.code);
284
- return { performed: { errcode: performed.errcode }, readback: { code: args.code, absent: readback === null } };
319
+ writeTool({
320
+ tools,
321
+ name: "custompage.delete",
322
+ lane: LANE,
323
+ scope: T["custompage.delete"].scope,
324
+ description: T["custompage.delete"].description,
325
+ inputSchema: {
326
+ code: z.string().min(1),
327
+ },
328
+ execute: async (session, args) => {
329
+ const current = await readPageOrNull(session, args.code);
330
+ if (current === null)
331
+ return { performed: { skipped: "already-absent" }, readback: { code: args.code, absent: true } };
332
+ const performed = await wireCall(session, { path: ENDPOINTS.customPageDelete, query: { code: args.code } });
333
+ const readback = await readPageOrNull(session, args.code);
334
+ return { performed: { errcode: performed.errcode }, readback: { code: args.code, absent: readback === null } };
335
+ },
285
336
  });
286
337
  }
287
338
  //# sourceMappingURL=custom-page.js.map
@@ -83,8 +83,8 @@ export const DATARULE_LANE_CONTRACT = Object.freeze({
83
83
  }),
84
84
  "datarule.create": Object.freeze({
85
85
  scope: DATARULE_SCOPE,
86
- description: describeTool("Create a field-level data rule (bizdatarule/create) and read it back from the authoritative list.", [
87
- "`dataRuleType`: 1 校验 / 2 计算拼接 / 3 显示 / 4 必填 / 5 只读 (8 EXTENSION is out of scope).",
86
+ description: describeTool("Create a field-level data rule (bizdatarule/create) and read it back from the authoritative list. **表单运行时条件优先 `field.update`**(本工具=字段级引擎/设计器读面)。", [
87
+ "`dataRuleType` → options 键:1→conditions/prompt/regexp/maxLength(+checkType);2→shortTextStitch|expression;3→displayFormula;4→requiredFormula(无条件必填=`\"true\"`);5→readonlyCondition。8 EXTENSION out of scope.",
88
88
  "**⚠️ type3/4/5 行的两个读面**:门户**运行时**读**字段 options**(`requiredFormula`/`readonlyCondition`/`readonlyFormula`/`displayFormula`,`field.create/update` 写);表单设计器**设计态** Set/Unset 读 **bizdatarule/list**(本行,network capture 2026-09-23)。**实测:type3/4/5 规则单独不会让门户表单控件变必填/只读**(中文或符号操作符都一样),符号式经 `historyOpt` 镜像还会让表单渲染崩溃——运行时条件请用中文操作符。表单条件优先走 `field.update`:它会**自动配对**本行(readback.formConditionSync)。本工具用于字段级引擎规则或独立增删改。",
89
89
  "**类型3/4/5 条件串协议(平台 create 不校验,坏形状落库即雷,工具按此协议拒绝)**:`{字段code} 操作符 [值]`。操作符取 **bizdatarule 词表**(与字段 options 的表单交互引擎不同——那是符号式):**中文** 等于/不等于/大于/小于/大于等于/小于等于/包含/不包含/包含全部/不包含全部/包含任意一个/不包含任意一个/等于任意一个/属于/不属于/介于/为空/不为空(实测 146 条真实规则以此为主),也接受符号 `==` `!=` `>` `<` `>=` `<=` `Contains` `IsEmpty` 等。为空/不为空**不带值**(`{field} 不为空`);其余带值(JSON 对象或标量)。多条件 ` && `、多组 ` || `。示例 type4:`options:{requiredFormula:\"{amount} 等于 {\\\"valueType\\\":\\\"specifyOption\\\",\\\"value\\\":\\\"A\\\"}\"}`;无条件必填用字面量 \"true\"。**字段绑定用 `propertyCodes`(数组)**,工具自动把 `options.propertyCodes` 也补上;存量的坏规则由 datarule.list 以 shapeWarning 标出。",
90
90
  "`options` is a JSON object sent to the wire as a JSON string; the engine reads `requiredFormula` (4), `readonlyCondition` (5), `displayFormula` (3), `verifyFormula` (1) and `shortTextStitch` (2).",