@stalfh233/omc-cli 0.4.73 → 0.4.75

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.
@@ -337,12 +337,54 @@
337
337
  "workflow.update",
338
338
  "workflow.publish",
339
339
  "workflow.bindRule",
340
+ "workflow.setButtons",
340
341
  "workflow.delete",
341
342
  "configure_workflow",
342
- "workflow.validate"
343
+ "workflow.validate",
344
+ "org.role.users",
345
+ "org.role.userScope",
346
+ "org.roleGroup.create",
347
+ "org.role.create",
348
+ "org.role.update",
349
+ "org.role.addUsers",
350
+ "org.role.removeUsers",
351
+ "org.role.setUserScope",
352
+ "org.role.delete",
353
+ "org.roleGroup.delete",
354
+ "org.user.transferDepartment",
355
+ "org.department.create",
356
+ "org.department.update",
357
+ "org.department.delete",
358
+ "org.department.users",
359
+ "org.user.create",
360
+ "org.user.update",
361
+ "org.user.delete",
362
+ "org.user.info",
363
+ "org.user.departments"
343
364
  ],
344
365
  "scopes": [
345
- "greenfield workflow authoring"
366
+ "greenfield workflow authoring",
367
+ "workflow node form-button checkboxes",
368
+ "organization role member read (role/users)",
369
+ "organization role member management scope read (role/get_ouscope)",
370
+ "organization role group create (role/add_role_group)",
371
+ "organization role create (role/add_role)",
372
+ "organization role update (role/modify_role)",
373
+ "organization role member add (role/add_role_user)",
374
+ "organization role member remove (role/strike_out_role_user)",
375
+ "organization role member scope update (role/update_ouscope)",
376
+ "organization role delete (role/strike_out_role)",
377
+ "organization role group delete (role/strike_out_role_group)",
378
+ "organization user department transfer (user/dept_transfer)",
379
+ "organization department create (department/add)",
380
+ "organization department update (department/modefied)",
381
+ "organization department delete (department/strike_out)",
382
+ "organization department member read (department/list_user)",
383
+ "organization user create (user/add)",
384
+ "organization user update (user/modify)",
385
+ "organization user delete (user/strike_out)",
386
+ "organization user read (user/info)",
387
+ "organization user department read (user/departments)"
346
388
  ]
347
389
  },
348
390
  {
@@ -456,10 +498,12 @@
456
498
  "rule.disable",
457
499
  "rule.delete",
458
500
  "rule.disableMany",
501
+ "rule.run",
459
502
  "configure_rule"
460
503
  ],
461
504
  "scopes": [
462
- "enable invoke disable delete"
505
+ "enable invoke disable delete",
506
+ "manual rule execution (button trigger)"
463
507
  ]
464
508
  },
465
509
  {
@@ -816,11 +860,13 @@
816
860
  "form.get",
817
861
  "form.draft",
818
862
  "form.publish",
819
- "configure_form"
863
+ "configure_form",
864
+ "form.runButton"
820
865
  ],
821
866
  "scopes": [
822
867
  "publishedHtmlJson authoritative readback",
823
- "draft publish publishedHtmlJson readback"
868
+ "draft publish publishedHtmlJson readback",
869
+ "form toolbar button execution"
824
870
  ]
825
871
  },
826
872
  {
@@ -191,13 +191,22 @@ export const CAPABILITY_CUES = Object.freeze({
191
191
  model: ["模型", "对象", "主数据", "物理删除", "汇总表", "统计表", "汇总对象", "月度汇总表", "汇总模型"],
192
192
  field: ["字段", "属性", "子表", "明细", "下拉", "单选", "多选", "附件", "人员", "部门", "日期", "数值", "文本", "关联", "公式", "计算", "计算字段", "求和", "汇总", "合计", "回主表", "超链接", "地址", "表单层", "必填", "只读", "条件必填", "恒必填", "条件只读", "恒只读", "表单必填", "表单只读"],
193
193
  form: ["表单", "页面", "布局", "控件", "只读", "显示"],
194
- listview: ["列表", "视图", "查询列", "筛选", "表头", "甘特"],
195
- datarule: ["字段级规则", "控件规则", "校验规则", "计算拼接", "显示规则", "规则", "type1", "type2", "type3"],
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
@@ -28,6 +28,7 @@ import { cmdFormDefault } from "./form-default.js";
28
28
  if (process.argv.includes("--utf8"))
29
29
  process.env.OMC_ASCII_STDOUT = "0";
30
30
  import { commandIndex, commandInventory, describeContract, describeContractFull, describeContractTool, describeExample, describeParam, describeSchemaOnly, describeTool, describeToolFull } from "./discovery.js";
31
+ import { repeatFullDescribe } from "./describe-cache.js";
31
32
  import { buildCapabilityOverview, capabilityDetail, compactCapabilities, executableCapabilityBundle, resolveCapabilityId, DISCOVERY_AUTHORITY } from "./capability-overview.js";
32
33
  import { goalRoute } from "./goal-routes.js";
33
34
  import { absencePointer } from "./capability-absences.js";
@@ -326,8 +327,15 @@ async function cmdCall(store, args) {
326
327
  validateAgentPaths(parsedArgs);
327
328
  }
328
329
  catch (error) {
329
- console.error(`--args 必须是合法 JSON:${error instanceof Error ? error.message : String(error)}(Windows PowerShell 下内联 JSON 引号转义不可靠,请改用 --args-file <path> 写入临时文件后引用)`);
330
- writeEnvelope(envelope("error", {}, request, { code: "invalid-input", message: error instanceof Error ? error.message : String(error), retryable: false }));
330
+ const detail = error instanceof Error ? error.message : String(error);
331
+ // r10 F9: an out-of-root `--args-file` was reported under "--args 必须是
332
+ // 合法 JSON", which points the reader at the wrong flag. Name the real
333
+ // cause (and the configuration root) when the failure came from the file.
334
+ const message = /must be inside the configuration root/.test(detail)
335
+ ? `--args-file 越界:${detail}(只接受配置根 ${resolve(process.env.OMC_CONFIG_HOME ?? process.cwd())} 内的路径;把参数文件放进项目目录后重试)`
336
+ : `--args 必须是合法 JSON:${detail}(Windows PowerShell 下内联 JSON 引号转义不可靠,请改用 --args-file <path> 写入临时文件后引用)`;
337
+ console.error(message);
338
+ writeEnvelope(envelope("error", {}, request, { code: "invalid-input", message: detail, retryable: false }));
331
339
  return CLI_EXIT_CODES.invalidInput;
332
340
  }
333
341
  const registry = buildToolRegistry();
@@ -630,8 +638,13 @@ function cmdDiscovery(kind, transport, value, options = {}) {
630
638
  result = describeParam(registry, value ?? "", options.param);
631
639
  else if (options.example !== undefined)
632
640
  result = describeExample(registry, value ?? "", typeof options.example === "string" ? options.example : undefined);
633
- else if (options.full)
634
- result = describeToolFull(registry, value ?? "");
641
+ else if (options.full) {
642
+ const full = describeToolFull(registry, value ?? "");
643
+ // r9: the same `--full` contract was re-shipped 8 times (134 KB) while the
644
+ // caller only wanted to re-check a shape. Within a project session the
645
+ // second identical read returns an artifact reference instead of the body.
646
+ result = full ? (repeatFullDescribe(value ?? "", full) ?? full) : full;
647
+ }
635
648
  else
636
649
  result = describeTool(registry, value ?? "");
637
650
  if (!result) {
@@ -1168,7 +1181,15 @@ const request = requestId();
1168
1181
  main().then((code) => {
1169
1182
  // Commands that own their stdout never grow a trailing envelope. `--version`/
1170
1183
  // `-V` are here because their short-circuit already wrote the bare version.
1171
- if (argv[2] === "call" || argv[2] === "script" || argv[2] === "artifact" || argv[2] === "commands" || argv[2] === "describe" || argv[2] === "contract" || argv[2] === "capabilities" || argv[2] === "capability" || argv[2] === "next" || argv[2] === "usage" || argv[2] === "intent" || argv[2] === "help" || argv[2] === "--version" || argv[2] === "-V") {
1184
+ // Composers (`form-default`, `compute`, `rule-when`, `approval-flow`) own their
1185
+ // stdout exactly like `call` does. Without them in this list every composer
1186
+ // printed its real envelope AND a trailing `{"ok":true,"result":{}}` — r10 read
1187
+ // that as a second, empty answer. `rollback` / `env` emit their own
1188
+ // envelope (or, for `env --json`, their own JSON) and were printing a second
1189
+ // empty envelope after it (live 2026-10-23: `omc rollback apply` answered two
1190
+ // lines, violating the one-envelope stdout contract).
1191
+ 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"];
1192
+ if (ownsStdout.includes(argv[2] ?? "")) {
1172
1193
  completeUsage(code);
1173
1194
  finish(code);
1174
1195
  return;
@@ -0,0 +1,91 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { join, resolve } from "node:path";
3
+ import { createHash } from "node:crypto";
4
+ import { artifactRoot, joinConfigRoot } from "./command-transport.js";
5
+ import { createArtifact, readArtifact } from "./artifacts.js";
6
+ import { projectSessionActive } from "./usage-log.js";
7
+ /**
8
+ * Repeat-read suppression for `describe <tool> --full`.
9
+ *
10
+ * r9 (2026-10-22) spent 134 KB on EIGHT `describe rule.save --full` calls: the
11
+ * same contract, unchanged, re-shipped every time the caller re-checked a shape.
12
+ * The hint at the bottom of the response ("先试 --example/--param") did not help
13
+ * because a caller editing a graph wants the authoritative schema again, and
14
+ * `--full` had no memory.
15
+ *
16
+ * The first `--full` in a project still returns the whole contract and spills a
17
+ * copy to an artifact. A later identical read returns `{artifactId, sha256,
18
+ * bytes, fetch}` instead: the caller can project the one key it needs with
19
+ * `omc artifact get <id> --jq <path>` instead of paying for the whole body
20
+ * again. Identity is the CONTENT hash, so a changed contract (new version, new
21
+ * field) always returns the full body again.
22
+ *
23
+ * Scoped to a project session by construction: the cache only exists where the
24
+ * usage log does (`.omc-env` in the working directory, or OMC_USAGE_LOG). Repo
25
+ * tests and the golden master run without it, so their bytes stay stable.
26
+ */
27
+ const CACHE_FILE = "describe-full-cache.json";
28
+ const CACHE_TTL_MS = 6 * 60 * 60 * 1000;
29
+ function cachePath() {
30
+ return resolve(joinConfigRoot(), ".omc", CACHE_FILE);
31
+ }
32
+ function readCache() {
33
+ try {
34
+ const parsed = JSON.parse(readFileSync(cachePath(), "utf8"));
35
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
36
+ }
37
+ catch {
38
+ return {};
39
+ }
40
+ }
41
+ function writeCache(cache) {
42
+ try {
43
+ const path = cachePath();
44
+ mkdirSync(join(path, ".."), { recursive: true });
45
+ writeFileSync(path, `${JSON.stringify(cache)}\n`, { mode: 0o600 });
46
+ }
47
+ catch {
48
+ // Best-effort: a cache write must never fail the command.
49
+ }
50
+ }
51
+ /**
52
+ * Return the compact repeat-read face for `tool`, or `undefined` when this is
53
+ * the first read (or the contract changed). Records the new entry as a side
54
+ * effect so the NEXT read is compact.
55
+ */
56
+ export function repeatFullDescribe(tool, result) {
57
+ // No project session (repo tests, golden master, one-off runs) ⇒ no cache:
58
+ // those callers need byte-stable output, and a cache write would be litter.
59
+ if (!projectSessionActive())
60
+ return undefined;
61
+ const body = `${JSON.stringify(result, null, 2)}\n`;
62
+ const bytes = Buffer.byteLength(body, "utf8");
63
+ const digest = createHash("sha256").update(body).digest("hex");
64
+ const cache = readCache();
65
+ const previous = cache[tool];
66
+ if (previous && previous.sha256 === digest && Date.now() - previous.at < CACHE_TTL_MS) {
67
+ // A pruned artifact would turn the summary into a dead end; re-ship instead.
68
+ if (existsSync(join(artifactRoot(), `${previous.artifactId}.artifact`))) {
69
+ try {
70
+ readArtifact(artifactRoot(), previous.artifactId);
71
+ return {
72
+ name: tool,
73
+ repeated: true,
74
+ artifactId: previous.artifactId,
75
+ sha256: digest,
76
+ bytes,
77
+ fetch: `omc artifact get ${previous.artifactId} --summary(再 --keys/--jq 取需要的键)`,
78
+ note: `本项目的会话里已读过同一个契约(${bytes}B,内容 sha256 未变),本次不再重发全文;用 fetch 里的命令按需投影`,
79
+ };
80
+ }
81
+ catch {
82
+ // fall through to a full re-ship
83
+ }
84
+ }
85
+ }
86
+ const artifact = createArtifact(artifactRoot(), body);
87
+ cache[tool] = { tool, sha256: digest, bytes, artifactId: artifact.artifactId, at: Date.now() };
88
+ writeCache(cache);
89
+ return undefined;
90
+ }
91
+ //# sourceMappingURL=describe-cache.js.map
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",
@@ -120,6 +144,28 @@ export const ENDPOINTS = {
120
144
  organizationRoleList: "/api/api/organization/role/list",
121
145
  organizationDepartmentTree: "/api/api/organization/department/tree",
122
146
  organizationUserSearch: "/api/api/organization/user/search",
147
+ organizationRoleUsers: "/api/api/organization/role/users",
148
+ organizationRoleGetOuScope: "/api/api/organization/role/get_ouscope",
149
+ organizationRoleUpdateOuScope: "/api/api/organization/role/update_ouscope",
150
+ organizationRoleAddGroup: "/api/api/organization/role/add_role_group",
151
+ organizationRoleAdd: "/api/api/organization/role/add_role",
152
+ organizationRoleModify: "/api/api/organization/role/modify_role",
153
+ organizationRoleGet: "/api/api/organization/role/get",
154
+ organizationDepartmentAdd: "/api/api/organization/department/add",
155
+ organizationDepartmentModify: "/api/api/organization/department/modefied",
156
+ organizationDepartmentDelete: "/api/api/organization/department/strike_out",
157
+ organizationDepartmentGet: "/api/api/organization/department/get_dept_info",
158
+ organizationDepartmentUsers: "/api/api/organization/department/list_user",
159
+ organizationUserAdd: "/api/api/organization/user/add",
160
+ organizationUserModify: "/api/api/organization/user/modify",
161
+ organizationUserDelete: "/api/api/organization/user/strike_out",
162
+ organizationUserInfo: "/api/api/organization/user/info",
163
+ organizationUserDepartments: "/api/api/organization/user/departments",
164
+ organizationRoleAddUser: "/api/api/organization/role/add_role_user",
165
+ organizationRoleRemoveUser: "/api/api/organization/role/strike_out_role_user",
166
+ organizationRoleDelete: "/api/api/organization/role/strike_out_role",
167
+ organizationRoleGroupDelete: "/api/api/organization/role/strike_out_role_group",
168
+ organizationUserDeptTransfer: "/api/api/organization/user/dept_transfer",
123
169
  listQueryCreate: "/api/api/app/query/create",
124
170
  listQueryGet: "/api/api/app/query/get",
125
171
  listQueryUpdate: "/api/api/app/query/update",
@@ -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
- const getAll = await runToolCall("form.get", { args: { schemaCode: schema, controls: true, ...(environment ? { environment } : {}) }, requestId: request, environment });
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 fullExtracted = extractControls(getAll.result ?? {});
84
- if (!fullExtracted)
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 fullControls = fullExtracted.controls.map((c) => (String(c.key ?? c.dataItemName ?? "") === field ? nextControl : c));
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: { schemaCode: schema, draftAttributesJson: JSON.stringify(fullControls), ...(environment ? { environment } : {}) },
89
- requestId: request, environment,
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);