@stalfh233/omc-cli 0.4.80 → 0.4.82
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/README.md +3 -1
- package/coverage/m1-coverage-manifest-v1.json +4 -0
- package/dist/capability-overview.js +7 -0
- package/dist/cli.js +7 -2
- package/dist/endpoints.js +12 -0
- package/dist/lanes/rule-graph.js +14 -0
- package/dist/lanes/runtime-data.js +2 -2
- package/dist/lanes/workflow-contract.js +16 -1
- package/dist/lanes/workflow-coverage.js +3 -0
- package/dist/lanes/workflow.js +256 -5
- package/dist/runtime-support.js +92 -1
- package/dist/usage-log.js +6 -1
- package/package.json +3 -3
- package/skills/omc-capabilities.md +1 -1
- package/skills/omc-requirement-vocabulary.md +6 -2
package/README.md
CHANGED
|
@@ -130,10 +130,12 @@ omc call discover_environment --env <env> --args '{}' # 环境就绪(真实
|
|
|
130
130
|
- **intent**:`partial` 表示该能力可声明式(`omc intent`)覆盖一部分——通常 create/update;
|
|
131
131
|
`none` 表示只走 `omc call`。删除类、方法面、分类/连接池等仍走 `omc call` 逃生舱。
|
|
132
132
|
|
|
133
|
-
## 近期能力(0.4.74 → 0.4.
|
|
133
|
+
## 近期能力(0.4.74 → 0.4.82)
|
|
134
134
|
|
|
135
135
|
| 版本 | 新增 / 修复 |
|
|
136
136
|
|---|---|
|
|
137
|
+
| 0.4.82 | **流程发起权限(40009)**:新增 `workflow.permissions` / `workflow.addPermission` / `workflow.removePermission`(`/workflow/list_permission`、`create_permission`、`delete_permission`,unitType 线上为枚举序号 1=部门 2=角色 3=用户)。`data.submit`/`data.save+workflowCode` 的 40009 现在直接回 `workflow-start-permission-denied`:带上该流程的权限行现状、两种成因(无权限行 / 发起人部门记录已失效)与可粘贴的修复命令,不再是「流程发起权限不足」五个字 |
|
|
138
|
+
| 0.4.81 | **r11 真机探针修复**:`rule.save` 对 `AGGREGATION` 规则节点**写前 fail-closed**(平台会存进去、运行时 50000,真实路径是 `omc compute`/datarule type2);`omc next "统计子表行数"` 路由补 `datarule` 阶梯与 `field-compute-subtable-sum` 配方;`org.user.search` 省略关键字时**按部门树列全员**(平台 `wd` 不支持空串,此前只能枚举 0–9);`omc artifact get --jq` 无值回键清单(不再 commander 报错);`usage` 的 `faces` 纳入 `--jq/--summary/--keys/--path-only/--extract`(此前读面投影完全不记账) |
|
|
137
139
|
| 0.4.80 | **省 token 与用量账本**:`omc help <topic>` 默认只回节索引(约 1KB,含每节字节数),`--all` 才读整篇;`omc artifact get` 正文 >8KB 默认只回形状(`--full` 才整包);`omc usage` 默认只回汇总(`byTool`/`redundancy`/`estimatedTokens`),`--records` 才展开;`omc script` 的**每行工具调用单独记账**(`script-call` + `parentId`);未绑定环境的目录回落 `$OMC_CONFIG_HOME/usage/<cwd-hash>.jsonl` |
|
|
138
140
|
| 0.4.79 | README 对齐 0.4.79 实际工具面(142 工具 / 16 能力) |
|
|
139
141
|
| 0.4.78 | **按钮 JS 直接读写**:`listview.get --buttonCode/--buttons`、`listview.configure --buttonScripts`、`form.get --buttonScripts`、`form.publish --buttonScripts`,外加 `buttons[].script` 简写 |
|
|
@@ -339,6 +339,9 @@
|
|
|
339
339
|
"workflow.bindRule",
|
|
340
340
|
"workflow.setButtons",
|
|
341
341
|
"workflow.delete",
|
|
342
|
+
"workflow.permissions",
|
|
343
|
+
"workflow.addPermission",
|
|
344
|
+
"workflow.removePermission",
|
|
342
345
|
"configure_workflow",
|
|
343
346
|
"workflow.validate",
|
|
344
347
|
"org.role.users",
|
|
@@ -365,6 +368,7 @@
|
|
|
365
368
|
"scopes": [
|
|
366
369
|
"greenfield workflow authoring",
|
|
367
370
|
"workflow node form-button checkboxes",
|
|
371
|
+
"workflow start permission (who may originate)",
|
|
368
372
|
"organization role member read (role/users)",
|
|
369
373
|
"organization role member management scope read (role/get_ouscope)",
|
|
370
374
|
"organization role group create (role/add_role_group)",
|
|
@@ -210,7 +210,14 @@ export const CAPABILITY_CUES = Object.freeze({
|
|
|
210
210
|
// 日期) because no cue named the VALIDATION intent, so the agent went to the
|
|
211
211
|
// rule graph and concluded "the platform has no today variable" — wrong, and
|
|
212
212
|
// the form designer's 提交校验 (datarule type1/checkType2) is the real face.
|
|
213
|
+
// r11 (2026-10-24): "统计子表行数聚合 COUNT 写入主表字段" routed to `field`
|
|
214
|
+
// (it matched 子表+字段) and answered with the "create a child table" recipe,
|
|
215
|
+
// so the agent hand-built an AGGREGATION rule node — a shape the platform
|
|
216
|
+
// accepts and then fails at runtime. Row aggregation is a FIELD COMPUTE RULE
|
|
217
|
+
// (`omc compute` / datarule type2). These compound cues outscore field's
|
|
218
|
+
// 子表+字段 so the aggregate intent reaches the right ladder.
|
|
213
219
|
datarule: ["字段级规则", "控件规则", "校验规则", "计算拼接", "显示规则", "规则", "type1", "type2", "type3",
|
|
220
|
+
"统计子表", "子表行数", "子表计数", "子表求和", "子表汇总", "行数统计", "计数写回", "汇总回主表", "求和回主表", "聚合计算",
|
|
214
221
|
"不早于", "不能早于", "不晚于", "不能晚于", "早于今天", "晚于今天", "日期校验", "提交校验", "校验不通过", "必填校验", "正则校验", "重复校验", "长度校验"],
|
|
215
222
|
// NOTE: bare "自动" was a cue (round 10 bug: "自动识别"/"自动发邮件" matched
|
|
216
223
|
// businessrule and routed an UNSUPPORTED ask onto the write ladder). A business
|
package/dist/cli.js
CHANGED
|
@@ -1231,8 +1231,13 @@ async function main() {
|
|
|
1231
1231
|
console.error(`已清理 ${result.removed} 个读回产物,释放 ${formatBytes(result.freedBytes)};保留 ${result.kept} 个(${formatBytes(result.keptBytes)})`);
|
|
1232
1232
|
commandExitCode = CLI_EXIT_CODES.ok;
|
|
1233
1233
|
});
|
|
1234
|
-
artifactCommand.command("get <id>").option("--jq
|
|
1235
|
-
|
|
1234
|
+
artifactCommand.command("get <id>").option("--jq [path]").option("--path-only").option("--summary").option("--keys [a,b]").option("--full", "整包返回正文(默认:正文超过 8KB 时只回形状 outline + fetch,按需 --jq/--keys 定向取值)").option("--max-output-bytes <bytes>").action(async (id, options) => {
|
|
1235
|
+
// A bare `--jq`/`--keys` is a key-inventory request, not a missing argument:
|
|
1236
|
+
// commander's "argument missing" (invalid-input + retry) is a dead end the
|
|
1237
|
+
// caller cannot fix without already knowing the flag takes a value (r11
|
|
1238
|
+
// burned six rounds on `artifact get <id> --jq` before trying `--summary`).
|
|
1239
|
+
const bareJq = options.jq === true;
|
|
1240
|
+
commandExitCode = await withTransport(options, request, (transport) => cmdArtifactGet(id, transport, { ...(typeof options.jq === "string" ? { jq: options.jq } : {}), pathOnly: options.pathOnly === true, summary: options.summary === true || options.keys === true || bareJq, ...(typeof options.keys === "string" ? { keys: options.keys } : {}), full: options.full === true }));
|
|
1236
1241
|
});
|
|
1237
1242
|
command.command("bench").option("--live-sample").option("--env <environment>").action(async (options) => { commandExitCode = await cmdBench([...(options.liveSample ? ["--live-sample"] : []), ...(options.env ? ["--env", options.env] : [])]); });
|
|
1238
1243
|
command.command("commands").option("--json").option("--compact").option("--filter <keyword>").option("--lane <id>").option("--max-output-bytes <bytes>").action(async (options) => { commandExitCode = await withTransport(options, request, (transport) => cmdDiscovery("commands", transport, undefined, { json: options.json === true, compact: options.compact === true, ...(options.filter ? { filter: options.filter } : {}), ...(options.lane ? { lane: options.lane } : {}) })); });
|
package/dist/endpoints.js
CHANGED
|
@@ -152,6 +152,18 @@ export const ENDPOINTS = {
|
|
|
152
152
|
workflowValidate: "/api/api/workflow/validate",
|
|
153
153
|
workflowPublish: "/api/api/workflow/publish",
|
|
154
154
|
workflowDelete: "/api/api/workflow/delete",
|
|
155
|
+
// 发起权限 (who may START the flow). Without a row here — or with a role/
|
|
156
|
+
// department row the caller is not in — `data.submit`/`data.save+workflowCode`
|
|
157
|
+
// answers 40009 流程发起权限不足 (WorkflowAspect.validateWorkflowPermission →
|
|
158
|
+
// WorkflowManagementFacadeImpl.getWorkflowTemplateHeadersBySchemaCode). Endpoints
|
|
159
|
+
// from WorkflowTemplateController (cloudpivot-engine 8.6.26; live 1.1.25):
|
|
160
|
+
// GET /workflow/list_permission?workflowCode= → [{id,name,unitType,fullName,corpId,code}]
|
|
161
|
+
// POST /workflow/create_permission body {workflowCode,unitId,unitType,corpId?}
|
|
162
|
+
// unitType is the ENUM INDEX on the wire: 1=部门 2=角色 3=用户 (UnitType @JsonValue)
|
|
163
|
+
// POST/DELETE /workflow/delete_permission?permissionId=
|
|
164
|
+
workflowListPermission: "/api/api/workflow/list_permission",
|
|
165
|
+
workflowCreatePermission: "/api/api/workflow/create_permission",
|
|
166
|
+
workflowDeletePermission: "/api/api/workflow/delete_permission",
|
|
155
167
|
workflowSearchWorkItems: "/api/api/runtime/workflow/search_workitems",
|
|
156
168
|
workflowGetBizObjectByWfInstanceId: "/api/api/runtime/workflow/getBizObjectByWfInstanceId",
|
|
157
169
|
workflowListInstanceActivity: "/api/api/runtime/workflow/list_workflow_instance_activity",
|
package/dist/lanes/rule-graph.js
CHANGED
|
@@ -1225,6 +1225,20 @@ export function validateDataAction(action, nodeCode, index) {
|
|
|
1225
1225
|
const blockers = [];
|
|
1226
1226
|
const where = `dataActions[${index}]`;
|
|
1227
1227
|
const valueType = String(action.ruleValueType ?? "");
|
|
1228
|
+
// 1.2 (r11 2026-10-24, 1.1.25): AGGREGATION on a rule node is a TRAP. The
|
|
1229
|
+
// platform accepts it (save + publish + enable all answer ok) and then throws
|
|
1230
|
+
// at runtime: the aggregate comes back as java.util.ArrayList and the scalar
|
|
1231
|
+
// target field rejects it (DecimalBizField.fillClientContent → 50000 服务异常).
|
|
1232
|
+
// Row aggregation is a FORMULA(100) FIELD feature, not a rule action. The r11
|
|
1233
|
+
// probe burned 6 rule.save + 4 data.submit + 3 docker-log reads before finding
|
|
1234
|
+
// the real path, and the only symptom the CLI ever showed was "服务异常".
|
|
1235
|
+
if (valueType.toUpperCase() === "AGGREGATION")
|
|
1236
|
+
return [{
|
|
1237
|
+
code: "rule-graph-aggregation-is-a-field-feature",
|
|
1238
|
+
nodeCode,
|
|
1239
|
+
key: `dataActions[${index}].ruleValueType`,
|
|
1240
|
+
message: `${nodeCode}: 规则节点不支持 AGGREGATION 赋值——平台会存进去并启用,但运行期聚合结果是集合(ArrayList),标量目标收到集合即 50000「服务异常」(DecimalBizField.fillClientContent)。**子表计数/求和/取最值回主表用字段计算规则**:目标字段建成普通数值字段,然后 \`omc compute --schema <主表> --target <目标字段> --formula 'COUNTIF({<子表code>.<子列code>},"异常")'\`(默认 dry-run,--apply 写入;等价 datarule.create dataRuleType:2,配方 \`field-compute-subtable-sum\`)。规则图里只做 FIXED/DYNAMIC/EXPRESSION 的直接赋值。注意计算由门户前端执行、API data.load 不回填(平台既有行为,见 FIELD_FORMULA_WIRE.runtimeVerification):要 API 侧断言结果,请在测试数据里显式写预算值,不要用回读判定公式对错。来源:r11 巡检沙盒 2026-10-24(1.1.25)。`,
|
|
1241
|
+
}];
|
|
1228
1242
|
if (!RULE_VALUE_TYPES.includes(valueType))
|
|
1229
1243
|
blockers.push({ code: "rule-graph-action-value-type", nodeCode, key: "ruleValueType", message: `${where}: ruleValueType "${valueType}" 不在 ${RULE_VALUE_TYPES.join("/")}` });
|
|
1230
1244
|
if (typeof action.targetDataItem !== "string" || !action.targetDataItem.trim())
|
|
@@ -699,7 +699,7 @@ export function registerRuntimeDataLane(tools) {
|
|
|
699
699
|
...(replayToken ? { replayToken } : {}),
|
|
700
700
|
bizObject: { schemaCode: args.schemaCode, sheetCode, data: { ...formData, id: objectId, sequenceStatus: "DRAFT" } },
|
|
701
701
|
},
|
|
702
|
-
}), formData);
|
|
702
|
+
}), formData, args.workflowCode);
|
|
703
703
|
const vo = (performed.data ?? {});
|
|
704
704
|
const returnedId = typeof vo.objectId === "string" && vo.objectId ? vo.objectId : typeof vo.bizObjectId === "string" && vo.bizObjectId ? vo.bizObjectId : objectId;
|
|
705
705
|
const workflowInstanceId = typeof vo.workflowInstanceId === "string" ? vo.workflowInstanceId : null;
|
|
@@ -1087,7 +1087,7 @@ export function registerRuntimeDataLane(tools) {
|
|
|
1087
1087
|
path: ENDPOINTS.runtimeFormSubmit,
|
|
1088
1088
|
method: "POST",
|
|
1089
1089
|
body: payload,
|
|
1090
|
-
}));
|
|
1090
|
+
}), undefined, hasWorkflowCode ? explicitWorkflowCode : undefined);
|
|
1091
1091
|
const performedData = (performed.data ?? {});
|
|
1092
1092
|
let readback = { formType };
|
|
1093
1093
|
if (formType === "2") {
|
|
@@ -174,6 +174,18 @@ export const WORKFLOW_LANE_CONTRACT = Object.freeze({
|
|
|
174
174
|
scope: WORKFLOW_SCOPE,
|
|
175
175
|
description: "删除工作流模板(`/api/api/workflow/delete`),回读确认缺失。",
|
|
176
176
|
}),
|
|
177
|
+
"workflow.permissions": Object.freeze({
|
|
178
|
+
scope: "workflow start permission (who may originate)",
|
|
179
|
+
description: "列出谁能发起该流程(`GET /api/api/workflow/list_permission?workflowCode=` → {id,name,unitType,fullName,corpId,code};1=部门 2=角色 3=用户)。**`data.submit` 报 40009 时先看这里**:PART_VISIABLE 且本表无命中行 ⇒ 除平台管理员(`h_perm_admin` 的 ADMIN/SYS_MNG,非登录 isAdmin)外无人能发起;发布流程不会自动建这一行。",
|
|
180
|
+
}),
|
|
181
|
+
"workflow.addPermission": Object.freeze({
|
|
182
|
+
scope: "workflow start permission (who may originate)",
|
|
183
|
+
description: "授予发起权限(`POST /api/api/workflow/create_permission`,body `{workflowCode,unitId,unitType,corpId}`;unitType 取枚举序号 1=部门 2=角色 3=用户)。unitId 用 org.department.list / org.role.list / org.user.search 取。重复授予按 402003 幂等跳过,写后回读确认。",
|
|
184
|
+
}),
|
|
185
|
+
"workflow.removePermission": Object.freeze({
|
|
186
|
+
scope: "workflow start permission (who may originate)",
|
|
187
|
+
description: "撤销发起权限(`POST /api/api/workflow/delete_permission?permissionId=`)。首选传 `permissionId`(`workflow.permissions` 的 id);也可传 `workflowCode`+`unitId`+`unitType`,工具解析该主体的显示名后匹配(`list_permission` 不回 unitId)。缺失即幂等成功。",
|
|
188
|
+
}),
|
|
177
189
|
"workflow.validate": Object.freeze({
|
|
178
190
|
scope: WORKFLOW_SCOPE,
|
|
179
191
|
description: "只读预检(`POST /api/api/workflow/validate`):合成草稿模板(或可选 `template` 按 merge 语义)后返回 ValidateMessage[],拆出 errors/warnings,`valid = 无 ERROR`。用于 publish 前先看平台会不会拒——平台对 publish 常回 errcode:0 但 ValidateMessage[] 里带 ERROR,本工具把它显式暴露,避免「publish 看似成功却没落库」。不写任何状态。",
|
|
@@ -192,7 +204,7 @@ export const WORKFLOW_LANE_CONTRACT = Object.freeze({
|
|
|
192
204
|
}),
|
|
193
205
|
"org.user.search": Object.freeze({
|
|
194
206
|
scope: "workflow participant resolution",
|
|
195
|
-
description: "按关键字搜索用户(`POST /api/api/organization/user/search`,body `{wd,page,size,filterType?}
|
|
207
|
+
description: "按关键字搜索用户(`POST /api/api/organization/user/search`,body `{wd,page,size,filterType?}`,返回 {id,name,username,userId,departmentId,corpId,status})。平台 `wd` 必填、空串报 10005,所以**省略 wd 时不打 user/search**:工具改为遍历部门树(`organization/department/tree` + `department/list_user`)聚合成全员清单(返回 `keyword:null` 与 `departmentsScanned`;>200 部门会带 `truncated`)。用于把「张三」解析成真实用户 id/username 再构造参与者对象(`[{\"code\":\"<username|userId>\",\"corpId\":\"<corpId|other>\",\"id\":\"<userId>\",\"name\":\"<姓名>\",\"type\":3}]`,type 3=用户 / 2=角色 / 多个用 `+` 连接);只要某部门的人用 org.department.users,角色用 org.role.list。",
|
|
196
208
|
}),
|
|
197
209
|
"workitem.list": Object.freeze({
|
|
198
210
|
scope: "runtime workflow work-item query",
|
|
@@ -223,6 +235,9 @@ export function assertWorkflowContract() {
|
|
|
223
235
|
assertDescriptionCarries(WORKFLOW_LANE_CONTRACT, "workflow.publish", ["workflow/publish", "template"]);
|
|
224
236
|
assertDescriptionCarries(WORKFLOW_LANE_CONTRACT, "workflow.setButtons", ["buttonPermission", "customBtns", "childTableBtns", "formButtonCodes", "form-button-absent", "publish:false"]);
|
|
225
237
|
assertDescriptionCarries(WORKFLOW_LANE_CONTRACT, "workflow.bindRule", ["startEventHandler", "cancelEventHandler", "activateEventHandler", "endEventHandler", "bizActions", "bizActionType", "BIZ_RULE", "publish:false", "enabledRuleCodes", "workflow-bind-rule-absent", "activityCode", "nodeEvent", "endActivity", "activityApproved"]);
|
|
238
|
+
assertDescriptionCarries(WORKFLOW_LANE_CONTRACT, "workflow.permissions", ["list_permission", "40009", "PART_VISIABLE"]);
|
|
239
|
+
assertDescriptionCarries(WORKFLOW_LANE_CONTRACT, "workflow.addPermission", ["create_permission", "unitType", "unitId"]);
|
|
240
|
+
assertDescriptionCarries(WORKFLOW_LANE_CONTRACT, "workflow.removePermission", ["delete_permission", "permissionId"]);
|
|
226
241
|
assertDescriptionCarries(WORKFLOW_LANE_CONTRACT, "org.role.list", ["organization/role/list", "type", "Originator"]);
|
|
227
242
|
assertDescriptionCarries(WORKFLOW_LANE_CONTRACT, "org.department.list", ["organization/department/tree", "parentId"]);
|
|
228
243
|
assertDescriptionCarries(WORKFLOW_LANE_CONTRACT, "org.user.search", ["organization/user/search", "wd", "type 3"]);
|
|
@@ -18,6 +18,9 @@ export const WORKFLOW_COVERAGE = declareLaneCoverage(WORKFLOW_LANE_CONTRACT.lane
|
|
|
18
18
|
claim("workflow.bindRule", T["workflow.bindRule"].scope),
|
|
19
19
|
claim("workflow.setButtons", T["workflow.setButtons"].scope),
|
|
20
20
|
claim("workflow.delete", T["workflow.delete"].scope),
|
|
21
|
+
claim("workflow.permissions", T["workflow.permissions"].scope),
|
|
22
|
+
claim("workflow.addPermission", T["workflow.addPermission"].scope),
|
|
23
|
+
claim("workflow.removePermission", T["workflow.removePermission"].scope),
|
|
21
24
|
claim("configure_workflow", T["configure_workflow"].scope),
|
|
22
25
|
claim("workflow.validate", T["workflow.validate"].scope),
|
|
23
26
|
// Organization writes (role group / role / members / management scope).
|
package/dist/lanes/workflow.js
CHANGED
|
@@ -2724,6 +2724,194 @@ export function registerWorkflowLane(tools) {
|
|
|
2724
2724
|
};
|
|
2725
2725
|
}),
|
|
2726
2726
|
}));
|
|
2727
|
+
// ── 发起权限 (who may originate) ────────────────────────────────────────────
|
|
2728
|
+
//
|
|
2729
|
+
// 40009 流程发起权限不足 is the single most opaque refusal on this platform:
|
|
2730
|
+
// `data.submit` (and `data.save` with workflowCode) answers it, the message
|
|
2731
|
+
// names no permission, and there is no CLI face to inspect or grant one. r11
|
|
2732
|
+
// (2026-10-24) burned 7 submit attempts and could not deliver the whole
|
|
2733
|
+
// workflow section; the platform source shows the check is concrete:
|
|
2734
|
+
// `WorkflowAspect.validateWorkflowPermission` → template header must exist and
|
|
2735
|
+
// be published → `getWorkflowTemplateHeadersBySchemaCode(userId, schemaCode)`:
|
|
2736
|
+
// • the user's root department must resolve (`getAllParentIds` non-empty),
|
|
2737
|
+
// • `visibleType=ALL_VISIABLE` templates are open to everyone,
|
|
2738
|
+
// • otherwise a `h_workflow_permission` row must match the user, their
|
|
2739
|
+
// department (or any ancestor), or one of their roles,
|
|
2740
|
+
// • platform admins (`h_perm_admin` ADMIN/SYS_MNG) bypass all of it.
|
|
2741
|
+
// Publishing a flow creates NO permission row, so a freshly published flow is
|
|
2742
|
+
// unusable by anyone who is not a platform admin.
|
|
2743
|
+
const permissionRows = async (session, workflowCode) => wireRows((await wireCall(session, { path: ENDPOINTS.workflowListPermission, query: { workflowCode } })).data);
|
|
2744
|
+
// `list_permission` echoes the PERMISSION row id plus the unit's display name
|
|
2745
|
+
// and code — never the unitId (WorkflowTemplateController.getPermissionMap).
|
|
2746
|
+
// So identity for readback/removal is resolved from the unit's own face.
|
|
2747
|
+
const unitIdentity = async (session, unitType, unitId) => {
|
|
2748
|
+
try {
|
|
2749
|
+
if (unitType === 1) {
|
|
2750
|
+
const row = (await wireCall(session, { path: ENDPOINTS.organizationDepartmentGet, query: { deptId: unitId } })).data;
|
|
2751
|
+
return { name: row ? String(row.name ?? "") || null : null, code: row ? (String(row.code ?? "") || null) : null };
|
|
2752
|
+
}
|
|
2753
|
+
if (unitType === 2) {
|
|
2754
|
+
const rows = wireRows((await wireCall(session, { path: ENDPOINTS.organizationRoleList, query: { expandAll: "true" } })).data);
|
|
2755
|
+
const walk = (entries) => {
|
|
2756
|
+
for (const entry of entries) {
|
|
2757
|
+
if (String(entry.id ?? "") === unitId)
|
|
2758
|
+
return entry;
|
|
2759
|
+
const children = Array.isArray(entry.children) ? entry.children : [];
|
|
2760
|
+
const hit = walk(children);
|
|
2761
|
+
if (hit)
|
|
2762
|
+
return hit;
|
|
2763
|
+
}
|
|
2764
|
+
return undefined;
|
|
2765
|
+
};
|
|
2766
|
+
const hit = walk(rows);
|
|
2767
|
+
return { name: hit ? String(hit.name ?? "") || null : null, code: hit ? (String(hit.code ?? "") || null) : null };
|
|
2768
|
+
}
|
|
2769
|
+
const row = (await wireCall(session, { path: ENDPOINTS.organizationUserInfo, query: { id: unitId } })).data;
|
|
2770
|
+
return { name: row ? String(row.name ?? "") || null : null, code: row ? (String(row.username ?? "") || null) : null };
|
|
2771
|
+
}
|
|
2772
|
+
catch {
|
|
2773
|
+
return { name: null, code: null };
|
|
2774
|
+
}
|
|
2775
|
+
};
|
|
2776
|
+
tools.push(defineTool({
|
|
2777
|
+
name: "workflow.permissions",
|
|
2778
|
+
layer: "low",
|
|
2779
|
+
kind: "read",
|
|
2780
|
+
lane: WORKFLOW_LANE,
|
|
2781
|
+
scope: WORKFLOW_TOOLS["workflow.permissions"].scope,
|
|
2782
|
+
description: WORKFLOW_TOOLS["workflow.permissions"].description,
|
|
2783
|
+
inputSchema: {
|
|
2784
|
+
environment: environmentArg,
|
|
2785
|
+
workflowCode: z.string().optional().describe("流程 code;省略则用 schemaCode 解析"),
|
|
2786
|
+
schemaCode: z.string().optional().describe("模型 code:省略 workflowCode 时用它解析该模型的流程"),
|
|
2787
|
+
capability: capabilityArg,
|
|
2788
|
+
},
|
|
2789
|
+
handler: async (ctx, args, self) => governedRead(ctx, self, args, async (session) => {
|
|
2790
|
+
const workflowCode = await resolveWorkflowArg(session, args);
|
|
2791
|
+
if (!workflowCode)
|
|
2792
|
+
return { schemaCode: args.schemaCode, exists: false, permissions: [], note: "该模型没有流程" };
|
|
2793
|
+
const rows = await permissionRows(session, workflowCode);
|
|
2794
|
+
const permissions = rows.map((row) => ({
|
|
2795
|
+
id: row.id ?? null,
|
|
2796
|
+
name: row.name ?? null,
|
|
2797
|
+
unitType: row.unitType ?? null,
|
|
2798
|
+
unitTypeName: row.unitType === 1 || row.unitType === "1" ? "部门" : row.unitType === 2 || row.unitType === "2" ? "角色" : row.unitType === 3 || row.unitType === "3" ? "用户" : null,
|
|
2799
|
+
fullName: row.fullName ?? null,
|
|
2800
|
+
corpId: row.corpId ?? null,
|
|
2801
|
+
code: row.code ?? null,
|
|
2802
|
+
}));
|
|
2803
|
+
return {
|
|
2804
|
+
workflowCode,
|
|
2805
|
+
count: permissions.length,
|
|
2806
|
+
permissions,
|
|
2807
|
+
...(permissions.length === 0
|
|
2808
|
+
? {
|
|
2809
|
+
blocker: "workflow-start-permission-empty",
|
|
2810
|
+
note: "该流程没有任何发起权限行:除平台管理员(h_perm_admin 里 ADMIN/SYS_MNG)外**没有人能发起**,data.submit 会回 40009「流程发起权限不足」。发布流程不会自动建这一行,用 workflow.addPermission 授给部门/角色/用户(或把模板 visibleType 改成 ALL_VISIABLE)。",
|
|
2811
|
+
nextCommand: `omc call workflow.addPermission --env <env> --args '{"workflowCode":"${workflowCode}","unitId":"<部门|角色|用户 id>","unitType":1}'`,
|
|
2812
|
+
}
|
|
2813
|
+
: {}),
|
|
2814
|
+
};
|
|
2815
|
+
}),
|
|
2816
|
+
}));
|
|
2817
|
+
writeTool(tools, "workflow.addPermission", WORKFLOW_LANE, WORKFLOW_TOOLS["workflow.addPermission"].scope, WORKFLOW_TOOLS["workflow.addPermission"].description, {
|
|
2818
|
+
workflowCode: z.string().optional().describe("流程 code;省略则用 schemaCode 解析"),
|
|
2819
|
+
schemaCode: z.string().optional().describe("模型 code:省略 workflowCode 时用它解析该模型的流程"),
|
|
2820
|
+
unitId: z.string().min(1).describe("部门 id / 角色 id / 用户 id(看 unitType)"),
|
|
2821
|
+
unitType: z.union([z.literal(1), z.literal(2), z.literal(3), z.literal("DEPARTMENT"), z.literal("ROLE"), z.literal("USER")]).describe("1=部门 2=角色 3=用户;也接受 DEPARTMENT/ROLE/USER"),
|
|
2822
|
+
corpId: z.string().optional().describe("企业 id;省略则用当前会话企业"),
|
|
2823
|
+
}, async (session, args) => {
|
|
2824
|
+
const workflowCode = await resolveWorkflowArg(session, args);
|
|
2825
|
+
if (!workflowCode)
|
|
2826
|
+
throw new ToolRefusalError({ status: "refused", blocker: "workflow-not-found", message: `模型 ${String(args.schemaCode ?? "")} 没有流程;先 workflow.create` });
|
|
2827
|
+
const unitType = args.unitType === 1 || args.unitType === "DEPARTMENT" ? 1 : args.unitType === 2 || args.unitType === "ROLE" ? 2 : 3;
|
|
2828
|
+
// Idempotent retry: the platform answers 402003 WORKFLOW_PERMISSION_EXIST
|
|
2829
|
+
// for a duplicate unitId, which would read as a failure. `list_permission`
|
|
2830
|
+
// does NOT echo unitId (only id/name/unitType/fullName/code), so identity
|
|
2831
|
+
// is decided by the platform's own refusal rather than a guessed pre-read.
|
|
2832
|
+
const before = await permissionRows(session, workflowCode);
|
|
2833
|
+
try {
|
|
2834
|
+
// Send the unit's own name/code too: `list_permission` reads them back
|
|
2835
|
+
// (it echoes `name`/`code`), which is the only way `workflow.permissions`
|
|
2836
|
+
// can describe a row by more than its opaque permission id.
|
|
2837
|
+
const identity = await unitIdentity(session, unitType, args.unitId);
|
|
2838
|
+
await wireCall(session, {
|
|
2839
|
+
path: ENDPOINTS.workflowCreatePermission,
|
|
2840
|
+
method: "POST",
|
|
2841
|
+
body: {
|
|
2842
|
+
workflowCode,
|
|
2843
|
+
unitId: args.unitId,
|
|
2844
|
+
unitType,
|
|
2845
|
+
corpId: args.corpId ?? session.enterpriseId(),
|
|
2846
|
+
...(identity.name ? { name: identity.name } : {}),
|
|
2847
|
+
...(identity.code ? { code: identity.code } : {}),
|
|
2848
|
+
},
|
|
2849
|
+
});
|
|
2850
|
+
}
|
|
2851
|
+
catch (error) {
|
|
2852
|
+
const errcode = error.details?.errcode ?? error.errcode;
|
|
2853
|
+
if (String(errcode ?? "") !== "402003")
|
|
2854
|
+
throw new ToolRefusalError({
|
|
2855
|
+
status: "refused",
|
|
2856
|
+
blocker: "workflow-permission-write-failed",
|
|
2857
|
+
workflowCode,
|
|
2858
|
+
unitId: args.unitId,
|
|
2859
|
+
unitType,
|
|
2860
|
+
message: `create_permission 被平台拒绝(err=${String(errcode ?? "n/a")})。核对 unitId 是否真实存在(部门用 org.department.list、角色用 org.role.list、用户用 org.user.search),以及发起权限是否被企业策略禁用。原始错误:${error instanceof Error ? error.message : String(error)}`,
|
|
2861
|
+
});
|
|
2862
|
+
return {
|
|
2863
|
+
performed: { skipped: "already-present" },
|
|
2864
|
+
readback: { workflowCode, alreadyPresent: true, unitId: args.unitId, unitType, count: before.length, note: "该主体已有发起权限(平台 402003),未重复写入" },
|
|
2865
|
+
};
|
|
2866
|
+
}
|
|
2867
|
+
const after = await permissionRows(session, workflowCode);
|
|
2868
|
+
const created = after.find((row) => !before.some((old) => String(old.id ?? "") === String(row.id ?? ""))) ?? after[after.length - 1];
|
|
2869
|
+
return {
|
|
2870
|
+
performed: { errcode: 0 },
|
|
2871
|
+
readback: {
|
|
2872
|
+
workflowCode,
|
|
2873
|
+
count: after.length,
|
|
2874
|
+
permissionId: created?.id ?? null,
|
|
2875
|
+
unitType: created?.unitType ?? unitType,
|
|
2876
|
+
name: created?.name ?? null,
|
|
2877
|
+
note: "发起权限已授予。若之前 data.submit 回 40009,现在应可发起;仍失败先 workflow.permissions 确认行存在,并核对发起人所属部门/角色。",
|
|
2878
|
+
},
|
|
2879
|
+
};
|
|
2880
|
+
});
|
|
2881
|
+
writeTool(tools, "workflow.removePermission", WORKFLOW_LANE, WORKFLOW_TOOLS["workflow.removePermission"].scope, WORKFLOW_TOOLS["workflow.removePermission"].description, {
|
|
2882
|
+
permissionId: z.string().min(1).optional().describe("首选:workflow.permissions 返回的 id(唯一确定)"),
|
|
2883
|
+
workflowCode: z.string().optional().describe("配合 unitId+unitType 使用(平台的 list_permission 不回 unitId,需按显示名解析)"),
|
|
2884
|
+
unitId: z.string().min(1).optional().describe("部门/角色/用户 id;按 unitType 解析显示名后匹配权限行"),
|
|
2885
|
+
unitType: z.union([z.literal(1), z.literal(2), z.literal(3), z.literal("DEPARTMENT"), z.literal("ROLE"), z.literal("USER")]).optional().describe("unitId 的类型:1=部门 2=角色 3=用户"),
|
|
2886
|
+
schemaCode: z.string().optional(),
|
|
2887
|
+
}, async (session, args) => {
|
|
2888
|
+
let permissionId = args.permissionId;
|
|
2889
|
+
if (!permissionId) {
|
|
2890
|
+
const workflowCode = await resolveWorkflowArg(session, { workflowCode: args.workflowCode, schemaCode: args.schemaCode });
|
|
2891
|
+
if (!workflowCode || !args.unitId || args.unitType === undefined)
|
|
2892
|
+
throw new ToolInputError({ status: "error", blocker: "tool-input-invalid", tool: "workflow.removePermission", message: "需要 permissionId(推荐),或 workflowCode(/schemaCode)+ unitId + unitType" });
|
|
2893
|
+
const unitType = args.unitType === 1 || args.unitType === "DEPARTMENT" ? 1 : args.unitType === 2 || args.unitType === "ROLE" ? 2 : 3;
|
|
2894
|
+
const identity = await unitIdentity(session, unitType, args.unitId);
|
|
2895
|
+
const rows = await permissionRows(session, workflowCode);
|
|
2896
|
+
const wantedType = String(unitType);
|
|
2897
|
+
const hit = rows.find((row) => String(row.unitType ?? "") === wantedType && (String(row.id ?? "") === args.unitId ||
|
|
2898
|
+
(identity.name !== null && String(row.name ?? "") === identity.name) ||
|
|
2899
|
+
(identity.code !== null && String(row.code ?? "") === identity.code)));
|
|
2900
|
+
if (!hit)
|
|
2901
|
+
return {
|
|
2902
|
+
performed: { skipped: "absent" },
|
|
2903
|
+
readback: {
|
|
2904
|
+
workflowCode,
|
|
2905
|
+
removed: false,
|
|
2906
|
+
note: `没有匹配该主体的权限行(幂等)。注意 list_permission 不回 unitId,匹配靠解析出的显示名「${identity.name ?? "?"}」;不确定时用 workflow.permissions 拿 id 再按 permissionId 删。`,
|
|
2907
|
+
permissions: rows.map((row) => ({ id: row.id ?? null, name: row.name ?? null, unitType: row.unitType ?? null })),
|
|
2908
|
+
},
|
|
2909
|
+
};
|
|
2910
|
+
permissionId = String(hit.id ?? "");
|
|
2911
|
+
}
|
|
2912
|
+
await wireCall(session, { path: ENDPOINTS.workflowDeletePermission, method: "POST", query: { permissionId } });
|
|
2913
|
+
return { performed: { errcode: 0 }, readback: { permissionId, removed: true } };
|
|
2914
|
+
});
|
|
2727
2915
|
tools.push(defineTool({
|
|
2728
2916
|
name: "org.role.list",
|
|
2729
2917
|
layer: "low",
|
|
@@ -2819,9 +3007,9 @@ export function registerWorkflowLane(tools) {
|
|
|
2819
3007
|
// `wd` is the platform's own key; `keyword`/`q` are accepted aliases so a
|
|
2820
3008
|
// caller that reaches for the natural word is not bounced with
|
|
2821
3009
|
// "wd: Required" (round 10: an agent sent `keyword` and burned a retry).
|
|
2822
|
-
wd: z.string().
|
|
2823
|
-
keyword: z.string().
|
|
2824
|
-
q: z.string().
|
|
3010
|
+
wd: z.string().optional().describe("搜索关键字(用户姓名或账号用户名);**省略则列出全部员工**(工具按部门树聚合,见 description)。可用 keyword/q 作别名"),
|
|
3011
|
+
keyword: z.string().optional().describe("`wd` 的别名"),
|
|
3012
|
+
q: z.string().optional().describe("`wd` 的别名"),
|
|
2825
3013
|
page: z.number().int().min(0).optional().describe("页号,默认 0"),
|
|
2826
3014
|
size: z.number().int().min(1).optional().describe("每页条数,默认 100"),
|
|
2827
3015
|
filterType: z.string().optional().describe("admin=只搜当前应用管理员可见范围(可省)"),
|
|
@@ -2829,8 +3017,71 @@ export function registerWorkflowLane(tools) {
|
|
|
2829
3017
|
},
|
|
2830
3018
|
handler: async (ctx, args, self) => {
|
|
2831
3019
|
const keyword = args.wd ?? args.keyword ?? args.q;
|
|
2832
|
-
|
|
2833
|
-
|
|
3020
|
+
// 0.4.81 (r11 2026-10-24): no keyword used to be a hard refusal, and the
|
|
3021
|
+
// platform rejects an empty `wd` (10005), so the r11 probe enumerated
|
|
3022
|
+
// `wd` = 0..9 to discover users — 11 calls, ~16 KB, to learn a roster the
|
|
3023
|
+
// platform can already answer. Omitted keyword now aggregates
|
|
3024
|
+
// `department/list_user` over the department tree (one tool call).
|
|
3025
|
+
if (!keyword) {
|
|
3026
|
+
return governedRead(ctx, self, args, async (session) => {
|
|
3027
|
+
const orgQuery = { corpId: session.enterpriseId() };
|
|
3028
|
+
const seenDepartments = new Set();
|
|
3029
|
+
const order = [];
|
|
3030
|
+
const pushRows = (rows) => {
|
|
3031
|
+
for (const row of rows) {
|
|
3032
|
+
const id = String(row.id ?? "");
|
|
3033
|
+
if (id === "" || seenDepartments.has(id))
|
|
3034
|
+
continue;
|
|
3035
|
+
seenDepartments.add(id);
|
|
3036
|
+
order.push(id);
|
|
3037
|
+
}
|
|
3038
|
+
};
|
|
3039
|
+
// `/department/tree` answers `{departmentList:[...]}`, not a bare array;
|
|
3040
|
+
// `wireRows` only understands arrays/page envelopes.
|
|
3041
|
+
const treeRows = async (deptId) => {
|
|
3042
|
+
const data = (await wireCall(session, {
|
|
3043
|
+
path: ENDPOINTS.organizationDepartmentTree,
|
|
3044
|
+
query: deptId === undefined ? orgQuery : { ...orgQuery, deptIds: deptId },
|
|
3045
|
+
})).data;
|
|
3046
|
+
return Array.isArray(data?.departmentList) ? data.departmentList : [];
|
|
3047
|
+
};
|
|
3048
|
+
pushRows(await treeRows());
|
|
3049
|
+
// `deptIds` returns the subtree BELOW that department; walk the roots.
|
|
3050
|
+
for (const rootId of [...order]) {
|
|
3051
|
+
if (order.length >= 200)
|
|
3052
|
+
break;
|
|
3053
|
+
pushRows(await treeRows(rootId));
|
|
3054
|
+
}
|
|
3055
|
+
const seenUsers = new Map();
|
|
3056
|
+
let departmentsScanned = 0;
|
|
3057
|
+
for (const deptId of order.slice(0, 200)) {
|
|
3058
|
+
departmentsScanned += 1;
|
|
3059
|
+
for (const row of wireRows((await wireCall(session, { path: ENDPOINTS.organizationDepartmentUsers, query: { ...orgQuery, deptId } })).data)) {
|
|
3060
|
+
const id = String(row.id ?? row.userId ?? "");
|
|
3061
|
+
if (id === "" || seenUsers.has(id))
|
|
3062
|
+
continue;
|
|
3063
|
+
seenUsers.set(id, {
|
|
3064
|
+
id: row.id ?? row.userId ?? null,
|
|
3065
|
+
name: row.name ?? null,
|
|
3066
|
+
username: row.username ?? null,
|
|
3067
|
+
userId: row.userId ?? null,
|
|
3068
|
+
departmentId: row.departmentId ?? deptId,
|
|
3069
|
+
mobile: row.mobile ?? null,
|
|
3070
|
+
});
|
|
3071
|
+
}
|
|
3072
|
+
}
|
|
3073
|
+
const users = [...seenUsers.values()];
|
|
3074
|
+
return {
|
|
3075
|
+
keyword: null,
|
|
3076
|
+
count: users.length,
|
|
3077
|
+
total: users.length,
|
|
3078
|
+
departmentsScanned,
|
|
3079
|
+
...(order.length > 200 ? { truncated: true, note: `部门树超过 200 个,只聚合了前 200 个;用 wd 关键字精确检索` } : {}),
|
|
3080
|
+
users,
|
|
3081
|
+
hint: "无关键字=列全员(按部门树聚合)。平台 user/search 的 wd 不支持空串(10005),所以这条路径走 department/list_user;只要某部门的人用 org.department.users。",
|
|
3082
|
+
};
|
|
3083
|
+
});
|
|
3084
|
+
}
|
|
2834
3085
|
return governedRead(ctx, self, { ...args, wd: keyword }, async (session) => {
|
|
2835
3086
|
const body = { wd: keyword, page: args.page ?? 0, size: args.size ?? 100 };
|
|
2836
3087
|
if (args.filterType !== undefined)
|
package/dist/runtime-support.js
CHANGED
|
@@ -54,11 +54,17 @@ export async function mapUnpublishedAppFailure(session, schemaCode, errcode, roo
|
|
|
54
54
|
* unpublished-app failure. Used by data.save/data.submit (errcode 50000) and
|
|
55
55
|
* datarule.create type2 (errcode 305003) so the try/catch wiring lives once.
|
|
56
56
|
*/
|
|
57
|
-
export async function runtimeWriteWithAppMapping(session, schemaCode, errcode, rootCause, call, formData) {
|
|
57
|
+
export async function runtimeWriteWithAppMapping(session, schemaCode, errcode, rootCause, call, formData, workflowCode) {
|
|
58
58
|
try {
|
|
59
59
|
return await call();
|
|
60
60
|
}
|
|
61
61
|
catch (error) {
|
|
62
|
+
// 40009 is diagnosed FIRST: it names a cause the app/relation mappings
|
|
63
|
+
// cannot see, and it is the one refusal whose fix is a concrete next
|
|
64
|
+
// command (r11: seven blind submit attempts). Guarded by the errcode so
|
|
65
|
+
// every other failure keeps its existing mapping.
|
|
66
|
+
if (error instanceof WireError && error.kind === "business" && error.errcode === 40009)
|
|
67
|
+
await diagnoseWorkflowStartPermission(session, schemaCode, workflowCode, error);
|
|
62
68
|
let mapped;
|
|
63
69
|
try {
|
|
64
70
|
mapped = await mapUnpublishedAppFailure(session, schemaCode, errcode, rootCause, error);
|
|
@@ -131,4 +137,89 @@ export async function diagnoseRelationShapeFailure(session, schemaCode, errcode,
|
|
|
131
137
|
message: `errcode ${errcode}——根因:单选关联字段 ${culprits.join(", ")} 传了数组(WORK_SHEET/propertyType 9 只接受 id 字符串或 {id};数组会稳定 ${errcode})。多选关联(11)与人员/组织(50/51)才用数组`,
|
|
132
138
|
});
|
|
133
139
|
}
|
|
140
|
+
/**
|
|
141
|
+
* 40009 流程发起权限不足 — the most opaque refusal on this platform.
|
|
142
|
+
*
|
|
143
|
+
* r11 (2026-10-24, 1.1.25) could not deliver an entire workflow section: seven
|
|
144
|
+
* `data.submit` attempts all answered the same two words, and neither the
|
|
145
|
+
* message nor any CLI face said WHY. The platform source
|
|
146
|
+
* (`WorkflowAspect.validateWorkflowPermission` →
|
|
147
|
+
* `WorkflowManagementFacadeImpl.getWorkflowTemplateHeadersBySchemaCode`) shows
|
|
148
|
+
* three independent causes, all of which produce the identical code:
|
|
149
|
+
*
|
|
150
|
+
* 1. `h_workflow_template` header missing / not published;
|
|
151
|
+
* 2. the caller's root department does not resolve (`getAllParentIds` empty →
|
|
152
|
+
* the allowed-template list is short-circuited to EMPTY, so a user whose
|
|
153
|
+
* department row was deleted/moved can never start anything);
|
|
154
|
+
* 3. no `h_workflow_permission` row matching the user, their department (or an
|
|
155
|
+
* ancestor), or one of their roles — and `visibleType != ALL_VISIABLE`.
|
|
156
|
+
* Publishing a flow creates NO permission row, so a freshly published flow
|
|
157
|
+
* is unusable by everyone except a `h_perm_admin` ADMIN/SYS_MNG.
|
|
158
|
+
*
|
|
159
|
+
* The diagnosis here reads only the cheap, authoritative faces (permission list
|
|
160
|
+
* + header presence) and names the recovery command instead of leaving the
|
|
161
|
+
* caller to guess. Anything that fails is swallowed: a diagnosis must never
|
|
162
|
+
* replace the original refusal.
|
|
163
|
+
*/
|
|
164
|
+
export async function diagnoseWorkflowStartPermission(session, schemaCode, workflowCode, error) {
|
|
165
|
+
if (!(error instanceof WireError && error.kind === "business" && error.errcode === 40009))
|
|
166
|
+
throw error;
|
|
167
|
+
const resolvedCode = workflowCode ?? (await resolvePublishedWorkflowCodeQuiet(session, schemaCode));
|
|
168
|
+
if (!resolvedCode)
|
|
169
|
+
throw error;
|
|
170
|
+
let permissions = [];
|
|
171
|
+
let headerPresent = true;
|
|
172
|
+
try {
|
|
173
|
+
headerPresent = (await wireCall(session, { path: ENDPOINTS.workflowGetHeader, query: { workflowCode: resolvedCode } })).data != null;
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
headerPresent = true;
|
|
177
|
+
}
|
|
178
|
+
try {
|
|
179
|
+
permissions = wireRows((await wireCall(session, { path: ENDPOINTS.workflowListPermission, query: { workflowCode: resolvedCode } })).data);
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
permissions = [];
|
|
183
|
+
}
|
|
184
|
+
const headerHint = headerPresent ? "" : ` 流程头 ${resolvedCode} 不存在或未发布(先 workflow.publish)。`;
|
|
185
|
+
const message = permissions.length === 0
|
|
186
|
+
? `流程 ${resolvedCode} 没有任何发起权限行。§ 发布流程不会自动建发起权限§:` +
|
|
187
|
+
`除平台管理员(平台 h_perm_admin 里的 ADMIN/SYS_MNG,注意这与登录接口返回的 isAdmin 不是一回事)外,任何人都无法发起,` +
|
|
188
|
+
`data.submit / data.save+workflowCode 一律回 40009。${headerHint}` +
|
|
189
|
+
` 修复:把发起权限授给一个部门/角色/用户 —— ` +
|
|
190
|
+
`omc call workflow.addPermission --env <env> --args '{"workflowCode":"${resolvedCode}","unitId":"<org.department.list|org.role.list|org.user.search 的 id>","unitType":1}'` +
|
|
191
|
+
`(1=部门 2=角色 3=用户;同一行也可用 workflow.permissions 复核)。` +
|
|
192
|
+
`另一种原因同样报 40009 且无法从权限行看出来:发起人所属部门记录已被删除/移动(平台 getRootDeptId 解析为空时直接短路成"无任何可发起模板")——` +
|
|
193
|
+
`用 org.user.search 看自己的 departmentId,再用 org.department.list 确认该部门仍在。`
|
|
194
|
+
: `流程 ${resolvedCode} 已有 ${permissions.length} 条发起权限行(${permissions.map((row) => `${String(row.name ?? row.code ?? "?")}/unitType=${String(row.unitType ?? "?")}`).join("、")}),` +
|
|
195
|
+
`但当前调用者不在其中。${headerHint}` +
|
|
196
|
+
` 核对顺序:① 发起人的 departmentId(org.user.search)是否等于某条 unitType=1 的部门,或是其祖先链上的一级;` +
|
|
197
|
+
`② 发起人是否属于某条 unitType=2 的角色(org.role.users);③ 是否该 unitType=3 的用户本人;` +
|
|
198
|
+
`④ 若部门记录已被删除/移动,平台会把可发起列表短路成空——同样报 40009,但与权限行无关。` +
|
|
199
|
+
` 补授权:omc call workflow.addPermission --env <env> --args '{"workflowCode":"${resolvedCode}","unitId":"<id>","unitType":1}';查看现状用 omc call workflow.permissions。`;
|
|
200
|
+
throw new ToolRefusalError({
|
|
201
|
+
status: "refused",
|
|
202
|
+
blocker: "workflow-start-permission-denied",
|
|
203
|
+
schemaCode,
|
|
204
|
+
workflowCode: resolvedCode,
|
|
205
|
+
errcode: 40009,
|
|
206
|
+
permissionCount: permissions.length,
|
|
207
|
+
permissions,
|
|
208
|
+
message,
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
/** The published workflow code for a schema, or undefined (never throws). */
|
|
212
|
+
async function resolvePublishedWorkflowCodeQuiet(session, schemaCode) {
|
|
213
|
+
try {
|
|
214
|
+
const isWorkflow = (await wireCall(session, { path: ENDPOINTS.workflowIsWorkflow, query: { schemaCode } })).data === true;
|
|
215
|
+
if (!isWorkflow)
|
|
216
|
+
return undefined;
|
|
217
|
+
const template = (await wireCall(session, { path: ENDPOINTS.workflowGetBySchema, query: { schemaCode } })).data;
|
|
218
|
+
const code = template && typeof template.workflowCode === "string" && template.workflowCode.length > 0 ? template.workflowCode : null;
|
|
219
|
+
return code ?? undefined;
|
|
220
|
+
}
|
|
221
|
+
catch {
|
|
222
|
+
return undefined;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
134
225
|
//# sourceMappingURL=runtime-support.js.map
|
package/dist/usage-log.js
CHANGED
|
@@ -222,7 +222,12 @@ function facesOf(argv) {
|
|
|
222
222
|
// (bounded index vs full catalogue) but `commandOf` drops every flag, so
|
|
223
223
|
// without this they were indistinguishable in the log and byte attribution
|
|
224
224
|
// was ambiguous (round 10).
|
|
225
|
-
|
|
225
|
+
// Read projections count as faces too. r11 (2026-10-24) reported `--jq`/`--summary`/
|
|
226
|
+
// `--keys` usage as 0 because only the `describe`/`call` flags were listed:
|
|
227
|
+
// `omc artifact get <id> --jq <path>` showed up as a bare `artifact get` with
|
|
228
|
+
// empty argKeys, so the cost report could not tell a 200 B projection from a
|
|
229
|
+
// 44 KB pull — precisely the read path these flags exist to make cheap.
|
|
230
|
+
const faces = ["--full", "--compact", "--json", "--schema-only", "--param", "--example", "--section", "--sections", "--out", "--quiet", "--jq", "--summary", "--keys", "--path-only", "--extract", "--extract-keys", "--records"];
|
|
226
231
|
return faces.filter((face) => argv.includes(face));
|
|
227
232
|
}
|
|
228
233
|
const safeArgumentKeys = new Set(["schemaCode", "propertyCode", "code", "workflowCode", "ruleCode", "sheetCode", "id", "dataRuleType", "propertyType", "published", "handler"]);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stalfh233/omc-cli",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.82",
|
|
4
4
|
"description": "CloudPivot 8.6 configuration capability CLI — governed, fail-closed, coverage-manifest bounded.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -50,9 +50,9 @@
|
|
|
50
50
|
"access": "public"
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
|
-
"@stalfh233/omc-fake-platform": "0.4.
|
|
53
|
+
"@stalfh233/omc-fake-platform": "0.4.82",
|
|
54
54
|
"commander": "14.0.3",
|
|
55
|
-
"omc-core": "0.4.
|
|
55
|
+
"omc-core": "0.4.82",
|
|
56
56
|
"proper-lockfile": "4.1.2",
|
|
57
57
|
"write-file-atomic": "6.0.0",
|
|
58
58
|
"zod": "^3.23.8",
|
|
@@ -148,7 +148,7 @@ only the section for the capability you need —
|
|
|
148
148
|
## 流程模板 `workflow`
|
|
149
149
|
|
|
150
150
|
- **能做哪些事**:流程模板 get_draft/get_published、create、update、publish、bindRule(事件处理器规则绑定)、delete、workitem.list、workflow.instanceActivity(实例节点状态诊断);参与人用组织对象。
|
|
151
|
-
- **工具**:`workflow.get_draft`、`workflow.setButtons`(节点「表单操作」按钮勾选)、`workflow.get_published`、`workflow.validate`、`workflow.create`、`workflow.update`、`workflow.publish`、`workflow.bindRule`、`workflow.delete`、`configure_workflow`、`workitem.list`、`workflow.instanceActivity`、`org.role.list`、`org.department.list`、`org.user.search`、`org.role.users`、`org.role.userScope`、`org.roleGroup.create`、`org.role.create`、`org.role.update`、`org.role.addUsers`、`org.role.removeUsers`、`org.role.setUserScope`、`org.department.create`、`org.department.update`、`org.department.delete`、`org.department.users`、`org.user.create`、`org.user.update`、`org.user.delete`、`org.user.info`、`org.user.departments`、`org.role.delete`、`org.roleGroup.delete`、`org.user.transferDepartment`
|
|
151
|
+
- **工具**:`workflow.get_draft`、`workflow.setButtons`(节点「表单操作」按钮勾选)、`workflow.get_published`、`workflow.validate`、`workflow.create`、`workflow.update`、`workflow.publish`、`workflow.bindRule`、`workflow.delete`、`workflow.permissions`、`workflow.addPermission`、`workflow.removePermission`(发起权限:发布流程不会自动建这一行,缺它一律 40009)、`configure_workflow`、`workitem.list`、`workflow.instanceActivity`、`org.role.list`、`org.department.list`、`org.user.search`、`org.role.users`、`org.role.userScope`、`org.roleGroup.create`、`org.role.create`、`org.role.update`、`org.role.addUsers`、`org.role.removeUsers`、`org.role.setUserScope`、`org.department.create`、`org.department.update`、`org.department.delete`、`org.department.users`、`org.user.create`、`org.user.update`、`org.user.delete`、`org.user.info`、`org.user.departments`、`org.role.delete`、`org.roleGroup.delete`、`org.user.transferDepartment`
|
|
152
152
|
- **recipe**:`omc describe workflow.bindRule --example workflow-handler-binding`
|
|
153
153
|
- **边界 / 陷阱**:greenfield 首版;`publish`/`bindRule` 不得在 `ValidateMessage[]` 拒绝时报告成功。运行时 workitem(提交/审批)走 `data.submit`;想知道实例停在哪/是否错派,用 `workflow.instanceActivity`(无 status=1=真停,参与人全是发起人=解析为空退回)。
|
|
154
154
|
|
|
@@ -44,8 +44,12 @@ capability's coverage/tools/boundaries from `omc-capabilities`.
|
|
|
44
44
|
Coverage status and boundaries for every capability (including 权限/子表/聚合/
|
|
45
45
|
上线/内联/OCR) live in **`omc-capabilities`** — its 能力索引 table and
|
|
46
46
|
[边界] section are the authority; read the relevant section there rather than a
|
|
47
|
-
copy here. In particular,
|
|
48
|
-
|
|
47
|
+
copy here. In particular, **跨模型/子表聚合、汇总、求和是配置能力**,不要当二开。
|
|
48
|
+
落点是**字段计算规则**:`omc compute --schema <主表> --target <目标字段> --formula 'COUNTIF({<子表code>.<子列code>},"异常")'`
|
|
49
|
+
(等价 `datarule.create` `dataRuleType:2`,配方 `field-compute-subtable-sum` / `field-compute-child-column`)。
|
|
50
|
+
**不要**在规则图(`rule.save`)里用 `ruleValueType:"AGGREGATION"` —— 平台会存进去并启用,运行期聚合结果是集合,
|
|
51
|
+
标量目标收到集合即 50000「服务异常」(r11 巡检沙盒 2026-10-24,1.1.25;OMC 现已写前 fail-closed)。
|
|
52
|
+
计算由门户前端执行、API `data.load` 不回填,属平台既有行为:要断言结果请在测试数据里写预算值。
|
|
49
53
|
|
|
50
54
|
Cue → capability is a starting point, never a substitute for 定面. A cue that
|
|
51
55
|
could name two faces is resolved by the 定面表 in the parent skill.
|