@stalfh233/omc-cli 0.4.23 → 0.4.25
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/dist/cli.js +2 -2
- package/dist/discovery.js +24 -2
- package/dist/goal-routes.js +19 -0
- package/dist/intent/capabilities/businessrule.js +29 -4
- package/dist/intent/schema.js +2 -2
- package/dist/lanes/list-view.js +5 -1
- package/dist/lanes/runtime-data.js +39 -10
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -734,11 +734,11 @@ function intentWarnings(intent) {
|
|
|
734
734
|
// default (数据新增/数据更新/…). `rule.save` keeps the stored name for these
|
|
735
735
|
// codes, so a declared `name` is ignored — say so rather than silently
|
|
736
736
|
// dropping it (a caller who meant to rename would otherwise be confused).
|
|
737
|
-
if (PLATFORM_TRIGGER_RULE_CODES.has(rule.code))
|
|
737
|
+
if (PLATFORM_TRIGGER_RULE_CODES.has(rule.code) && rule.nameImplicit !== true)
|
|
738
738
|
warnings.push({
|
|
739
739
|
resource: `rule:${model.code}.${rule.code}`,
|
|
740
740
|
code: "rule-preset-name-ignored",
|
|
741
|
-
message: `规则 ${rule.code} 是平台预置触发规则,其名称由平台维护(如 Create=数据新增);声明的 name "${rule.name}"
|
|
741
|
+
message: `规则 ${rule.code} 是平台预置触发规则,其名称由平台维护(如 Create=数据新增);声明的 name "${rule.name}" 不会生效(只有自定义规则的名称与节点名可改)——预置行可直接省略 name 以消除本警告;要改显示名请改 nodeGraph/actions 中节点的 nodeName。`,
|
|
742
742
|
});
|
|
743
743
|
let graph;
|
|
744
744
|
try {
|
package/dist/discovery.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
2
|
+
import { closestMatch } from "./closest-match.js";
|
|
3
|
+
import { RUNNABLE_EXAMPLES } from "./intent/schema.js";
|
|
4
|
+
const INTENT_EXAMPLE_IDS = RUNNABLE_EXAMPLES.map((example) => example.id);
|
|
2
5
|
import { z } from "zod";
|
|
3
6
|
import { LANE_CONTRACTS } from "./contract-resources.js";
|
|
4
7
|
import { intentCapabilityBlock } from "./intent/cli.js";
|
|
@@ -213,12 +216,31 @@ export function describeExample(registry, name, exampleId) {
|
|
|
213
216
|
// No id (or an unknown one) lists the runnable ids instead of a dead end, so
|
|
214
217
|
// an agent that guessed never burns a retry (sandbox26: `--example` with no
|
|
215
218
|
// id was a hard commander error; an unknown id already listed `available`).
|
|
219
|
+
// r18 retro: two more layers — a did-you-mean (the id was GUESSED, e.g.
|
|
220
|
+
// `scalar-numeric-radio` on configure_model) and a cross-tool reverse
|
|
221
|
+
// lookup (that id exists under ANOTHER tool's recipes — point there
|
|
222
|
+
// instead of a bare miss). Machine-recoverable status, copy-paste next.
|
|
223
|
+
const ids = recipes.map((entry) => entry.id);
|
|
224
|
+
const closest = exampleId !== undefined ? closestMatch(exampleId, ids) : undefined;
|
|
225
|
+
const otherTool = exampleId === undefined
|
|
226
|
+
? undefined
|
|
227
|
+
: registry.find((entry) => entry !== tool && (entry.executionMetadata?.recipes ?? []).some((recipe2) => recipe2.id === exampleId));
|
|
228
|
+
// The intent examples (scalar-numeric-radio, relation-work-sheet, …) live
|
|
229
|
+
// on the `intent` CLI face, not a registry tool — r18 asked lane tools for
|
|
230
|
+
// them repeatedly. Redirect to the face that owns them.
|
|
231
|
+
const intentExample = exampleId !== undefined && otherTool === undefined ? INTENT_EXAMPLE_IDS.find((id) => id === exampleId) : undefined;
|
|
216
232
|
return {
|
|
217
233
|
name: tool.name,
|
|
234
|
+
// NOTE: no `status` key — the envelope classifier would turn a non-"ok"
|
|
235
|
+
// status into a non-zero exit; a discovery miss is a successful query
|
|
236
|
+
// with a miss payload (`found:false` is the machine signal).
|
|
218
237
|
...(exampleId !== undefined ? { example: exampleId } : {}),
|
|
219
238
|
found: false,
|
|
220
|
-
available:
|
|
221
|
-
...(
|
|
239
|
+
available: ids,
|
|
240
|
+
...(closest !== undefined ? { didYouMean: closest, next: `omc describe ${tool.name} --example ${closest}` } : recipes[0] ? { next: `omc describe ${tool.name} --example ${recipes[0].id}` } : {}),
|
|
241
|
+
...(otherTool !== undefined ? { existsUnder: otherTool.name, crossToolNext: `omc describe ${otherTool.name} --example ${exampleId}` } : {}),
|
|
242
|
+
...(intentExample !== undefined ? { existsUnder: "intent", crossToolNext: `omc intent schema --example ${exampleId}(整包任务用声明式形状)` } : {}),
|
|
243
|
+
...(ids.length === 0 && !otherTool ? { hint: `本工具没有配方示例;整包任务用 omc intent schema --example <id> 取声明式形状` } : {}),
|
|
222
244
|
};
|
|
223
245
|
}
|
|
224
246
|
return {
|
package/dist/goal-routes.js
CHANGED
|
@@ -44,7 +44,26 @@ function intentGuidance() {
|
|
|
44
44
|
return {
|
|
45
45
|
path: "declarative",
|
|
46
46
|
why: "整包需求:用一份声明式意图文件描述 models/fields/form/listview/rules/workflow/onlinejs/services,由编译器合成平台原生结构并按依赖顺序执行;比逐能力拼装更省上下文、更不易漂移。",
|
|
47
|
+
starter: `intentVersion: 1
|
|
48
|
+
models:
|
|
49
|
+
- code: <模型code> # 如 omc_todo_task
|
|
50
|
+
name: <业务名>
|
|
51
|
+
fields:
|
|
52
|
+
- {code: title, name: 标题, type: SHORT_TEXT}
|
|
53
|
+
- {code: amount, name: 数量, type: NUMERICAL, options: {scale: 2}} # NUMERICAL 必带 scale
|
|
54
|
+
- {code: level, name: 等级, type: RADIO, choices: [高, 中, 低]} # 选择族用顶层 choices
|
|
55
|
+
form: {publish: true}
|
|
56
|
+
listview: {fieldCodes: [title, level]}
|
|
57
|
+
rules:
|
|
58
|
+
- code: Create # 平台预置触发行(name 省略)
|
|
59
|
+
name: 新增规则 # 仅自定义规则需要 name;预置行可省
|
|
60
|
+
enabled: true
|
|
61
|
+
actions:
|
|
62
|
+
- when: [{field: amount, gt: 10}]
|
|
63
|
+
set: [{field: title, value: "超限"}]
|
|
64
|
+
`,
|
|
47
65
|
commands: [
|
|
66
|
+
"把上面的 starter 存成 <file>.yaml 按需求改字段后:omc intent validate <file>.yaml(离线)→ omc intent plan <file>.yaml --env <env> → omc intent apply <file>.yaml --env <env>(apply 也能直接吃 YAML)",
|
|
48
67
|
"omc intent schema [--example <id>]:离线看 intent YAML 的键位/操作符/可运行示例(写 intent 前先看,省去猜键名与试错)",
|
|
49
68
|
"omc intent plan <file> --env <env>:离线先校验(形体错在接触环境前返回,故 validate 可选);大 plan 自动外置,回 planExternalized:true + planFile,直接 `omc intent apply <planFile>`;小 plan 才内联在 result.plan",
|
|
50
69
|
"omc intent apply <planFile|plan.json>(默认最终读回等价校验;--no-verify 关闭)",
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ENDPOINTS } from "../../endpoints.js";
|
|
2
|
+
import { PLATFORM_TRIGGER_RULE_CODES } from "../../lanes/rule-graph.js";
|
|
2
3
|
import { decodeJsonColumn, JSON_COLUMN_NULL_COALESCE } from "../../json-column.js";
|
|
3
4
|
import { wireCall, wireRows } from "../../wire.js";
|
|
4
5
|
import { countConditionLeaves, prepareRuleGraph, RULE_IDENTITY_FILTER, validateRuleNodeGraph } from "../../lanes/rule-graph.js";
|
|
@@ -7,6 +8,16 @@ import { isScalarString, pushError } from "../validation.js";
|
|
|
7
8
|
import { toPlain } from "../yaml.js";
|
|
8
9
|
/** Keys a rule item accepts. */
|
|
9
10
|
const RULE_KEYS = new Set(["code", "name", "enabled", "bizRuleType", "customTrigger", "actions", "nodeGraph"]);
|
|
11
|
+
/** Platform-managed display names of the preset trigger rows (live rule.list evidence 2026-10-12). */
|
|
12
|
+
const PRESET_RULE_DEFAULT_NAMES = Object.freeze({
|
|
13
|
+
Create: "数据新增",
|
|
14
|
+
Update: "数据更新",
|
|
15
|
+
Delete: "数据删除",
|
|
16
|
+
GetList: "加载列表数据",
|
|
17
|
+
Load: "加载表单详情",
|
|
18
|
+
Available: "流程生效",
|
|
19
|
+
Cancel: "流程作废",
|
|
20
|
+
});
|
|
10
21
|
/** Keys a compact action accepts. */
|
|
11
22
|
const ACTION_KEYS = new Set(["type", "target", "filter", "when", "set"]);
|
|
12
23
|
/** Keys a compact filter/assignment item accepts. */
|
|
@@ -369,8 +380,21 @@ export const businessruleCapability = {
|
|
|
369
380
|
pushError(ctx, "intent-rule-code-duplicate", `规则 code 在模型内重复:${code}`, `${base}.code`, "value");
|
|
370
381
|
seen.add(code);
|
|
371
382
|
}
|
|
372
|
-
|
|
373
|
-
|
|
383
|
+
// r18 retro (G4, parse-level): a PRESET trigger row's name is
|
|
384
|
+
// platform-managed (Create=数据新增…; rule.save's update branch ignores
|
|
385
|
+
// the caller's name for these codes) — so it may be OMITTED for preset
|
|
386
|
+
// codes and the compiler fills the platform default. CUSTOM rules keep
|
|
387
|
+
// the required name (the designer enforces one there too).
|
|
388
|
+
let nameImplicit = false;
|
|
389
|
+
let ruleName;
|
|
390
|
+
if (isScalarString(nameNode))
|
|
391
|
+
ruleName = nameNode.value;
|
|
392
|
+
else if (PLATFORM_TRIGGER_RULE_CODES.has(code)) {
|
|
393
|
+
nameImplicit = true;
|
|
394
|
+
ruleName = PRESET_RULE_DEFAULT_NAMES[code] ?? code;
|
|
395
|
+
}
|
|
396
|
+
else
|
|
397
|
+
pushError(ctx, "intent-rule-name-invalid", "规则 name 必须是非空字符串(预置触发行 Create/Update/… 可省略,省略即用平台默认名)", `${base}.name`, "value");
|
|
374
398
|
let enabled = false;
|
|
375
399
|
if (enabledNode !== undefined) {
|
|
376
400
|
if (enabledNode.kind !== "scalar" || typeof enabledNode.value !== "boolean")
|
|
@@ -406,11 +430,12 @@ export const businessruleCapability = {
|
|
|
406
430
|
else
|
|
407
431
|
nodeGraph = toPlain(nodeGraphNode);
|
|
408
432
|
}
|
|
409
|
-
if (code.length === 0 ||
|
|
433
|
+
if (code.length === 0 || ruleName === undefined)
|
|
410
434
|
return;
|
|
411
435
|
const rule = {
|
|
412
436
|
code,
|
|
413
|
-
name:
|
|
437
|
+
name: ruleName,
|
|
438
|
+
...(nameImplicit ? { nameImplicit: true } : {}),
|
|
414
439
|
enabled,
|
|
415
440
|
...(bizRuleType !== undefined ? { bizRuleType } : {}),
|
|
416
441
|
...(customTrigger !== undefined ? { customTrigger } : {}),
|
package/dist/intent/schema.js
CHANGED
|
@@ -22,7 +22,7 @@ export const INTENT_CONDITION_OPERATORS = Object.freeze({
|
|
|
22
22
|
});
|
|
23
23
|
/** A `when` gate leaf compares against a literal; `from` (field-to-field) is only for `filter`. */
|
|
24
24
|
export const INTENT_WHEN_OPERATORS = Object.freeze(["equals", "notEquals", "gt", "gte", "lt", "lte"]);
|
|
25
|
-
const RUNNABLE_EXAMPLES = Object.freeze([
|
|
25
|
+
export const RUNNABLE_EXAMPLES = Object.freeze([
|
|
26
26
|
{
|
|
27
27
|
id: "scalar-numeric-radio",
|
|
28
28
|
title: "标量/数值/RADIO(choices)——type 用族名,NUMERICAL 必带 options.scale,选择族用顶层 choices",
|
|
@@ -126,7 +126,7 @@ export function intentSchemaBlock() {
|
|
|
126
126
|
listview: { required: [], optional: ["code", "name", "fieldCodes", "absent"], note: "intent 只声明列;业务筛选/排序/按钮 apply 后用 omc call listview.configure 补" },
|
|
127
127
|
datarules: { required: ["propertyCode", "dataRuleType"], optional: ["options", "checkType", "childRule", "name", "sortKey", "absent"], note: "type3/4/5 不驱动门户;门户条件走 fields[].options" },
|
|
128
128
|
rules: {
|
|
129
|
-
required: ["code", "name", "enabled"],
|
|
129
|
+
required: ["code", "name(预置触发行 Create/Update/Delete… 可省略,声明的 name 不会生效)", "enabled"],
|
|
130
130
|
optional: ["bizRuleType", "customTrigger", "actions", "nodeGraph"],
|
|
131
131
|
note: "actions(紧凑 UPDATE)与 nodeGraph 二选一;非 UPDATE 节点用 nodeGraph",
|
|
132
132
|
actionLeaf: {
|
package/dist/lanes/list-view.js
CHANGED
|
@@ -241,7 +241,11 @@ export function registerListViewLane(tools) {
|
|
|
241
241
|
code,
|
|
242
242
|
present: true,
|
|
243
243
|
name: model.name ?? null,
|
|
244
|
-
|
|
244
|
+
// r18 retro (G5): the placeholder flag misleads ("is my view
|
|
245
|
+
// inactive?"). Views are ALWAYS-LIVE on save; keep the legacy key
|
|
246
|
+
// null-ed for shape compatibility and name the lifecycle instead.
|
|
247
|
+
published: null,
|
|
248
|
+
lifecycle: "always-live",
|
|
245
249
|
presentationType: model.queryPresentationType ?? null,
|
|
246
250
|
columnCount: columns.length,
|
|
247
251
|
columns,
|
|
@@ -548,6 +548,7 @@ export function registerRuntimeDataLane(tools) {
|
|
|
548
548
|
sheetCode: z.string().optional(),
|
|
549
549
|
formData: runtimeFormDataArg,
|
|
550
550
|
workflowCode: z.string().optional().describe("portal draft-save shape: create the row AND a DRAFT workflow instance + start workItem (finishStart=FALSE); submit later via data.submit with the returned workItemId. formData must NOT carry an id — in-place workflow start has no REST surface in 8.6.26"),
|
|
551
|
+
submit: z.boolean().optional().describe("r18 retro:true=一调完成态——保存后立即直提(无流程模型 DRAFT→COMPLETED,触发 Create 业务规则);与 workflowCode 互斥(流程草稿须走 data.submit 两段式)。默认 false 仅存草稿"),
|
|
551
552
|
...readbackProjectionArgs,
|
|
552
553
|
}, async (session, args) => {
|
|
553
554
|
const { sheetCode, resolvedFrom } = await resolveFormSheetCode(session, { schemaCode: args.schemaCode, sheetCode: args.sheetCode });
|
|
@@ -630,18 +631,46 @@ export function registerRuntimeDataLane(tools) {
|
|
|
630
631
|
if (typeof bizObjectId === "string" && bizObjectId) {
|
|
631
632
|
readbackData = (await wireCall(session, { path: ENDPOINTS.runtimeFormLoad, query: { schemaCode: args.schemaCode, sheetCode, objectId: bizObjectId } })).data;
|
|
632
633
|
}
|
|
634
|
+
// Ticket 111: a plain save lands as sequenceStatus DRAFT, and DRAFT
|
|
635
|
+
// rows SKIP the Create/Update rule dispatch entirely
|
|
636
|
+
// (DomainServiceImpl.java:240-248 buckets drafts before the rule
|
|
637
|
+
// branch) — business rules only run once data.submit advances the
|
|
638
|
+
// row. Saying so on every plain-save readback prevents the
|
|
639
|
+
// "rule didn't fire" misdiagnosis.
|
|
640
|
+
const plainReadback = {
|
|
641
|
+
authoritative: projectReadbackRecord(normalizeLoadedRecord((readbackData ?? {})), args),
|
|
642
|
+
draftRuleHint: "草稿态(DRAFT)保存不触发业务规则——DRAFT 行跳过 Create/Update 分发(DomainServiceImpl.java:240-248);要一调完成态本调用加 submit:true;已存草稿直提形状:data.submit {schemaCode, bizObjectId: 本读回的 bizObjectId, formData}(submit 自持久化)",
|
|
643
|
+
};
|
|
644
|
+
// r18 retro (G3): one-call COMPLETED creation — the agent goal is usually
|
|
645
|
+
// "record AND rule effect", not "a draft". Chain the no-flow submit in
|
|
646
|
+
// the same governed write (the same sibling execute apply_changes uses).
|
|
647
|
+
if (args.submit === true) {
|
|
648
|
+
const submitTool = tools.find((entry) => entry.name === "data.submit");
|
|
649
|
+
const submitOutcome = submitTool?.writeExecute
|
|
650
|
+
? await submitTool.writeExecute(session, { schemaCode: args.schemaCode, bizObjectId, formData: args.formData })
|
|
651
|
+
: { status: "error", message: "data.submit 工具不可用" };
|
|
652
|
+
if ("status" in submitOutcome && typeof submitOutcome.status === "string" && submitOutcome.status !== "ok") {
|
|
653
|
+
// The save itself DELIVERED — report it honestly with the submit
|
|
654
|
+
// refusal attached, never as a plain refusal.
|
|
655
|
+
const envelope = "envelope" in submitOutcome ? submitOutcome.envelope : submitOutcome;
|
|
656
|
+
return {
|
|
657
|
+
performed: { errcode: performed.errcode, bizObjectId, sheetCode, resolvedFrom, submitted: false },
|
|
658
|
+
readback: { ...plainReadback, submitRefused: envelope },
|
|
659
|
+
};
|
|
660
|
+
}
|
|
661
|
+
const okOutcome = submitOutcome;
|
|
662
|
+
return {
|
|
663
|
+
performed: { errcode: performed.errcode, bizObjectId, sheetCode, resolvedFrom, submitted: true },
|
|
664
|
+
readback: {
|
|
665
|
+
...okOutcome.readback,
|
|
666
|
+
submitted: true,
|
|
667
|
+
ruleHint: "已直提 COMPLETED——Create 业务规则本次已分发;规则效果以本读回字段值为准",
|
|
668
|
+
},
|
|
669
|
+
};
|
|
670
|
+
}
|
|
633
671
|
return {
|
|
634
672
|
performed: { errcode: performed.errcode, bizObjectId, sheetCode, resolvedFrom },
|
|
635
|
-
readback:
|
|
636
|
-
authoritative: projectReadbackRecord(normalizeLoadedRecord((readbackData ?? {})), args),
|
|
637
|
-
// Ticket 111: a plain save lands as sequenceStatus DRAFT, and DRAFT
|
|
638
|
-
// rows SKIP the Create/Update rule dispatch entirely
|
|
639
|
-
// (DomainServiceImpl.java:240-248 buckets drafts before the rule
|
|
640
|
-
// branch) — business rules only run once data.submit advances the
|
|
641
|
-
// row. Saying so on every plain-save readback prevents the
|
|
642
|
-
// "rule didn't fire" misdiagnosis.
|
|
643
|
-
draftRuleHint: "草稿态(DRAFT)保存不触发业务规则——DRAFT 行跳过 Create/Update 分发(DomainServiceImpl.java:240-248);data.submit 提交(DRAFT→COMPLETED)后才触发 Create 规则。无流程模型直提形状:data.submit {schemaCode, bizObjectId: 本读回的 bizObjectId, formData}(免流程身份,submit 自持久化)",
|
|
644
|
-
},
|
|
673
|
+
readback: plainReadback,
|
|
645
674
|
};
|
|
646
675
|
});
|
|
647
676
|
writeTool(tools, "data.update", RUNTIME_LANE, RUNTIME_TOOLS["data.update"].scope, RUNTIME_TOOLS["data.update"].description, {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stalfh233/omc-cli",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.25",
|
|
4
4
|
"description": "CloudPivot 8.6 configuration capability CLI — governed, fail-closed, coverage-manifest bounded.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
53
|
"commander": "14.0.3",
|
|
54
|
-
"omc-core": "0.4.
|
|
54
|
+
"omc-core": "0.4.25",
|
|
55
55
|
"proper-lockfile": "4.1.2",
|
|
56
56
|
"write-file-atomic": "6.0.0",
|
|
57
57
|
"zod": "^3.23.8",
|