@stalfh233/omc-cli 0.4.87 → 0.4.90
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/dist/cli-contract.js +97 -7
- package/dist/cli.js +103 -15
- package/dist/endpoints.js +10 -0
- package/dist/goal-routes.js +73 -13
- package/dist/lanes/field.js +74 -2
- package/dist/lanes/list-view.js +21 -1
- package/dist/lanes/model.js +4 -1
- package/dist/lanes/org-tree.js +52 -0
- package/dist/lanes/organization.js +110 -6
- package/dist/lanes/rule-lifecycle.js +26 -5
- package/dist/lanes/runtime-data.js +59 -3
- package/dist/lanes/workflow.js +95 -70
- package/dist/model-purge.js +343 -0
- package/dist/path-projection.js +40 -0
- package/dist/recipes/businessrule.js +3 -0
- package/dist/recipes/list-view.js +15 -0
- package/dist/rule-support.js +8 -0
- package/dist/runtime-support.js +1 -0
- package/dist/write.js +1 -1
- package/package.json +3 -3
- package/skills/omc-start-here.md +2 -1
package/README.md
CHANGED
|
@@ -130,10 +130,11 @@ 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.88)
|
|
134
134
|
|
|
135
135
|
| 版本 | 新增 / 修复 |
|
|
136
136
|
|---|---|
|
|
137
|
+
| 0.4.88 | **`omc model-purge`**:一条命令按依赖倒序清掉一个模型(流程实例 → model.delete 级联带走流程/视图/字段/表单/规则)。这是 r12–r14 三次「脏沙盒幽灵故障」的根治——顺手修了它的死锁(视图最后一条不能删 306103 + 列表绑定流程 303024,所以不删流程、让模型级联)。另:ladder 的 **recipe 命令提到 firstCommands**(r14 抱怨「ladder 没给可直接跑的命令」)、recipe 增加 `aliases` 意图词、`listview.configure` 的拒绝恢复补上 `targetCode` 按 bindAction 的语义 |
|
|
137
138
|
| 0.4.87 | **r14 真机探针修复**:`workitem.list` 的 50000 **不再是无解题**——按 `participant` 走 JPA 查询端点(能查**别人**名下的待办、且不解析流程模板),全局搜索失败时回 `workitem-search-unavailable` 带成因与三条绕行;`workflow.delete` 的 402508 改 `workflow-has-instances` 并说明**不要改用 model.delete 绕过**;`model.delete` 在有未完结待办时回 `workflowWarning`(级联会留脏待办 → 全局待办搜索整页 NPE)。另修两个 composer 模板参数名写错(`form-default --value`→`--default`、`rule-when --rule`→`--on`),并加**模板 vs `--help` 自动校验测试** |
|
|
138
139
|
| 0.4.86 | **r13 真机探针修复**:`omc next` 增加 **composer 路由**(含建模型+字段/审批流/计算/条件规则/表单默认值 → 直接给出对应一条命令;读面仍走 ladder,条件分支流程仍走 recipe)——r13 读了文档里那行 `model-scaffold` 却仍调 26 次 `field.create`,说明「写进文档」不等于「能被发现」。`data.submit` 审批提交**写前校验身份**:非本人在办的待办直接回 `submit-workitem-not-mine`(带待办持有者 + 三条出路),不再让平台回一个 `40005 权限不足`让 agent 猜 |
|
|
139
140
|
| 0.4.84 | **回合数优化(从省字节转向省往返)**:新增 `omc model-scaffold`(一条命令建模型+N 字段+子表+发布+表单,先 dry-run 预检零写入)与 `omc call <tool> --args-list`(同工具 N 次、逐条执行、回汇总)。修三个真 bug:键名提示自相矛盾(`workflow.delete` 两边互指)、`--dry-run` 与真写对 NUMERICAL 判定不一致(两套真相)、`runToolCall` 的 dry-run 被判失败 |
|
|
@@ -236,6 +237,7 @@ omc intent readback intent.yaml --env <env> # 解码回 intent 形状做等价
|
|
|
236
237
|
| `omc bench [--live-sample] [--env <env>]` | 工具面/wire 面的双面埋点跑分(离线 fake 或 live 采样) |
|
|
237
238
|
| `omc model-scaffold --code <c> --name <n> --fields '<json>'` | 一条命令建模型:模型 → N 字段(含 `subFields` 子表)→ 发布字段 → 发布表单。**先对每个字段跑 dry-run 预检**(零写入),有错一次报全;执行期幂等(已存在记 skipped) |
|
|
238
239
|
| `omc call <tool> --args-list '<json array>'` | 同一工具跑 N 次、一个进程:逐条执行、默认不中断,回 `succeeded`/`failed` 汇总;失败只列 `failures[]`(带 index),成功不回包。省掉手写 ndjson |
|
|
240
|
+
| `omc model-purge --code <c> [--purge-instances] --apply` | 一条命令按依赖倒序清掉模型:**流程实例 → model.delete 级联带走**流程模板/列表视图/字段/表单/规则。默认 dry-run;有实例不删时会 `blocked` 并说明为何不能改用 model.delete 绕过 |
|
|
239
241
|
| `omc usage [--top <n>] [--records]` | 用量账本:默认只回汇总(调用数/字节/`redundancy` 重复浪费/`byTool` 排名/`estimatedTokens`≈字节/4);`--records` 才回原始记录。数据源 `.omc/usage.jsonl`(每条 `omc` 调用一行,`omc script` 的每行工具调用另记 `script-call`,带 `parentId`) |
|
|
240
242
|
| `npm run eval:discovery-cost --workspace omc-cli` | 三组离线 discovery 成本基线(调用次数、stdout 字节、拒绝/重试、CloudPivot 写入) |
|
|
241
243
|
|
package/dist/cli-contract.js
CHANGED
|
@@ -75,8 +75,83 @@ export function serializeEnvelope(value) {
|
|
|
75
75
|
export function requestId() {
|
|
76
76
|
return randomUUID();
|
|
77
77
|
}
|
|
78
|
+
/**
|
|
79
|
+
* Derive a runnable next command from a refusal payload. Tools that already
|
|
80
|
+
* carry a structured recovery (an explicit `nextCommand`, an artifact `fetch`
|
|
81
|
+
* hint, `recovery[].command`, command-shaped `checks`, or a prepared
|
|
82
|
+
* `nextSubmit`) do not need their prose parsed; everything else stays null
|
|
83
|
+
* rather than inventing a command that does not apply.
|
|
84
|
+
*/
|
|
85
|
+
export function nextCommandFor(result) {
|
|
86
|
+
const commandFrom = (text) => {
|
|
87
|
+
if (typeof text !== "string")
|
|
88
|
+
return undefined;
|
|
89
|
+
const trimmed = text.trim();
|
|
90
|
+
if (/^omc\s/.test(trimmed))
|
|
91
|
+
return trimmed;
|
|
92
|
+
// Recovery entries are often "① 按流程收窄(最可靠):omc call …" — the
|
|
93
|
+
// command is there, just after a human marker.
|
|
94
|
+
const index = trimmed.indexOf("omc ");
|
|
95
|
+
return index > 0 ? trimmed.slice(index).trim() : undefined;
|
|
96
|
+
};
|
|
97
|
+
const explicit = result.nextCommand;
|
|
98
|
+
if (typeof explicit === "string" && explicit.trim().length > 0)
|
|
99
|
+
return explicit.trim();
|
|
100
|
+
const fromFetch = commandFrom(result.fetch);
|
|
101
|
+
if (fromFetch !== undefined)
|
|
102
|
+
return fromFetch;
|
|
103
|
+
const recovery = result.recovery;
|
|
104
|
+
if (Array.isArray(recovery)) {
|
|
105
|
+
for (const entry of recovery) {
|
|
106
|
+
if (entry === null || typeof entry !== "object")
|
|
107
|
+
continue;
|
|
108
|
+
const command = commandFrom(entry.command);
|
|
109
|
+
if (command !== undefined)
|
|
110
|
+
return command;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
for (const list of [result.checks, result.workaround]) {
|
|
114
|
+
if (!Array.isArray(list))
|
|
115
|
+
continue;
|
|
116
|
+
for (const entry of list) {
|
|
117
|
+
const command = commandFrom(entry);
|
|
118
|
+
if (command !== undefined)
|
|
119
|
+
return command;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
// A submit that advanced the flow already prepared the NEXT approval args —
|
|
123
|
+
// expose the command instead of making the caller reassemble JSON by hand.
|
|
124
|
+
const nextSubmit = result.nextSubmit;
|
|
125
|
+
if (nextSubmit !== null && typeof nextSubmit === "object" && !Array.isArray(nextSubmit)) {
|
|
126
|
+
const record = nextSubmit;
|
|
127
|
+
if (typeof record.schemaCode === "string" && typeof record.workItemId === "string") {
|
|
128
|
+
const args = {
|
|
129
|
+
schemaCode: record.schemaCode,
|
|
130
|
+
bizObjectId: record.bizObjectId ?? null,
|
|
131
|
+
workflowInstanceId: record.workflowInstanceId ?? null,
|
|
132
|
+
workItemId: record.workItemId,
|
|
133
|
+
agree: true,
|
|
134
|
+
};
|
|
135
|
+
return `omc call data.submit --args '${JSON.stringify(args)}'`;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
const nextWorkItems = result.nextWorkItems;
|
|
139
|
+
if (Array.isArray(nextWorkItems) && nextWorkItems.length > 0 && typeof result.schemaCode === "string") {
|
|
140
|
+
const item = nextWorkItems[0];
|
|
141
|
+
if (item !== null && typeof item === "object") {
|
|
142
|
+
const workItemId = item.workItemId;
|
|
143
|
+
if (typeof workItemId === "string")
|
|
144
|
+
return `omc call data.submit --args '${JSON.stringify({ schemaCode: result.schemaCode, workItemId, agree: true })}'`;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return undefined;
|
|
148
|
+
}
|
|
78
149
|
export function classifyResult(result) {
|
|
79
150
|
const status = String(result.status ?? "ok");
|
|
151
|
+
const withNext = (error) => {
|
|
152
|
+
const nextCommand = nextCommandFor(result);
|
|
153
|
+
return nextCommand === undefined ? error : { ...error, nextCommand };
|
|
154
|
+
};
|
|
80
155
|
if (status === "ok")
|
|
81
156
|
return { status, error: null, exitCode: CLI_EXIT_CODES.ok };
|
|
82
157
|
if (status === "dry-run")
|
|
@@ -88,30 +163,45 @@ export function classifyResult(result) {
|
|
|
88
163
|
const succeeded = Number(result.succeeded ?? 0);
|
|
89
164
|
const requested = Number(result.requested ?? 0);
|
|
90
165
|
const failed = Number(result.failed ?? 0);
|
|
166
|
+
// A `--preflight` batch shape-checks entries instead of executing them; the
|
|
167
|
+
// message must not claim "N 成功/失败" for calls that never ran.
|
|
168
|
+
// A step-chain command (model-purge) also answers `partial`, and the batch
|
|
169
|
+
// wording read "6/7 成功,0 失败" — meaningless for steps (r15).
|
|
170
|
+
const isBatch = typeof result.tool === "string";
|
|
171
|
+
const message = result.mode === "preflight"
|
|
172
|
+
? `${failed}/${requested} 条未过写前校验(preflight:零 wire、零写入);逐条原因见 failures[]`
|
|
173
|
+
: isBatch
|
|
174
|
+
? `${succeeded}/${requested} 成功,${failed} 失败;逐条原因见 failures[] / failedSteps[](--args-list 的 index 指向第几条)`
|
|
175
|
+
: `部分步骤失败:逐条原因见 steps[] 的 status/detail(已在失败处停下;幂等,修好后重跑同一条命令)`;
|
|
91
176
|
return {
|
|
92
177
|
status,
|
|
93
|
-
error: { code: "partial-batch", message
|
|
178
|
+
error: withNext({ code: "partial-batch", message, retryable: false }),
|
|
94
179
|
exitCode: 1,
|
|
95
180
|
};
|
|
96
181
|
}
|
|
97
182
|
if (status === "unknown-tool") {
|
|
98
183
|
const candidates = Array.isArray(result.candidates) ? result.candidates : [];
|
|
99
184
|
const suffix = candidates.length > 0 ? `;did you mean: ${candidates.join("、")}` : ";用 `omc commands --json --compact --lane <lane>` 或 `omc next` 定位工具";
|
|
100
|
-
return { status: "error", error: { code: "invalid-input", message: `unknown tool ${String(result.tool ?? "")}${suffix}`, retryable: false }, exitCode: CLI_EXIT_CODES.invalidInput };
|
|
185
|
+
return { status: "error", error: withNext({ code: "invalid-input", message: `unknown tool ${String(result.tool ?? "")}${suffix}`, retryable: false }), exitCode: CLI_EXIT_CODES.invalidInput };
|
|
101
186
|
}
|
|
102
187
|
if (result.blocker === "tool-input-invalid")
|
|
103
|
-
return { status: "error", error: { code: "invalid-input", message: String(result.message ?? "tool input is invalid"), retryable: false }, exitCode: CLI_EXIT_CODES.invalidInput };
|
|
188
|
+
return { status: "error", error: withNext({ code: "invalid-input", message: String(result.message ?? "tool input is invalid"), retryable: false }), exitCode: CLI_EXIT_CODES.invalidInput };
|
|
104
189
|
if (status === "uncertain")
|
|
105
|
-
return { status, error: { code: "uncertain", message: String(result.message ?? result.reason ?? "outcome is uncertain"), retryable: true }, exitCode: CLI_EXIT_CODES.uncertain };
|
|
190
|
+
return { status, error: withNext({ code: "uncertain", message: String(result.message ?? result.reason ?? "outcome is uncertain"), retryable: true }), exitCode: CLI_EXIT_CODES.uncertain };
|
|
106
191
|
if (status === "refused") {
|
|
107
192
|
if (result.blocker === "write-lock-held")
|
|
108
|
-
return { status, error: { code: "policy-refused", message: String(result.message ?? "write lock held"), retryable: true }, exitCode: CLI_EXIT_CODES.policyRefused };
|
|
193
|
+
return { status, error: withNext({ code: "policy-refused", message: String(result.message ?? "write lock held"), retryable: true }), exitCode: CLI_EXIT_CODES.policyRefused };
|
|
109
194
|
const auth = String(result.blocker ?? "").startsWith("auth") || String(result.blocker ?? "").includes("credential");
|
|
110
|
-
|
|
195
|
+
// Every refusal gets a next step: a specific command when the tool knows
|
|
196
|
+
// one, otherwise the refusal router (`omc-refusals` explains which gate
|
|
197
|
+
// produced it — the documented path in omc-start-here).
|
|
198
|
+
const error = { code: auth ? "auth-config" : "policy-refused", message: String(result.message ?? result.blocker ?? "operation refused"), retryable: false };
|
|
199
|
+
const nextCommand = nextCommandFor(result) ?? (auth ? undefined : "omc help omc-refusals");
|
|
200
|
+
return { status, error: nextCommand === undefined ? error : { ...error, nextCommand }, exitCode: auth ? CLI_EXIT_CODES.authConfig : CLI_EXIT_CODES.policyRefused };
|
|
111
201
|
}
|
|
112
202
|
const message = String(result.message ?? "internal error");
|
|
113
203
|
const auth = /credential|configuration|environment|login|authentication|auth/i.test(message);
|
|
114
|
-
return { status: "error", error: { code: auth ? "auth-config" : "internal", message, retryable: false }, exitCode: auth ? CLI_EXIT_CODES.authConfig : CLI_EXIT_CODES.internal };
|
|
204
|
+
return { status: "error", error: withNext({ code: auth ? "auth-config" : "internal", message, retryable: false }), exitCode: auth ? CLI_EXIT_CODES.authConfig : CLI_EXIT_CODES.internal };
|
|
115
205
|
}
|
|
116
206
|
export function errorEnvelope(error, code, exitCode, request) {
|
|
117
207
|
const message = error instanceof Error ? error.message : String(error);
|
package/dist/cli.js
CHANGED
|
@@ -13,13 +13,14 @@ import { redactText } from "./redact.js";
|
|
|
13
13
|
import { readArgsFile, readStdinArgs, runToolCall, validateAgentPaths } from "./call.js";
|
|
14
14
|
import { BATCH_MAX_ENTRIES, runBatchCall } from "./batch-call.js";
|
|
15
15
|
import { cmdModelScaffold } from "./model-scaffold.js";
|
|
16
|
+
import { cmdModelPurge } from "./model-purge.js";
|
|
16
17
|
import { environmentWritable, requiresProductionConfirmation } from "./environment-policy.js";
|
|
17
|
-
import { CLI_EXIT_CODES, classifyResult, envelope, errorEnvelope, requestId } from "./cli-contract.js";
|
|
18
|
+
import { CLI_EXIT_CODES, classifyResult, envelope, errorEnvelope, refusalText, requestId } from "./cli-contract.js";
|
|
18
19
|
import { configureOutput, humanSummary, stripOutputFlags, writeEnvelope } from "./cli-output.js";
|
|
19
20
|
import { beginUsage, completeUsage, ensureProjectIgnore, recordUsageOutput, usageReport } from "./usage-log.js";
|
|
20
21
|
import { artifactFetchHint, artifactRoot, boundResult, emitInvalid, emitResult, emitTransportResult, outputBudget, readArtifact, withTransport, } from "./command-transport.js";
|
|
21
22
|
import { CLI_DEFAULT_OUTPUT_BYTES, assertSafeOutputPath, pruneArtifacts } from "./artifacts.js";
|
|
22
|
-
import { projectJsonPath, projectResultExtract } from "./path-projection.js";
|
|
23
|
+
import { projectJsonPath, projectJsonPathFanout, projectResultExtract } from "./path-projection.js";
|
|
23
24
|
import { basename, join, resolve } from "node:path";
|
|
24
25
|
import { buildToolRegistry } from "./tools.js";
|
|
25
26
|
import { acquireWriteLock } from "./write-lock.js";
|
|
@@ -318,6 +319,52 @@ async function cmdBatchCall(store, toolName, rawList, options) {
|
|
|
318
319
|
}));
|
|
319
320
|
return CLI_EXIT_CODES.invalidInput;
|
|
320
321
|
}
|
|
322
|
+
// Whole-batch shape precheck (issue 2026-10-25 §6): `--args-list --dry-run`
|
|
323
|
+
// runs every entry through the full tool (reads included) to report N
|
|
324
|
+
// previews. When the caller only wants "will this batch be accepted", the
|
|
325
|
+
// per-tool semantic gate is the thing that refuses mis-shapes, and it is
|
|
326
|
+
// pure — no wire, no writes. Run it for every entry and report ALL failures
|
|
327
|
+
// in one envelope instead of N round-trips.
|
|
328
|
+
if (options.preflight === true) {
|
|
329
|
+
const registry = buildToolRegistry();
|
|
330
|
+
const tool = registry.find((definition) => definition.name === toolName);
|
|
331
|
+
if (!tool) {
|
|
332
|
+
writeEnvelope(envelope("error", {}, request, { code: "invalid-input", message: `unknown tool ${toolName};用 omc next 或 omc commands --json --compact --lane <lane> 定位`, retryable: false }));
|
|
333
|
+
return CLI_EXIT_CODES.invalidInput;
|
|
334
|
+
}
|
|
335
|
+
const failures = [];
|
|
336
|
+
entries.forEach((entry, index) => {
|
|
337
|
+
try {
|
|
338
|
+
const parsedEntry = parseToolInput(tool, {
|
|
339
|
+
...entry,
|
|
340
|
+
...(options.environment !== undefined && entry.environment === undefined ? { environment: options.environment } : {}),
|
|
341
|
+
});
|
|
342
|
+
tool.writePreflight?.(parsedEntry);
|
|
343
|
+
}
|
|
344
|
+
catch (error) {
|
|
345
|
+
// ToolRefusalError carries the real payload (blocker/citation/validation);
|
|
346
|
+
// rebuild it only when the failure is a plain Error.
|
|
347
|
+
const carried = error.payload;
|
|
348
|
+
const payload = error instanceof ToolInputError ? error.payload : carried ?? { status: "refused", blocker: "tool-input-invalid", message: error instanceof Error ? error.message : String(error) };
|
|
349
|
+
failures.push({ index, blocker: payload.blocker ?? null, message: refusalText(payload), ...(Array.isArray(payload.validation) ? { validation: payload.validation } : {}), ...(payload.citation !== undefined ? { citation: payload.citation } : {}) });
|
|
350
|
+
}
|
|
351
|
+
});
|
|
352
|
+
const result = {
|
|
353
|
+
status: failures.length > 0 ? "partial" : "ok",
|
|
354
|
+
mode: "preflight",
|
|
355
|
+
tool: toolName,
|
|
356
|
+
requested: entries.length,
|
|
357
|
+
shapeChecked: entries.length,
|
|
358
|
+
failed: failures.length,
|
|
359
|
+
writes: 0,
|
|
360
|
+
// On failure the caller has to change the JSON first, so there is no
|
|
361
|
+
// runnable command yet; on success, hand over the exact execution command.
|
|
362
|
+
...(failures.length > 0 ? { failures } : { nextCommand: `omc call ${toolName} --args-list ${JSON.stringify(JSON.stringify(entries))}` }),
|
|
363
|
+
};
|
|
364
|
+
const classified = classifyResult(result);
|
|
365
|
+
writeEnvelope(envelope(classified.status, result, request, classified.error));
|
|
366
|
+
return failures.length > 0 ? 1 : 0;
|
|
367
|
+
}
|
|
321
368
|
const batched = await runBatchCall({
|
|
322
369
|
tool: toolName,
|
|
323
370
|
argsList: entries,
|
|
@@ -351,7 +398,7 @@ async function cmdCall(store, args) {
|
|
|
351
398
|
const positional = args.filter((token, index) => !token.startsWith("--") && (index === 0 || !args[index - 1]?.startsWith("--env") && !args[index - 1]?.startsWith("--environment") && !args[index - 1]?.startsWith("--args")));
|
|
352
399
|
const toolName = positional[0];
|
|
353
400
|
if (!toolName || toolName === "help" || args.includes("--help")) {
|
|
354
|
-
console.error("用法:omc call <tool> [--env <环境>] [--args '<json>' | --args - (stdin) | --args-file <配置根内路径>] [--extract <path>]... [--extract-keys]");
|
|
401
|
+
console.error("用法:omc call <tool> [--env <环境>] [--args '<json>' | --args - (stdin) | --args-file <配置根内路径>] [--extract <path>]... [--jq [path]] [--extract-keys] [--args-list <json数组> [--preflight]]");
|
|
355
402
|
console.error("写入:由环境 writable 决定(只读环境拒写);生产环境的写必须在 args 里带 confirmProduction:true。");
|
|
356
403
|
return emit(toolName && toolName !== "help" ? "error" : "ok", {}, toolName && toolName !== "help" ? CLI_EXIT_CODES.invalidInput : 0);
|
|
357
404
|
}
|
|
@@ -360,14 +407,19 @@ async function cmdCall(store, args) {
|
|
|
360
407
|
// Batch mode: the SAME tool N times, one process (r12: 36 field.create calls).
|
|
361
408
|
const argsList = flag("--args-list");
|
|
362
409
|
if (argsList !== undefined)
|
|
363
|
-
return cmdBatchCall(store, toolName, argsList, { environment, requestValue, args });
|
|
410
|
+
return cmdBatchCall(store, toolName, argsList, { environment, requestValue, args, preflight: args.includes("--preflight") });
|
|
364
411
|
// 1.2 of issue 2026-10-23: read faces answer {detailArtifactId, sha256} and the
|
|
365
412
|
// caller then needs a SECOND call to see one value (16 two-step chains in one
|
|
366
413
|
// session; 15 more were pure --keys probes). `--extract <path>` (repeatable)
|
|
367
414
|
// resolves the path inside the artifact of THIS result and returns only the
|
|
368
415
|
// value; `--extract-keys` lists the artifact's top-level keys with sizes.
|
|
369
|
-
|
|
370
|
-
|
|
416
|
+
//
|
|
417
|
+
// issue 2026-10-25 §8: `artifact get` had `--jq` while `omc call` did not, so
|
|
418
|
+
// every list read that only wanted one column paid a second artifact round.
|
|
419
|
+
// `--jq` is the same path projection plus `[]` fan-out; a bare `--jq` is the
|
|
420
|
+
// key inventory (the same muscle memory as `artifact get <id> --jq`).
|
|
421
|
+
const extractPaths = [...argvValues(args, "--extract"), ...argvValues(args, "--jq")];
|
|
422
|
+
const extractKeys = args.includes("--extract-keys") || args.some((token, index) => token === "--jq" && (args[index + 1] === undefined || args[index + 1].startsWith("--")));
|
|
371
423
|
const dryRun = args.includes("--dry-run");
|
|
372
424
|
const waitValue = flag("--wait");
|
|
373
425
|
let waitMs = 0;
|
|
@@ -546,6 +598,23 @@ function parseDurationMs(text) {
|
|
|
546
598
|
* template re-read three times is 81 KB of the session.
|
|
547
599
|
*/
|
|
548
600
|
const ARTIFACT_INLINE_CEILING_BYTES = 8192;
|
|
601
|
+
/**
|
|
602
|
+
* `artifact get --jq`: one path walk, with `[]` fan-out (same syntax as
|
|
603
|
+
* `omc call --jq`). Returns the envelope fields so both the `--summary` and the
|
|
604
|
+
* plain branch stay identical.
|
|
605
|
+
*/
|
|
606
|
+
function projectArtifactPath(value, path) {
|
|
607
|
+
if (!path.includes("[]")) {
|
|
608
|
+
const projected = projectJsonPath(value, path);
|
|
609
|
+
return projected.missed
|
|
610
|
+
? { missed: true, ...(projected.at !== undefined ? { at: projected.at } : {}), ...(projected.available !== undefined ? { available: projected.available } : {}) }
|
|
611
|
+
: { missed: false, content: projected.value ?? null };
|
|
612
|
+
}
|
|
613
|
+
const fanout = projectJsonPathFanout(value, path);
|
|
614
|
+
return fanout.missed
|
|
615
|
+
? { missed: true, ...(fanout.at !== undefined ? { at: fanout.at } : {}), ...(fanout.available !== undefined ? { available: fanout.available } : {}) }
|
|
616
|
+
: { missed: false, content: fanout.values, mapped: fanout.values.length, ...(fanout.missedEntries > 0 ? { missedEntries: fanout.missedEntries } : {}) };
|
|
617
|
+
}
|
|
549
618
|
function formatBytes(bytes) {
|
|
550
619
|
if (bytes < 1024)
|
|
551
620
|
return `${bytes}B`;
|
|
@@ -578,10 +647,10 @@ async function cmdArtifactGet(id, transport, options = {}) {
|
|
|
578
647
|
// this the caller had to spend one round on the shape and another on the
|
|
579
648
|
// path (or pull the whole body); r5 hit that on 67 KB workflow templates.
|
|
580
649
|
if (options.jq !== undefined) {
|
|
581
|
-
const projected =
|
|
650
|
+
const projected = projectArtifactPath(artifact.content.toString("utf8") === "" ? undefined : content, options.jq);
|
|
582
651
|
return emitTransportResult(projected.missed
|
|
583
652
|
? { status: "ok", artifactId: id, sha256: artifact.sha256, bytes: artifact.content.length, keys, projection: { path: options.jq, content: null, pathMiss: true, missedAt: projected.at, available: projected.available }, fetch: artifactFetchHint(id) }
|
|
584
|
-
: { status: "ok", artifactId: id, sha256: artifact.sha256, bytes: artifact.content.length, keys, projection: { path: options.jq, content: projected.
|
|
653
|
+
: { status: "ok", artifactId: id, sha256: artifact.sha256, bytes: artifact.content.length, keys, projection: { path: options.jq, content: projected.content ?? null, ...(projected.mapped !== undefined ? { mapped: projected.mapped } : {}), ...(projected.missedEntries !== undefined ? { missedEntries: projected.missedEntries } : {}) } }, transport);
|
|
585
654
|
}
|
|
586
655
|
return emitTransportResult({ status: "ok", artifactId: id, sha256: artifact.sha256, bytes: artifact.content.length, keys, fetch: artifactFetchHint(id) }, transport);
|
|
587
656
|
}
|
|
@@ -610,10 +679,10 @@ async function cmdArtifactGet(id, transport, options = {}) {
|
|
|
610
679
|
return emitTransportResult(result, transport);
|
|
611
680
|
}
|
|
612
681
|
if (options.jq !== undefined) {
|
|
613
|
-
const projected =
|
|
682
|
+
const projected = projectArtifactPath(artifact.content.toString("utf8") === "" ? undefined : content, options.jq);
|
|
614
683
|
return emitTransportResult(projected.missed
|
|
615
|
-
? { status: "ok", artifactId: id, sha256: artifact.sha256, path: options.jq, content: null, pathMiss: true, missedAt: projected.at, available: projected.available, hint: `路径未命中段 "${projected.at}"——available 是该层可用的键/范围;方括号下标(a[0])与点式(a.0
|
|
616
|
-
: { status: "ok", artifactId: id, sha256: artifact.sha256, path: options.jq, content: projected.
|
|
684
|
+
? { status: "ok", artifactId: id, sha256: artifact.sha256, path: options.jq, content: null, pathMiss: true, missedAt: projected.at, available: projected.available, hint: `路径未命中段 "${projected.at}"——available 是该层可用的键/范围;方括号下标(a[0])与点式(a.0)等价;[] 映射数组(items[].fields.x)` }
|
|
685
|
+
: { status: "ok", artifactId: id, sha256: artifact.sha256, path: options.jq, content: projected.content ?? null, ...(projected.mapped !== undefined ? { mapped: projected.mapped } : {}), ...(projected.missedEntries !== undefined ? { missedEntries: projected.missedEntries } : {}) }, transport);
|
|
617
686
|
}
|
|
618
687
|
// r6-onboarding 2026-10-21: 47 artifact fetches, 0 used a projection; the 10
|
|
619
688
|
// large ones (up to 55 KB) pulled whole workflow templates just to read one
|
|
@@ -1089,7 +1158,7 @@ function onboardingHelp() {
|
|
|
1089
1158
|
"首次使用、或不确定从哪开始:omc help omc-start-here(随包权威 topic 路线);被拒或状态异常:omc help omc-refusals。",
|
|
1090
1159
|
"方法论按需读:omc help <topic> 默认只回节索引(每节字节数);用 --section \"<标题>\" 取正文(可重复),确需整篇才 --all。",
|
|
1091
1160
|
"目标→工具一步定位:omc next \"<你要做什么>\"(离线、只读,给能力/说明/首批命令;不确定会明说并回退到 omc-requirement-analysis)。",
|
|
1092
|
-
"大结果取值一步到位:读面回 detailArtifactId 时,用 omc call <tool> … --
|
|
1161
|
+
"大结果取值一步到位:读面回 detailArtifactId 时,用 omc call <tool> … --jq <path>(可重复;`[]` 映射数组,如 items[].fields.planNo)或 --extract/--extract-keys,省掉再跑一次 omc artifact get;artifact get 正文超 8KB 默认也只回形状(--jq/--keys 定向取,--full 才整包)。批量形状预检用 --args-list … --preflight(零 wire、零写入,一次报全)。",
|
|
1093
1162
|
"工具发现:omc commands --json(含 capability id/lane/recipes;加 --compact 只回工具行、无目录散文)· omc capabilities(能力 id/lane/aliases/tools/依赖顺序)· omc capability <id> --executable(单能力完整执行包)。",
|
|
1094
1163
|
"单工具/契约:omc describe <tool>(schema+recipe+能力路由)· omc contract <lane>(wire 契约+能力路由);环境就绪:omc call discover_environment --env <env> --args '{}'。",
|
|
1095
1164
|
"写入两道门:对话层草稿确认(omc help omc-config-draft-review)+ 环境 writable 开关;生产环境每笔写需在 args 里带 confirmProduction:true。",
|
|
@@ -1213,8 +1282,8 @@ async function main() {
|
|
|
1213
1282
|
commandExitCode = CLI_EXIT_CODES.ok;
|
|
1214
1283
|
});
|
|
1215
1284
|
command.command("doctor").option("--environment <environment>").option("--env <environment>").option("--service <serviceCode>", "可选:体检单个业务集成服务的鉴权参数形状(掩码/占位/全 0 检出,WARN 非致命)").action(async (options) => { commandExitCode = await cmdDoctor(new ConfigStore(defaultPaths()), options.environment ?? options.env, options.service); });
|
|
1216
|
-
command.command("call <tool>").option("--env <environment>").option("--environment <environment>").option("--args <json>").option("--args-file <path>").option("--args-list <json-array>", '同一工具跑 N 次(JSON 数组,每条一个 args 对象);逐条执行、默认不中断,最后回 succeeded/failed 汇总。适合批量建字段等').addOption(new Option("--extract <path>", "在本次结果的 artifact 内取一个路径的值(可重复;省掉一次 artifact get)").argParser((value, previous = []) => [...previous, value]).default([])).option("--extract-keys", "列出本次结果 artifact 的顶层键与体积(省掉一次 artifact get --keys)").option("--max-output-bytes <bytes>").option("--request-id <requestId>").option("--timeout-ms <milliseconds>").option("--wait <milliseconds>").option("--dry-run").addHelpText("after", `\n${onboardingHelp()}\n`).action(async (tool, options) => {
|
|
1217
|
-
const rest = [tool, ...(options.env ? ["--env", options.env] : []), ...(options.environment ? ["--environment", options.environment] : []), ...(options.args !== undefined ? ["--args", options.args] : []), ...(options.argsFile !== undefined ? ["--args-file", options.argsFile] : []), ...(options.argsList !== undefined ? ["--args-list", options.argsList] : []), ...(Array.isArray(options.extract) ? options.extract.flatMap((path) => ["--extract", path]) : []), ...(options.extractKeys ? ["--extract-keys"] : []), ...(options.maxOutputBytes !== undefined ? ["--max-output-bytes", options.maxOutputBytes] : []), ...(options.requestId ? ["--request-id", options.requestId] : []), ...(options.timeoutMs !== undefined ? ["--timeout-ms", options.timeoutMs] : []), ...(options.wait !== undefined ? ["--wait", options.wait] : []), ...(options.dryRun ? ["--dry-run"] : [])];
|
|
1285
|
+
command.command("call <tool>").option("--env <environment>").option("--environment <environment>").option("--args <json>").option("--args-file <path>").option("--args-list <json-array>", '同一工具跑 N 次(JSON 数组,每条一个 args 对象);逐条执行、默认不中断,最后回 succeeded/failed 汇总。适合批量建字段等').option("--preflight", "与 --args-list 一起用:只跑参数校验 + 写前语义闸(零 wire、零写入),一次报全所有形状问题").addOption(new Option("--extract <path>", "在本次结果的 artifact 内取一个路径的值(可重复;省掉一次 artifact get)").argParser((value, previous = []) => [...previous, value]).default([])).addOption(new Option("--jq [path]", "同 --extract,并支持 [] 映射(如 items[].fields.planNo);裸 --jq 列出结果的顶层键").argParser((value, previous = []) => [...previous, value]).default([])).option("--extract-keys", "列出本次结果 artifact 的顶层键与体积(省掉一次 artifact get --keys)").option("--max-output-bytes <bytes>").option("--request-id <requestId>").option("--timeout-ms <milliseconds>").option("--wait <milliseconds>").option("--dry-run").addHelpText("after", `\n${onboardingHelp()}\n`).action(async (tool, options) => {
|
|
1286
|
+
const rest = [tool, ...(options.env ? ["--env", options.env] : []), ...(options.environment ? ["--environment", options.environment] : []), ...(options.args !== undefined ? ["--args", options.args] : []), ...(options.argsFile !== undefined ? ["--args-file", options.argsFile] : []), ...(options.argsList !== undefined ? ["--args-list", options.argsList] : []), ...(options.preflight ? ["--preflight"] : []), ...(Array.isArray(options.extract) ? options.extract.flatMap((path) => ["--extract", path]) : []), ...(Array.isArray(options.jq) ? options.jq.flatMap((path) => path === true ? ["--jq"] : ["--jq", path]) : []), ...(options.extractKeys ? ["--extract-keys"] : []), ...(options.maxOutputBytes !== undefined ? ["--max-output-bytes", options.maxOutputBytes] : []), ...(options.requestId ? ["--request-id", options.requestId] : []), ...(options.timeoutMs !== undefined ? ["--timeout-ms", options.timeoutMs] : []), ...(options.wait !== undefined ? ["--wait", options.wait] : []), ...(options.dryRun ? ["--dry-run"] : [])];
|
|
1218
1287
|
commandExitCode = await cmdCall(new ConfigStore(defaultPaths()), rest);
|
|
1219
1288
|
});
|
|
1220
1289
|
command.command("compute").option("--schema <schemaCode>").option("--schema-code <schemaCode>").option("--target <propertyCode>").option("--property <propertyCode>").option("--formula <formula>").option("--where <condition>", "条件聚合:\"<子列code> = <值>\",与 --formula \"{<子表code>.<子列code>}\" 配合编译为 COUNTIF/SUMIF").option("--aggregate <count|sum>", "条件聚合用哪种:count(默认,→COUNTIF)/ sum(→SUMIF)").option("--allow-unfiltered", "显式接受「对整个子表列无条件聚合」;不给则拒绝(会把不符合条件的行也算进去)").option("--name <name>").option("--env <environment>").option("--environment <environment>").option("--apply").addHelpText("after", "\n字段计算规则拼装器:公式 {字段} 引用 → datarule type2 完整形状(editorText/editorMark 自动生成)。默认 dry-run,--apply 写入。条件聚合用 --formula \"{子表.子列}\" --where \"子列 = 值\"。复杂场景(条件分支/跨模型)走 omc next 的 recipe 路径。\n").action(async (options) => {
|
|
@@ -1233,6 +1302,25 @@ async function main() {
|
|
|
1233
1302
|
const rest = ["approval-flow", ...(options.schema ? ["--schema", options.schema] : []), ...(options.schemaCode ? ["--schema-code", options.schemaCode] : []), ...(Array.isArray(options.steps) ? options.steps.flatMap((s) => ["--steps", s]) : []), ...(options.env ? ["--env", options.env] : []), ...(options.environment ? ["--environment", options.environment] : []), ...(options.apply ? ["--apply"] : []), ...(options.originatorNode === false ? ["--no-originator-node"] : []), ...(options.rejectToStart === false ? ["--no-reject-to-start"] : [])];
|
|
1234
1303
|
commandExitCode = await cmdApprovalFlow(rest);
|
|
1235
1304
|
});
|
|
1305
|
+
command
|
|
1306
|
+
.command("model-purge")
|
|
1307
|
+
.option("--code <schemaCode>", "模型 code")
|
|
1308
|
+
.option("--schema <schemaCode>")
|
|
1309
|
+
.option("--purge-instances", "连流程实例一起删(workflow.delete 遇到实例会 402508)")
|
|
1310
|
+
.option("--env <environment>")
|
|
1311
|
+
.option("--environment <environment>")
|
|
1312
|
+
.option("--apply")
|
|
1313
|
+
.addHelpText("after", "\n一条命令按依赖倒序清掉一个模型:列表视图 → 业务规则 → 计算规则 → 流程实例 → 流程 → 模型(字段/表单随模型级联)。默认 dry-run。\n")
|
|
1314
|
+
.action(async (options) => {
|
|
1315
|
+
const rest = ["model-purge",
|
|
1316
|
+
...(options.code ? ["--code", options.code] : []),
|
|
1317
|
+
...(options.schema ? ["--schema", options.schema] : []),
|
|
1318
|
+
...(options.purgeInstances ? ["--purge-instances"] : []),
|
|
1319
|
+
...(options.env ? ["--env", options.env] : []),
|
|
1320
|
+
...(options.environment ? ["--environment", options.environment] : []),
|
|
1321
|
+
...(options.apply ? ["--apply"] : [])];
|
|
1322
|
+
commandExitCode = await cmdModelPurge(rest);
|
|
1323
|
+
});
|
|
1236
1324
|
command
|
|
1237
1325
|
.command("model-scaffold")
|
|
1238
1326
|
.option("--code <schemaCode>", "模型 code")
|
|
@@ -1404,7 +1492,7 @@ main().then((code) => {
|
|
|
1404
1492
|
// envelope (or, for `env --json`, their own JSON) and were printing a second
|
|
1405
1493
|
// empty envelope after it (live 2026-10-23: `omc rollback apply` answered two
|
|
1406
1494
|
// lines, violating the one-envelope stdout contract).
|
|
1407
|
-
const ownsStdout = ["call", "script", "artifact", "commands", "describe", "contract", "capabilities", "capability", "next", "usage", "intent", "help", "form-default", "compute", "rule-when", "approval-flow", "model-scaffold", "rollback", "env", "--version", "-V"];
|
|
1495
|
+
const ownsStdout = ["call", "script", "artifact", "commands", "describe", "contract", "capabilities", "capability", "next", "usage", "intent", "help", "form-default", "compute", "rule-when", "approval-flow", "model-scaffold", "model-purge", "rollback", "env", "--version", "-V"];
|
|
1408
1496
|
if (ownsStdout.includes(argv[2] ?? "")) {
|
|
1409
1497
|
completeUsage(code);
|
|
1410
1498
|
finish(code);
|
package/dist/endpoints.js
CHANGED
|
@@ -177,6 +177,16 @@ export const ENDPOINTS = {
|
|
|
177
177
|
* so it can answer "what is pending for user X", not only "for me".
|
|
178
178
|
*/
|
|
179
179
|
workflowListWorkItems: "/api/api/runtime/workflow/list_workitems",
|
|
180
|
+
/** Instances of a workflow (WorkflowInstanceController.list_instances). */
|
|
181
|
+
workflowListInstances: "/api/api/runtime/workflow/list_instances",
|
|
182
|
+
/**
|
|
183
|
+
* Delete one workflow instance (WorkflowInstanceController.delete_instance).
|
|
184
|
+
* Only workflowInstanceId is required — `userId` is taken from the login
|
|
185
|
+
* session, so an instance started by another user can be erased from a single
|
|
186
|
+
* admin session. Required before `workflow.delete`, which refuses (402508)
|
|
187
|
+
* while any instance exists.
|
|
188
|
+
*/
|
|
189
|
+
workflowDeleteInstance: "/api/api/runtime/workflow/delete_instance",
|
|
180
190
|
workflowGetBizObjectByWfInstanceId: "/api/api/runtime/workflow/getBizObjectByWfInstanceId",
|
|
181
191
|
workflowListInstanceActivity: "/api/api/runtime/workflow/list_workflow_instance_activity",
|
|
182
192
|
workflowListFinishedWorkItems: "/api/api/runtime/workflow/list_finished_workitems",
|
package/dist/goal-routes.js
CHANGED
|
@@ -113,13 +113,36 @@ function capabilityScores(registry, goal) {
|
|
|
113
113
|
}
|
|
114
114
|
return scores;
|
|
115
115
|
}
|
|
116
|
+
/**
|
|
117
|
+
* Default ladder examples (r15). A ladder step with no goal-matching recipe only
|
|
118
|
+
* got `omc commands --lane`, so "创建业务规则" handed the caller nothing runnable
|
|
119
|
+
* even though `rule.save` ships examples. These are the lane's canonical shapes,
|
|
120
|
+
* used ONLY when the goal does not match a specific recipe.
|
|
121
|
+
*/
|
|
122
|
+
const DEFAULT_LADDER_RECIPES = {
|
|
123
|
+
businessrule: { tool: "rule.save", recipe: "business-rule-branch-conditional" },
|
|
124
|
+
datarule: { tool: "datarule.create", recipe: "field-compute-rule" },
|
|
125
|
+
};
|
|
126
|
+
function defaultRecipeFor(registry, capabilityId, lane) {
|
|
127
|
+
const wanted = DEFAULT_LADDER_RECIPES[capabilityId];
|
|
128
|
+
if (!wanted)
|
|
129
|
+
return undefined;
|
|
130
|
+
for (const tool of registry) {
|
|
131
|
+
if (tool.name !== wanted.tool || tool.lane !== lane)
|
|
132
|
+
continue;
|
|
133
|
+
const recipe = (tool.executionMetadata?.recipes ?? []).find((entry) => entry.id === wanted.recipe);
|
|
134
|
+
if (recipe)
|
|
135
|
+
return { tool: tool.name, recipe: recipe.id, title: recipe.title };
|
|
136
|
+
}
|
|
137
|
+
return undefined;
|
|
138
|
+
}
|
|
116
139
|
function bestRecipe(registry, capabilityId, lane, goal, minScore = 3) {
|
|
117
140
|
let best;
|
|
118
141
|
for (const tool of registry) {
|
|
119
142
|
if (tool.lane !== lane)
|
|
120
143
|
continue;
|
|
121
144
|
for (const recipe of tool.executionMetadata?.recipes ?? []) {
|
|
122
|
-
const score = overlap(goal, `${recipe.title} ${recipe.id}`);
|
|
145
|
+
const score = overlap(goal, `${recipe.title} ${recipe.id} ${(recipe.aliases ?? []).join(" ")}`);
|
|
123
146
|
if (score >= minScore && (best === undefined || score > best.score))
|
|
124
147
|
best = { tool: tool.name, recipe: recipe.id, title: recipe.title, score };
|
|
125
148
|
}
|
|
@@ -238,17 +261,27 @@ function sectionPointersFor(ids) {
|
|
|
238
261
|
return pointers;
|
|
239
262
|
}
|
|
240
263
|
/**
|
|
241
|
-
* Ready-to-run methodology reads
|
|
242
|
-
*
|
|
243
|
-
*
|
|
244
|
-
*
|
|
245
|
-
*
|
|
264
|
+
* Ready-to-run methodology reads.
|
|
265
|
+
*
|
|
266
|
+
* Round 15 (bench60-62) collapsed ~18 per-section reads into one per topic to
|
|
267
|
+
* save turns. r10 F7 found the other edge: following that hint fetched
|
|
268
|
+
* EVERYTHING at once (~15 KB) when most tasks need only the first section.
|
|
269
|
+
* So the primary command per topic fetches the FIRST required section, and the
|
|
270
|
+
* remaining sections are offered as separate incremental commands (each keeps
|
|
271
|
+
* its byte count in `readSections`) — cheap by default, still one command away.
|
|
246
272
|
*/
|
|
247
|
-
function
|
|
273
|
+
function splitReadCommands(pointers) {
|
|
248
274
|
const byTopic = new Map();
|
|
249
275
|
for (const pointer of pointers)
|
|
250
276
|
byTopic.set(pointer.topic, [...(byTopic.get(pointer.topic) ?? []), pointer.section]);
|
|
251
|
-
|
|
277
|
+
const readCommands = [];
|
|
278
|
+
const readMore = [];
|
|
279
|
+
for (const [topic, sections] of byTopic.entries()) {
|
|
280
|
+
readCommands.push(`omc help ${topic} --section "${sections[0]}"`);
|
|
281
|
+
for (const section of sections.slice(1))
|
|
282
|
+
readMore.push(`omc help ${topic} --section "${section}"`);
|
|
283
|
+
}
|
|
284
|
+
return { readCommands, readMore };
|
|
252
285
|
}
|
|
253
286
|
/**
|
|
254
287
|
* The route's methodology-read surface: pointers (+ per-section bytes) +
|
|
@@ -279,14 +312,15 @@ function routeReads(readSections) {
|
|
|
279
312
|
}
|
|
280
313
|
}
|
|
281
314
|
}
|
|
282
|
-
|
|
315
|
+
const commands = splitReadCommands(readSections);
|
|
316
|
+
return { readSections: budgeted, readCommands: commands.readCommands, ...(commands.readMore.length > 0 ? { readMore: commands.readMore } : {}), readHint: READ_HINT, ...(firstSection !== undefined ? { firstSection } : {}) };
|
|
283
317
|
}
|
|
284
318
|
/** UTF-8 byte length of one methodology section (best-effort; 0 when unreadable). */
|
|
285
319
|
function sectionBytes(pointer) {
|
|
286
320
|
const resolved = readSkillSection(pointer.topic, pointer.section);
|
|
287
321
|
return resolved ? Buffer.byteLength(resolved.section, "utf8") : 0;
|
|
288
322
|
}
|
|
289
|
-
const READ_HINT = '
|
|
323
|
+
const READ_HINT = '先跑 readCommands(每个 topic 只取第一节,形状 `omc help <topic> --section "<section>"`,字节数见 readSections);不够再按需补 readMore,不要一次全读。`omc help <topic>` 默认只回节索引(每节带字节数,约 1KB),不要再用 `--all` 读整篇(5–14KB,读一次就留在后续每一轮)。';
|
|
290
324
|
function orderedCapabilities(ids, index) {
|
|
291
325
|
const selected = new Set(ids);
|
|
292
326
|
const nodes = [...index.nodeById.values()];
|
|
@@ -322,6 +356,18 @@ function orderedCapabilities(ids, index) {
|
|
|
322
356
|
*/
|
|
323
357
|
const WHOLE_PACKAGE_CAPABILITIES = ["model", "field", "form", "listview", "datarule", "businessrule", "workflow", "onlinejs"];
|
|
324
358
|
const COMPOSER_ROUTES = Object.freeze([
|
|
359
|
+
{
|
|
360
|
+
id: "model-purge",
|
|
361
|
+
command: "omc model-purge --code <模型code> --purge-instances --apply",
|
|
362
|
+
usage: "omc model-purge --help",
|
|
363
|
+
summary: "清理一个模型:先删流程实例,再 model.delete 级联带走流程模板/视图/字段/表单/规则;默认 dry-run,不可删的停滞实例会跳过并在结尾列出",
|
|
364
|
+
// r15: "清理本轮创建的模型和流程实例" matched approval-flow (流程 + 创建)
|
|
365
|
+
// and would have CREATED a workflow instead of removing one. Cleanup intents
|
|
366
|
+
// are their own composer, and this one comes first.
|
|
367
|
+
groups: [["清理", "删除", "移除", "回收", "清除", "purge", "teardown", "clean", "remove"], ["模型", "实例", "数据", "model", "instance", "data"]],
|
|
368
|
+
avoid: ["字段", "角色", "部门", "视图", "按钮", "规则图"],
|
|
369
|
+
topics: ["omc-write-hazards"],
|
|
370
|
+
},
|
|
325
371
|
{
|
|
326
372
|
id: "model-scaffold",
|
|
327
373
|
command: "omc model-scaffold --code <模型code> --name <模型名> --fields '<JSON数组>' --apply",
|
|
@@ -339,7 +385,7 @@ const COMPOSER_ROUTES = Object.freeze([
|
|
|
339
385
|
summary: "线性审批流一条命令:建流程 → 模板+字段只读权限 → 发布(多级用重复 --steps)",
|
|
340
386
|
groups: [["审批", "流程"], ["建", "创建", "新建", "配置", "加", "搭"]],
|
|
341
387
|
// Branching / countersign shapes are NOT this composer's business.
|
|
342
|
-
avoid: ["条件分支", "分支", "会签", "并行", "或签", "网关", "子流程", "动态审批人"],
|
|
388
|
+
avoid: ["条件分支", "分支", "会签", "并行", "或签", "网关", "子流程", "动态审批人", "清理", "删除", "移除", "purge", "teardown"],
|
|
343
389
|
topics: ["omc-five-piece-flow"],
|
|
344
390
|
},
|
|
345
391
|
{
|
|
@@ -531,7 +577,7 @@ export function goalRoute(registry, goal) {
|
|
|
531
577
|
if (readish) {
|
|
532
578
|
return { capability: id, lane: capability.lane, readTools: readToolsInLane(registry, capability.lane), firstCommand: `omc capability ${id}` };
|
|
533
579
|
}
|
|
534
|
-
const recipe = bestRecipe(registry, id, capability.lane, goal);
|
|
580
|
+
const recipe = bestRecipe(registry, id, capability.lane, goal) ?? defaultRecipeFor(registry, id, capability.lane);
|
|
535
581
|
return {
|
|
536
582
|
capability: id,
|
|
537
583
|
lane: capability.lane,
|
|
@@ -553,7 +599,21 @@ export function goalRoute(registry, goal) {
|
|
|
553
599
|
confident: true,
|
|
554
600
|
mode: whole ? "whole-package" : mode,
|
|
555
601
|
match: { kind: readish ? "read-ladder" : "ladder", ref: ordered.join("→"), capabilities: ordered },
|
|
556
|
-
route: {
|
|
602
|
+
route: {
|
|
603
|
+
...(whole ? { primary: "intent", atomicSupplement } : { ladder }),
|
|
604
|
+
...routeReads(readSections),
|
|
605
|
+
// Each ladder step already carried a runnable recipe command, but it sat
|
|
606
|
+
// buried at `ladder[i].recipe.command` behind a readiness line — r14's
|
|
607
|
+
// probe reported "ladder 本身没给可直接跑的命令" and hand-built the rule
|
|
608
|
+
// graph instead. Surface them at the top level where `firstCommands` is
|
|
609
|
+
// the thing an agent actually reads.
|
|
610
|
+
firstCommands: [
|
|
611
|
+
DISCOVERY_AUTHORITY.readiness,
|
|
612
|
+
...ladder.flatMap((step) => step.recipe?.command ? [step.recipe.command] : []),
|
|
613
|
+
],
|
|
614
|
+
write,
|
|
615
|
+
...(readish ? { readback: true } : {}),
|
|
616
|
+
},
|
|
557
617
|
write,
|
|
558
618
|
...(whole ? { intent: intentGuidance() } : {}),
|
|
559
619
|
};
|