@stalfh233/omc-cli 0.6.7 → 0.6.10
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 +2 -1
- package/dist/approval-flow.js +5 -3
- package/dist/bizservice-config.js +26 -10
- package/dist/call.js +47 -0
- package/dist/capability-overview.js +47 -24
- package/dist/cli-contract.js +73 -26
- package/dist/cli-output.js +41 -2
- package/dist/cli-stdout.js +74 -0
- package/dist/cli.js +135 -52
- package/dist/command-transport.js +8 -1
- package/dist/config-store.js +17 -2
- package/dist/discovery.js +62 -3
- package/dist/field-families.js +35 -6
- package/dist/goal-routes.js +118 -11
- package/dist/human-summary.js +11 -0
- package/dist/lanes/app-contract.js +12 -5
- package/dist/lanes/app.js +221 -134
- package/dist/lanes/apply-changes.js +2 -1
- package/dist/lanes/b2-registration-contract.js +3 -3
- package/dist/lanes/b2-registration.js +890 -693
- package/dist/lanes/businessrule.js +75 -51
- package/dist/lanes/custom-page.js +166 -115
- package/dist/lanes/datarule-contract.js +2 -2
- package/dist/lanes/datarule.js +238 -154
- package/dist/lanes/dictionary.js +253 -110
- package/dist/lanes/environment.js +57 -9
- package/dist/lanes/field.js +160 -107
- package/dist/lanes/folder-contract.js +7 -3
- package/dist/lanes/folder.js +249 -118
- package/dist/lanes/form-contract.js +1 -1
- package/dist/lanes/form.js +188 -64
- package/dist/lanes/list-view.js +405 -369
- package/dist/lanes/meta.js +31 -6
- package/dist/lanes/model-contract.js +1 -1
- package/dist/lanes/model.js +610 -512
- package/dist/lanes/online-js-contract.js +1 -1
- package/dist/lanes/online-js.js +160 -146
- package/dist/lanes/organization.js +905 -640
- package/dist/lanes/programming.js +274 -136
- package/dist/lanes/report-contract.js +2 -2
- package/dist/lanes/report.js +354 -198
- package/dist/lanes/rule-lifecycle.js +294 -140
- package/dist/lanes/runtime-data-contract.js +4 -4
- package/dist/lanes/runtime-data.js +869 -745
- package/dist/lanes/workflow-contract.js +3 -3
- package/dist/lanes/workflow.js +732 -605
- package/dist/model-scaffold.js +26 -5
- package/dist/path-projection.js +196 -14
- package/dist/plan.js +25 -0
- package/dist/programming-workflow.js +55 -3
- package/dist/read-shapes.js +146 -0
- package/dist/read.js +40 -9
- package/dist/recipes/businessrule.js +74 -24
- package/dist/recipes/dictionary.js +7 -4
- package/dist/recipes/form.js +46 -0
- package/dist/recipes/runtime-data.js +3 -2
- package/dist/refusal-guidance.js +29 -5
- package/dist/rule-support.js +14 -1
- package/dist/rule-when.js +2 -2
- package/dist/tool-types.js +9 -3
- package/dist/version-family-gaps.js +101 -0
- package/dist/write.js +2 -1
- package/package.json +6 -4
- package/skills/omc-glossary.md +13 -0
- package/skills/omc-refusals.md +13 -5
- package/skills/omc-tool-selection.md +2 -1
- package/skills/omc-write-hazards.md +5 -2
package/dist/cli.js
CHANGED
|
@@ -10,7 +10,7 @@ import { Command, CommanderError, Option } from "commander";
|
|
|
10
10
|
import { ConfigStore, defaultPaths, removedProfileKeys } from "./config-store.js";
|
|
11
11
|
import { loadCoverageManifest, versionFamilyNotice } from "./coverage.js";
|
|
12
12
|
import { redactText } from "./redact.js";
|
|
13
|
-
import { readArgsFile, readStdinArgs, runToolCall, validateAgentPaths } from "./call.js";
|
|
13
|
+
import { readArgsFile, readStdinArgs, runToolCall, toolsByTextIntent, validateAgentPaths } from "./call.js";
|
|
14
14
|
import { BATCH_MAX_ENTRIES, runBatchCall } from "./batch-call.js";
|
|
15
15
|
import { cmdModelScaffold } from "./model-scaffold.js";
|
|
16
16
|
import { cmdFieldAdd } from "./field-add.js";
|
|
@@ -18,11 +18,13 @@ import { cmdModelPurge } from "./model-purge.js";
|
|
|
18
18
|
import { cmdProgrammingAdopt, cmdProgrammingApply, cmdProgrammingPull, cmdProgrammingStatus, cmdProgrammingTest } from "./programming-workflow.js";
|
|
19
19
|
import { environmentWritable, requiresProductionConfirmation } from "./environment-policy.js";
|
|
20
20
|
import { CLI_EXIT_CODES, classifyResult, envelope, errorEnvelope, humanUtf8, installCodePageSafeStderr, refusalText, requestId } from "./cli-contract.js";
|
|
21
|
-
import {
|
|
21
|
+
import { ownsStdout } from "./cli-stdout.js";
|
|
22
|
+
import { formatDoctorHumanSummary, unsupportedCapabilities } from "./version-family-gaps.js";
|
|
23
|
+
import { beginWriteFace, configureOutput, endWriteFace, humanSummary, maybeWarnMergedStreams, stripOutputFlags, writeEnvelope } from "./cli-output.js";
|
|
22
24
|
import { beginUsage, completeUsage, currentUsageArgsDigest, ensureProjectIgnore, priorIdenticalSuccess, recordUsageOutput, usageReport } from "./usage-log.js";
|
|
23
25
|
import { artifactFetchHint, artifactRoot, boundResult, emitInvalid, emitResult, emitThrown, emitTransportResult, outputBudget, readArtifact, withTransport, } from "./command-transport.js";
|
|
24
26
|
import { CLI_DEFAULT_OUTPUT_BYTES, assertSafeOutputPath, pruneArtifacts } from "./artifacts.js";
|
|
25
|
-
import { projectJsonPath, projectJsonPathFanout, projectResultExtract } from "./path-projection.js";
|
|
27
|
+
import { artifactPathMissHint, nullProjectionHint, partialPathMissHint, projectJsonPath, projectJsonPathFanout, projectResultExtract } from "./path-projection.js";
|
|
26
28
|
import { basename, join, resolve } from "node:path";
|
|
27
29
|
import { buildToolRegistry } from "./tools.js";
|
|
28
30
|
import { acquireWriteLock } from "./write-lock.js";
|
|
@@ -177,6 +179,9 @@ async function cmdDoctor(store, environment, serviceCode) {
|
|
|
177
179
|
const manifest = loadCoverageManifest();
|
|
178
180
|
const steps = [];
|
|
179
181
|
let ok = true;
|
|
182
|
+
// 版本族缺口(§三.3-9):没有版本证据时也要照实说「无证据 → 写 fail closed」,
|
|
183
|
+
// 所以用 undefined 而不是拼一个默认值。
|
|
184
|
+
let detectedVersion;
|
|
180
185
|
// r45 Windows 实测:PowerShell 管道按 GBK(936) 解码 CLI 的 UTF-8 输出 → 全屏乱码,
|
|
181
186
|
// 且用户常误判为「OMC 输出坏了」。doctor 是入口体检,顺手检测活动代码页。
|
|
182
187
|
if (process.platform === "win32") {
|
|
@@ -222,6 +227,7 @@ async function cmdDoctor(store, environment, serviceCode) {
|
|
|
222
227
|
for (const note of session.loginNotes())
|
|
223
228
|
steps.push(`OAuth 配置 WARN ${note}`);
|
|
224
229
|
const version = session.detectedVersion();
|
|
230
|
+
detectedVersion = version;
|
|
225
231
|
if (!version) {
|
|
226
232
|
steps.push(`版本 WARN 无版本证据;写入将保守拒绝(fail closed)`);
|
|
227
233
|
}
|
|
@@ -306,16 +312,56 @@ async function cmdDoctor(store, environment, serviceCode) {
|
|
|
306
312
|
? `环境 ${kind} 已配置环境:${configured || "(none)"};用 omc env list 查看,再以 --env <name> 指定或 omc setup 绑定;对象不存在(modelPresent:false)不是环境错误`
|
|
307
313
|
: `环境 ${kind} 环境已配置但平台不可达;先查网络/服务,不要改环境名试错(已配置:${configured || "(none)"})`);
|
|
308
314
|
}
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
//
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
315
|
+
// r26b 读回体积审计(2026-11-05):人类体检行 + 结构化信封曾把同一批 check
|
|
316
|
+
// **各写一份**(3073 B 里约 1450 B 是重复)。管道/agent 场景(非 TTY)只留一行
|
|
317
|
+
// 汇总 + 指针;真人在终端里看到的仍是完整逐项明细。
|
|
318
|
+
const interactive = process.stderr.isTTY === true;
|
|
319
|
+
if (interactive) {
|
|
320
|
+
for (const line of steps)
|
|
321
|
+
console.error(line);
|
|
322
|
+
console.error(doctorAnchor());
|
|
323
|
+
console.error(ok ? "doctor:通过" : "doctor:失败");
|
|
324
|
+
}
|
|
325
|
+
else {
|
|
326
|
+
const warns = steps.filter((line) => /(^|\s)(⚠|WARN|FAIL)(\s|$)/.test(line)).length;
|
|
327
|
+
// r28i F1: name version + unavailable capability ids in the human line
|
|
328
|
+
// (formatDoctorHumanSummary lives in version-family-gaps.ts).
|
|
329
|
+
const gaps = unsupportedCapabilities(manifest, detectedVersion);
|
|
330
|
+
console.error(formatDoctorHumanSummary({
|
|
331
|
+
ok,
|
|
332
|
+
warnCount: warns,
|
|
333
|
+
detectedVersion,
|
|
334
|
+
unsupported: gaps,
|
|
335
|
+
}));
|
|
336
|
+
}
|
|
337
|
+
// 结构化回执(HANDOFF §三.2-6 / §三.3-9):doctor 此前只写 stderr,主流程于是
|
|
338
|
+
// 补一个空 `ok` 信封(失败路径还补过谎报 `internal` 的)。现在它自己写**一个**
|
|
339
|
+
// 信封:体检行 + 冷启动锚点 + 已知能力缺口。冷启动锚点从 stderr 移到这里
|
|
340
|
+
// (r26b:stderr 给了 agent 也读不到结构化字段,而锚点是每会话都要用的指引)。
|
|
341
|
+
writeEnvelope(envelope(ok ? "ok" : "failed", {
|
|
342
|
+
checks: steps.map(parseDoctorStep),
|
|
343
|
+
anchor: doctorAnchor(),
|
|
344
|
+
unsupportedCapabilities: unsupportedCapabilities(manifest, detectedVersion),
|
|
345
|
+
}, requestId()));
|
|
317
346
|
return ok ? 0 : 1;
|
|
318
347
|
}
|
|
348
|
+
/**
|
|
349
|
+
* `配置 ok 环境=x` → `{area:"配置", level:"ok", message:"环境=x"}`。
|
|
350
|
+
* 解析失败的行(极少数自由文本)退化成 `{area:"doctor", level:"info"}`,
|
|
351
|
+
* 保证结构化回执不缺行——宁可字段糙一点,也不要静默丢一条体检结论。
|
|
352
|
+
*/
|
|
353
|
+
function parseDoctorStep(line) {
|
|
354
|
+
const match = /^(\S+)\s+(ok|⚠|WARN|FAIL|--)\s+(.*)$/.exec(line);
|
|
355
|
+
if (!match) {
|
|
356
|
+
// `环境 env-not-pinned …` 这类第二档:区域 + 机器码 + 说明。
|
|
357
|
+
const typed = /^(\S+)\s+([a-z][a-z0-9-]+)\s+(.*)$/.exec(line);
|
|
358
|
+
if (typed)
|
|
359
|
+
return { area: typed[1], level: "info", message: `${typed[2]}:${typed[3]}` };
|
|
360
|
+
return { area: "doctor", level: "info", message: line };
|
|
361
|
+
}
|
|
362
|
+
const level = match[2] === "⚠" ? "warn" : match[2] === "--" ? "skip" : match[2].toLowerCase();
|
|
363
|
+
return { area: match[1], level, message: match[3] };
|
|
364
|
+
}
|
|
319
365
|
/** The cold-start anchor `doctor` appends: minimal path, artifact convention, readiness. */
|
|
320
366
|
function doctorAnchor() {
|
|
321
367
|
const topics = listSkills().map((entry) => entry.name).join("、");
|
|
@@ -456,6 +502,7 @@ async function cmdCall(store, args) {
|
|
|
456
502
|
result = { ...result, nextAction: "read back the requestId before retrying; retry only after the outcome is established" };
|
|
457
503
|
const classified = classifyResult(result);
|
|
458
504
|
writeEnvelope(envelope(status, boundResult(result, maxOutputBytes), request, classified.error));
|
|
505
|
+
maybeWarnMergedStreams();
|
|
459
506
|
return exitCode;
|
|
460
507
|
};
|
|
461
508
|
const flag = (name) => argvValue(args, name);
|
|
@@ -574,16 +621,38 @@ async function cmdCall(store, args) {
|
|
|
574
621
|
// 「无声」——`{"limt":5}` / `{"filterr":{…}}` 被 zod 剥掉后返回全量,调用方
|
|
575
622
|
// 会读成「过滤没生效 / 数据就是这么多」。读面在**不阻断**的前提下把被忽略的
|
|
576
623
|
// 键回显出来(写面仍是 fail-closed,见 parseToolInput)。
|
|
624
|
+
//
|
|
625
|
+
// F6(r27):应用级键(appCode/applicationCode/application)静默回落会把写
|
|
626
|
+
// 操作打到错误应用——这些键被忽略时直接拒绝,不进 ignoredArgs。
|
|
577
627
|
const ignoredReadKeys = [];
|
|
578
|
-
if (tool
|
|
628
|
+
if (tool) {
|
|
579
629
|
const schemaKeys = new Set(Object.keys(tool.inputSchema));
|
|
580
630
|
const recognized = new Set(["environment", "capability", "requestId", "dryRun"]);
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
631
|
+
const unknownKeys = Object.keys(parsedArgs).filter((key) => !schemaKeys.has(key) && !recognized.has(key));
|
|
632
|
+
const appScopeUnknown = unknownKeys.filter((key) => APP_SCOPE_ARG_KEYS.has(key));
|
|
633
|
+
if (appScopeUnknown.length > 0) {
|
|
634
|
+
const canonical = schemaKeys.has("applicationCode")
|
|
635
|
+
? "applicationCode"
|
|
636
|
+
: schemaKeys.has("appCode")
|
|
637
|
+
? "appCode"
|
|
638
|
+
: "applicationCode(或 appCode,以 omc describe 为准)";
|
|
639
|
+
const wrong = appScopeUnknown.join("、");
|
|
640
|
+
const result = {
|
|
641
|
+
status: "refused",
|
|
642
|
+
blocker: "unknown-app-scope-arg",
|
|
643
|
+
tool: toolName,
|
|
644
|
+
ignored: appScopeUnknown,
|
|
645
|
+
acceptedAppScopeArg: canonical,
|
|
646
|
+
message: `本工具的应用范围参数叫 ${canonical},你传的 ${wrong} 不会生效——为避免写到错误应用,本次调用已拒绝。请改用 ${canonical} 后重试(\`omc describe ${toolName}\` 可确认参数表)。`,
|
|
647
|
+
};
|
|
648
|
+
return emit("refused", result, CLI_EXIT_CODES.policyRefused);
|
|
649
|
+
}
|
|
650
|
+
if (tool.kind !== "write") {
|
|
651
|
+
for (const key of unknownKeys) {
|
|
652
|
+
// r25 真机:`limt:5` 被就近指到 `filter`(共享 "fi" 就够过阈值),照提示改
|
|
653
|
+
// 会把「取 5 条」变成「按条件过滤」。短键走显式缩写表,语义才不跑偏。
|
|
654
|
+
ignoredReadKeys.push({ key, ...suggestArgumentKey(key, [...schemaKeys]) });
|
|
655
|
+
}
|
|
587
656
|
}
|
|
588
657
|
}
|
|
589
658
|
if (tool) {
|
|
@@ -601,6 +670,9 @@ async function cmdCall(store, args) {
|
|
|
601
670
|
}
|
|
602
671
|
}
|
|
603
672
|
const toolKind = tool?.kind;
|
|
673
|
+
// F2':只对写面(含 dry-run 预览)标记;读面人话不追加 2>&1 提示。
|
|
674
|
+
if (toolKind === "write")
|
|
675
|
+
beginWriteFace();
|
|
604
676
|
let lock;
|
|
605
677
|
// A `--dry-run` never mutates the environment: the governed pipeline settles it
|
|
606
678
|
// before write admission, so it must not contend for the cross-process lock.
|
|
@@ -673,7 +745,13 @@ async function cmdCall(store, args) {
|
|
|
673
745
|
};
|
|
674
746
|
}
|
|
675
747
|
const classified = classifyResult(result);
|
|
676
|
-
|
|
748
|
+
try {
|
|
749
|
+
return emit(classified.status, result, classified.exitCode);
|
|
750
|
+
}
|
|
751
|
+
finally {
|
|
752
|
+
if (toolKind === "write")
|
|
753
|
+
endWriteFace();
|
|
754
|
+
}
|
|
677
755
|
}
|
|
678
756
|
/** Collect every value of a repeatable flag (`--extract a --extract b`). */
|
|
679
757
|
function argvValues(args, name) {
|
|
@@ -687,6 +765,8 @@ function argvValues(args, name) {
|
|
|
687
765
|
}
|
|
688
766
|
return values;
|
|
689
767
|
}
|
|
768
|
+
/** F6:应用级范围键。被工具 schema 忽略时必须拒绝,不能静默回落到默认应用。 */
|
|
769
|
+
const APP_SCOPE_ARG_KEYS = new Set(["appCode", "applicationCode", "application"]);
|
|
690
770
|
/**
|
|
691
771
|
* 读面「拼错键」建议(r25 真机实证,2026-11-04):`limt:5` 曾按编辑距离被指到
|
|
692
772
|
* `filter`——两者共享 "fi" 片段就过阈值,但语义完全不同(取 5 条 vs 按条件过滤)。
|
|
@@ -750,15 +830,12 @@ function projectArtifactPath(value, path) {
|
|
|
750
830
|
}
|
|
751
831
|
/**
|
|
752
832
|
* T6(GitLab #17):`mapped`/`nullEntries` 的读法要跟数据一起走,否则 `mapped:5`
|
|
753
|
-
* 会被读成"取到 5 个值"
|
|
833
|
+
* 会被读成"取到 5 个值"。文案的单一真相源在 path-projection(与 `--extract`
|
|
834
|
+
* 面共用),这里只负责从产物内容算 total。
|
|
754
835
|
*/
|
|
755
|
-
function
|
|
756
|
-
if (nullEntries === undefined || nullEntries === 0)
|
|
757
|
-
return undefined;
|
|
836
|
+
function artifactNullHint(content, nullEntries) {
|
|
758
837
|
const total = Array.isArray(content) ? content.length : 0;
|
|
759
|
-
|
|
760
|
-
return `其中 ${nullEntries} 个命中值为 null(不区分"字段为空"与"列表未投影")。`;
|
|
761
|
-
return `路径全部命中但值都是 null——mapped=${total} 只表示命中条目数。常见成因:列表视图未配置该列(data.list 只投影视图列)→ 用 data.load 读单条权威值,或先 listview.configure 配列。`;
|
|
838
|
+
return nullProjectionHint(nullEntries, total);
|
|
762
839
|
}
|
|
763
840
|
function formatBytes(bytes) {
|
|
764
841
|
if (bytes < 1024)
|
|
@@ -806,7 +883,7 @@ async function cmdArtifactGet(id, transport, options = {}) {
|
|
|
806
883
|
// 一层 `projection:{path,content}`——`artifact get --jq` 与 `--summary --jq`
|
|
807
884
|
// 因此同形(顶层 path/content),换读面不用重新探根键。
|
|
808
885
|
return emitTransportResult(projected.missed
|
|
809
|
-
? { status: "ok", artifactId: id, sha256: artifact.sha256, bytes: artifact.content.length, keys, path: options.jq, content: null, pathMiss: true, missedAt: projected.at, available: projected.available, unanswered: true, hint:
|
|
886
|
+
? { status: "ok", artifactId: id, sha256: artifact.sha256, bytes: artifact.content.length, keys, path: options.jq, content: null, pathMiss: true, missedAt: projected.at, available: projected.available, unanswered: true, hint: artifactPathMissHint(projected.at) }
|
|
810
887
|
: {
|
|
811
888
|
status: "ok",
|
|
812
889
|
artifactId: id,
|
|
@@ -818,7 +895,7 @@ async function cmdArtifactGet(id, transport, options = {}) {
|
|
|
818
895
|
...(projected.mapped !== undefined ? { mapped: projected.mapped } : {}),
|
|
819
896
|
...(projected.nullEntries !== undefined && projected.nullEntries > 0 ? { nullEntries: projected.nullEntries } : {}),
|
|
820
897
|
...(projected.missedEntries !== undefined ? { missedEntries: projected.missedEntries } : {}),
|
|
821
|
-
...(
|
|
898
|
+
...(artifactNullHint(projected.content, projected.nullEntries) !== undefined ? { hint: artifactNullHint(projected.content, projected.nullEntries) } : {}),
|
|
822
899
|
}, transport);
|
|
823
900
|
}
|
|
824
901
|
// 裸 `--keys` / 裸 `--jq` 只要键表(`pureKeys`),不带 summary 的形状事实——
|
|
@@ -858,14 +935,14 @@ async function cmdArtifactGet(id, transport, options = {}) {
|
|
|
858
935
|
const projected = projectArtifactPath(artifactContent, path);
|
|
859
936
|
return projected.missed
|
|
860
937
|
? { path, content: null, pathMiss: true, missedAt: projected.at, available: projected.available }
|
|
861
|
-
: { path, content: projected.content ?? null, ...(projected.mapped !== undefined ? { mapped: projected.mapped } : {}), ...(projected.missedEntries !== undefined ? { missedEntries: projected.missedEntries } : {}), ...(projected.nullEntries !== undefined && projected.nullEntries > 0 ? { nullEntries: projected.nullEntries } : {}), ...(
|
|
938
|
+
: { path, content: projected.content ?? null, ...(projected.mapped !== undefined ? { mapped: projected.mapped } : {}), ...(projected.missedEntries !== undefined ? { missedEntries: projected.missedEntries } : {}), ...(projected.nullEntries !== undefined && projected.nullEntries > 0 ? { nullEntries: projected.nullEntries } : {}), ...(artifactNullHint(projected.content, projected.nullEntries) !== undefined ? { hint: artifactNullHint(projected.content, projected.nullEntries) } : {}) };
|
|
862
939
|
});
|
|
863
|
-
return emitTransportResult({ status: "ok", artifactId: id, sha256: artifact.sha256, projections, ...(projections.some((entry) => entry.pathMiss) ? { unanswered: true, hint:
|
|
940
|
+
return emitTransportResult({ status: "ok", artifactId: id, sha256: artifact.sha256, projections, ...(projections.some((entry) => entry.pathMiss) ? { unanswered: true, hint: partialPathMissHint() } : {}) }, transport);
|
|
864
941
|
}
|
|
865
942
|
const projected = projectArtifactPath(artifactContent, options.jq);
|
|
866
943
|
return emitTransportResult(projected.missed
|
|
867
|
-
? { status: "ok", artifactId: id, sha256: artifact.sha256, path: options.jq, content: null, pathMiss: true, missedAt: projected.at, available: projected.available, unanswered: true, hint:
|
|
868
|
-
: { 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 } : {}), ...(projected.nullEntries !== undefined && projected.nullEntries > 0 ? { nullEntries: projected.nullEntries } : {}), ...(
|
|
944
|
+
? { status: "ok", artifactId: id, sha256: artifact.sha256, path: options.jq, content: null, pathMiss: true, missedAt: projected.at, available: projected.available, unanswered: true, hint: artifactPathMissHint(projected.at) }
|
|
945
|
+
: { 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 } : {}), ...(projected.nullEntries !== undefined && projected.nullEntries > 0 ? { nullEntries: projected.nullEntries } : {}), ...(artifactNullHint(projected.content, projected.nullEntries) !== undefined ? { hint: artifactNullHint(projected.content, projected.nullEntries) } : {}) }, transport);
|
|
869
946
|
}
|
|
870
947
|
// r6-onboarding 2026-10-21: 47 artifact fetches, 0 used a projection; the 10
|
|
871
948
|
// large ones (up to 55 KB) pulled whole workflow templates just to read one
|
|
@@ -1129,11 +1206,15 @@ function cmdDiscovery(kind, transport, value, options = {}) {
|
|
|
1129
1206
|
.map((tool) => tool.name)
|
|
1130
1207
|
.filter((name) => name === segment || name.startsWith(`${segment}.`) || name.includes(requested))
|
|
1131
1208
|
.slice(0, 8);
|
|
1132
|
-
|
|
1209
|
+
// F7:前缀候选之外,按 description/scope 意图词补 1–2 个(排在原候选之后)。
|
|
1210
|
+
const byText = toolsByTextIntent(requested, registry, 2).filter((name) => !candidates.includes(name));
|
|
1211
|
+
const shown = [...candidates, ...byText];
|
|
1212
|
+
const pointer = candidates.length === 0 && byText.length === 0 && (kind === "describe" || kind === "contract")
|
|
1133
1213
|
? `;describe 的目标是工具名(如 rule.save);图/模板/节点形状用 describe <tool> --full 或 contract <lane>,工具清单用 omc next "<目标>" / omc commands --json --compact`
|
|
1134
1214
|
: "";
|
|
1135
1215
|
const absence = absencePointer(requested);
|
|
1136
|
-
|
|
1216
|
+
const textNote = byText.length > 0 ? `;didYouMeanByText: ${byText.join("、")}` : "";
|
|
1217
|
+
return emitInvalid(transport, { message: `${kind} target not found: "${requested}"${shown.length > 0 ? `; did you mean: ${shown.join("、")}` : ""}${textNote}${absence}${approvalPointer}${intentPointer}${formPointer}${pointer}` });
|
|
1137
1218
|
}
|
|
1138
1219
|
return emitTransportResult({ status: "ok", ...result }, transport);
|
|
1139
1220
|
}
|
|
@@ -1153,8 +1234,8 @@ function cmdNext(goal, transport) {
|
|
|
1153
1234
|
* capability overview. Read-only, zero environment contact; a projection of
|
|
1154
1235
|
* the tool registry, the coverage manifest, and the intent capability registry.
|
|
1155
1236
|
*/
|
|
1156
|
-
function cmdCapabilities(transport) {
|
|
1157
|
-
const result = compactCapabilities(buildToolRegistry());
|
|
1237
|
+
function cmdCapabilities(transport, compact = false) {
|
|
1238
|
+
const result = compactCapabilities(buildToolRegistry(), { compact });
|
|
1158
1239
|
return emitTransportResult({ status: "ok", ...result }, transport);
|
|
1159
1240
|
}
|
|
1160
1241
|
function cmdCapability(id, transport, executable = false) {
|
|
@@ -1798,11 +1879,11 @@ async function main() {
|
|
|
1798
1879
|
// landed in the argParser slot and silently dropped every --steps value
|
|
1799
1880
|
// (live 2026-10-18: `--steps "审批@role:x"` answered "至少一个 --steps").
|
|
1800
1881
|
const collectValues = (value, previous = []) => [...previous, value];
|
|
1801
|
-
command.command("rule-when").option("--schema <schemaCode>").option("--schema-code <schemaCode>").option("--on <trigger>").addOption(new Option("--when <condition>", "条件,可重复").argParser(collectValues).default([])).addOption(new Option("--then <action>", "动作,可重复").argParser(collectValues).default([])).option("--name <name>").option("--env <environment>").option("--environment <environment>").option("--apply").addHelpText("after", "\n条件-动作规则拼装器:--when 条件(可多个 AND)→ --then set:字段=值/引用/公式。编译为设计器验证形状(预置触发+UPDATE filter/赋值)。默认 dry-run,--apply 保存并发布;前台生效按输出清单人工验证。\n").action(async (options) => {
|
|
1882
|
+
command.command("rule-when").option("--schema <schemaCode>").option("--schema-code <schemaCode>").option("--on <trigger>", "触发时机:create | update(**没有 submit**——提交后执行请用 create 或 update,按平台触发面语义选)").addOption(new Option("--when <condition>", "条件,可重复").argParser(collectValues).default([])).addOption(new Option("--then <action>", "动作,可重复").argParser(collectValues).default([])).option("--name <name>").option("--env <environment>").option("--environment <environment>").option("--apply").addHelpText("after", "\n条件-动作规则拼装器:--when 条件(可多个 AND)→ --then set:字段=值/引用/公式。编译为设计器验证形状(预置触发+UPDATE filter/赋值)。默认 dry-run,--apply 保存并发布;前台生效按输出清单人工验证。\n触发时机只有 create / update 两个(无 submit):\"提交后/审批前\"这类需求在平台侧没有独立触发点,用 create(新建即触发)或 update(更新即触发)表达;要按状态过滤,把条件写进 --when。\n").action(async (options) => {
|
|
1802
1883
|
const rest = ["rule-when", ...(options.schema ? ["--schema", options.schema] : []), ...(options.schemaCode ? ["--schema-code", options.schemaCode] : []), ...(options.on ? ["--on", options.on] : []), ...(Array.isArray(options.when) ? options.when.flatMap((w) => ["--when", w]) : []), ...(Array.isArray(options.then) ? options.then.flatMap((a) => ["--then", a]) : []), ...(options.name ? ["--name", options.name] : []), ...(options.env ? ["--env", options.env] : []), ...(options.environment ? ["--environment", options.environment] : []), ...(options.apply ? ["--apply"] : [])];
|
|
1803
1884
|
commandExitCode = await cmdRuleWhen(rest);
|
|
1804
1885
|
});
|
|
1805
|
-
command.command("approval-flow").option("--schema <schemaCode>").option("--schema-code <schemaCode>").addOption(new Option("--steps <step>", "审批步骤,可重复").argParser(collectValues).default([])).option("--env <environment>").option("--environment <environment>").option("--apply").option("--no-originator-node").option("--no-reject-to-start").addHelpText("after", "\n审批流拼装器:--steps 名称@role:<角色code>(多级给多个)。编译为验证模板(START→填写→审批链→END,审批节点字段只读),--apply 建流+权限+发布。\n").action(async (options) => {
|
|
1886
|
+
command.command("approval-flow").option("--schema <schemaCode>").option("--schema-code <schemaCode>").addOption(new Option("--steps <step>", "审批步骤,可重复").argParser(collectValues).default([])).option("--env <environment>").option("--environment <environment>").option("--apply").option("--no-originator-node").option("--no-reject-to-start").addHelpText("after", "\n审批流拼装器:--steps 名称@role:<角色code>(多级给多个)。编译为验证模板(START→填写→审批链→END,审批节点字段只读),--apply 建流+权限+发布。\n边界:非发起人身份的审批无法在单会话 CLI 内推进(单 token);若要验跨账号审批,用第二账号另建环境,或把参与人设为 {Originator}。\n").action(async (options) => {
|
|
1806
1887
|
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"] : [])];
|
|
1807
1888
|
commandExitCode = await cmdApprovalFlow(rest);
|
|
1808
1889
|
});
|
|
@@ -2071,13 +2152,18 @@ async function main() {
|
|
|
2071
2152
|
console.error(`已清理 ${result.removed} 个读回产物,释放 ${formatBytes(result.freedBytes)};保留 ${result.kept} 个(${formatBytes(result.keptBytes)})`);
|
|
2072
2153
|
commandExitCode = CLI_EXIT_CODES.ok;
|
|
2073
2154
|
});
|
|
2074
|
-
|
|
2155
|
+
// r28h F3(真机 2026-11-05):`omc call` 用 `--extract`、`artifact get` 用 `--jq`,
|
|
2156
|
+
// 语义完全相同。探针先试 `--extract` 拿到 `unknown option`(白花一次往返),
|
|
2157
|
+
// 才想起两者不同名。别名照收,help 里也标出来。
|
|
2158
|
+
artifactCommand.command("get <id>").option("--jq [path]").option("--extract <path>", "同 --jq(与 omc call 的 --extract 同名,省一次命名切换)").option("--path-only").option("--summary").option("--keys [a,b]").option("--full", "整包返回正文(默认:正文超过 8KB 时只回形状 outline + fetch,按需 --jq/--keys 定向取值)").option("--max-output-bytes <bytes>").action(async (id, options) => {
|
|
2075
2159
|
// A bare `--jq`/`--keys` is a key-inventory request, not a missing argument:
|
|
2076
2160
|
// commander's "argument missing" (invalid-input + retry) is a dead end the
|
|
2077
2161
|
// caller cannot fix without already knowing the flag takes a value (r11
|
|
2078
2162
|
// burned six rounds on `artifact get <id> --jq` before trying `--summary`).
|
|
2163
|
+
// --extract 是 --jq 的同义别名(r28h F3)。
|
|
2164
|
+
const jqPath = typeof options.jq === "string" ? options.jq : typeof options.extract === "string" ? options.extract : undefined;
|
|
2079
2165
|
const bareJq = options.jq === true;
|
|
2080
|
-
commandExitCode = await withTransport(options, request, (transport) => cmdArtifactGet(id, transport, { ...(
|
|
2166
|
+
commandExitCode = await withTransport(options, request, (transport) => cmdArtifactGet(id, transport, { ...(jqPath !== undefined ? { jq: jqPath } : {}), pathOnly: options.pathOnly === true, summary: options.summary === true || options.keys === true || bareJq,
|
|
2081
2167
|
pureKeys: (options.keys === true || bareJq) && options.summary !== true, ...(typeof options.keys === "string" ? { keys: options.keys } : {}), full: options.full === true }));
|
|
2082
2168
|
});
|
|
2083
2169
|
command.command("bench").option("--live-sample").option("--env <environment>").action(async (options) => { commandExitCode = await cmdBench([...(options.liveSample ? ["--live-sample"] : []), ...(options.env ? ["--env", options.env] : [])]); });
|
|
@@ -2086,7 +2172,7 @@ async function main() {
|
|
|
2086
2172
|
commandExitCode = await withTransport(options, request, (transport) => cmdDiscovery("describe", transport, tool, { full: options.full === true, schemaOnly: options.schemaOnly === true, ...(options.param ? { param: options.param } : {}), ...(options.example ? { example: options.example } : {}) }));
|
|
2087
2173
|
});
|
|
2088
2174
|
command.command("contract <lane>").option("--full").option("--tool <name>").option("--max-output-bytes <bytes>").action(async (lane, options) => { commandExitCode = await withTransport(options, request, (transport) => cmdDiscovery("contract", transport, lane, { full: options.full === true, ...(options.tool ? { tool: options.tool } : {}) })); });
|
|
2089
|
-
command.command("capabilities").option("--max-output-bytes <bytes>").action(async (options) => { commandExitCode = await withTransport(options, request, (transport) => cmdCapabilities(transport)); });
|
|
2175
|
+
command.command("capabilities").option("--compact").option("--max-output-bytes <bytes>").action(async (options) => { commandExitCode = await withTransport(options, request, (transport) => cmdCapabilities(transport, options.compact === true)); });
|
|
2090
2176
|
// `--env` is accepted but ignored: `next` is a zero-environment-contact route
|
|
2091
2177
|
// solver, and a caller who reflexively passes `--env` should not be refused
|
|
2092
2178
|
// (round 15 dogfood: `next "<goal>" --env local` → unknown option, one round).
|
|
@@ -2190,17 +2276,14 @@ function finish(code) {
|
|
|
2190
2276
|
}
|
|
2191
2277
|
const request = requestId();
|
|
2192
2278
|
main().then((code) => {
|
|
2193
|
-
// Commands that own their stdout never grow a trailing envelope.
|
|
2194
|
-
//
|
|
2195
|
-
//
|
|
2196
|
-
//
|
|
2197
|
-
//
|
|
2198
|
-
//
|
|
2199
|
-
//
|
|
2200
|
-
|
|
2201
|
-
// lines, violating the one-envelope stdout contract).
|
|
2202
|
-
const ownsStdout = ["call", "script", "artifact", "commands", "describe", "contract", "capabilities", "capability", "next", "usage", "intent", "help", "form-default", "compute", "rule-when", "approval-flow", "model-scaffold", "field-add", "model-purge", "rollback", "env", "setup", "graph", "programming", "--version", "-V"];
|
|
2203
|
-
if (ownsStdout.includes(argv[2] ?? "")) {
|
|
2279
|
+
// Commands that own their stdout never grow a trailing envelope. The list is
|
|
2280
|
+
// an explicit declaration in `cli-stdout.ts` (HANDOFF §三.2-6): an inline array
|
|
2281
|
+
// was maintained by hand and twice lost a command — composers in r10 and
|
|
2282
|
+
// `setup` in 0.6.7 both answered two envelopes, and the compound writes
|
|
2283
|
+
// (`submit-verify` / `rule-save-enable` / `workflow-update-publish`) still did
|
|
2284
|
+
// on a bad JSON flag. `test/cli-stdout-contract.test.ts` walks every registered
|
|
2285
|
+
// root command so a new one cannot be forgotten.
|
|
2286
|
+
if (ownsStdout(argv[2] ?? "")) {
|
|
2204
2287
|
completeUsage(code);
|
|
2205
2288
|
finish(code);
|
|
2206
2289
|
return;
|
|
@@ -60,7 +60,14 @@ export function checkedOutputBudget(value) {
|
|
|
60
60
|
return value;
|
|
61
61
|
}
|
|
62
62
|
export function artifactRoot() {
|
|
63
|
-
|
|
63
|
+
// r28k F11(真机 2026-11-05):旧默认落在项目根 `artifacts/`,`describe --full`
|
|
64
|
+
// 等 transport spill 与「全程只读」探针预期的「零残留」冲突(账本 createdCodes
|
|
65
|
+
// 仍为空,但磁盘多了文件)。默认与工具产物目录对齐到 `.omc/artifacts`。
|
|
66
|
+
// OMC_ARTIFACTS_HOME 若设置,则它本身就是最终 artifact 根(历史契约,不再拼一层)。
|
|
67
|
+
const home = process.env.OMC_ARTIFACTS_HOME;
|
|
68
|
+
return home !== undefined && home.trim() !== ""
|
|
69
|
+
? resolve(home)
|
|
70
|
+
: resolve(joinConfigRoot(), ".omc", "artifacts");
|
|
64
71
|
}
|
|
65
72
|
export function joinConfigRoot() {
|
|
66
73
|
return resolve(process.env.OMC_CONFIG_HOME ?? process.cwd());
|
package/dist/config-store.js
CHANGED
|
@@ -277,9 +277,24 @@ export class ConfigStore {
|
|
|
277
277
|
console.error(`cross-env: 环境 "${name}" 不在本项目允许列表(${own.join("、") || "空"});已按 OMC_ALLOW_CROSS_ENV=1 放行本次访问。请确认这是用户批准的临时跨环境操作。`);
|
|
278
278
|
return;
|
|
279
279
|
}
|
|
280
|
+
// r28j F8: cwd decides the project root. Agents often assemble JSON under
|
|
281
|
+
// /tmp then call --env local, and the refusal used to say only "允许:test"
|
|
282
|
+
// while `omc env` in another directory showed local — two sources, one
|
|
283
|
+
// unexplained. Spell both files + the resolved project root in the refusal.
|
|
284
|
+
const projectRoot = dirname(this.paths.projectEnvPath);
|
|
285
|
+
const bound = this.readProjectEnvBinding();
|
|
286
|
+
const allowlist = this.readProjectEnvsFile();
|
|
287
|
+
const boundText = bound === undefined
|
|
288
|
+
? `.omc-env=(无;未绑定)`
|
|
289
|
+
: `.omc-env=${bound}`;
|
|
290
|
+
const allowText = allowlist.length === 0
|
|
291
|
+
? `.omc-envs=(无或不存在;准入回退到 .omc-env ∪ 全局 default)`
|
|
292
|
+
: `.omc-envs=${allowlist.join(",")}`;
|
|
280
293
|
throw new ConfigurationError(`跨环境访问被拒绝:环境 "${name}" 不属于本项目(本项目允许:${own.join("、") || "(none)"})。` +
|
|
281
|
-
|
|
282
|
-
|
|
294
|
+
`解析到的项目根=${projectRoot};${boundText};${allowText}。` +
|
|
295
|
+
`准入由「当前工作目录」下的 .omc-envs(白名单)+ .omc-env(默认绑定)共同决定,不是由 --env 参数单独决定;` +
|
|
296
|
+
`在无声明的目录里 --env local 会落到机器全局 default(常见是 test)。` +
|
|
297
|
+
`请改到声明了该环境的项目根再跑,或先向用户报告并取得同意后执行 \`omc env allow ${name}\`(写入项目 .omc-envs),` +
|
|
283
298
|
`或临时设置 OMC_ALLOW_CROSS_ENV=1 单次放行。`);
|
|
284
299
|
}
|
|
285
300
|
bindProjectEnvironment(name) {
|
package/dist/discovery.js
CHANGED
|
@@ -7,6 +7,7 @@ import { LANE_CONTRACTS } from "./contract-resources.js";
|
|
|
7
7
|
import { intentCapabilityBlock } from "./intent/cli.js";
|
|
8
8
|
import { listSkills } from "./skills.js";
|
|
9
9
|
import { capabilityRouteForTool, capabilityRouteForLane, capabilityRouteIndex, DISCOVERY_AUTHORITY } from "./capability-overview.js";
|
|
10
|
+
import { readShapeFor } from "./read-shapes.js";
|
|
10
11
|
/**
|
|
11
12
|
* `commands --json` — the tool catalogue.
|
|
12
13
|
*
|
|
@@ -169,7 +170,11 @@ function jsonSchemaFor(shape) {
|
|
|
169
170
|
/** First sentence, whitespace-collapsed and bounded — enough to pick a tool. */
|
|
170
171
|
function summarizeDescription(description) {
|
|
171
172
|
const collapsed = description.replace(/\s+/g, " ").trim();
|
|
172
|
-
|
|
173
|
+
// r28k F8(真机 2026-11-05):此前把**任何**点号都当句末,于是
|
|
174
|
+
// `setup.commit` 的摘要被切在 "写 omc." ——恰好切掉了「写 omc.config.json 与
|
|
175
|
+
// 0600 凭据库」这个决定「要不要执行」的核心事实(探针必须再 --full 才拿到)。
|
|
176
|
+
// 点号只有当后面是空白或结尾时才是句末;`omc.config.json` / `v1.2.3` 里的不属于。
|
|
177
|
+
const firstStop = collapsed.search(/[。;;!!??]|\.(?=\s|$)/);
|
|
173
178
|
const sentence = firstStop >= 0 ? collapsed.slice(0, firstStop + 1) : collapsed;
|
|
174
179
|
return sentence.length > 200 ? `${sentence.slice(0, 197)}...` : sentence;
|
|
175
180
|
}
|
|
@@ -422,6 +427,23 @@ function firstCallFor(tool) {
|
|
|
422
427
|
};
|
|
423
428
|
return firstCall;
|
|
424
429
|
}
|
|
430
|
+
/**
|
|
431
|
+
* Compact `returns` block for the default describe / --param faces.
|
|
432
|
+
* Only tools with an authored entry in read-shapes.ts emit this field —
|
|
433
|
+
* undeclared tools stay at zero extra bytes. Keep notes to at most one line.
|
|
434
|
+
*/
|
|
435
|
+
function returnsFace(toolName) {
|
|
436
|
+
const shape = readShapeFor(toolName);
|
|
437
|
+
if (!shape)
|
|
438
|
+
return undefined;
|
|
439
|
+
const notes = shape.notes?.slice(0, 1);
|
|
440
|
+
return {
|
|
441
|
+
returns: {
|
|
442
|
+
keys: shape.keys,
|
|
443
|
+
...(notes && notes.length > 0 ? { notes } : {}),
|
|
444
|
+
},
|
|
445
|
+
};
|
|
446
|
+
}
|
|
425
447
|
/** Default `omc describe`: the compact contract projection. */
|
|
426
448
|
export function describeTool(registry, name) {
|
|
427
449
|
const tool = registry.find((entry) => entry.name === name);
|
|
@@ -451,12 +473,18 @@ export function describeTool(registry, name) {
|
|
|
451
473
|
// reachable through --param/--full, i.e. discovering a param needed its
|
|
452
474
|
// name first. Name the documented params inline (cheap: names only) so one
|
|
453
475
|
// `describe` answers "does this tool take X, and what is X called".
|
|
476
|
+
// r26b 读回体积审计(2026-11-05):提示原写「单个参数注释,几百字节」,而
|
|
477
|
+
// `--param nodeGraph` 实测 **8773 B**(object 参数展开≈整份 --full)——照提示
|
|
478
|
+
// 做反而更贵。现在点名哪些参数是大对象,并改指 --example。
|
|
454
479
|
...(documentedParamNames(tool).length > 0
|
|
455
|
-
? { paramDocs: { available: documentedParamNames(tool), read:
|
|
480
|
+
? { paramDocs: { available: documentedParamNames(tool), read: largeParamDocsHint(tool) } }
|
|
456
481
|
: {}),
|
|
457
482
|
// T3(GitLab #17):封闭取值(schema 里没有 enum 的那些)在默认面内联一次,
|
|
458
483
|
// 省掉「写错 → 报错 → 再写」的循环;报错里的 accepted* 仍是权威兜底。
|
|
459
484
|
...(tool.paramEnums !== undefined ? { paramEnums: tool.paramEnums } : {}),
|
|
485
|
+
// High-frequency read faces: declare the RESULT keys so `--jq` does not
|
|
486
|
+
// have to learn them from a pathMiss. Zero bytes when undeclared.
|
|
487
|
+
...(returnsFace(tool.name) ?? {}),
|
|
460
488
|
// Keep the cost salient at the decision point: agents reached for --full even
|
|
461
489
|
// when --example/--param existed (sandbox22: 8 --full calls, one 19 KB).
|
|
462
490
|
full: `omc describe ${tool.name} --full(5–20KB;先试 --example/--param)`,
|
|
@@ -473,6 +501,24 @@ function documentedParamNames(tool) {
|
|
|
473
501
|
}
|
|
474
502
|
return names.sort();
|
|
475
503
|
}
|
|
504
|
+
/**
|
|
505
|
+
* `--param` 的价码提示(r26b 实测):标量参数 150–900 B,**object 参数 ≈ 整份
|
|
506
|
+
* `--full`**(`--param nodeGraph` = 8773 B,`--full` = 16950 B)。原来的提示只写
|
|
507
|
+
* 「几百字节」,照着做会付 10–50 倍。这里点名当前工具里哪些参数是大对象。
|
|
508
|
+
*/
|
|
509
|
+
function largeParamDocsHint(tool) {
|
|
510
|
+
const schema = jsonSchemaFor((tool.inputSchema ?? {}));
|
|
511
|
+
// 只算 **object**(record)参数:字符串数组(displayFields/columns)展开只要几十字节,
|
|
512
|
+
// 把它们也标成"大"会误导(r26b 实测 data.list --param displayFields 很小)。
|
|
513
|
+
const heavy = Object.entries(schema.properties ?? {})
|
|
514
|
+
.filter(([, node]) => node.type === "object")
|
|
515
|
+
.map(([key]) => key);
|
|
516
|
+
const base = `omc describe ${tool.name} --param <name>(标量 150–900B`;
|
|
517
|
+
if (heavy.length === 0)
|
|
518
|
+
return `${base})`;
|
|
519
|
+
const named = heavy.slice(0, 3).join("、") + (heavy.length > 3 ? " 等" : "");
|
|
520
|
+
return `${base};对象参数 ${named} ≈ --full,改用 --example)`;
|
|
521
|
+
}
|
|
476
522
|
/** `omc describe <tool> --example <id>`: one recipe in full, by id. */
|
|
477
523
|
export function describeExample(registry, name, exampleId) {
|
|
478
524
|
const tool = registry.find((entry) => entry.name === name);
|
|
@@ -535,7 +581,18 @@ export function describeParam(registry, name, param) {
|
|
|
535
581
|
const schema = jsonSchemaFor(tool.inputSchema);
|
|
536
582
|
const node = schema.properties?.[param];
|
|
537
583
|
if (!node) {
|
|
538
|
-
|
|
584
|
+
// r28e 真机(2026-11-05):调用方看到 `status:"ok"` + 一堆键,误以为"参数存在、
|
|
585
|
+
// 只是描述为空"。`found:false` 是机器信号,但人读时需要一句话点明——否则
|
|
586
|
+
// 会照着 `available` 之外的名字继续试。
|
|
587
|
+
const nearest = closestMatch(param, Object.keys(schema.properties ?? {}));
|
|
588
|
+
return {
|
|
589
|
+
name: tool.name,
|
|
590
|
+
param,
|
|
591
|
+
found: false,
|
|
592
|
+
message: `本工具没有参数 "${param}"${nearest !== undefined ? `;最接近的是 "${nearest}"` : ""}——available 是本工具的全部合法参数`,
|
|
593
|
+
...(nearest !== undefined ? { didYouMean: nearest, nextCommand: `omc describe ${tool.name} --param ${nearest}` } : {}),
|
|
594
|
+
available: Object.keys(schema.properties ?? {}),
|
|
595
|
+
};
|
|
539
596
|
}
|
|
540
597
|
return {
|
|
541
598
|
name: tool.name,
|
|
@@ -544,6 +601,8 @@ export function describeParam(registry, name, param) {
|
|
|
544
601
|
type: terseType(node),
|
|
545
602
|
...(node.description ? { description: node.description } : {}),
|
|
546
603
|
schema: node,
|
|
604
|
+
// Same authored return face as the default describe (only when declared).
|
|
605
|
+
...(returnsFace(tool.name) ?? {}),
|
|
547
606
|
};
|
|
548
607
|
}
|
|
549
608
|
/**
|
package/dist/field-families.js
CHANGED
|
@@ -11,7 +11,7 @@ export { FIELD_FAMILY_LIST, FIELD_CONTROL_CODES };
|
|
|
11
11
|
export const propertyTypeArg = z
|
|
12
12
|
.string()
|
|
13
13
|
.min(1)
|
|
14
|
-
.describe(`field family. Accepted: ${FIELD_FAMILY_LIST}, CHILD_TABLE (子表, requires \`subFields\`). Out of coverage (refused with a citation): OCR, ELECTRONIC_SIGN, and nested CHILD_TABLE. Selection families take \`choices\`; relation families take \`relativeCode\` + \`options\`.`);
|
|
14
|
+
.describe(`field family. Accepted: ${FIELD_FAMILY_LIST}, CHILD_TABLE (子表, requires \`subFields\`). **NUMERICAL 必填 options.scale**(0=整数 … 8=8 位小数;金额两位小数用 scale:2,不要照搬列格式枚举)。Out of coverage (refused with a citation): OCR, ELECTRONIC_SIGN, and nested CHILD_TABLE. Selection families take \`choices\`; relation families take \`relativeCode\` + \`options\`.`);
|
|
15
15
|
export const relativeCodeArg = z.string().optional().describe("relation target model code (required for WORK_SHEET / MULT_WORK_SHEET)");
|
|
16
16
|
/**
|
|
17
17
|
* Ticket 59 (P0): the wire `name_i18n` column must be a *parseable JSON object
|
|
@@ -177,6 +177,12 @@ export const fieldOptionsArg = z
|
|
|
177
177
|
referenceField: z.string().optional().describe("RELEVANCE_DATA(16) required: referenced field code on the related model"),
|
|
178
178
|
referenceFieldPropertyType: z.number().int().optional().describe("RELEVANCE_DATA(16) required: referenced field propertyType (BizPropertyType int)"),
|
|
179
179
|
referenceFieldControlType: z.number().int().optional().describe("RELEVANCE_DATA(16): referenced field control type (BizFormPropertyType int)"),
|
|
180
|
+
// r28b F1: scale is the hard NUMERICAL gate — declare it here so
|
|
181
|
+
// `describe field.create --param options` / `--param propertyType` both
|
|
182
|
+
// surface the constraint before preflight. Bounds stay in
|
|
183
|
+
// assertNumericalScale (not zod min/max) so scale:9 still returns the
|
|
184
|
+
// teaching refusal, not a generic tool-input-invalid.
|
|
185
|
+
scale: z.number().optional().describe("NUMERICAL 必填:小数位数(0=整数,1=1位,2=2位 … 8=8位)。金额两位小数用 2;不是 listview displayFormat 枚举"),
|
|
180
186
|
nested: z.boolean().optional().describe("nested child-table metadata — refused (coverage boundary)"),
|
|
181
187
|
relationLinkage: z.boolean().optional().describe("advanced relation linkage — refused (coverage boundary)"),
|
|
182
188
|
})
|
|
@@ -687,12 +693,31 @@ export function normalizeFieldPropertyType(args) {
|
|
|
687
693
|
const type = String(raw ?? "");
|
|
688
694
|
if (!(type in FIELD_PROPERTY_TYPES)) {
|
|
689
695
|
const hint = fieldTypeHint(type);
|
|
696
|
+
// r28e F1(真机 2026-11-05):把族名写错(DATE_TIME 这类)原先归
|
|
697
|
+
// `capability-out-of-coverage`——读起来像「平台不支持该能力」,实际只是枚举
|
|
698
|
+
// 拼错。真实家族是 DATE(3),**默认 options.format 只有 YYYY-MM-DD**,
|
|
699
|
+
// 要时分必须显式把 format 设成 "YYYY-MM-DD HH:mm:ss"(见 FIELD_DATE_FORMATS),
|
|
700
|
+
// 或者用 DATE + TIME 两个字段。这个区别决定回执能不能直接照做。
|
|
701
|
+
const datetimeHint = /DATE.?TIME|DATETIME|日期时间/i.test(type)
|
|
702
|
+
? "「日期时间」不是族名:用 DATE(3),要落到时分就把 options.format 设为 \"YYYY-MM-DD HH:mm:ss\"(默认 YYYY-MM-DD 只有日期);需要日期与时间分开排两列时用 DATE + TIME(28) 两个字段"
|
|
703
|
+
: undefined;
|
|
690
704
|
throw new ToolRefusalError({
|
|
691
705
|
status: "refused",
|
|
692
|
-
|
|
693
|
-
|
|
706
|
+
// 枚举拼错(有近邻 / 概念名 / 日期时间这类)→ 输入问题,不是能力缺口。
|
|
707
|
+
blocker: hint !== undefined || datetimeHint !== undefined ? "tool-input-invalid" : "capability-out-of-coverage",
|
|
708
|
+
citation: {
|
|
709
|
+
code: hint !== undefined || datetimeHint !== undefined ? "tool-input-invalid" : "capability-out-of-coverage",
|
|
710
|
+
lane: "field-families",
|
|
711
|
+
capability: "field family full chain",
|
|
712
|
+
status: "partial",
|
|
713
|
+
reason: `${type || "missing"} is not a live-accepted family`,
|
|
714
|
+
message: `rejected: field family ${type || "(missing)"} is outside the live-accepted set; citing lane field-families`,
|
|
715
|
+
},
|
|
716
|
+
...(datetimeHint !== undefined ? { familyHint: datetimeHint } : {}),
|
|
694
717
|
...(hint ? { hint } : {}),
|
|
695
|
-
|
|
718
|
+
// hint("可能是 DATE")与 datetimeHint("要怎么让 DATE 带时分")互补:
|
|
719
|
+
// 前者回答"该写哪个族名",后者回答"写对了怎么落地时分"。两者都要给。
|
|
720
|
+
message: `propertyType「${type || "(missing)"}」不是可用的字段族名。${[hint, datetimeHint].filter((entry) => entry !== undefined).join(" ")} 可用族名:${Object.keys(FIELD_PROPERTY_TYPES).join("、")}`,
|
|
696
721
|
});
|
|
697
722
|
}
|
|
698
723
|
return FIELD_PROPERTY_TYPES[type];
|
|
@@ -962,8 +987,12 @@ export function numericalScaleWarning(type, options) {
|
|
|
962
987
|
}
|
|
963
988
|
export function assertNumericalScale(type, options) {
|
|
964
989
|
const warning = numericalScaleWarning(type, options);
|
|
965
|
-
if (warning !== undefined)
|
|
966
|
-
|
|
990
|
+
if (warning !== undefined) {
|
|
991
|
+
// r28b F1: preflight used to stop at the reason. Attach a paste-ready
|
|
992
|
+
// options patch so the next call does not need another describe round.
|
|
993
|
+
const patched = { ...(options ?? {}), scale: 2 };
|
|
994
|
+
refuseFieldOptions("NUMERICAL requires an explicit options.scale (verified: absent scale renders as raw decimal in the portal designer)", `rejected: ${warning}。可直接粘贴补齐:\"options\":${JSON.stringify(patched)}(两位小数示例;整数改 scale:0)`);
|
|
995
|
+
}
|
|
967
996
|
}
|
|
968
997
|
/**
|
|
969
998
|
* Round 4 (ADR 0005): form-interaction conditions (required/readonly/display)
|