@stalfh233/omc-cli 0.6.12 → 0.6.14
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/coverage/m1-coverage-manifest-v1.json +2 -0
- package/dist/approval-flow.js +84 -3
- package/dist/batch-call.js +32 -2
- package/dist/bench/call-face-eval.js +2 -2
- package/dist/call.js +35 -2
- package/dist/capability-overview.js +21 -7
- package/dist/cli-contract.js +8 -2
- package/dist/cli.js +313 -72
- package/dist/describe-cache.js +8 -4
- package/dist/discovery.js +4 -0
- package/dist/environment-policy.js +20 -0
- package/dist/field-families.js +62 -4
- package/dist/form-support.js +293 -6
- package/dist/goal-routes.js +137 -22
- package/dist/identity.js +63 -11
- package/dist/lanes/app-contract.js +4 -4
- package/dist/lanes/app.js +20 -2
- package/dist/lanes/dictionary-contract.js +16 -5
- package/dist/lanes/dictionary-coverage.js +1 -0
- package/dist/lanes/dictionary.js +200 -8
- package/dist/lanes/environment-contract.js +18 -6
- package/dist/lanes/environment.js +129 -16
- package/dist/lanes/field-contract.js +3 -2
- package/dist/lanes/field.js +785 -63
- package/dist/lanes/form.js +52 -6
- package/dist/lanes/list-view-contract.js +3 -3
- package/dist/lanes/list-view.js +143 -12
- package/dist/lanes/meta.js +36 -10
- package/dist/lanes/model-contract.js +7 -6
- package/dist/lanes/model.js +154 -10
- package/dist/lanes/organization.js +67 -7
- package/dist/lanes/runtime-data-contract.js +4 -2
- package/dist/lanes/runtime-data.js +395 -41
- package/dist/lanes/workflow-contract.js +2 -2
- package/dist/lanes/workflow.js +381 -11
- package/dist/model-scaffold.js +11 -3
- package/dist/path-projection.js +181 -31
- package/dist/read-shapes.js +31 -11
- package/dist/recipes/b2-registration.js +1 -1
- package/dist/recipes/field.js +16 -3
- package/dist/recipes/form.js +4 -3
- package/dist/recipes/runtime-data.js +58 -8
- package/dist/recipes/workflow.js +86 -1
- package/dist/refusal-guidance.js +18 -6
- package/dist/rollback-spec.js +41 -2
- package/dist/rollback.js +49 -11
- package/dist/script.js +72 -12
- package/dist/skills.js +91 -3
- package/dist/tools.js +113 -1
- package/dist/wire.js +13 -1
- package/dist/write-lock.js +92 -4
- package/dist/write.js +129 -16
- package/package.json +3 -3
- package/skills/omc-business-configuration.md +1 -1
- package/skills/omc-capabilities.md +5 -4
- package/skills/omc-playbook.md +41 -0
- package/skills/omc-refusals.md +58 -6
- package/skills/omc-tool-selection.md +2 -0
- package/skills/omc-write-hazards.md +3 -2
package/dist/cli.js
CHANGED
|
@@ -27,7 +27,7 @@ import { CLI_DEFAULT_OUTPUT_BYTES, assertSafeOutputPath, pruneArtifacts } from "
|
|
|
27
27
|
import { artifactPathMissHint, nullProjectionHint, partialPathMissHint, projectJsonPath, projectJsonPathFanout, projectResultExtract } from "./path-projection.js";
|
|
28
28
|
import { basename, join, resolve } from "node:path";
|
|
29
29
|
import { buildToolRegistry } from "./tools.js";
|
|
30
|
-
import { acquireWriteLock } from "./write-lock.js";
|
|
30
|
+
import { acquireWriteLock, writeLockHeldFace } from "./write-lock.js";
|
|
31
31
|
import { cmdCompute } from "./compute.js";
|
|
32
32
|
import { cmdRuleWhen } from "./rule-when.js";
|
|
33
33
|
import { cmdApprovalFlow } from "./approval-flow.js";
|
|
@@ -209,7 +209,9 @@ async function cmdDoctor(store, environment, serviceCode) {
|
|
|
209
209
|
try {
|
|
210
210
|
const resolved = store.resolveEnvironment(environment);
|
|
211
211
|
steps.push(`配置 ok 环境=${resolved.name}(来源 ${resolved.source})`);
|
|
212
|
-
steps.push(
|
|
212
|
+
steps.push(`远端写 ok 远端写=${environmentWritable(resolved.profile) ? "允许" : "只读"}${requiresProductionConfirmation(resolved.profile) ? ";生产环境:每笔写入需 confirmProduction" : ""}(平台侧变更)`);
|
|
213
|
+
// r29f F9:读面也会本地落盘(artifacts/usage/describe 缓存);与远端写分开说。
|
|
214
|
+
steps.push(`本地落盘 ok .omc/(artifacts·usage·describe 缓存;读面也会写)。重定位:OMC_CONFIG_HOME=<根>(其下 .omc/)或 OMC_ARTIFACTS_HOME=<产物目录>`);
|
|
213
215
|
const removed = removedProfileKeys(resolved.profile);
|
|
214
216
|
if (removed.length > 0)
|
|
215
217
|
steps.push(`配置 ⚠ 含已移除键(请删除):${removed.join("、")}`);
|
|
@@ -367,8 +369,10 @@ function doctorAnchor() {
|
|
|
367
369
|
const topics = listSkills().map((entry) => entry.name).join("、");
|
|
368
370
|
return [
|
|
369
371
|
"最小路径 omc next \"<目标>\"(目标→工具一步定位,不确定会明说)→ omc help omc-start-here → omc call discover_environment --env <env> --args '{}'(<env> 名以 doctor 的「环境=」或项目 .omc-env 为准,勿猜)→ omc capabilities / omc describe <tool>",
|
|
370
|
-
`方法论 omc help <topic
|
|
372
|
+
`方法论 omc help <topic>(或 --topic <name>)--sections / --section "<标题>"(按节读,随包 skills/);topic:${topics}`,
|
|
371
373
|
"大输出 超预算结果只回 artifactId/sha256/fetch;用 fetch 里的 omc artifact get <id>(可加 --jq <path>)取回,勿全量读入",
|
|
374
|
+
// r29f F9:只读面不碰远端,但仍会本地落盘——写清,避免「零写入」核对误判。
|
|
375
|
+
"落盘 远端写=环境 writable 决定的平台变更;本地落盘=.omc/(artifacts/usage/describe 缓存,读面也会写)。可用 OMC_CONFIG_HOME 重定位配置根(其下 .omc/),或 OMC_ARTIFACTS_HOME 单独重定位产物目录",
|
|
372
376
|
].join("\n");
|
|
373
377
|
}
|
|
374
378
|
function flag0(args, name) {
|
|
@@ -509,7 +513,20 @@ async function cmdCall(store, args) {
|
|
|
509
513
|
const positional = args.filter((token, index) => !token.startsWith("--") && (index === 0 || !args[index - 1]?.startsWith("--env") && !args[index - 1]?.startsWith("--environment") && !args[index - 1]?.startsWith("--args")));
|
|
510
514
|
const toolName = positional[0];
|
|
511
515
|
if (!toolName || toolName === "help" || args.includes("--help")) {
|
|
516
|
+
// r29f F3:`omc call <tool> --help` 识别具名工具时转发 describe 摘要,
|
|
517
|
+
// 并写死输出预算默认值(别只回通用 call 选项)。
|
|
518
|
+
if (toolName && toolName !== "help") {
|
|
519
|
+
const registry = buildToolRegistry();
|
|
520
|
+
const described = describeTool(registry, toolName);
|
|
521
|
+
if (described) {
|
|
522
|
+
console.error(`工具:${toolName}`);
|
|
523
|
+
console.error(`输出预算:--max-output-bytes 默认 65536(只约束最终 stdout 信封;读面 diet ~2048 独立外置 detailArtifactId);artifact get 内联上限 8192`);
|
|
524
|
+
console.error(`投影:--jq/--extract 相对 result(不要写 result. 前缀;误写会自动剥并标 autoStripped);先投影再判预算`);
|
|
525
|
+
return emitTransportResult({ status: "ok", ...described, helpNote: "来自 omc describe;完整面用 omc describe <tool> [--full|--param|--example]" }, { request, maxOutputBytes });
|
|
526
|
+
}
|
|
527
|
+
}
|
|
512
528
|
console.error("用法:omc call <tool> [--env <环境>] [--args '<json>' | --args - (stdin) | --args-file <配置根内路径>] [--extract <path>]... [--jq [path]] [--extract-keys] [--args-list <json数组> [--preflight]]");
|
|
529
|
+
console.error("输出预算:--max-output-bytes 默认 65536(只约束最终 stdout 信封;读面 diet ~2048 独立外置 detailArtifactId);artifact get 内联上限 8192");
|
|
513
530
|
console.error("写入:由环境 writable 决定(只读环境拒写);生产环境的写必须在 args 里带 confirmProduction:true。");
|
|
514
531
|
return emit(toolName && toolName !== "help" ? "error" : "ok", {}, toolName && toolName !== "help" ? CLI_EXIT_CODES.invalidInput : 0);
|
|
515
532
|
}
|
|
@@ -617,26 +634,44 @@ async function cmdCall(store, args) {
|
|
|
617
634
|
}
|
|
618
635
|
const registry = buildToolRegistry();
|
|
619
636
|
const tool = registry.find((definition) => definition.name === toolName);
|
|
620
|
-
//
|
|
621
|
-
//
|
|
622
|
-
// 会读成「过滤没生效 / 数据就是这么多」。读面在**不阻断**的前提下把被忽略的
|
|
623
|
-
// 键回显出来(写面仍是 fail-closed,见 parseToolInput)。
|
|
637
|
+
// r29f F7:读面未知参数默认硬拒(与写面 tool-input-unknown-keys 对齐);
|
|
638
|
+
// 显式 `lenientArgs:true` 才回退到旧的 ignoredArgs 回显。
|
|
624
639
|
//
|
|
625
640
|
// F6(r27):应用级键(appCode/applicationCode/application)静默回落会把写
|
|
626
641
|
// 操作打到错误应用——这些键被忽略时直接拒绝,不进 ignoredArgs。
|
|
627
642
|
const ignoredReadKeys = [];
|
|
628
643
|
if (tool) {
|
|
629
644
|
const schemaKeys = new Set(Object.keys(tool.inputSchema));
|
|
630
|
-
const recognized = new Set(["environment", "capability", "requestId", "dryRun"]);
|
|
645
|
+
const recognized = new Set(["environment", "capability", "requestId", "dryRun", "lenientArgs"]);
|
|
646
|
+
const lenientArgs = parsedArgs.lenientArgs === true;
|
|
631
647
|
const unknownKeys = Object.keys(parsedArgs).filter((key) => !schemaKeys.has(key) && !recognized.has(key));
|
|
632
648
|
const appScopeUnknown = unknownKeys.filter((key) => APP_SCOPE_ARG_KEYS.has(key));
|
|
633
649
|
if (appScopeUnknown.length > 0) {
|
|
634
|
-
const
|
|
635
|
-
|
|
636
|
-
: schemaKeys.has("appCode")
|
|
637
|
-
? "appCode"
|
|
638
|
-
: "applicationCode(或 appCode,以 omc describe 为准)";
|
|
650
|
+
const hasApplicationCode = schemaKeys.has("applicationCode");
|
|
651
|
+
const hasAppCode = schemaKeys.has("appCode");
|
|
639
652
|
const wrong = appScopeUnknown.join("、");
|
|
653
|
+
// r29a F7:schema 两个应用范围键都没有时,旧文案回退成
|
|
654
|
+
// 「请改用 applicationCode(或 appCode…)」——调用方传的正是它,形成死循环。
|
|
655
|
+
// 这种工具根本没有应用范围参数(如 rule.* 按 schemaCode 定位),走专用 blocker。
|
|
656
|
+
if (!hasApplicationCode && !hasAppCode) {
|
|
657
|
+
const locateBy = schemaKeys.has("schemaCode")
|
|
658
|
+
? "schemaCode(模型 code)"
|
|
659
|
+
: schemaKeys.has("code")
|
|
660
|
+
? "code"
|
|
661
|
+
: "本工具自己的定位键(见 omc describe)";
|
|
662
|
+
const result = {
|
|
663
|
+
status: "refused",
|
|
664
|
+
blocker: "tool-has-no-app-scope",
|
|
665
|
+
tool: toolName,
|
|
666
|
+
ignored: appScopeUnknown,
|
|
667
|
+
locateBy,
|
|
668
|
+
message: `本工具没有应用范围参数(不接受 applicationCode/appCode);你传的 ${wrong} 不会生效。` +
|
|
669
|
+
`它按 ${locateBy} 定位——应用边界请在 model.*/folder.*/app.* 面使用。` +
|
|
670
|
+
`(\`omc describe ${toolName}\` 可确认参数表)`,
|
|
671
|
+
};
|
|
672
|
+
return emit("refused", result, CLI_EXIT_CODES.policyRefused);
|
|
673
|
+
}
|
|
674
|
+
const canonical = hasApplicationCode ? "applicationCode" : "appCode";
|
|
640
675
|
const result = {
|
|
641
676
|
status: "refused",
|
|
642
677
|
blocker: "unknown-app-scope-arg",
|
|
@@ -647,10 +682,29 @@ async function cmdCall(store, args) {
|
|
|
647
682
|
};
|
|
648
683
|
return emit("refused", result, CLI_EXIT_CODES.policyRefused);
|
|
649
684
|
}
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
685
|
+
const otherUnknown = unknownKeys.filter((key) => !APP_SCOPE_ARG_KEYS.has(key));
|
|
686
|
+
if (tool.kind !== "write" && otherUnknown.length > 0) {
|
|
687
|
+
if (!lenientArgs) {
|
|
688
|
+
const suggestions = otherUnknown.map((key) => ({ key, ...suggestArgumentKey(key, [...schemaKeys]) }));
|
|
689
|
+
const pairs = suggestions.map((entry) => {
|
|
690
|
+
if (entry.closest)
|
|
691
|
+
return `${entry.key}→${entry.closest}`;
|
|
692
|
+
if (entry.absentInTool)
|
|
693
|
+
return `${entry.key}(本工具没有 ${entry.absentInTool})`;
|
|
694
|
+
return entry.key;
|
|
695
|
+
});
|
|
696
|
+
const result = {
|
|
697
|
+
status: "error",
|
|
698
|
+
blocker: "tool-input-unknown-keys",
|
|
699
|
+
tool: toolName,
|
|
700
|
+
unknownKeys: otherUnknown,
|
|
701
|
+
suggestions,
|
|
702
|
+
message: `读面不接受未知键(与写面对齐):${pairs.join("、")}。` +
|
|
703
|
+
`改用合法键后重试;确需宽松忽略传 lenientArgs:true。参数表:omc describe ${toolName}`,
|
|
704
|
+
};
|
|
705
|
+
return emit("error", result, CLI_EXIT_CODES.invalidInput);
|
|
706
|
+
}
|
|
707
|
+
for (const key of otherUnknown) {
|
|
654
708
|
ignoredReadKeys.push({ key, ...suggestArgumentKey(key, [...schemaKeys]) });
|
|
655
709
|
}
|
|
656
710
|
}
|
|
@@ -684,15 +738,16 @@ async function cmdCall(store, args) {
|
|
|
684
738
|
if (lock.held)
|
|
685
739
|
break;
|
|
686
740
|
if (Date.now() >= deadline) {
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
741
|
+
// r31b F2: structured retryable/lockOwner/nextCommand so scripts do not
|
|
742
|
+
// invent a shell retry loop (help says use --wait; the refusal must too).
|
|
743
|
+
const conflict = writeLockHeldFace({
|
|
690
744
|
environment: lockTarget,
|
|
691
745
|
owner: lock.owner,
|
|
692
746
|
recovery: lock.recovery,
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
747
|
+
refusedTool: toolName,
|
|
748
|
+
waitMs: waitMs > 0 ? waitMs : 30_000,
|
|
749
|
+
});
|
|
750
|
+
console.error(`write lock conflict: ${JSON.stringify({ pid: lock.owner?.pid, tool: lock.owner?.tool ?? lock.owner?.command, startedAt: lock.owner?.startedAt })}`);
|
|
696
751
|
writeEnvelope(envelope("refused", conflict, request, classifyResult(conflict).error));
|
|
697
752
|
return CLI_EXIT_CODES.policyRefused;
|
|
698
753
|
}
|
|
@@ -828,6 +883,18 @@ function projectArtifactPath(value, path) {
|
|
|
828
883
|
? { missed: true, ...(fanout.at !== undefined ? { at: fanout.at } : {}), ...(fanout.available !== undefined ? { available: fanout.available } : {}) }
|
|
829
884
|
: { missed: false, content: fanout.values, mapped: fanout.values.length, ...(fanout.missedEntries > 0 ? { missedEntries: fanout.missedEntries } : {}), nullEntries: fanout.nullEntries };
|
|
830
885
|
}
|
|
886
|
+
/** Preserve source completeness/count facts on every artifact projection. */
|
|
887
|
+
function artifactProjectionMetadata(value) {
|
|
888
|
+
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
889
|
+
return {};
|
|
890
|
+
const record = value;
|
|
891
|
+
const metadata = {};
|
|
892
|
+
for (const key of ["truncated", "truncatedKeys", "itemsCount", "total"]) {
|
|
893
|
+
if (Object.prototype.hasOwnProperty.call(record, key))
|
|
894
|
+
metadata[key] = record[key];
|
|
895
|
+
}
|
|
896
|
+
return metadata;
|
|
897
|
+
}
|
|
831
898
|
/**
|
|
832
899
|
* T6(GitLab #17):`mapped`/`nullEntries` 的读法要跟数据一起走,否则 `mapped:5`
|
|
833
900
|
* 会被读成"取到 5 个值"。文案的单一真相源在 path-projection(与 `--extract`
|
|
@@ -883,15 +950,17 @@ async function cmdArtifactGet(id, transport, options = {}) {
|
|
|
883
950
|
// 一层 `projection:{path,content}`——`artifact get --jq` 与 `--summary --jq`
|
|
884
951
|
// 因此同形(顶层 path/content),换读面不用重新探根键。
|
|
885
952
|
return emitTransportResult(projected.missed
|
|
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) }
|
|
953
|
+
? { status: "ok", artifactId: id, sha256: artifact.sha256, bytes: artifact.content.length, ...artifactProjectionMetadata(content), keys, path: options.jq, content: null, projections: [{ path: options.jq, content: null, pathMiss: true, missedAt: projected.at, available: projected.available }], pathMiss: true, missedAt: projected.at, available: projected.available, unanswered: true, hint: artifactPathMissHint(projected.at) }
|
|
887
954
|
: {
|
|
888
955
|
status: "ok",
|
|
889
956
|
artifactId: id,
|
|
890
957
|
sha256: artifact.sha256,
|
|
891
958
|
bytes: artifact.content.length,
|
|
959
|
+
...artifactProjectionMetadata(content),
|
|
892
960
|
keys,
|
|
893
961
|
path: options.jq,
|
|
894
962
|
content: projected.content ?? null,
|
|
963
|
+
projections: [{ 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 } : {}) }],
|
|
895
964
|
...(projected.mapped !== undefined ? { mapped: projected.mapped } : {}),
|
|
896
965
|
...(projected.nullEntries !== undefined && projected.nullEntries > 0 ? { nullEntries: projected.nullEntries } : {}),
|
|
897
966
|
...(projected.missedEntries !== undefined ? { missedEntries: projected.missedEntries } : {}),
|
|
@@ -937,12 +1006,12 @@ async function cmdArtifactGet(id, transport, options = {}) {
|
|
|
937
1006
|
? { path, content: null, pathMiss: true, missedAt: projected.at, available: projected.available }
|
|
938
1007
|
: { 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) } : {}) };
|
|
939
1008
|
});
|
|
940
|
-
return emitTransportResult({ status: "ok", artifactId: id, sha256: artifact.sha256, projections, ...(projections.some((entry) => entry.pathMiss) ? { unanswered: true, hint: partialPathMissHint() } : {}) }, transport);
|
|
1009
|
+
return emitTransportResult({ status: "ok", artifactId: id, sha256: artifact.sha256, ...artifactProjectionMetadata(content), projections, ...(projections.some((entry) => entry.pathMiss) ? { unanswered: true, hint: partialPathMissHint() } : {}) }, transport);
|
|
941
1010
|
}
|
|
942
1011
|
const projected = projectArtifactPath(artifactContent, options.jq);
|
|
943
1012
|
return emitTransportResult(projected.missed
|
|
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);
|
|
1013
|
+
? { status: "ok", artifactId: id, sha256: artifact.sha256, ...artifactProjectionMetadata(content), path: options.jq, content: null, pathMiss: true, missedAt: projected.at, available: projected.available, unanswered: true, hint: artifactPathMissHint(projected.at) }
|
|
1014
|
+
: { status: "ok", artifactId: id, sha256: artifact.sha256, ...artifactProjectionMetadata(content), path: options.jq, content: projected.content ?? null, projections: [{ 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 } : {}) }], ...(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);
|
|
946
1015
|
}
|
|
947
1016
|
// r6-onboarding 2026-10-21: 47 artifact fetches, 0 used a projection; the 10
|
|
948
1017
|
// large ones (up to 55 KB) pulled whole workflow templates just to read one
|
|
@@ -1053,6 +1122,76 @@ async function cmdBench(args) {
|
|
|
1053
1122
|
const dims = report.dimensions;
|
|
1054
1123
|
return failedSteps === 0 && dims.intent.passed === dims.intent.cases ? 0 : 1;
|
|
1055
1124
|
}
|
|
1125
|
+
/** r29f F8:discovery 面复用同一投影层(相对本命令 result,不是 call 信封)。 */
|
|
1126
|
+
function emitProjectedResult(result, transport, jq) {
|
|
1127
|
+
const raw = Array.isArray(jq) ? jq : jq === undefined ? [] : [jq];
|
|
1128
|
+
const keysOnly = raw.some((entry) => entry === true) || (raw.length === 0 && jq === true);
|
|
1129
|
+
const paths = raw
|
|
1130
|
+
.filter((entry) => typeof entry === "string")
|
|
1131
|
+
.flatMap((path) => path.split(","))
|
|
1132
|
+
.map((path) => path.trim())
|
|
1133
|
+
.filter(Boolean);
|
|
1134
|
+
if (paths.length === 0 && !keysOnly)
|
|
1135
|
+
return emitTransportResult(result, transport);
|
|
1136
|
+
const projected = projectResultExtract(result, paths, keysOnly);
|
|
1137
|
+
return emitTransportResult(projected ?? result, transport);
|
|
1138
|
+
}
|
|
1139
|
+
function enrichDescribeParamMiss(registry, toolName, param, miss) {
|
|
1140
|
+
// r31b F1: a miss must teach the next move — closest name, or "that param
|
|
1141
|
+
// lives on another tool" — instead of dumping the whole available table.
|
|
1142
|
+
const available = Array.isArray(miss.available) ? miss.available.map((entry) => String(entry)) : [];
|
|
1143
|
+
const didYouMean = typeof miss.didYouMean === "string" ? miss.didYouMean : undefined;
|
|
1144
|
+
const family = toolName.includes(".") ? toolName.split(".")[0] : toolName;
|
|
1145
|
+
// Known cross-tool identity pairs first (新建 vs 更新), then same-family,
|
|
1146
|
+
// then any other tool that declares the param.
|
|
1147
|
+
const preferredPair = toolName === "data.save" && param === "bizObjectId"
|
|
1148
|
+
? "data.update"
|
|
1149
|
+
: toolName === "data.update" && param === "formData"
|
|
1150
|
+
? "data.save"
|
|
1151
|
+
: undefined;
|
|
1152
|
+
const otherTools = registry
|
|
1153
|
+
.filter((tool) => tool.name !== toolName && tool.inputSchema && Object.prototype.hasOwnProperty.call(tool.inputSchema, param))
|
|
1154
|
+
.map((tool) => tool.name)
|
|
1155
|
+
.sort((left, right) => {
|
|
1156
|
+
const rank = (name) => {
|
|
1157
|
+
if (preferredPair !== undefined && name === preferredPair)
|
|
1158
|
+
return 0;
|
|
1159
|
+
if (name.startsWith(`${family}.`))
|
|
1160
|
+
return 1;
|
|
1161
|
+
return 2;
|
|
1162
|
+
};
|
|
1163
|
+
return rank(left) - rank(right) || left.localeCompare(right);
|
|
1164
|
+
});
|
|
1165
|
+
const otherTool = preferredPair && otherTools.includes(preferredPair) ? preferredPair : otherTools[0];
|
|
1166
|
+
// Known cross-tool identity guidance for the data lane (新建 vs 更新).
|
|
1167
|
+
const dataIdentityHint = toolName === "data.save" && param === "bizObjectId"
|
|
1168
|
+
? "新建用 data.save(schemaCode+formData);更新用 data.update + bizObjectId"
|
|
1169
|
+
: undefined;
|
|
1170
|
+
const cappedAvailable = available.slice(0, 8);
|
|
1171
|
+
const hintParts = [
|
|
1172
|
+
didYouMean !== undefined ? `本工具最接近的参数是 ${didYouMean}` : undefined,
|
|
1173
|
+
otherTool !== undefined ? `参数 "${param}" 属于 ${otherTool}${otherTools.length > 1 ? `(同名还在 ${otherTools.slice(1, 3).join("、")})` : ""}` : undefined,
|
|
1174
|
+
dataIdentityHint,
|
|
1175
|
+
].filter((part) => typeof part === "string" && part.length > 0);
|
|
1176
|
+
return {
|
|
1177
|
+
...miss,
|
|
1178
|
+
...(didYouMean !== undefined ? { didYouMean, nextCommand: `omc describe ${toolName} --param ${didYouMean}` } : {}),
|
|
1179
|
+
...(otherTool !== undefined
|
|
1180
|
+
? {
|
|
1181
|
+
otherTool,
|
|
1182
|
+
otherTools: otherTools.slice(0, 5),
|
|
1183
|
+
nextCommand: `omc describe ${otherTool} --param ${param}`,
|
|
1184
|
+
}
|
|
1185
|
+
: {}),
|
|
1186
|
+
...(hintParts.length > 0 ? { hint: hintParts.join(";") } : {}),
|
|
1187
|
+
...(dataIdentityHint !== undefined ? { guidance: dataIdentityHint } : {}),
|
|
1188
|
+
available: cappedAvailable,
|
|
1189
|
+
...(available.length > cappedAvailable.length ? { availableTruncated: available.length - cappedAvailable.length } : {}),
|
|
1190
|
+
message: hintParts.length > 0
|
|
1191
|
+
? `本工具没有参数 "${param}"——${hintParts.join(";")}`
|
|
1192
|
+
: `本工具没有参数 "${param}"——available 是本工具的合法参数(已截断)`,
|
|
1193
|
+
};
|
|
1194
|
+
}
|
|
1056
1195
|
function cmdDiscovery(kind, transport, value, options = {}) {
|
|
1057
1196
|
const registry = buildToolRegistry();
|
|
1058
1197
|
let result;
|
|
@@ -1079,7 +1218,7 @@ function cmdDiscovery(kind, transport, value, options = {}) {
|
|
|
1079
1218
|
// agent paid 4×54 KB on the default face before any hint could land).
|
|
1080
1219
|
if (options.json !== true && options.compact !== true && !scoped) {
|
|
1081
1220
|
result = commandIndex(registry);
|
|
1082
|
-
return
|
|
1221
|
+
return emitProjectedResult({ status: "ok", ...result }, transport, options.jq);
|
|
1083
1222
|
}
|
|
1084
1223
|
const inventory = commandInventory(registry, { compact: options.compact === true });
|
|
1085
1224
|
// r19 ticket 05: above this many hits a filter word is "broad" — the filtered
|
|
@@ -1170,16 +1309,28 @@ function cmdDiscovery(kind, transport, value, options = {}) {
|
|
|
1170
1309
|
}
|
|
1171
1310
|
else if (options.schemaOnly)
|
|
1172
1311
|
result = describeSchemaOnly(registry, value ?? "");
|
|
1173
|
-
else if (options.param)
|
|
1174
|
-
|
|
1312
|
+
else if (options.param) {
|
|
1313
|
+
const missOrHit = describeParam(registry, value ?? "", options.param);
|
|
1314
|
+
// r31b F1: enrich a param miss with otherTool / capped available / guidance.
|
|
1315
|
+
result =
|
|
1316
|
+
missOrHit && missOrHit.found === false
|
|
1317
|
+
? enrichDescribeParamMiss(registry, value ?? "", options.param, missOrHit)
|
|
1318
|
+
: missOrHit;
|
|
1319
|
+
}
|
|
1175
1320
|
else if (options.example !== undefined)
|
|
1176
1321
|
result = describeExample(registry, value ?? "", typeof options.example === "string" ? options.example : undefined);
|
|
1177
1322
|
else if (options.full) {
|
|
1178
1323
|
const full = describeToolFull(registry, value ?? "");
|
|
1179
|
-
//
|
|
1180
|
-
// caller
|
|
1181
|
-
|
|
1182
|
-
|
|
1324
|
+
// r31b F3: `--full` means "give me the body". Short-circuit only when the
|
|
1325
|
+
// caller opts in with `--cache-ok` (legacy r9 repeat-read suppression).
|
|
1326
|
+
if (!full)
|
|
1327
|
+
result = full;
|
|
1328
|
+
else if (options.cacheOk)
|
|
1329
|
+
result = repeatFullDescribe(value ?? "", full) ?? full;
|
|
1330
|
+
else {
|
|
1331
|
+
repeatFullDescribe(value ?? "", full, { recordOnly: true });
|
|
1332
|
+
result = full;
|
|
1333
|
+
}
|
|
1183
1334
|
}
|
|
1184
1335
|
else {
|
|
1185
1336
|
result = describeTool(registry, value ?? "");
|
|
@@ -1236,7 +1387,7 @@ function cmdDiscovery(kind, transport, value, options = {}) {
|
|
|
1236
1387
|
const textNote = byText.length > 0 ? `;didYouMeanByText: ${byText.join("、")}` : "";
|
|
1237
1388
|
return emitInvalid(transport, { message: `${kind} target not found: "${requested}"${shown.length > 0 ? `; did you mean: ${shown.join("、")}` : ""}${textNote}${absence}${approvalPointer}${intentPointer}${formPointer}${pointer}` });
|
|
1238
1389
|
}
|
|
1239
|
-
return
|
|
1390
|
+
return emitProjectedResult({ status: "ok", ...result }, transport, options.jq);
|
|
1240
1391
|
}
|
|
1241
1392
|
function cmdUsage(transport, options = {}) {
|
|
1242
1393
|
return emitTransportResult(usageReport(undefined, { records: options.records === true, top: options.top, resolveLikeWriter: true }), transport);
|
|
@@ -1254,11 +1405,11 @@ function cmdNext(goal, transport) {
|
|
|
1254
1405
|
* capability overview. Read-only, zero environment contact; a projection of
|
|
1255
1406
|
* the tool registry, the coverage manifest, and the intent capability registry.
|
|
1256
1407
|
*/
|
|
1257
|
-
function cmdCapabilities(transport, compact = false) {
|
|
1408
|
+
function cmdCapabilities(transport, compact = false, jq) {
|
|
1258
1409
|
const result = compactCapabilities(buildToolRegistry(), { compact });
|
|
1259
|
-
return
|
|
1410
|
+
return emitProjectedResult({ status: "ok", ...result }, transport, jq);
|
|
1260
1411
|
}
|
|
1261
|
-
function cmdCapability(id, transport, executable = false) {
|
|
1412
|
+
function cmdCapability(id, transport, executable = false, jq) {
|
|
1262
1413
|
const registry = buildToolRegistry();
|
|
1263
1414
|
const resolved = resolveCapabilityId(id);
|
|
1264
1415
|
if (!resolved) {
|
|
@@ -1269,9 +1420,64 @@ function cmdCapability(id, transport, executable = false) {
|
|
|
1269
1420
|
const bundle = executableCapabilityBundle(registry, resolved);
|
|
1270
1421
|
if (!bundle)
|
|
1271
1422
|
return emitInvalid(transport, { message: `capability "${id}" 没有可执行 bundle` });
|
|
1272
|
-
return
|
|
1273
|
-
}
|
|
1274
|
-
|
|
1423
|
+
return emitProjectedResult({ status: "ok", capability: bundle }, transport, jq);
|
|
1424
|
+
}
|
|
1425
|
+
const detail = capabilityDetail(registry, resolved);
|
|
1426
|
+
if (!detail)
|
|
1427
|
+
return emitInvalid(transport, { message: `capability "${id}" 无详情` });
|
|
1428
|
+
// r29a F4:app/folder/model/rule 面附应用参数名矩阵(主/别名),一次看完。
|
|
1429
|
+
const appScopeMatrix = buildAppScopeMatrix(registry, resolved);
|
|
1430
|
+
return emitProjectedResult({
|
|
1431
|
+
status: "ok",
|
|
1432
|
+
capability: {
|
|
1433
|
+
...detail,
|
|
1434
|
+
...(appScopeMatrix !== undefined ? { appScopeArgs: appScopeMatrix } : {}),
|
|
1435
|
+
},
|
|
1436
|
+
}, transport, jq);
|
|
1437
|
+
}
|
|
1438
|
+
/**
|
|
1439
|
+
* r29a F4:跨 app/folder/model/rule 的「工具 → 应用参数名(主/别名)」小表。
|
|
1440
|
+
* 只在这四个能力面上挂,避免每条 capability 都膨胀。
|
|
1441
|
+
*/
|
|
1442
|
+
function buildAppScopeMatrix(registry, capabilityId) {
|
|
1443
|
+
const faces = new Set(["app", "folder", "model", "businessrule", "rule"]);
|
|
1444
|
+
if (!faces.has(capabilityId))
|
|
1445
|
+
return undefined;
|
|
1446
|
+
const laneByCapability = {
|
|
1447
|
+
app: "app-management",
|
|
1448
|
+
folder: "app-tree-folders",
|
|
1449
|
+
model: "model-management",
|
|
1450
|
+
businessrule: "B1-businessrule",
|
|
1451
|
+
rule: "B1-businessrule",
|
|
1452
|
+
};
|
|
1453
|
+
const lane = laneByCapability[capabilityId];
|
|
1454
|
+
const rows = registry
|
|
1455
|
+
.filter((tool) => tool.lane === lane || ((capabilityId === "rule" || capabilityId === "businessrule") && tool.name.startsWith("rule.")))
|
|
1456
|
+
.map((tool) => {
|
|
1457
|
+
const keys = Object.keys(tool.inputSchema);
|
|
1458
|
+
const primary = keys.includes("applicationCode") ? "applicationCode" : keys.includes("appCode") ? "appCode" : null;
|
|
1459
|
+
const aliases = keys.includes("applicationCode") && keys.includes("appCode")
|
|
1460
|
+
? ["applicationCode", "appCode"]
|
|
1461
|
+
: primary === "applicationCode"
|
|
1462
|
+
? ["applicationCode"]
|
|
1463
|
+
: primary === "appCode"
|
|
1464
|
+
? ["appCode"]
|
|
1465
|
+
: [];
|
|
1466
|
+
return {
|
|
1467
|
+
tool: tool.name,
|
|
1468
|
+
primary,
|
|
1469
|
+
aliases,
|
|
1470
|
+
note: primary === null ? "无应用范围参数(按 schemaCode/自身键定位)" : aliases.length > 1 ? "双别名;冲突报 app-code-conflict" : undefined,
|
|
1471
|
+
};
|
|
1472
|
+
});
|
|
1473
|
+
if (rows.length === 0)
|
|
1474
|
+
return undefined;
|
|
1475
|
+
return {
|
|
1476
|
+
face: capabilityId,
|
|
1477
|
+
rows,
|
|
1478
|
+
relatedTopics: ["app-scope"],
|
|
1479
|
+
hint: "完整矩阵也见 `omc help app-scope`",
|
|
1480
|
+
};
|
|
1275
1481
|
}
|
|
1276
1482
|
/**
|
|
1277
1483
|
* Read + validate an intent file, or emit the shared invalid-input envelope.
|
|
@@ -1621,14 +1827,14 @@ function cmdHelpSearch(topic, query, transport, searchMode, limit) {
|
|
|
1621
1827
|
: "")
|
|
1622
1828
|
: "";
|
|
1623
1829
|
if (topic === undefined) {
|
|
1624
|
-
console.error("用法:omc help <topic> | omc help --search \"<词>\"(跨 topic 检索,命中节正文随结果返回)");
|
|
1830
|
+
console.error("用法:omc help <topic> | omc help --topic <name> | omc help --search \"<词>\"(跨 topic 检索,命中节正文随结果返回)");
|
|
1625
1831
|
console.error("权威路线:omc help omc-start-here(随包 skills/,不是分发镜像)");
|
|
1626
1832
|
console.error("环境就绪:omc call discover_environment --env <env> --args '{}'");
|
|
1627
1833
|
console.error(`命令:${commands.join("、")}`);
|
|
1628
1834
|
console.error(`方法论 topic:${topics.map((entry) => entry.name).join("、")}`);
|
|
1629
1835
|
return emitTransportResult({
|
|
1630
1836
|
status: "ok",
|
|
1631
|
-
usage: "omc help <topic>",
|
|
1837
|
+
usage: "omc help <topic> | omc help --topic <name>",
|
|
1632
1838
|
discovery: DISCOVERY_AUTHORITY,
|
|
1633
1839
|
commands,
|
|
1634
1840
|
topics,
|
|
@@ -1702,7 +1908,22 @@ function cmdHelpSearch(topic, query, transport, searchMode, limit) {
|
|
|
1702
1908
|
}
|
|
1703
1909
|
if (all) {
|
|
1704
1910
|
console.error(`omc help ${document.name} --all(${Buffer.byteLength(document.content, "utf8")} B)`);
|
|
1705
|
-
|
|
1911
|
+
// r32 F3(真机):三种模式三种形状——默认回 sections[]、--section 回顶层 content、
|
|
1912
|
+
// --all 却回 {topic:{name,description,content}}。按前两种写的解析(.content /
|
|
1913
|
+
// .result.topic 当字符串)在 --all 上全部取不到,而默认模式的 hint 恰恰把 --all
|
|
1914
|
+
// 指成"确需整篇"的下一步。
|
|
1915
|
+
// 现在 --all 与前两者同形:topic 是 topic 名(字符串),正文在顶层 content,
|
|
1916
|
+
// 并附 bytes/chars/complete。**不要再把 content 放进 topic**——那会让整篇在
|
|
1917
|
+
// 信封里出现两次,撑破 64KiB 预算后被外置成 artifact(r32 实测:help-write-hazards
|
|
1918
|
+
// 因此从"直接回正文"退化成"回一个每次不同的 artifactId",golden 永久漂移)。
|
|
1919
|
+
return emitTransportResult({
|
|
1920
|
+
status: "ok",
|
|
1921
|
+
topic: document.name,
|
|
1922
|
+
content: document.content,
|
|
1923
|
+
bytes: Buffer.byteLength(document.content, "utf8"),
|
|
1924
|
+
chars: document.content.length,
|
|
1925
|
+
complete: true,
|
|
1926
|
+
}, transport);
|
|
1706
1927
|
}
|
|
1707
1928
|
// Shape first. Round 10 paid 35,872 B per full read of one topic, seven times
|
|
1708
1929
|
// in a single session, to use one or two headings. The index names every
|
|
@@ -1738,14 +1959,16 @@ function onboardingHelp() {
|
|
|
1738
1959
|
return [
|
|
1739
1960
|
"首次使用、或不确定从哪开始:omc help omc-start-here(随包权威 topic 路线);被拒或状态异常:omc help omc-refusals。",
|
|
1740
1961
|
"中文可读输出:任何命令加 --utf8——它让人类可读诊断(stderr/散文)按 UTF-8 原文输出。stdout 的 JSON 信封在非 UTF-8 代码页下仍保持 \\uXXXX 转义(保证 JSON.parse/ConvertFrom-Json 一定可解析,这是有意为之)。要连信封也是中文明文,必须先让管道按 UTF-8 解码(chcp 65001)——只设 OMC_ASCII_STDOUT=0 关写出端转义,在 cp936 管道下会把信封解码坏。",
|
|
1741
|
-
"方法论按需读:omc help <topic
|
|
1962
|
+
"方法论按需读:omc help <topic>(或 omc help --topic <name>)默认只回节索引(每节字节数);用 --section \"<标题>\" 取正文(可重复),确需整篇才 --all。找具体事实(错误码/字段名/命令名)不要整篇 dump:omc help --search \"<词>\"(跨 topic,命中节正文随结果返回)。",
|
|
1742
1963
|
"目标→工具一步定位:omc next \"<你要做什么>\"(离线、只读,给能力/说明/首批命令;不确定会明说并回退到 omc-requirement-analysis)。",
|
|
1743
|
-
|
|
1964
|
+
// r30f F20:写清两道门——读面 diet(~2048) 与 stdout 预算(--max-output-bytes,默认 64KiB) 不是一回事。
|
|
1965
|
+
"输出预算:`--max-output-bytes` 默认 65536(64KiB;超则回 artifact 指针 + sha256),只约束最终 stdout 信封(投影/--jq 之后);`artifact get` 内联上限 8192。读面另有独立 diet 门(~2048)会先外置 detailArtifactId——抬高 --max-output-bytes 不会把已 diet 的大表重新内联。",
|
|
1966
|
+
"大结果取值一步到位:读面回 detailArtifactId 时,用 omc call <tool> … --jq <path>(可重复;`[]` 映射数组,如 items[].fields.planNo)或 --extract/--extract-keys,省掉再跑一次 omc artifact get。commands/capabilities/describe 也支持 --jq。批量形状预检用 --args-list … --preflight(零 wire、零写入,一次报全)。",
|
|
1744
1967
|
// r25 真机(2026-11-04):探针连跑同一读面 2–3 次想验证「第二次是否 unchanged」,
|
|
1745
1968
|
// 结果三次都是全量,还去 describe 找 dedupe 参数(多花 3 轮)。读面确实不做跨调用
|
|
1746
1969
|
// 结果去重——**明说**比留白强,省掉一次探索。
|
|
1747
1970
|
"读面不做跨调用结果去重:同一读面同参数连跑 N 次就是 N 次真实读(不会回 unchanged,也没有 dedupe 开关)。要省就 --jq 定向取值或把结果落到自己的变量里复用;`unchanged:true` 只出现在写面回读缓存那一条路径。",
|
|
1748
|
-
"工具发现:omc commands --json(含 capability id/lane/recipes;加 --compact 只回工具行、无目录散文)· omc capabilities(能力 id/lane/aliases/tools/依赖顺序)· omc capability <id> --executable
|
|
1971
|
+
"工具发现:omc commands --json(含 capability id/lane/recipes;加 --compact 只回工具行、无目录散文)· omc capabilities(能力 id/lane/aliases/tools/依赖顺序)· omc capability <id> --executable(单能力完整执行包)。应用参数矩阵:omc help app-scope。",
|
|
1749
1972
|
"单工具/契约:omc describe <tool>(schema+recipe+能力路由)· omc contract <lane>(wire 契约+能力路由);环境就绪:omc call discover_environment --env <env> --args '{}'。",
|
|
1750
1973
|
"写入两道门:对话层草稿确认(omc help omc-config-draft-review)+ 环境 writable 开关;生产环境每笔写需在 args 里带 confirmProduction:true。",
|
|
1751
1974
|
].join("\n");
|
|
@@ -1819,6 +2042,22 @@ async function main() {
|
|
|
1819
2042
|
process.stdout.write(`${ownPackageVersion()}\n`);
|
|
1820
2043
|
return 0;
|
|
1821
2044
|
}
|
|
2045
|
+
// r29f F3:commander 在 action 前就吞掉 `--help`。`omc call <tool> --help`
|
|
2046
|
+
// 必须在这里短路转发 describe,否则永远只见通用 call 选项。
|
|
2047
|
+
if (bare[0] === "call" && bare[1] && bare[1] !== "help" && !bare[1].startsWith("-") && (bare.includes("--help") || bare.includes("-h"))) {
|
|
2048
|
+
const toolName = bare[1];
|
|
2049
|
+
const registry = buildToolRegistry();
|
|
2050
|
+
const described = describeTool(registry, toolName);
|
|
2051
|
+
console.error(`工具:${toolName}`);
|
|
2052
|
+
console.error(`输出预算:--max-output-bytes 默认 2048(超则回 artifact 指针 + sha256);artifact get 内联上限 8192`);
|
|
2053
|
+
console.error(`投影:--jq/--extract 相对 result(不要写 result. 前缀;误写会自动剥并标 autoStripped)`);
|
|
2054
|
+
if (described) {
|
|
2055
|
+
writeEnvelope(envelope("ok", { ...described, helpNote: "来自 omc describe;完整面用 omc describe <tool> [--full|--param|--example]" }, request));
|
|
2056
|
+
return CLI_EXIT_CODES.ok;
|
|
2057
|
+
}
|
|
2058
|
+
writeEnvelope(envelope("error", { tool: toolName }, request, { code: "invalid-input", message: `未知工具 ${toolName};用 omc commands --json --compact 查名单`, retryable: false }));
|
|
2059
|
+
return CLI_EXIT_CODES.invalidInput;
|
|
2060
|
+
}
|
|
1822
2061
|
command.addHelpText("after", `\n${onboardingHelp()}\n`);
|
|
1823
2062
|
// No command at all: guide the agent (help text + onboarding), exit 0. This
|
|
1824
2063
|
// is a benign "show me the surface" invocation, not a failure.
|
|
@@ -1826,7 +2065,11 @@ async function main() {
|
|
|
1826
2065
|
command.outputHelp();
|
|
1827
2066
|
return CLI_EXIT_CODES.ok;
|
|
1828
2067
|
}
|
|
1829
|
-
|
|
2068
|
+
// r30f F15:topic 既是位置参数也接受 `--topic <name>` 别名(肌肉记忆对齐 capability/contract)。
|
|
2069
|
+
command.command("help [topic]").option("--topic <name>", "topic 别名(与位置参数等价:`omc help omc-refusals` = `omc help --topic omc-refusals`)").option("--section [heading]", "按标题取该节内容;可重复或用逗号一次取多节:--section a --section b / --section a,b(不带值则列出该 topic 的节标题)").option("--sections").option("--all", "回整篇正文(默认只回节索引:标题 + 各节字节)").option("--search <query>", "跨 topic 全文检索(错误码/字段名/命令名);命中节正文随结果返回,不需要再取一次").option("--search-mode <mode>", "all(默认,同一节包含全部词)或 any(包含任一)").option("--limit <n>", "检索最多返回几节(默认 5,1–50)").option("--max-output-bytes <bytes>").action(async (topic, options) => {
|
|
2070
|
+
const resolvedTopic = typeof options.topic === "string" && options.topic.trim() !== "" ? options.topic.trim() : topic;
|
|
2071
|
+
commandExitCode = await withTransport(options, request, (transport) => cmdHelp(resolvedTopic, command.commands.map((entry) => entry.name()), transport, options.section, options.sections === true, options.all === true, typeof options.search === "string" ? options.search : undefined, typeof options.searchMode === "string" ? options.searchMode : undefined, typeof options.limit === "string" ? options.limit : undefined));
|
|
2072
|
+
});
|
|
1830
2073
|
command.command("setup").option("--list").option("--edit <environment>").option("--remove <environment>").action(async (options) => {
|
|
1831
2074
|
// r25 真机(2026-11-04):setup 不在 ownsStdout 里,于是非 TTY 拒绝时主流程会补一个
|
|
1832
2075
|
// `error.code=internal` 的信封——明明是可预期的用法错误(stdin 不是终端),却被包成
|
|
@@ -1886,7 +2129,7 @@ async function main() {
|
|
|
1886
2129
|
commandExitCode = CLI_EXIT_CODES.ok;
|
|
1887
2130
|
});
|
|
1888
2131
|
command.command("doctor").option("--environment <environment>").option("--env <environment>").option("--service <serviceCode>", "可选:体检单个业务集成服务的鉴权参数形状(掩码/占位/全 0 检出,WARN 非致命)").action(async (options) => { commandExitCode = await cmdDoctor(new ConfigStore(defaultPaths()), options.environment ?? options.env, options.service); });
|
|
1889
|
-
command.command("call <tool>").option("--env <environment>").option("--environment <environment>").option("--args <json>").option("--args-file <path>").option("--args-list <json-array>", '同一工具跑 N 次(JSON 数组,每条一个 args 对象);逐条执行、默认不中断,最后回 succeeded/failed 汇总。适合批量建字段等').option("--preflight", "与 --args-list 一起用:只跑参数校验 + 写前语义闸(零 wire、零写入),一次报全所有形状问题").option("--keys", "与 artifact get 的 --keys 同义(= --extract-keys):列出本次结果 artifact 的顶层键与体积").addOption(new Option("--extract <path>", "在本次结果的 artifact 内取一个路径的值(可重复;省掉一次 artifact get)").argParser((value, previous = []) => [...previous, value]).default([])).addOption(new Option("--jq [path]", "同 --extract,并支持 [] 映射(如 items[].fields.planNo);裸 --jq 列出结果的顶层键").argParser((value, previous = []) => [...previous, value]).default([])).option("--extract-keys", "列出本次结果 artifact 的顶层键与体积(省掉一次 artifact get --keys)").option("--max-output-bytes <bytes>").option("--request-id <requestId>").option("--timeout-ms <milliseconds>").option("--wait <milliseconds>").option("--dry-run").addHelpText("after", `\n${onboardingHelp()}\n`).action(async (tool, options) => {
|
|
2132
|
+
command.command("call <tool>").option("--env <environment>").option("--environment <environment>").option("--args <json>").option("--args-file <path>").option("--args-list <json-array>", '同一工具跑 N 次(JSON 数组,每条一个 args 对象);逐条执行、默认不中断,最后回 succeeded/failed 汇总。适合批量建字段等').option("--preflight", "与 --args-list 一起用:只跑参数校验 + 写前语义闸(零 wire、零写入),一次报全所有形状问题").option("--keys", "与 artifact get 的 --keys 同义(= --extract-keys):列出本次结果 artifact 的顶层键与体积").addOption(new Option("--extract <path>", "在本次结果的 artifact 内取一个路径的值(可重复;省掉一次 artifact get)").argParser((value, previous = []) => [...previous, value]).default([])).addOption(new Option("--jq [path]", "同 --extract,并支持 [] 映射(如 items[].fields.planNo);裸 --jq 列出结果的顶层键").argParser((value, previous = []) => [...previous, value]).default([])).option("--extract-keys", "列出本次结果 artifact 的顶层键与体积(省掉一次 artifact get --keys)").option("--max-output-bytes <bytes>", "最终 stdout 信封预算;默认 65536(投影/--jq 之后再判);读面 diet(~2048) 独立外置 detailArtifactId,抬高本参数不会把已 diet 的大表重新内联;artifact get 内联上限 8192").option("--request-id <requestId>").option("--timeout-ms <milliseconds>").option("--wait <milliseconds>").option("--dry-run").addHelpText("after", `\n${onboardingHelp()}\n`).action(async (tool, options) => {
|
|
1890
2133
|
const rest = [tool, ...(options.env ? ["--env", options.env] : []), ...(options.environment ? ["--environment", options.environment] : []), ...(options.args !== undefined ? ["--args", options.args] : []), ...(options.argsFile !== undefined ? ["--args-file", options.argsFile] : []), ...(options.argsList !== undefined ? ["--args-list", options.argsList] : []), ...(options.preflight ? ["--preflight"] : []), ...(Array.isArray(options.extract) ? options.extract.flatMap((path) => ["--extract", path]) : []), ...(Array.isArray(options.jq) ? options.jq.flatMap((path) => path === true ? ["--jq"] : ["--jq", path]) : []), ...(options.extractKeys ? ["--extract-keys"] : []),
|
|
1891
2134
|
...(options.keys ? ["--keys"] : []), ...(options.maxOutputBytes !== undefined ? ["--max-output-bytes", options.maxOutputBytes] : []), ...(options.requestId ? ["--request-id", options.requestId] : []), ...(options.timeoutMs !== undefined ? ["--timeout-ms", options.timeoutMs] : []), ...(options.wait !== undefined ? ["--wait", options.wait] : []), ...(options.dryRun ? ["--dry-run"] : [])];
|
|
1892
2135
|
commandExitCode = await cmdCall(new ConfigStore(defaultPaths()), rest);
|
|
@@ -2030,7 +2273,7 @@ async function main() {
|
|
|
2030
2273
|
.option("--code <schemaCode>", "模型 code")
|
|
2031
2274
|
.option("--schema <schemaCode>")
|
|
2032
2275
|
.option("--name <name>", "模型名")
|
|
2033
|
-
.option("--fields <json>", "JSON 数组,每条是一个 field.create 参数;子表加
|
|
2276
|
+
.option("--fields <json>", "JSON 数组,每条是一个 field.create 参数;子表加 subFields")
|
|
2034
2277
|
.option("--fields-json <json>")
|
|
2035
2278
|
.option("--folder <folderId>", "建在哪个应用目录(省略=默认目录)")
|
|
2036
2279
|
.option("--app <applicationCode>")
|
|
@@ -2049,7 +2292,7 @@ async function main() {
|
|
|
2049
2292
|
` 常见错写:DEPARTMENT_MULTI→DEPARTMENT_MULTI_SELECTOR、STAFF_SINGLE→STAFF_SELECTOR、DROP_DOWN_BOX→DROPDOWN_BOX\n\n` +
|
|
2050
2293
|
`choices(RADIO/CHECKBOX/DROPDOWN 系列):**字符串数组**,如 [\"待处理\",\"已完成\"]——不是 [{key,value}]\n` +
|
|
2051
2294
|
`关联字段(WORK_SHEET/MULT_WORK_SHEET):options:{\"schemaCode\":\"<目标模型>\"} + relativeCode:\"<目标模型>\"\n` +
|
|
2052
|
-
`子表:
|
|
2295
|
+
`子表:subFields:[…](每条同字段形状;childFields 仍作别名接受)\n`)
|
|
2053
2296
|
.action(async (options) => {
|
|
2054
2297
|
const rest = ["model-scaffold",
|
|
2055
2298
|
...(options.code ? ["--code", options.code] : []),
|
|
@@ -2107,23 +2350,20 @@ async function main() {
|
|
|
2107
2350
|
...(options.apply ? ["--apply"] : [])];
|
|
2108
2351
|
commandExitCode = await cmdFieldAdd(rest);
|
|
2109
2352
|
});
|
|
2110
|
-
command.command("script [file]").option("--env <environment>", "行内无 env 时的默认环境").option("--continue", "
|
|
2353
|
+
command.command("script [file]").option("--env <environment>", "行内无 env 时的默认环境").option("--continue", "某行失败后继续执行剩余行(默认首败即停)").description("NDJSON 多调脚本:每行 {\"tool\":\"<名称>\",\"args\":{...},\"env\":\"<环境>\",\"dryRun\":true}(# 与 // 注释/空行忽略;其它非法行跳过计入 summary.skipped;file 为 - 读 stdin)。一个进程一次会话串行跑完全部(共用登录,比逐条 omc call 快;测写锁竞争请用多个独立 omc call 进程)。逐行输出 {index,tool,exitCode,result},末行 {summary:{total,ok,failed,skipped,stoppedAt?}}。").action(async (file, options) => {
|
|
2111
2354
|
const { runScript, parseScript, readScriptInput } = await import("./script.js");
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
catch (error) {
|
|
2117
|
-
console.error(String(error instanceof Error ? error.message : error));
|
|
2355
|
+
const parsed = parseScript(readScriptInput(file ?? "-"));
|
|
2356
|
+
const lines = parsed.lines;
|
|
2357
|
+
if (lines.length === 0 && parsed.skipped.length === 0) {
|
|
2358
|
+
console.error("脚本为空:每行一个 {\"tool\":\"<名称>\",\"args\":{...}} 调用(# 与 // 开头为注释)");
|
|
2118
2359
|
commandExitCode = CLI_EXIT_CODES.invalidInput;
|
|
2119
2360
|
return;
|
|
2120
2361
|
}
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
}
|
|
2126
|
-
commandExitCode = await runScript(lines, { defaultEnv: options.env, continueOnError: options.continue === true });
|
|
2362
|
+
commandExitCode = await runScript(lines, {
|
|
2363
|
+
defaultEnv: options.env,
|
|
2364
|
+
continueOnError: options.continue === true,
|
|
2365
|
+
skipped: parsed.skipped,
|
|
2366
|
+
});
|
|
2127
2367
|
// `script` emits NDJSON (one envelope per line), so the single-envelope
|
|
2128
2368
|
// recorder never ran and usage.jsonl left every batch at its initial
|
|
2129
2369
|
// "no-envelope" status — a successful 4-call batch looked like 4 failures
|
|
@@ -2132,7 +2372,7 @@ async function main() {
|
|
|
2132
2372
|
recordUsageOutput({
|
|
2133
2373
|
status: commandExitCode === 0 ? "ok" : "refused",
|
|
2134
2374
|
...(commandExitCode === 0 ? {} : { error: { code: "script-batch-failed" } }),
|
|
2135
|
-
script: { lines: lines.length },
|
|
2375
|
+
script: { lines: lines.length, skipped: parsed.skipped.length },
|
|
2136
2376
|
}, 0);
|
|
2137
2377
|
});
|
|
2138
2378
|
const rollbackCommand = command.command("rollback").description("配置回滚(git-revert 语义):list 列写时间线 / plan 预览恢复(diff+后续写覆盖警告)/ apply 执行恢复——恢复走完整写闸门并记 journal(回滚可再回滚)。接入全部配置写面(workflow/规则/数据规则/视图/表单草稿/在线JS/字典/字段/模型/应用/报表);publish 类生命周期推进不可逆(plan 会说明);journal 与 before 快照为本机边界(不跨机器)");
|
|
@@ -2187,14 +2427,15 @@ async function main() {
|
|
|
2187
2427
|
pureKeys: (options.keys === true || bareJq) && options.summary !== true, ...(typeof options.keys === "string" ? { keys: options.keys } : {}), full: options.full === true }));
|
|
2188
2428
|
});
|
|
2189
2429
|
command.command("bench").option("--live-sample").option("--env <environment>").action(async (options) => { commandExitCode = await cmdBench([...(options.liveSample ? ["--live-sample"] : []), ...(options.env ? ["--env", options.env] : [])]); });
|
|
2190
|
-
|
|
2191
|
-
command.command("
|
|
2192
|
-
|
|
2430
|
+
const jqOption = () => new Option("--jq [path]", "路径投影(与 call --jq 同 DSL;可逗号多路径;裸 --jq 列顶层键)").argParser((value, previous = []) => [...previous, value]).default([]);
|
|
2431
|
+
command.command("commands").option("--json").option("--compact").option("--filter <keyword>").option("--lane <id>").option("--max-output-bytes <bytes>").addOption(jqOption()).action(async (options) => { commandExitCode = await withTransport(options, request, (transport) => cmdDiscovery("commands", transport, undefined, { json: options.json === true, compact: options.compact === true, ...(options.filter ? { filter: options.filter } : {}), ...(options.lane ? { lane: options.lane } : {}), ...(options.jq !== undefined ? { jq: options.jq } : {}) })); });
|
|
2432
|
+
command.command("describe <tool>").option("--full").option("--cache-ok", "允许 --full 在同会话重复读时短路为 artifact 指针(默认每次回全文)").option("--schema-only").option("--param <name>").option("--example [id]").option("--max-output-bytes <bytes>").addOption(jqOption()).action(async (tool, options) => {
|
|
2433
|
+
commandExitCode = await withTransport(options, request, (transport) => cmdDiscovery("describe", transport, tool, { full: options.full === true, cacheOk: options.cacheOk === true, schemaOnly: options.schemaOnly === true, ...(options.param ? { param: options.param } : {}), ...(options.example ? { example: options.example } : {}), ...(options.jq !== undefined ? { jq: options.jq } : {}) }));
|
|
2193
2434
|
});
|
|
2194
|
-
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 } : {}) })); });
|
|
2435
|
+
command.command("contract <lane>").option("--full").option("--tool <name>").option("--max-output-bytes <bytes>").addOption(jqOption()).action(async (lane, options) => { commandExitCode = await withTransport(options, request, (transport) => cmdDiscovery("contract", transport, lane, { full: options.full === true, ...(options.tool ? { tool: options.tool } : {}), ...(options.jq !== undefined ? { jq: options.jq } : {}) })); });
|
|
2195
2436
|
// 2026-11-05 用户实测:`commands --json` 存在,于是大家自然写 `capabilities --json`
|
|
2196
2437
|
// ——旧实现报 `unknown option`(退出码 10)。输出本来就是 JSON 信封,接受它做兼容别名。
|
|
2197
|
-
command.command("capabilities").option("--compact").option("--json", "兼容别名:输出本就是 JSON 信封(与 commands --json 对齐)").option("--max-output-bytes <bytes>").action(async (options) => { commandExitCode = await withTransport(options, request, (transport) => cmdCapabilities(transport, options.compact === true)); });
|
|
2438
|
+
command.command("capabilities").option("--compact").option("--json", "兼容别名:输出本就是 JSON 信封(与 commands --json 对齐)").option("--max-output-bytes <bytes>").addOption(jqOption()).action(async (options) => { commandExitCode = await withTransport(options, request, (transport) => cmdCapabilities(transport, options.compact === true, options.jq)); });
|
|
2198
2439
|
// `--env` is accepted but ignored: `next` is a zero-environment-contact route
|
|
2199
2440
|
// solver, and a caller who reflexively passes `--env` should not be refused
|
|
2200
2441
|
// (round 15 dogfood: `next "<goal>" --env local` → unknown option, one round).
|
|
@@ -2208,8 +2449,8 @@ async function main() {
|
|
|
2208
2449
|
...(Number.isFinite(parsedTop) && parsedTop > 0 ? { top: parsedTop } : {}),
|
|
2209
2450
|
}));
|
|
2210
2451
|
});
|
|
2211
|
-
command.command("capability <id>").option("--executable").option("--max-output-bytes <bytes>").action(async (id, options) => {
|
|
2212
|
-
commandExitCode = await withTransport(options, request, (transport) => cmdCapability(id, transport, options.executable === true));
|
|
2452
|
+
command.command("capability <id>").option("--executable").option("--max-output-bytes <bytes>").addOption(jqOption()).action(async (id, options) => {
|
|
2453
|
+
commandExitCode = await withTransport(options, request, (transport) => cmdCapability(id, transport, options.executable === true, options.jq));
|
|
2213
2454
|
});
|
|
2214
2455
|
const intentCommand = command.command("intent");
|
|
2215
2456
|
intentCommand.command("schema").option("--example [id]").action((options) => { commandExitCode = cmdIntentSchema({ request, maxOutputBytes: CLI_DEFAULT_OUTPUT_BYTES }, typeof options.example === "string" ? options.example : undefined, options.example !== undefined); });
|