@stalfh233/omc-cli 0.4.99 → 0.4.101
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 +13 -1
- package/coverage/m1-coverage-manifest-v1.json +9 -3
- package/dist/call.js +8 -3
- package/dist/capability-overview.js +12 -0
- package/dist/cli-contract.js +5 -1
- package/dist/cli.js +238 -12
- package/dist/closest-match.js +34 -0
- package/dist/compound-writes.js +310 -0
- package/dist/discovery.js +230 -9
- package/dist/goal-routes.js +159 -57
- package/dist/governed-execution.js +1 -1
- package/dist/lanes/b2-registration.js +20 -3
- package/dist/lanes/businessrule-contract.js +4 -0
- package/dist/lanes/businessrule-coverage.js +1 -1
- package/dist/lanes/businessrule.js +93 -0
- package/dist/lanes/field.js +10 -1
- package/dist/lanes/form.js +2 -2
- package/dist/lanes/report.js +21 -5
- package/dist/lanes/workflow.js +38 -3
- package/dist/recipes/b2-registration.js +8 -7
- package/dist/recipes/businessrule.js +58 -1
- package/dist/refusal-guidance.js +207 -0
- package/dist/rule-support.js +96 -0
- package/dist/skills.js +89 -21
- package/dist/tools.js +35 -0
- package/dist/usage-log.js +21 -2
- package/dist/write.js +2 -2
- package/docs/images/omc-nixi/README.md +41 -0
- package/docs/omc-introduction.md +851 -0
- package/package.json +3 -3
- package/skills/omc-capabilities.md +3 -3
- package/skills/omc-refusals.md +1 -1
- package/skills/omc-start-here.md +2 -1
- package/skills/omc-tool-selection.md +1 -1
- package/skills/omc-write-hazards.md +5 -0
package/dist/goal-routes.js
CHANGED
|
@@ -146,7 +146,13 @@ function bestRecipe(registry, capabilityId, lane, goal, minScore = 3) {
|
|
|
146
146
|
if (tool.lane !== lane)
|
|
147
147
|
continue;
|
|
148
148
|
for (const recipe of tool.executionMetadata?.recipes ?? []) {
|
|
149
|
-
|
|
149
|
+
// r19 ticket 04: an authored ALIAS phrase present in the goal is a much
|
|
150
|
+
// stronger signal than bigram overlap — "…汇总另一个模型的…" must route to
|
|
151
|
+
// formula-aggregation-cross-model, not the title-overlapping dropdown
|
|
152
|
+
// recipe. +20 per exact alias hit keeps authored intent decisive while
|
|
153
|
+
// overlap still ranks within equally aliased candidates.
|
|
154
|
+
const aliasBonus = (recipe.aliases ?? []).reduce((sum, alias) => (goal.includes(alias) ? sum + 20 : sum), 0);
|
|
155
|
+
const score = overlap(goal, `${recipe.title} ${recipe.id} ${(recipe.aliases ?? []).join(" ")}`) + aliasBonus;
|
|
150
156
|
if (score >= minScore && (best === undefined || score > best.score))
|
|
151
157
|
best = { tool: tool.name, recipe: recipe.id, title: recipe.title, score };
|
|
152
158
|
}
|
|
@@ -265,50 +271,39 @@ function sectionPointersFor(ids) {
|
|
|
265
271
|
return pointers;
|
|
266
272
|
}
|
|
267
273
|
/**
|
|
268
|
-
* Ready-to-run methodology reads.
|
|
274
|
+
* Ready-to-run methodology reads — r19 ticket 04: ONE pointer, not a shelf.
|
|
269
275
|
*
|
|
270
|
-
* Round 15
|
|
271
|
-
*
|
|
272
|
-
*
|
|
273
|
-
*
|
|
274
|
-
*
|
|
275
|
-
*
|
|
276
|
+
* Round 15 collapsed per-section reads; r10 F7 made the first section cheap.
|
|
277
|
+
* r18 then showed the residual cost is ATTENTION, not bytes: seven pointers
|
|
278
|
+
* made the agent crawl methodology before acting, and the generic draft
|
|
279
|
+
* template ("3. Draft template") was read and produced nothing on
|
|
280
|
+
* recipe-matched routes. So: a recipe-matched route carries NO pointer (the
|
|
281
|
+
* recipe command is the answer); otherwise exactly one — the first authored
|
|
282
|
+
* pointer that is not the omc-config-draft-review human-gate template (that
|
|
283
|
+
* gate is conversational, not a section to prefetch), falling back to the
|
|
284
|
+
* first pointer when every authored pointer is draft-review.
|
|
276
285
|
*/
|
|
277
|
-
function
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
const readMore = [];
|
|
283
|
-
for (const [topic, sections] of byTopic.entries()) {
|
|
284
|
-
readCommands.push(`omc help ${topic} --section "${sections[0]}"`);
|
|
285
|
-
for (const section of sections.slice(1))
|
|
286
|
-
readMore.push(`omc help ${topic} --section "${section}"`);
|
|
287
|
-
}
|
|
288
|
-
return { readCommands, readMore };
|
|
286
|
+
function pickSectionPointer(pointers, recipeMatched) {
|
|
287
|
+
if (recipeMatched || pointers.length === 0)
|
|
288
|
+
return [];
|
|
289
|
+
const specific = pointers.find((pointer) => pointer.topic !== "omc-config-draft-review");
|
|
290
|
+
return [specific ?? pointers[0]];
|
|
289
291
|
}
|
|
292
|
+
const FIRST_SECTION_INLINE_BUDGET_BYTES = 2048;
|
|
290
293
|
/**
|
|
291
|
-
*
|
|
292
|
-
*
|
|
293
|
-
*
|
|
294
|
-
*
|
|
295
|
-
* IMMEDIATELY by fetching the first readSection — one extra process +
|
|
296
|
-
* round-trip every task. The first section now rides inline when it fits the
|
|
297
|
-
* inline budget (~2 KB); larger sections stay pointer-only so `next` never
|
|
298
|
-
* balloons.
|
|
294
|
+
* r19 ticket 04: a multi-capability ladder already carries tool maps and
|
|
295
|
+
* recipes, so its inline section budget is tighter — the 4096B `next` ceiling
|
|
296
|
+
* must survive a ladder + a 2.5KB section (r18: "工作流审批流程" would have
|
|
297
|
+
* been 4729B). Single-capability routes keep the fuller budget (F9).
|
|
299
298
|
*/
|
|
300
|
-
const
|
|
301
|
-
function routeReads(readSections) {
|
|
302
|
-
|
|
303
|
-
//
|
|
304
|
-
//
|
|
305
|
-
const budgeted = readSections.map((pointer) => ({ ...pointer, bytes: sectionBytes(pointer) }));
|
|
306
|
-
// Inline the FIRST pointer that fits the budget (order = relevance); an
|
|
307
|
-
// oversized head section falls through to the next fitting one rather than
|
|
308
|
-
// dropping the inline content entirely.
|
|
299
|
+
const LADDER_INLINE_BUDGET_BYTES = 1200;
|
|
300
|
+
function routeReads(readSections, recipeMatched = false, inlineBudget = FIRST_SECTION_INLINE_BUDGET_BYTES) {
|
|
301
|
+
const budgeted = pickSectionPointer(readSections, recipeMatched).map((pointer) => ({ ...pointer, bytes: sectionBytes(pointer) }));
|
|
302
|
+
// Inline the ONE pointer's content when it fits the budget: the agent then
|
|
303
|
+
// needs zero extra calls for methodology (F9, r10-friction).
|
|
309
304
|
let firstSection;
|
|
310
|
-
for (const pointer of budgeted
|
|
311
|
-
if (pointer.bytes > 0 && pointer.bytes <=
|
|
305
|
+
for (const pointer of budgeted) {
|
|
306
|
+
if (pointer.bytes > 0 && pointer.bytes <= inlineBudget) {
|
|
312
307
|
const resolved = readSkillSection(pointer.topic, pointer.section);
|
|
313
308
|
if (resolved) {
|
|
314
309
|
firstSection = { topic: pointer.topic, section: pointer.section, bytes: pointer.bytes, content: resolved.section };
|
|
@@ -316,15 +311,19 @@ function routeReads(readSections) {
|
|
|
316
311
|
}
|
|
317
312
|
}
|
|
318
313
|
}
|
|
319
|
-
|
|
320
|
-
|
|
314
|
+
return {
|
|
315
|
+
readSections: budgeted,
|
|
316
|
+
readCommands: budgeted.map((pointer) => `omc help ${pointer.topic} --section "${pointer.section}"`),
|
|
317
|
+
readHint: READ_HINT,
|
|
318
|
+
...(firstSection !== undefined ? { firstSection } : {}),
|
|
319
|
+
};
|
|
321
320
|
}
|
|
322
321
|
/** UTF-8 byte length of one methodology section (best-effort; 0 when unreadable). */
|
|
323
322
|
function sectionBytes(pointer) {
|
|
324
323
|
const resolved = readSkillSection(pointer.topic, pointer.section);
|
|
325
324
|
return resolved ? Buffer.byteLength(resolved.section, "utf8") : 0;
|
|
326
325
|
}
|
|
327
|
-
const READ_HINT = '
|
|
326
|
+
const READ_HINT = '方法论只挂最相关的一节(readSections[0] 带字节数,`omc help <topic> --section "<节>"` 可回放,firstSection 已内联时就不用再取);其他具体问题(错误码/跨能力语义/某个词)用 `omc help --search "<词>"` 一步全文检索;不要 `--all` 读整篇。';
|
|
328
327
|
function orderedCapabilities(ids, index) {
|
|
329
328
|
const selected = new Set(ids);
|
|
330
329
|
const nodes = [...index.nodeById.values()];
|
|
@@ -434,6 +433,21 @@ const COMPOSER_ROUTES = Object.freeze([
|
|
|
434
433
|
]);
|
|
435
434
|
/** Test seam: the composer registry, so a template typo can be caught offline. */
|
|
436
435
|
export const COMPOSER_ROUTES_FOR_TEST = COMPOSER_ROUTES;
|
|
436
|
+
/**
|
|
437
|
+
* r19 ticket 04 — the ONE question that unlocks each composer. A composer
|
|
438
|
+
* skeleton always carries placeholders, so a composer match is a `needs-input`
|
|
439
|
+
* first answer by definition; the question names exactly what to fill in.
|
|
440
|
+
*/
|
|
441
|
+
const COMPOSER_QUESTIONS = Object.freeze({
|
|
442
|
+
"model-purge": "要清理哪个模型?给我 schemaCode(不确定就先跑下面的只读判别动作拿清单)",
|
|
443
|
+
"model-scaffold": "给我模型 code、显示名和字段清单(每字段 code/名称/类型;子表放 subFields),我一条命令建完",
|
|
444
|
+
"approval-flow": "目标模型 schemaCode 和每级审批节点(节点名@角色code,多级用重复 --steps)是什么?",
|
|
445
|
+
compute: "目标模型 schemaCode、结果字段 code、公式是什么?(子表条件聚合再加 --where \"子列 = 值\")",
|
|
446
|
+
"rule-when": "目标模型 schemaCode、触发时机(Create/Update…)、条件(--when)和动作(--then)各是什么?",
|
|
447
|
+
"form-default": "目标模型 schemaCode、字段 code 和默认值是什么?",
|
|
448
|
+
});
|
|
449
|
+
/** The read-only discriminator that supplies a missing schema code. */
|
|
450
|
+
const LIST_MODELS_ACTION = "omc call model.list --args '{}'";
|
|
437
451
|
function matchComposer(goal, mode) {
|
|
438
452
|
// A composer is a WRITE path: reads and scouting asks must keep their ladder.
|
|
439
453
|
if (mode !== "build" && mode !== "modify")
|
|
@@ -471,11 +485,19 @@ function wholePackageRoute(registry, goal, index) {
|
|
|
471
485
|
primary: "intent",
|
|
472
486
|
atomicSupplement: { note: "intent 已覆盖下列能力的创建;这些原子工具只用于 intent 未覆盖面(删除/生命周期/运行数据/精确 patch):", ladder },
|
|
473
487
|
...routeReads(sectionPointersFor(ordered)),
|
|
474
|
-
|
|
488
|
+
// r19 ticket 04: readiness (discover_environment) left firstCommands —
|
|
489
|
+
// the probe executed it 0 times in 184 calls; it rides its own field now.
|
|
490
|
+
readiness: DISCOVERY_AUTHORITY.readiness,
|
|
491
|
+
firstCommands: ["omc intent schema"],
|
|
475
492
|
write: true,
|
|
476
493
|
},
|
|
477
494
|
write: true,
|
|
478
495
|
intent: intentGuidance(),
|
|
496
|
+
firstAnswer: {
|
|
497
|
+
state: "ready",
|
|
498
|
+
action: "omc intent schema",
|
|
499
|
+
why: "整包需求走声明式意图:先看 intent YAML 的键位与可运行示例,再按 starter 写文件(validate → plan → apply)",
|
|
500
|
+
},
|
|
479
501
|
};
|
|
480
502
|
}
|
|
481
503
|
/** A configuration read-back ask that names no capability ("给我一份现状说明"). */
|
|
@@ -490,8 +512,13 @@ function readOverviewRoute(registry, goal, index) {
|
|
|
490
512
|
confident: true,
|
|
491
513
|
mode: "read",
|
|
492
514
|
match: { kind: "read-overview", ref: ordered.join("→"), capabilities: ordered },
|
|
493
|
-
route: { ladder, ...routeReads(sectionPointersFor(["environment"])),
|
|
515
|
+
route: { ladder, ...routeReads(sectionPointersFor(["environment"])), readiness: DISCOVERY_AUTHORITY.readiness, firstCommands: [LIST_MODELS_ACTION], write: false, readback: true },
|
|
494
516
|
write: false,
|
|
517
|
+
firstAnswer: {
|
|
518
|
+
state: "ready",
|
|
519
|
+
action: LIST_MODELS_ACTION,
|
|
520
|
+
why: "盘点从模型清单开始:一次调用拿到全部 schemaCode,后续读面(inspect/field.list)据此展开",
|
|
521
|
+
},
|
|
495
522
|
};
|
|
496
523
|
}
|
|
497
524
|
export function goalRoute(registry, goal) {
|
|
@@ -522,12 +549,45 @@ export function goalRoute(registry, goal) {
|
|
|
522
549
|
verify: "omc capabilities / omc capability <id>(运行时清单才是权威边界;能力表只是阅读辅助)",
|
|
523
550
|
topics: ["omc-requirement-analysis", "omc-capability-scouting"].filter((topic) => availableTopics.has(topic)),
|
|
524
551
|
},
|
|
525
|
-
route: { write: false, readback: false, firstCommands: ["omc capabilities", "omc capability <id>", "omc help omc-capability-scouting"] },
|
|
552
|
+
route: { write: false, readback: false, readiness: DISCOVERY_AUTHORITY.readiness, firstCommands: ["omc capabilities", "omc capability <id>", "omc help omc-capability-scouting"] },
|
|
553
|
+
firstAnswer: { state: "blocked", why: boundary.note },
|
|
526
554
|
};
|
|
527
555
|
}
|
|
528
556
|
// Composer before the capability solver: a recurring write SHAPE has a single
|
|
529
557
|
// command that does the job with preflight + idempotency, which beats a ladder
|
|
530
558
|
// of atomic tools even when the ladder also matches (r13: 26 field.create).
|
|
559
|
+
// r19 券商 issue 1.1(2026-10-27):子表**控件**(type 201)的显示/只读条件
|
|
560
|
+
// (displayFormula/readonlyFormula)不在字段面——field.update 对子表只管**列**、
|
|
561
|
+
// 对控件条件必拒;唯一路径是表单 attributes(form.get 读 draftAttributesJson
|
|
562
|
+
// 定位控件 → form.draft 改 → form.publish)。此前该场景路由进 field→form 梯子
|
|
563
|
+
// 且首级无可行工具(firstCommands 为空),agent 只能靠拒收文案自己悟。
|
|
564
|
+
if (/子表|控件/.test(goal) && /子表.*(显示|隐藏|只读|显隐).*(条件|公式|规则)|displayFormula|readonlyFormula/.test(goal)) {
|
|
565
|
+
return {
|
|
566
|
+
status: "ok",
|
|
567
|
+
goal,
|
|
568
|
+
confident: true,
|
|
569
|
+
mode: "modify",
|
|
570
|
+
write: true,
|
|
571
|
+
match: { kind: "capability", ref: "form", title: "子表控件显示/只读条件(表单 attributes 面)" },
|
|
572
|
+
route: {
|
|
573
|
+
capability: "form",
|
|
574
|
+
note: "子表控件的 displayFormula/readonlyFormula 走表单 attributes:form.get(controls:true) 读 draftAttributesJson 定位控件 → form.draft 改该控件条件 → form.publish。field.update 只管子表列,对控件条件必拒(券商 issue 2026-10-27 实证)",
|
|
575
|
+
readiness: DISCOVERY_AUTHORITY.readiness,
|
|
576
|
+
firstCommands: [
|
|
577
|
+
"omc call form.get --args '{\"schemaCode\":\"<schemaCode>\",\"controls\":true}'",
|
|
578
|
+
"omc describe form.draft",
|
|
579
|
+
],
|
|
580
|
+
write: true,
|
|
581
|
+
},
|
|
582
|
+
firstAnswer: {
|
|
583
|
+
state: "needs-input",
|
|
584
|
+
question: "目标模型 schemaCode 与子表控件 code 是什么?(form.get controls:true 读 draftAttributesJson 定位控件,form.draft 改其条件后 form.publish)",
|
|
585
|
+
action: LIST_MODELS_ACTION,
|
|
586
|
+
pendingCommand: "omc call form.draft --args '{\"schemaCode\":\"<schemaCode>\",\"draftAttributesJson\":\"<改好后的完整 attributes JSON>\"}'",
|
|
587
|
+
why: "子表控件(type 201)的显示/只读条件在表单 attributes,不在字段面——field.update 对子表只管列、必拒",
|
|
588
|
+
},
|
|
589
|
+
};
|
|
590
|
+
}
|
|
531
591
|
const composer = matchComposer(goal, mode);
|
|
532
592
|
if (composer) {
|
|
533
593
|
const topics = (composer.topics ?? []).filter((topic) => availableTopics.has(topic));
|
|
@@ -542,14 +602,22 @@ export function goalRoute(registry, goal) {
|
|
|
542
602
|
composer: composer.id,
|
|
543
603
|
command: composer.command,
|
|
544
604
|
summary: composer.summary,
|
|
545
|
-
|
|
546
|
-
// the
|
|
547
|
-
|
|
605
|
+
readiness: DISCOVERY_AUTHORITY.readiness,
|
|
606
|
+
// The composer IS the first step (its skeleton waits on the ONE
|
|
607
|
+
// question in firstAnswer; discover_environment no longer occupies
|
|
608
|
+
// the head slot — r18 executed it 0 times in 184 calls).
|
|
609
|
+
firstCommands: [composer.command, composer.usage],
|
|
548
610
|
write: true,
|
|
549
611
|
readback: true,
|
|
550
|
-
...(topics.length > 0 ? { readCommands: topics.map((topic) => `omc help ${topic} --section "<节>"`), readHint: "只在 composer 的 --help 不够用时再读这些 topic 的对应节" } : {}),
|
|
551
612
|
},
|
|
552
613
|
...(topics.length > 0 ? { topics } : {}),
|
|
614
|
+
firstAnswer: {
|
|
615
|
+
state: "needs-input",
|
|
616
|
+
question: COMPOSER_QUESTIONS[composer.id] ?? "补齐命令里的尖括号占位符后运行(形状见 pendingCommand)",
|
|
617
|
+
pendingCommand: composer.command,
|
|
618
|
+
...(composer.id === "model-purge" || composer.id === "compute" ? { action: LIST_MODELS_ACTION } : {}),
|
|
619
|
+
why: composer.summary,
|
|
620
|
+
},
|
|
553
621
|
};
|
|
554
622
|
}
|
|
555
623
|
const scores = [...capabilityScores(registry, goal).entries()].sort((left, right) => right[1] - left[1]);
|
|
@@ -562,6 +630,13 @@ export function goalRoute(registry, goal) {
|
|
|
562
630
|
topics: [...availableTopics],
|
|
563
631
|
write: false,
|
|
564
632
|
mode,
|
|
633
|
+
// Not confident → say so and hand ONE next move instead of a topic shelf.
|
|
634
|
+
firstAnswer: {
|
|
635
|
+
state: "needs-input",
|
|
636
|
+
question: "把目标说成一个具体动作短语(对象 + 动作,如「给 X 模型加一个汇总字段」),我就能给出一步可跑的命令",
|
|
637
|
+
action: "omc help omc-requirement-analysis",
|
|
638
|
+
why: "没命中任何能力线索;不猜路径(precision over recall)",
|
|
639
|
+
},
|
|
565
640
|
};
|
|
566
641
|
const best = scores[0];
|
|
567
642
|
if (best === undefined) {
|
|
@@ -601,6 +676,12 @@ export function goalRoute(registry, goal) {
|
|
|
601
676
|
const readSections = sectionPointersFor(ordered);
|
|
602
677
|
const write = readish ? false : ordered.some((id) => routeById.get(id)?.tools.some((name) => registryTools.get(name)?.kind === "write"));
|
|
603
678
|
const whole = isWholePackageWrite(mode, goal, ordered.length);
|
|
679
|
+
// r19 ticket 04: the ladder's first RECIPE command is the first answer
|
|
680
|
+
// when one exists (its describe carries the copyable firstCall shape);
|
|
681
|
+
// otherwise the only missing input is the target object code.
|
|
682
|
+
const recipeCommands = ladder.flatMap((step) => step.recipe?.command ? [step.recipe.command] : []);
|
|
683
|
+
const firstAction = recipeCommands[0];
|
|
684
|
+
const firstRecipeId = ladder.find((step) => step.recipe) && (ladder.find((step) => step.recipe).recipe.id);
|
|
604
685
|
// When intent is the primary path, the atomic ladder is only a POINTER to the
|
|
605
686
|
// capabilities (dogfood R1: repeating every tool map was ~60% unused bytes).
|
|
606
687
|
const atomicSupplement = { note: "intent 已覆盖这些能力的创建;原子工具只用于 intent 未覆盖面:", ladder: ladder.map((step) => ({ capability: step.capability, lane: step.lane, firstCommand: step.firstCommand ?? `omc commands --json --compact --lane ${step.lane}` })) };
|
|
@@ -612,13 +693,14 @@ export function goalRoute(registry, goal) {
|
|
|
612
693
|
match: { kind: readish ? "read-ladder" : "ladder", ref: ordered.join("→"), capabilities: ordered },
|
|
613
694
|
route: {
|
|
614
695
|
...(whole ? { primary: "intent", atomicSupplement } : { ladder }),
|
|
615
|
-
...routeReads(readSections),
|
|
696
|
+
...routeReads(readSections, recipeCommands.length > 0, LADDER_INLINE_BUDGET_BYTES),
|
|
697
|
+
readiness: DISCOVERY_AUTHORITY.readiness,
|
|
616
698
|
// r18: when a step already carries a goal-matched recipe, the generic
|
|
617
699
|
// first section ("3. Draft template") is noise — the probe read it and
|
|
618
700
|
// got nothing. Say that the recipe command is the answer and the
|
|
619
701
|
// sections are only a fallback.
|
|
620
|
-
...(
|
|
621
|
-
? { readHint: "已命中配方:先跑 firstCommands 里的 `omc describe … --example …`
|
|
702
|
+
...(recipeCommands.length > 0
|
|
703
|
+
? { readHint: "已命中配方:先跑 firstCommands 里的 `omc describe … --example …` 取形状(首答自带 firstCall 可照抄);方法论节已按需收缩,确实需要时用 `omc help <topic> --section \"<节>\"`。" }
|
|
622
704
|
: {}),
|
|
623
705
|
// Each ladder step already carried a runnable recipe command, but it sat
|
|
624
706
|
// buried at `ladder[i].recipe.command` behind a readiness line — r14's
|
|
@@ -626,8 +708,7 @@ export function goalRoute(registry, goal) {
|
|
|
626
708
|
// graph instead. Surface them at the top level where `firstCommands` is
|
|
627
709
|
// the thing an agent actually reads.
|
|
628
710
|
firstCommands: [
|
|
629
|
-
|
|
630
|
-
...ladder.flatMap((step) => step.recipe?.command ? [step.recipe.command] : []),
|
|
711
|
+
...recipeCommands,
|
|
631
712
|
// r13 #3: `capability workflow --executable` is 23 KB and still did not
|
|
632
713
|
// answer "how do I submit an approval" — the answer is a data.submit
|
|
633
714
|
// recipe, which lives in a DIFFERENT capability. When the goal is about
|
|
@@ -640,7 +721,17 @@ export function goalRoute(registry, goal) {
|
|
|
640
721
|
...(readish ? { readback: true } : {}),
|
|
641
722
|
},
|
|
642
723
|
write,
|
|
643
|
-
...(whole
|
|
724
|
+
...(whole
|
|
725
|
+
? { intent: intentGuidance(), firstAnswer: { state: "ready", action: "omc intent schema", why: "整包需求走声明式意图:先看 intent 的键位与示例,再按 starter 写文件" } }
|
|
726
|
+
: readish
|
|
727
|
+
// A read ask about the model inventory itself is answered BY the
|
|
728
|
+
// inventory call; any other read needs the target object code first.
|
|
729
|
+
? ordered.every((id) => id === "environment" || id === "model")
|
|
730
|
+
? { firstAnswer: { state: "ready", action: LIST_MODELS_ACTION, why: "问的就是模型清单本身:model.list 一次调用直接回答" } }
|
|
731
|
+
: { firstAnswer: { state: "needs-input", question: "要查看哪个对象?给我 schemaCode(或业务名,我列清单核对)", action: LIST_MODELS_ACTION, why: "读路径命中多个能力:readTools 已内联,只差目标对象 code" } }
|
|
732
|
+
: firstAction !== undefined
|
|
733
|
+
? { firstAnswer: { state: "ready", action: firstAction, why: `命中配方 ${firstRecipeId}:describe 首答自带 firstCall 形状,代入你的 schemaCode/字段 code 即可执行` } }
|
|
734
|
+
: { firstAnswer: { state: "needs-input", question: "第一步要操作哪个对象?给我 schemaCode(或模型业务名,我列清单核对)", action: LIST_MODELS_ACTION, why: "未命中配方:需要目标对象 code 才能给出一步可跑的写命令" } }),
|
|
644
735
|
};
|
|
645
736
|
}
|
|
646
737
|
// Single-capability mode: when several capabilities tie, a matching recipe
|
|
@@ -651,7 +742,7 @@ export function goalRoute(registry, goal) {
|
|
|
651
742
|
return fallback;
|
|
652
743
|
const write = readish ? false : route.tools.some((name) => registryTools.get(name)?.kind === "write");
|
|
653
744
|
const recipe = readish ? undefined : bestRecipe(registry, capabilityId, route.lane, goal);
|
|
654
|
-
const firstCommands = [
|
|
745
|
+
const firstCommands = [];
|
|
655
746
|
if (recipe)
|
|
656
747
|
firstCommands.push(`omc describe ${recipe.tool} --example ${recipe.recipe}`);
|
|
657
748
|
if (readish)
|
|
@@ -666,6 +757,7 @@ export function goalRoute(registry, goal) {
|
|
|
666
757
|
if (!readish && /审批|通过|驳回|提交|签收/.test(goal) && (capabilityId === "workflow" || capabilityId === "runtimeData"))
|
|
667
758
|
firstCommands.push("omc describe data.submit --example workflow-backed-data-submit");
|
|
668
759
|
const whole = isWholePackageWrite(mode, goal, 1);
|
|
760
|
+
const recipeCommand = recipe ? `omc describe ${recipe.tool} --example ${recipe.recipe}` : undefined;
|
|
669
761
|
return {
|
|
670
762
|
status: "ok",
|
|
671
763
|
goal,
|
|
@@ -683,13 +775,23 @@ export function goalRoute(registry, goal) {
|
|
|
683
775
|
lane: route.lane,
|
|
684
776
|
...(whole ? { primary: "intent" } : {}),
|
|
685
777
|
...(readish ? { readTools: readToolsInLane(registry, route.lane) } : { tools: route.tools }),
|
|
686
|
-
...routeReads(sectionPointersFor([capabilityId])),
|
|
778
|
+
...routeReads(sectionPointersFor([capabilityId]), recipe !== undefined),
|
|
779
|
+
readiness: DISCOVERY_AUTHORITY.readiness,
|
|
687
780
|
firstCommands,
|
|
688
781
|
write,
|
|
689
782
|
...(readish ? { readback: true } : {}),
|
|
690
783
|
},
|
|
691
784
|
write,
|
|
692
785
|
...(whole ? { intent: intentGuidance() } : {}),
|
|
786
|
+
...(whole
|
|
787
|
+
? { firstAnswer: { state: "ready", action: "omc intent schema", why: "整包需求走声明式意图:先看 intent 的键位与示例,再按 starter 写文件" } }
|
|
788
|
+
: readish
|
|
789
|
+
? capabilityId === "model"
|
|
790
|
+
? { firstAnswer: { state: "ready", action: LIST_MODELS_ACTION, why: "问的就是模型清单本身:model.list 一次调用直接回答" } }
|
|
791
|
+
: { firstAnswer: { state: "needs-input", question: "要查看哪个对象?给我 schemaCode(或业务名,我列清单核对)", action: LIST_MODELS_ACTION, why: `读路径命中能力 ${capabilityId}:readTools 已内联,只差目标对象 code` } }
|
|
792
|
+
: recipeCommand !== undefined
|
|
793
|
+
? { firstAnswer: { state: "ready", action: recipeCommand, why: `命中配方 ${recipe.recipe}:describe 首答自带 firstCall 形状,代入你的 schemaCode/字段 code 即可执行` } }
|
|
794
|
+
: { firstAnswer: { state: "needs-input", question: "要操作哪个对象?给我 schemaCode(或模型业务名,我列清单核对)", action: LIST_MODELS_ACTION, why: "未命中配方:需要目标对象 code 才能给出一步可跑的写命令" } }),
|
|
693
795
|
};
|
|
694
796
|
}
|
|
695
797
|
//# sourceMappingURL=goal-routes.js.map
|
|
@@ -20,7 +20,7 @@ export async function admitGovernedWrite(ctx, request) {
|
|
|
20
20
|
blocker: "environment-write-disabled",
|
|
21
21
|
environment: resolved.name,
|
|
22
22
|
production: resolved.profile.production === true,
|
|
23
|
-
message: `${environmentNotice(resolved.profile)} 请在 omc setup
|
|
23
|
+
message: `${environmentNotice(resolved.profile)} 请在 omc setup 中开启“允许写入此环境”。若你本意是写项目环境:检查当前目录是否项目根(.omc-env 所在目录)——在其它目录跑 omc 会解析不到项目环境而落到全局只读策略,cd 回项目根再试。`,
|
|
24
24
|
});
|
|
25
25
|
let session;
|
|
26
26
|
try {
|
|
@@ -416,7 +416,7 @@ export function registerB2Lane(tools) {
|
|
|
416
416
|
});
|
|
417
417
|
const afterRows = wireRows((await wireCall(session, { path: ENDPOINTS.bizServiceMethodList, query: { serviceCode: args.serviceCode, filterFlag: "false" } })).data);
|
|
418
418
|
const after = afterRows.find((row) => row.code === args.methodCode);
|
|
419
|
-
return { performed: { errcode: performed.errcode }, readback: { methodPresent: after != null, nameMatches: args.name === undefined || after?.name === args.name, configMatches: methodConfigPlain === undefined || after?.configJson === methodConfigPlain, inputParameters: decodeJsonColumn(after?.inputParametersJson), outputParameters: decodeJsonColumn(after?.outputParametersJson) } };
|
|
419
|
+
return { performed: { errcode: performed.errcode }, readback: { methodCode: args.methodCode, methodPresent: after != null, nameMatches: args.name === undefined || after?.name === args.name, configMatches: methodConfigPlain === undefined || after?.configJson === methodConfigPlain, inputParameters: decodeJsonColumn(after?.inputParametersJson), outputParameters: decodeJsonColumn(after?.outputParametersJson), mappingNote: "规则映射直接用这里的参数 code(bizPropertyType 供类型对账),不必再 service.get" } };
|
|
420
420
|
}, (args) => {
|
|
421
421
|
assertStructuredConfig(args.methodConfig, "methodConfig");
|
|
422
422
|
}, undefined, b2ServiceRollbackSpec("method.update"));
|
|
@@ -463,7 +463,7 @@ export function registerB2Lane(tools) {
|
|
|
463
463
|
});
|
|
464
464
|
const afterRows = wireRows((await wireCall(session, { path: ENDPOINTS.bizServiceMethodList, query: { serviceCode: args.serviceCode, filterFlag: "false" } })).data);
|
|
465
465
|
const after = afterRows.find((row) => row.code === args.methodCode);
|
|
466
|
-
return { performed: { errcode: performed.errcode }, readback: { methodPresent: after != null, nameMatches: after?.name === args.name, configMatches: methodConfigPlain === undefined || after?.configJson === methodConfigPlain, inputParameters: decodeJsonColumn(after?.inputParametersJson), outputParameters: decodeJsonColumn(after?.outputParametersJson) } };
|
|
466
|
+
return { performed: { errcode: performed.errcode }, readback: { methodCode: args.methodCode, methodPresent: after != null, nameMatches: after?.name === args.name, configMatches: methodConfigPlain === undefined || after?.configJson === methodConfigPlain, inputParameters: decodeJsonColumn(after?.inputParametersJson), outputParameters: decodeJsonColumn(after?.outputParametersJson), mappingNote: "规则映射直接用这里的参数 code(bizPropertyType 供类型对账),不必再 service.get" } };
|
|
467
467
|
}, (args) => {
|
|
468
468
|
assertStructuredConfig(args.methodConfig, "methodConfig");
|
|
469
469
|
}, undefined, b2ServiceRollbackSpec("method.create"));
|
|
@@ -850,9 +850,26 @@ export function registerB2Lane(tools) {
|
|
|
850
850
|
? wireRows((await wireCall(session, { path: ENDPOINTS.bizServiceMethodList, query: { serviceCode: args.serviceCode, filterFlag: "false" } })).data)
|
|
851
851
|
: [];
|
|
852
852
|
const registered = args.methodName ? methodRows.some((row) => row.code === args.methodName) : undefined;
|
|
853
|
+
// r19 p7:readback 只回布尔时,agent 只能再调 service.get 拿参数 code(12 次里
|
|
854
|
+
// 6 次是这种写后验证读)。方法视图内联进 readback,rule.save 的 methodMapping
|
|
855
|
+
// 直接用这里的 code,不必再读。
|
|
856
|
+
const methodRow = args.methodName ? methodRows.find((row) => row.code === args.methodName) : undefined;
|
|
853
857
|
return {
|
|
854
858
|
performed: { errcode: performed.errcode },
|
|
855
|
-
readback: {
|
|
859
|
+
readback: {
|
|
860
|
+
servicePresent: readback.data != null,
|
|
861
|
+
...(registered !== undefined ? { methodPresent: registered, methodCount: methodRows.length } : {}),
|
|
862
|
+
...(methodRow !== undefined
|
|
863
|
+
? {
|
|
864
|
+
method: {
|
|
865
|
+
code: methodRow.code,
|
|
866
|
+
inputParameters: decodeJsonColumn(methodRow.inputParametersJson),
|
|
867
|
+
outputParameters: decodeJsonColumn(methodRow.outputParametersJson),
|
|
868
|
+
},
|
|
869
|
+
mappingNote: "rule.save 的 methodMapping 直接用上面的参数 code:inputMappings.bizCode=来源字段 code、serviceMethodParameterCode=参数 code;不必再 service.get",
|
|
870
|
+
}
|
|
871
|
+
: {}),
|
|
872
|
+
},
|
|
856
873
|
};
|
|
857
874
|
}, (args) => {
|
|
858
875
|
// Dry-lint (zero wire). `omc call` bypasses zod, so every schema-level
|
|
@@ -151,6 +151,10 @@ export const BUSINESSRULE_LANE_CONTRACT = Object.freeze({
|
|
|
151
151
|
scope: "rule node route readback",
|
|
152
152
|
description: "读回一条业务规则的完整定义(businessrule/get?id=),解析并回显 node/route 图 JSON(触发节点、UPDATE/CREATE 动作节点的 targetObjectCode/inputParam/dataActions/filterCondition 等)——规则族读写链的权威读回面,也是对照设计器生成图形的取证入口。",
|
|
153
153
|
}),
|
|
154
|
+
"rule.references": Object.freeze({
|
|
155
|
+
scope: "cross-rule reference search",
|
|
156
|
+
description: "跨规则引用检索(r23 实证的能力缺失:agent 为查『谁引用了服务 X』枚举了全平台 147 个 schema×ruleCode 组合/171 次 rule.get——本工具一调替代)。kind=service 查 BIZ_ACTION 的 methodMapping.serviceCode;kind=model 查 CREATE/UPDATE/DELETE 的 targetObjectCode 与 methodMapping.schemaCode(删模型前的影响面);kind=field 查 dataActions.targetDataItem 与 inputMappings/outputMappings 的 bizCode(删字段前的影响面;字段 code 跨模型不唯一,建议配 schemaCodes 限范围)。实现为客户端聚合(model.list→rule.list→rule.get 全扫,约几十次 wire 读、几秒完),输出只回命中行。第一版不含:条件叶子里的字段引用、datarule 引用、workflow.bindRule 绑定。",
|
|
157
|
+
}),
|
|
154
158
|
"rule.save": Object.freeze({
|
|
155
159
|
scope: "dead-state CRUD conditional node graphs",
|
|
156
160
|
description: RULE_SAVE_DESCRIPTION,
|
|
@@ -10,7 +10,7 @@ export const BUSINESSRULE_COVERAGE = declareLaneCoverage(BUSINESSRULE_LANE_CONTR
|
|
|
10
10
|
{
|
|
11
11
|
capability: "rule.list latest business-rule listing and rule.get node route definition readback",
|
|
12
12
|
evidence: ["packages/omc-cli/src/tools.ts", "packages/omc-cli/test/rule-list.test.ts"],
|
|
13
|
-
tools: [claim("rule.list", T["rule.list"].scope), claim("rule.get", T["rule.get"].scope)],
|
|
13
|
+
tools: [claim("rule.list", T["rule.list"].scope), claim("rule.get", T["rule.get"].scope), claim("rule.references", T["rule.references"].scope)],
|
|
14
14
|
},
|
|
15
15
|
]);
|
|
16
16
|
//# sourceMappingURL=businessrule-coverage.js.map
|
|
@@ -116,6 +116,99 @@ export function registerBusinessRuleLane(tools) {
|
|
|
116
116
|
};
|
|
117
117
|
}, { full: args.full === true, target: `rule.list:${args.schemaCode}` }),
|
|
118
118
|
}));
|
|
119
|
+
tools.push(defineTool({
|
|
120
|
+
name: "rule.references",
|
|
121
|
+
layer: "low",
|
|
122
|
+
kind: "read",
|
|
123
|
+
lane: RULE_LANE,
|
|
124
|
+
scope: RULE_TOOLS["rule.references"].scope,
|
|
125
|
+
description: RULE_TOOLS["rule.references"].description,
|
|
126
|
+
inputSchema: {
|
|
127
|
+
environment: environmentArg,
|
|
128
|
+
kind: z.enum(["service", "model", "field"]).describe("按什么找引用:service=BIZ_ACTION methodMapping.serviceCode;model=CREATE/UPDATE/DELETE targetObjectCode 与 methodMapping.schemaCode;field=dataActions.targetDataItem 与映射 bizCode"),
|
|
129
|
+
code: z.string().min(1).describe("要查引用的编码(服务 code / 模型 code / 字段 code)"),
|
|
130
|
+
schemaCodes: z.union([z.array(z.string().min(1)), z.string()]).optional().describe("只扫这些模型(省 wire 读);省略=全平台模型"),
|
|
131
|
+
capability: capabilityArg,
|
|
132
|
+
},
|
|
133
|
+
handler: async (ctx, args, self) => governedRead(ctx, self, args, async (session) => {
|
|
134
|
+
// r23(2026-10-27):agent 为查「谁引用了服务 X」枚举了 147 个
|
|
135
|
+
// schema×ruleCode 组合(171 次 rule.get / 229KB)。没有引用检索面时,
|
|
136
|
+
// 全库枚举是理性选择——所以给它一个。客户端聚合:模型清单 → 每模型
|
|
137
|
+
// rule.list → 每规则 rule.get → 图内匹配,一次调用、只回命中行。
|
|
138
|
+
const scoped = args.schemaCodes === undefined
|
|
139
|
+
? wireRows((await wireCall(session, { path: ENDPOINTS.modelList })).data).map((row) => String(row.schemaCode ?? "")).filter((code) => code !== "")
|
|
140
|
+
: (Array.isArray(args.schemaCodes) ? args.schemaCodes : [args.schemaCodes]);
|
|
141
|
+
const wanted = args.code;
|
|
142
|
+
const matches = [];
|
|
143
|
+
let scannedRules = 0;
|
|
144
|
+
let parseFailures = 0;
|
|
145
|
+
for (const schemaCode of scoped) {
|
|
146
|
+
let rows = [];
|
|
147
|
+
try {
|
|
148
|
+
rows = wireRows((await wireCall(session, { path: ENDPOINTS.businessRuleListBySchemaCode, query: { schemaCode } })).data);
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
continue; // 模型无规则面(读失败/空)按无规则处理,不让单个模型挡住全扫
|
|
152
|
+
}
|
|
153
|
+
for (const row of rows) {
|
|
154
|
+
const ruleCode = String(row.code ?? "");
|
|
155
|
+
const ruleId = row.id;
|
|
156
|
+
scannedRules += 1;
|
|
157
|
+
if (typeof ruleId !== "string" || ruleId === "")
|
|
158
|
+
continue;
|
|
159
|
+
let record = null;
|
|
160
|
+
try {
|
|
161
|
+
record = (await wireCall(session, { path: ENDPOINTS.businessRuleGet, query: { id: ruleId } })).data;
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
if (!record)
|
|
167
|
+
continue;
|
|
168
|
+
const nodes = decodeJsonColumn(record.node, JSON_COLUMN_NULL_COALESCE);
|
|
169
|
+
if (!Array.isArray(nodes)) {
|
|
170
|
+
parseFailures += 1;
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
for (const rawNode of nodes) {
|
|
174
|
+
const nodeCode = String(rawNode.nodeCode ?? "");
|
|
175
|
+
const nodeType = String(rawNode.nodeType ?? "");
|
|
176
|
+
const mapping = rawNode.methodMapping;
|
|
177
|
+
if (args.kind === "service") {
|
|
178
|
+
if (nodeType === "BIZ_ACTION" && mapping && String(mapping.serviceCode ?? "") === wanted)
|
|
179
|
+
matches.push({ schemaCode, ruleCode, nodeCode, nodeType, via: "methodMapping.serviceCode" });
|
|
180
|
+
}
|
|
181
|
+
else if (args.kind === "model") {
|
|
182
|
+
if (["CREATE", "UPDATE", "DELETE"].includes(nodeType) && String(rawNode.targetObjectCode ?? "") === wanted)
|
|
183
|
+
matches.push({ schemaCode, ruleCode, nodeCode, nodeType, via: "targetObjectCode" });
|
|
184
|
+
if (mapping && String(mapping.schemaCode ?? "") === wanted)
|
|
185
|
+
matches.push({ schemaCode, ruleCode, nodeCode, nodeType, via: "methodMapping.schemaCode" });
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
if (["CREATE", "UPDATE", "DELETE"].includes(nodeType) && Array.isArray(rawNode.dataActions)
|
|
189
|
+
&& rawNode.dataActions.some((action) => String(action.targetDataItem ?? "") === wanted))
|
|
190
|
+
matches.push({ schemaCode, ruleCode, nodeCode, nodeType, via: "dataActions.targetDataItem" });
|
|
191
|
+
if (mapping) {
|
|
192
|
+
for (const [key, label] of [["inputMappings", "inputMappings.bizCode"], ["outputMappings", "outputMappings.bizCode"]]) {
|
|
193
|
+
if (Array.isArray(mapping[key]) && mapping[key].some((row2) => String(row2.bizCode ?? "") === wanted && (row2.codeType === undefined || row2.codeType === 1)))
|
|
194
|
+
matches.push({ schemaCode, ruleCode, nodeCode, nodeType, via: label });
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return {
|
|
202
|
+
kind: args.kind,
|
|
203
|
+
code: wanted,
|
|
204
|
+
scanned: { models: scoped.length, rules: scannedRules, ...(parseFailures > 0 ? { parseFailures } : {}) },
|
|
205
|
+
matches,
|
|
206
|
+
...(matches.length === 0
|
|
207
|
+
? { note: "零命中=全平台(或指定 schemaCodes 范围内)没有规则引用它。范围:service→methodMapping.serviceCode;model→targetObjectCode/methodMapping.schemaCode;field→dataActions.targetDataItem/映射 bizCode(不含条件叶子与 datarule,第一版边界)" }
|
|
208
|
+
: {}),
|
|
209
|
+
};
|
|
210
|
+
}, { target: `rule.references:${args.kind}:${args.code}` }),
|
|
211
|
+
}));
|
|
119
212
|
tools.push(defineTool({
|
|
120
213
|
name: "rule.get",
|
|
121
214
|
layer: "low",
|
package/dist/lanes/field.js
CHANGED
|
@@ -333,7 +333,16 @@ export function registerFieldLane(tools) {
|
|
|
333
333
|
...(selector.length > 0 ? { selected: selector } : {}),
|
|
334
334
|
...(args.all !== true ? { businessOnly: true } : {}),
|
|
335
335
|
fields: projected,
|
|
336
|
-
|
|
336
|
+
// 券商 issue 1.4(2026-10-27):默认面空清单曾被误读成「模型没有字段」。
|
|
337
|
+
// 空清单要自己解释三种可能,别让 agent 二次试错。
|
|
338
|
+
...(detailed
|
|
339
|
+
? {}
|
|
340
|
+
: {
|
|
341
|
+
inventory: true,
|
|
342
|
+
hint: projected.length === 0
|
|
343
|
+
? "字段详情(options/subSchema)用 code/fields 选择器或 full:true。当前 draft 面没有业务字段——字段可能都在 published 面(published:true)、尚未创建,或只有系统字段(all:true 核对)"
|
|
344
|
+
: "字段详情(options/subSchema)用 code/fields 选择器或 full:true",
|
|
345
|
+
}),
|
|
337
346
|
};
|
|
338
347
|
}, {
|
|
339
348
|
// Round 4: the default diet reduced the whole list to a count, so agents
|
package/dist/lanes/form.js
CHANGED
|
@@ -50,7 +50,7 @@ export function registerFormLane(tools) {
|
|
|
50
50
|
controls: z
|
|
51
51
|
.union([z.boolean(), z.array(z.string().min(1))])
|
|
52
52
|
.optional()
|
|
53
|
-
.describe("
|
|
53
|
+
.describe("布尔或字符串数组:true=返回全部控件的 draft/published 定义;数组如 [\"controlA\",\"controlB\"]=只回这些控件码的定义;省略=只回控件码清单。注意:逗号拼出的单个字符串不是合法取值(invalid_union)——多控件用 JSON 数组。要控件的 options/subSchema 时改用 full:true 再 artifact get --jq <field> 取单字段"),
|
|
54
54
|
full: z.boolean().optional().describe("返回全部六份布局 JSON 原文(大体积)"),
|
|
55
55
|
buttonScripts: z.boolean().optional().describe("附带表单按钮的脚本原文(options.customButtonJson 里 SCRIPT 按钮的 actionConfig.scriptData)及 sha/字节数"),
|
|
56
56
|
capability: capabilityArg,
|
|
@@ -148,7 +148,7 @@ export function registerFormLane(tools) {
|
|
|
148
148
|
writeTool(tools, "form.draft", FORM_LANE, FORM_TOOLS["form.draft"].scope, FORM_TOOLS["form.draft"].description, {
|
|
149
149
|
schemaCode: z.string().min(1),
|
|
150
150
|
sheetCode: z.string().min(1).optional().describe("表单槽位码;省略时默认 = schemaCode(模型码即默认表单槽位)"),
|
|
151
|
-
draftViewJson: z.string().optional(),
|
|
151
|
+
draftViewJson: z.string().optional().describe("行分组:一行 N 个 code=N 列;改列数须改行分组,非改 span"),
|
|
152
152
|
draftAttributesJson: z.string().optional(),
|
|
153
153
|
draftHtmlJson: z.string().optional(),
|
|
154
154
|
fieldCodes: z.array(z.string().min(1)).optional().describe("脚手架绑定的已发布字段(按此顺序);由 configure_form/intent 显式传入"),
|
package/dist/lanes/report.js
CHANGED
|
@@ -387,11 +387,27 @@ export function registerReportLane(tools) {
|
|
|
387
387
|
const page = (await wireCall(session, { path: ENDPOINTS.reportGet, query: { code } })).data;
|
|
388
388
|
const objectId = String(page.id ?? page.reportObjectId ?? "");
|
|
389
389
|
if (objectId) {
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
390
|
+
try {
|
|
391
|
+
await biCall(session, {
|
|
392
|
+
path: ENDPOINTS.biReportPageSave,
|
|
393
|
+
body: { report: { objectId, title: args.name, attributes: "{}", global: JSON.stringify(DEFAULT_REPORT_GLOBAL) } },
|
|
394
|
+
auth: authOf(args.corpId, code, args.appCode),
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
catch (error) {
|
|
398
|
+
// r19 p14(p9 实证):报表服务未启动时 BI 初始化统一 502,但平台层的
|
|
399
|
+
// 报表页行**已经建了**。裸抛 uncertain 会掩盖这个残留——半成功必须
|
|
400
|
+
// 显式:回执带 halfSuccess 警示 + 清理命令,让 agent 下一调用就能收拾。
|
|
401
|
+
return {
|
|
402
|
+
performed: { errcode: 0, code, appCode: args.appCode, objectId, biInit: "failed" },
|
|
403
|
+
readback: { present: page.code === code, report: reportPageView(page), biInitialized: false },
|
|
404
|
+
halfSuccess: {
|
|
405
|
+
message: `平台层报表页已建(code=${code},objectId=${objectId}),但 BI 初始化失败(${error instanceof Error ? error.message : String(error)})——元数据已残留`,
|
|
406
|
+
cleanup: `omc call report.delete --args '{"code":"${code}"}'(或保留,待报表服务启动后重试 BI 配置)`,
|
|
407
|
+
boundary: "报表服务未启动时 BI 层(/dashboard/v1/*)统一 502——这是环境边界,不要原样重试",
|
|
408
|
+
},
|
|
409
|
+
};
|
|
410
|
+
}
|
|
395
411
|
}
|
|
396
412
|
void created;
|
|
397
413
|
return {
|