@stalfh233/omc-cli 0.6.12 → 0.6.13
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 +279 -67
- package/dist/describe-cache.js +8 -4
- 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 +19 -1
- 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 +2 -1
- package/dist/lanes/field.js +707 -59
- package/dist/lanes/form.js +52 -6
- package/dist/lanes/list-view-contract.js +2 -2
- package/dist/lanes/list-view.js +98 -2
- 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 +168 -31
- package/dist/read-shapes.js +31 -11
- package/dist/recipes/field.js +16 -3
- package/dist/recipes/form.js +2 -1
- 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
|
}
|
|
@@ -1053,6 +1108,76 @@ async function cmdBench(args) {
|
|
|
1053
1108
|
const dims = report.dimensions;
|
|
1054
1109
|
return failedSteps === 0 && dims.intent.passed === dims.intent.cases ? 0 : 1;
|
|
1055
1110
|
}
|
|
1111
|
+
/** r29f F8:discovery 面复用同一投影层(相对本命令 result,不是 call 信封)。 */
|
|
1112
|
+
function emitProjectedResult(result, transport, jq) {
|
|
1113
|
+
const raw = Array.isArray(jq) ? jq : jq === undefined ? [] : [jq];
|
|
1114
|
+
const keysOnly = raw.some((entry) => entry === true) || (raw.length === 0 && jq === true);
|
|
1115
|
+
const paths = raw
|
|
1116
|
+
.filter((entry) => typeof entry === "string")
|
|
1117
|
+
.flatMap((path) => path.split(","))
|
|
1118
|
+
.map((path) => path.trim())
|
|
1119
|
+
.filter(Boolean);
|
|
1120
|
+
if (paths.length === 0 && !keysOnly)
|
|
1121
|
+
return emitTransportResult(result, transport);
|
|
1122
|
+
const projected = projectResultExtract(result, paths, keysOnly);
|
|
1123
|
+
return emitTransportResult(projected ?? result, transport);
|
|
1124
|
+
}
|
|
1125
|
+
function enrichDescribeParamMiss(registry, toolName, param, miss) {
|
|
1126
|
+
// r31b F1: a miss must teach the next move — closest name, or "that param
|
|
1127
|
+
// lives on another tool" — instead of dumping the whole available table.
|
|
1128
|
+
const available = Array.isArray(miss.available) ? miss.available.map((entry) => String(entry)) : [];
|
|
1129
|
+
const didYouMean = typeof miss.didYouMean === "string" ? miss.didYouMean : undefined;
|
|
1130
|
+
const family = toolName.includes(".") ? toolName.split(".")[0] : toolName;
|
|
1131
|
+
// Known cross-tool identity pairs first (新建 vs 更新), then same-family,
|
|
1132
|
+
// then any other tool that declares the param.
|
|
1133
|
+
const preferredPair = toolName === "data.save" && param === "bizObjectId"
|
|
1134
|
+
? "data.update"
|
|
1135
|
+
: toolName === "data.update" && param === "formData"
|
|
1136
|
+
? "data.save"
|
|
1137
|
+
: undefined;
|
|
1138
|
+
const otherTools = registry
|
|
1139
|
+
.filter((tool) => tool.name !== toolName && tool.inputSchema && Object.prototype.hasOwnProperty.call(tool.inputSchema, param))
|
|
1140
|
+
.map((tool) => tool.name)
|
|
1141
|
+
.sort((left, right) => {
|
|
1142
|
+
const rank = (name) => {
|
|
1143
|
+
if (preferredPair !== undefined && name === preferredPair)
|
|
1144
|
+
return 0;
|
|
1145
|
+
if (name.startsWith(`${family}.`))
|
|
1146
|
+
return 1;
|
|
1147
|
+
return 2;
|
|
1148
|
+
};
|
|
1149
|
+
return rank(left) - rank(right) || left.localeCompare(right);
|
|
1150
|
+
});
|
|
1151
|
+
const otherTool = preferredPair && otherTools.includes(preferredPair) ? preferredPair : otherTools[0];
|
|
1152
|
+
// Known cross-tool identity guidance for the data lane (新建 vs 更新).
|
|
1153
|
+
const dataIdentityHint = toolName === "data.save" && param === "bizObjectId"
|
|
1154
|
+
? "新建用 data.save(schemaCode+formData);更新用 data.update + bizObjectId"
|
|
1155
|
+
: undefined;
|
|
1156
|
+
const cappedAvailable = available.slice(0, 8);
|
|
1157
|
+
const hintParts = [
|
|
1158
|
+
didYouMean !== undefined ? `本工具最接近的参数是 ${didYouMean}` : undefined,
|
|
1159
|
+
otherTool !== undefined ? `参数 "${param}" 属于 ${otherTool}${otherTools.length > 1 ? `(同名还在 ${otherTools.slice(1, 3).join("、")})` : ""}` : undefined,
|
|
1160
|
+
dataIdentityHint,
|
|
1161
|
+
].filter((part) => typeof part === "string" && part.length > 0);
|
|
1162
|
+
return {
|
|
1163
|
+
...miss,
|
|
1164
|
+
...(didYouMean !== undefined ? { didYouMean, nextCommand: `omc describe ${toolName} --param ${didYouMean}` } : {}),
|
|
1165
|
+
...(otherTool !== undefined
|
|
1166
|
+
? {
|
|
1167
|
+
otherTool,
|
|
1168
|
+
otherTools: otherTools.slice(0, 5),
|
|
1169
|
+
nextCommand: `omc describe ${otherTool} --param ${param}`,
|
|
1170
|
+
}
|
|
1171
|
+
: {}),
|
|
1172
|
+
...(hintParts.length > 0 ? { hint: hintParts.join(";") } : {}),
|
|
1173
|
+
...(dataIdentityHint !== undefined ? { guidance: dataIdentityHint } : {}),
|
|
1174
|
+
available: cappedAvailable,
|
|
1175
|
+
...(available.length > cappedAvailable.length ? { availableTruncated: available.length - cappedAvailable.length } : {}),
|
|
1176
|
+
message: hintParts.length > 0
|
|
1177
|
+
? `本工具没有参数 "${param}"——${hintParts.join(";")}`
|
|
1178
|
+
: `本工具没有参数 "${param}"——available 是本工具的合法参数(已截断)`,
|
|
1179
|
+
};
|
|
1180
|
+
}
|
|
1056
1181
|
function cmdDiscovery(kind, transport, value, options = {}) {
|
|
1057
1182
|
const registry = buildToolRegistry();
|
|
1058
1183
|
let result;
|
|
@@ -1079,7 +1204,7 @@ function cmdDiscovery(kind, transport, value, options = {}) {
|
|
|
1079
1204
|
// agent paid 4×54 KB on the default face before any hint could land).
|
|
1080
1205
|
if (options.json !== true && options.compact !== true && !scoped) {
|
|
1081
1206
|
result = commandIndex(registry);
|
|
1082
|
-
return
|
|
1207
|
+
return emitProjectedResult({ status: "ok", ...result }, transport, options.jq);
|
|
1083
1208
|
}
|
|
1084
1209
|
const inventory = commandInventory(registry, { compact: options.compact === true });
|
|
1085
1210
|
// r19 ticket 05: above this many hits a filter word is "broad" — the filtered
|
|
@@ -1170,16 +1295,28 @@ function cmdDiscovery(kind, transport, value, options = {}) {
|
|
|
1170
1295
|
}
|
|
1171
1296
|
else if (options.schemaOnly)
|
|
1172
1297
|
result = describeSchemaOnly(registry, value ?? "");
|
|
1173
|
-
else if (options.param)
|
|
1174
|
-
|
|
1298
|
+
else if (options.param) {
|
|
1299
|
+
const missOrHit = describeParam(registry, value ?? "", options.param);
|
|
1300
|
+
// r31b F1: enrich a param miss with otherTool / capped available / guidance.
|
|
1301
|
+
result =
|
|
1302
|
+
missOrHit && missOrHit.found === false
|
|
1303
|
+
? enrichDescribeParamMiss(registry, value ?? "", options.param, missOrHit)
|
|
1304
|
+
: missOrHit;
|
|
1305
|
+
}
|
|
1175
1306
|
else if (options.example !== undefined)
|
|
1176
1307
|
result = describeExample(registry, value ?? "", typeof options.example === "string" ? options.example : undefined);
|
|
1177
1308
|
else if (options.full) {
|
|
1178
1309
|
const full = describeToolFull(registry, value ?? "");
|
|
1179
|
-
//
|
|
1180
|
-
// caller
|
|
1181
|
-
|
|
1182
|
-
|
|
1310
|
+
// r31b F3: `--full` means "give me the body". Short-circuit only when the
|
|
1311
|
+
// caller opts in with `--cache-ok` (legacy r9 repeat-read suppression).
|
|
1312
|
+
if (!full)
|
|
1313
|
+
result = full;
|
|
1314
|
+
else if (options.cacheOk)
|
|
1315
|
+
result = repeatFullDescribe(value ?? "", full) ?? full;
|
|
1316
|
+
else {
|
|
1317
|
+
repeatFullDescribe(value ?? "", full, { recordOnly: true });
|
|
1318
|
+
result = full;
|
|
1319
|
+
}
|
|
1183
1320
|
}
|
|
1184
1321
|
else {
|
|
1185
1322
|
result = describeTool(registry, value ?? "");
|
|
@@ -1236,7 +1373,7 @@ function cmdDiscovery(kind, transport, value, options = {}) {
|
|
|
1236
1373
|
const textNote = byText.length > 0 ? `;didYouMeanByText: ${byText.join("、")}` : "";
|
|
1237
1374
|
return emitInvalid(transport, { message: `${kind} target not found: "${requested}"${shown.length > 0 ? `; did you mean: ${shown.join("、")}` : ""}${textNote}${absence}${approvalPointer}${intentPointer}${formPointer}${pointer}` });
|
|
1238
1375
|
}
|
|
1239
|
-
return
|
|
1376
|
+
return emitProjectedResult({ status: "ok", ...result }, transport, options.jq);
|
|
1240
1377
|
}
|
|
1241
1378
|
function cmdUsage(transport, options = {}) {
|
|
1242
1379
|
return emitTransportResult(usageReport(undefined, { records: options.records === true, top: options.top, resolveLikeWriter: true }), transport);
|
|
@@ -1254,11 +1391,11 @@ function cmdNext(goal, transport) {
|
|
|
1254
1391
|
* capability overview. Read-only, zero environment contact; a projection of
|
|
1255
1392
|
* the tool registry, the coverage manifest, and the intent capability registry.
|
|
1256
1393
|
*/
|
|
1257
|
-
function cmdCapabilities(transport, compact = false) {
|
|
1394
|
+
function cmdCapabilities(transport, compact = false, jq) {
|
|
1258
1395
|
const result = compactCapabilities(buildToolRegistry(), { compact });
|
|
1259
|
-
return
|
|
1396
|
+
return emitProjectedResult({ status: "ok", ...result }, transport, jq);
|
|
1260
1397
|
}
|
|
1261
|
-
function cmdCapability(id, transport, executable = false) {
|
|
1398
|
+
function cmdCapability(id, transport, executable = false, jq) {
|
|
1262
1399
|
const registry = buildToolRegistry();
|
|
1263
1400
|
const resolved = resolveCapabilityId(id);
|
|
1264
1401
|
if (!resolved) {
|
|
@@ -1269,9 +1406,64 @@ function cmdCapability(id, transport, executable = false) {
|
|
|
1269
1406
|
const bundle = executableCapabilityBundle(registry, resolved);
|
|
1270
1407
|
if (!bundle)
|
|
1271
1408
|
return emitInvalid(transport, { message: `capability "${id}" 没有可执行 bundle` });
|
|
1272
|
-
return
|
|
1273
|
-
}
|
|
1274
|
-
|
|
1409
|
+
return emitProjectedResult({ status: "ok", capability: bundle }, transport, jq);
|
|
1410
|
+
}
|
|
1411
|
+
const detail = capabilityDetail(registry, resolved);
|
|
1412
|
+
if (!detail)
|
|
1413
|
+
return emitInvalid(transport, { message: `capability "${id}" 无详情` });
|
|
1414
|
+
// r29a F4:app/folder/model/rule 面附应用参数名矩阵(主/别名),一次看完。
|
|
1415
|
+
const appScopeMatrix = buildAppScopeMatrix(registry, resolved);
|
|
1416
|
+
return emitProjectedResult({
|
|
1417
|
+
status: "ok",
|
|
1418
|
+
capability: {
|
|
1419
|
+
...detail,
|
|
1420
|
+
...(appScopeMatrix !== undefined ? { appScopeArgs: appScopeMatrix } : {}),
|
|
1421
|
+
},
|
|
1422
|
+
}, transport, jq);
|
|
1423
|
+
}
|
|
1424
|
+
/**
|
|
1425
|
+
* r29a F4:跨 app/folder/model/rule 的「工具 → 应用参数名(主/别名)」小表。
|
|
1426
|
+
* 只在这四个能力面上挂,避免每条 capability 都膨胀。
|
|
1427
|
+
*/
|
|
1428
|
+
function buildAppScopeMatrix(registry, capabilityId) {
|
|
1429
|
+
const faces = new Set(["app", "folder", "model", "businessrule", "rule"]);
|
|
1430
|
+
if (!faces.has(capabilityId))
|
|
1431
|
+
return undefined;
|
|
1432
|
+
const laneByCapability = {
|
|
1433
|
+
app: "app-management",
|
|
1434
|
+
folder: "app-tree-folders",
|
|
1435
|
+
model: "model-management",
|
|
1436
|
+
businessrule: "B1-businessrule",
|
|
1437
|
+
rule: "B1-businessrule",
|
|
1438
|
+
};
|
|
1439
|
+
const lane = laneByCapability[capabilityId];
|
|
1440
|
+
const rows = registry
|
|
1441
|
+
.filter((tool) => tool.lane === lane || ((capabilityId === "rule" || capabilityId === "businessrule") && tool.name.startsWith("rule.")))
|
|
1442
|
+
.map((tool) => {
|
|
1443
|
+
const keys = Object.keys(tool.inputSchema);
|
|
1444
|
+
const primary = keys.includes("applicationCode") ? "applicationCode" : keys.includes("appCode") ? "appCode" : null;
|
|
1445
|
+
const aliases = keys.includes("applicationCode") && keys.includes("appCode")
|
|
1446
|
+
? ["applicationCode", "appCode"]
|
|
1447
|
+
: primary === "applicationCode"
|
|
1448
|
+
? ["applicationCode"]
|
|
1449
|
+
: primary === "appCode"
|
|
1450
|
+
? ["appCode"]
|
|
1451
|
+
: [];
|
|
1452
|
+
return {
|
|
1453
|
+
tool: tool.name,
|
|
1454
|
+
primary,
|
|
1455
|
+
aliases,
|
|
1456
|
+
note: primary === null ? "无应用范围参数(按 schemaCode/自身键定位)" : aliases.length > 1 ? "双别名;冲突报 app-code-conflict" : undefined,
|
|
1457
|
+
};
|
|
1458
|
+
});
|
|
1459
|
+
if (rows.length === 0)
|
|
1460
|
+
return undefined;
|
|
1461
|
+
return {
|
|
1462
|
+
face: capabilityId,
|
|
1463
|
+
rows,
|
|
1464
|
+
relatedTopics: ["app-scope"],
|
|
1465
|
+
hint: "完整矩阵也见 `omc help app-scope`",
|
|
1466
|
+
};
|
|
1275
1467
|
}
|
|
1276
1468
|
/**
|
|
1277
1469
|
* Read + validate an intent file, or emit the shared invalid-input envelope.
|
|
@@ -1621,14 +1813,14 @@ function cmdHelpSearch(topic, query, transport, searchMode, limit) {
|
|
|
1621
1813
|
: "")
|
|
1622
1814
|
: "";
|
|
1623
1815
|
if (topic === undefined) {
|
|
1624
|
-
console.error("用法:omc help <topic> | omc help --search \"<词>\"(跨 topic 检索,命中节正文随结果返回)");
|
|
1816
|
+
console.error("用法:omc help <topic> | omc help --topic <name> | omc help --search \"<词>\"(跨 topic 检索,命中节正文随结果返回)");
|
|
1625
1817
|
console.error("权威路线:omc help omc-start-here(随包 skills/,不是分发镜像)");
|
|
1626
1818
|
console.error("环境就绪:omc call discover_environment --env <env> --args '{}'");
|
|
1627
1819
|
console.error(`命令:${commands.join("、")}`);
|
|
1628
1820
|
console.error(`方法论 topic:${topics.map((entry) => entry.name).join("、")}`);
|
|
1629
1821
|
return emitTransportResult({
|
|
1630
1822
|
status: "ok",
|
|
1631
|
-
usage: "omc help <topic>",
|
|
1823
|
+
usage: "omc help <topic> | omc help --topic <name>",
|
|
1632
1824
|
discovery: DISCOVERY_AUTHORITY,
|
|
1633
1825
|
commands,
|
|
1634
1826
|
topics,
|
|
@@ -1738,14 +1930,16 @@ function onboardingHelp() {
|
|
|
1738
1930
|
return [
|
|
1739
1931
|
"首次使用、或不确定从哪开始:omc help omc-start-here(随包权威 topic 路线);被拒或状态异常:omc help omc-refusals。",
|
|
1740
1932
|
"中文可读输出:任何命令加 --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
|
|
1933
|
+
"方法论按需读:omc help <topic>(或 omc help --topic <name>)默认只回节索引(每节字节数);用 --section \"<标题>\" 取正文(可重复),确需整篇才 --all。找具体事实(错误码/字段名/命令名)不要整篇 dump:omc help --search \"<词>\"(跨 topic,命中节正文随结果返回)。",
|
|
1742
1934
|
"目标→工具一步定位:omc next \"<你要做什么>\"(离线、只读,给能力/说明/首批命令;不确定会明说并回退到 omc-requirement-analysis)。",
|
|
1743
|
-
|
|
1935
|
+
// r30f F20:写清两道门——读面 diet(~2048) 与 stdout 预算(--max-output-bytes,默认 64KiB) 不是一回事。
|
|
1936
|
+
"输出预算:`--max-output-bytes` 默认 65536(64KiB;超则回 artifact 指针 + sha256),只约束最终 stdout 信封(投影/--jq 之后);`artifact get` 内联上限 8192。读面另有独立 diet 门(~2048)会先外置 detailArtifactId——抬高 --max-output-bytes 不会把已 diet 的大表重新内联。",
|
|
1937
|
+
"大结果取值一步到位:读面回 detailArtifactId 时,用 omc call <tool> … --jq <path>(可重复;`[]` 映射数组,如 items[].fields.planNo)或 --extract/--extract-keys,省掉再跑一次 omc artifact get。commands/capabilities/describe 也支持 --jq。批量形状预检用 --args-list … --preflight(零 wire、零写入,一次报全)。",
|
|
1744
1938
|
// r25 真机(2026-11-04):探针连跑同一读面 2–3 次想验证「第二次是否 unchanged」,
|
|
1745
1939
|
// 结果三次都是全量,还去 describe 找 dedupe 参数(多花 3 轮)。读面确实不做跨调用
|
|
1746
1940
|
// 结果去重——**明说**比留白强,省掉一次探索。
|
|
1747
1941
|
"读面不做跨调用结果去重:同一读面同参数连跑 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
|
|
1942
|
+
"工具发现:omc commands --json(含 capability id/lane/recipes;加 --compact 只回工具行、无目录散文)· omc capabilities(能力 id/lane/aliases/tools/依赖顺序)· omc capability <id> --executable(单能力完整执行包)。应用参数矩阵:omc help app-scope。",
|
|
1749
1943
|
"单工具/契约:omc describe <tool>(schema+recipe+能力路由)· omc contract <lane>(wire 契约+能力路由);环境就绪:omc call discover_environment --env <env> --args '{}'。",
|
|
1750
1944
|
"写入两道门:对话层草稿确认(omc help omc-config-draft-review)+ 环境 writable 开关;生产环境每笔写需在 args 里带 confirmProduction:true。",
|
|
1751
1945
|
].join("\n");
|
|
@@ -1819,6 +2013,22 @@ async function main() {
|
|
|
1819
2013
|
process.stdout.write(`${ownPackageVersion()}\n`);
|
|
1820
2014
|
return 0;
|
|
1821
2015
|
}
|
|
2016
|
+
// r29f F3:commander 在 action 前就吞掉 `--help`。`omc call <tool> --help`
|
|
2017
|
+
// 必须在这里短路转发 describe,否则永远只见通用 call 选项。
|
|
2018
|
+
if (bare[0] === "call" && bare[1] && bare[1] !== "help" && !bare[1].startsWith("-") && (bare.includes("--help") || bare.includes("-h"))) {
|
|
2019
|
+
const toolName = bare[1];
|
|
2020
|
+
const registry = buildToolRegistry();
|
|
2021
|
+
const described = describeTool(registry, toolName);
|
|
2022
|
+
console.error(`工具:${toolName}`);
|
|
2023
|
+
console.error(`输出预算:--max-output-bytes 默认 2048(超则回 artifact 指针 + sha256);artifact get 内联上限 8192`);
|
|
2024
|
+
console.error(`投影:--jq/--extract 相对 result(不要写 result. 前缀;误写会自动剥并标 autoStripped)`);
|
|
2025
|
+
if (described) {
|
|
2026
|
+
writeEnvelope(envelope("ok", { ...described, helpNote: "来自 omc describe;完整面用 omc describe <tool> [--full|--param|--example]" }, request));
|
|
2027
|
+
return CLI_EXIT_CODES.ok;
|
|
2028
|
+
}
|
|
2029
|
+
writeEnvelope(envelope("error", { tool: toolName }, request, { code: "invalid-input", message: `未知工具 ${toolName};用 omc commands --json --compact 查名单`, retryable: false }));
|
|
2030
|
+
return CLI_EXIT_CODES.invalidInput;
|
|
2031
|
+
}
|
|
1822
2032
|
command.addHelpText("after", `\n${onboardingHelp()}\n`);
|
|
1823
2033
|
// No command at all: guide the agent (help text + onboarding), exit 0. This
|
|
1824
2034
|
// is a benign "show me the surface" invocation, not a failure.
|
|
@@ -1826,7 +2036,11 @@ async function main() {
|
|
|
1826
2036
|
command.outputHelp();
|
|
1827
2037
|
return CLI_EXIT_CODES.ok;
|
|
1828
2038
|
}
|
|
1829
|
-
|
|
2039
|
+
// r30f F15:topic 既是位置参数也接受 `--topic <name>` 别名(肌肉记忆对齐 capability/contract)。
|
|
2040
|
+
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) => {
|
|
2041
|
+
const resolvedTopic = typeof options.topic === "string" && options.topic.trim() !== "" ? options.topic.trim() : topic;
|
|
2042
|
+
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));
|
|
2043
|
+
});
|
|
1830
2044
|
command.command("setup").option("--list").option("--edit <environment>").option("--remove <environment>").action(async (options) => {
|
|
1831
2045
|
// r25 真机(2026-11-04):setup 不在 ownsStdout 里,于是非 TTY 拒绝时主流程会补一个
|
|
1832
2046
|
// `error.code=internal` 的信封——明明是可预期的用法错误(stdin 不是终端),却被包成
|
|
@@ -1886,7 +2100,7 @@ async function main() {
|
|
|
1886
2100
|
commandExitCode = CLI_EXIT_CODES.ok;
|
|
1887
2101
|
});
|
|
1888
2102
|
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) => {
|
|
2103
|
+
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
2104
|
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
2105
|
...(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
2106
|
commandExitCode = await cmdCall(new ConfigStore(defaultPaths()), rest);
|
|
@@ -2030,7 +2244,7 @@ async function main() {
|
|
|
2030
2244
|
.option("--code <schemaCode>", "模型 code")
|
|
2031
2245
|
.option("--schema <schemaCode>")
|
|
2032
2246
|
.option("--name <name>", "模型名")
|
|
2033
|
-
.option("--fields <json>", "JSON 数组,每条是一个 field.create 参数;子表加
|
|
2247
|
+
.option("--fields <json>", "JSON 数组,每条是一个 field.create 参数;子表加 subFields")
|
|
2034
2248
|
.option("--fields-json <json>")
|
|
2035
2249
|
.option("--folder <folderId>", "建在哪个应用目录(省略=默认目录)")
|
|
2036
2250
|
.option("--app <applicationCode>")
|
|
@@ -2049,7 +2263,7 @@ async function main() {
|
|
|
2049
2263
|
` 常见错写:DEPARTMENT_MULTI→DEPARTMENT_MULTI_SELECTOR、STAFF_SINGLE→STAFF_SELECTOR、DROP_DOWN_BOX→DROPDOWN_BOX\n\n` +
|
|
2050
2264
|
`choices(RADIO/CHECKBOX/DROPDOWN 系列):**字符串数组**,如 [\"待处理\",\"已完成\"]——不是 [{key,value}]\n` +
|
|
2051
2265
|
`关联字段(WORK_SHEET/MULT_WORK_SHEET):options:{\"schemaCode\":\"<目标模型>\"} + relativeCode:\"<目标模型>\"\n` +
|
|
2052
|
-
`子表:
|
|
2266
|
+
`子表:subFields:[…](每条同字段形状;childFields 仍作别名接受)\n`)
|
|
2053
2267
|
.action(async (options) => {
|
|
2054
2268
|
const rest = ["model-scaffold",
|
|
2055
2269
|
...(options.code ? ["--code", options.code] : []),
|
|
@@ -2107,23 +2321,20 @@ async function main() {
|
|
|
2107
2321
|
...(options.apply ? ["--apply"] : [])];
|
|
2108
2322
|
commandExitCode = await cmdFieldAdd(rest);
|
|
2109
2323
|
});
|
|
2110
|
-
command.command("script [file]").option("--env <environment>", "行内无 env 时的默认环境").option("--continue", "
|
|
2324
|
+
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
2325
|
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));
|
|
2118
|
-
commandExitCode = CLI_EXIT_CODES.invalidInput;
|
|
2119
|
-
return;
|
|
2120
|
-
}
|
|
2121
|
-
if (lines.length === 0) {
|
|
2122
|
-
console.error("脚本为空:每行一个 {\"tool\":\"<名称>\",\"args\":{...}} 调用");
|
|
2326
|
+
const parsed = parseScript(readScriptInput(file ?? "-"));
|
|
2327
|
+
const lines = parsed.lines;
|
|
2328
|
+
if (lines.length === 0 && parsed.skipped.length === 0) {
|
|
2329
|
+
console.error("脚本为空:每行一个 {\"tool\":\"<名称>\",\"args\":{...}} 调用(# 与 // 开头为注释)");
|
|
2123
2330
|
commandExitCode = CLI_EXIT_CODES.invalidInput;
|
|
2124
2331
|
return;
|
|
2125
2332
|
}
|
|
2126
|
-
commandExitCode = await runScript(lines, {
|
|
2333
|
+
commandExitCode = await runScript(lines, {
|
|
2334
|
+
defaultEnv: options.env,
|
|
2335
|
+
continueOnError: options.continue === true,
|
|
2336
|
+
skipped: parsed.skipped,
|
|
2337
|
+
});
|
|
2127
2338
|
// `script` emits NDJSON (one envelope per line), so the single-envelope
|
|
2128
2339
|
// recorder never ran and usage.jsonl left every batch at its initial
|
|
2129
2340
|
// "no-envelope" status — a successful 4-call batch looked like 4 failures
|
|
@@ -2132,7 +2343,7 @@ async function main() {
|
|
|
2132
2343
|
recordUsageOutput({
|
|
2133
2344
|
status: commandExitCode === 0 ? "ok" : "refused",
|
|
2134
2345
|
...(commandExitCode === 0 ? {} : { error: { code: "script-batch-failed" } }),
|
|
2135
|
-
script: { lines: lines.length },
|
|
2346
|
+
script: { lines: lines.length, skipped: parsed.skipped.length },
|
|
2136
2347
|
}, 0);
|
|
2137
2348
|
});
|
|
2138
2349
|
const rollbackCommand = command.command("rollback").description("配置回滚(git-revert 语义):list 列写时间线 / plan 预览恢复(diff+后续写覆盖警告)/ apply 执行恢复——恢复走完整写闸门并记 journal(回滚可再回滚)。接入全部配置写面(workflow/规则/数据规则/视图/表单草稿/在线JS/字典/字段/模型/应用/报表);publish 类生命周期推进不可逆(plan 会说明);journal 与 before 快照为本机边界(不跨机器)");
|
|
@@ -2187,14 +2398,15 @@ async function main() {
|
|
|
2187
2398
|
pureKeys: (options.keys === true || bareJq) && options.summary !== true, ...(typeof options.keys === "string" ? { keys: options.keys } : {}), full: options.full === true }));
|
|
2188
2399
|
});
|
|
2189
2400
|
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
|
-
|
|
2401
|
+
const jqOption = () => new Option("--jq [path]", "路径投影(与 call --jq 同 DSL;可逗号多路径;裸 --jq 列顶层键)").argParser((value, previous = []) => [...previous, value]).default([]);
|
|
2402
|
+
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 } : {}) })); });
|
|
2403
|
+
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) => {
|
|
2404
|
+
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
2405
|
});
|
|
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 } : {}) })); });
|
|
2406
|
+
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
2407
|
// 2026-11-05 用户实测:`commands --json` 存在,于是大家自然写 `capabilities --json`
|
|
2196
2408
|
// ——旧实现报 `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)); });
|
|
2409
|
+
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
2410
|
// `--env` is accepted but ignored: `next` is a zero-environment-contact route
|
|
2199
2411
|
// solver, and a caller who reflexively passes `--env` should not be refused
|
|
2200
2412
|
// (round 15 dogfood: `next "<goal>" --env local` → unknown option, one round).
|
|
@@ -2208,8 +2420,8 @@ async function main() {
|
|
|
2208
2420
|
...(Number.isFinite(parsedTop) && parsedTop > 0 ? { top: parsedTop } : {}),
|
|
2209
2421
|
}));
|
|
2210
2422
|
});
|
|
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));
|
|
2423
|
+
command.command("capability <id>").option("--executable").option("--max-output-bytes <bytes>").addOption(jqOption()).action(async (id, options) => {
|
|
2424
|
+
commandExitCode = await withTransport(options, request, (transport) => cmdCapability(id, transport, options.executable === true, options.jq));
|
|
2213
2425
|
});
|
|
2214
2426
|
const intentCommand = command.command("intent");
|
|
2215
2427
|
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); });
|
package/dist/describe-cache.js
CHANGED
|
@@ -51,9 +51,13 @@ function writeCache(cache) {
|
|
|
51
51
|
/**
|
|
52
52
|
* Return the compact repeat-read face for `tool`, or `undefined` when this is
|
|
53
53
|
* the first read (or the contract changed). Records the new entry as a side
|
|
54
|
-
* effect so the NEXT read
|
|
54
|
+
* effect so the NEXT `--cache-ok` read can be compact.
|
|
55
|
+
*
|
|
56
|
+
* r31b F3: `--full` alone always wants the body. Pass `{ recordOnly:true }` to
|
|
57
|
+
* refresh the cache without short-circuiting; pass nothing / `{cacheOk:true}`
|
|
58
|
+
* (via CLI `--full --cache-ok`) to allow the compact face.
|
|
55
59
|
*/
|
|
56
|
-
export function repeatFullDescribe(tool, result) {
|
|
60
|
+
export function repeatFullDescribe(tool, result, options = {}) {
|
|
57
61
|
// No project session (repo tests, golden master, one-off runs) ⇒ no cache:
|
|
58
62
|
// those callers need byte-stable output, and a cache write would be litter.
|
|
59
63
|
if (!projectSessionActive())
|
|
@@ -63,7 +67,7 @@ export function repeatFullDescribe(tool, result) {
|
|
|
63
67
|
const digest = createHash("sha256").update(body).digest("hex");
|
|
64
68
|
const cache = readCache();
|
|
65
69
|
const previous = cache[tool];
|
|
66
|
-
if (previous && previous.sha256 === digest && Date.now() - previous.at < CACHE_TTL_MS) {
|
|
70
|
+
if (!options.recordOnly && previous && previous.sha256 === digest && Date.now() - previous.at < CACHE_TTL_MS) {
|
|
67
71
|
// A pruned artifact would turn the summary into a dead end; re-ship instead.
|
|
68
72
|
if (existsSync(join(artifactRoot(), `${previous.artifactId}.artifact`))) {
|
|
69
73
|
try {
|
|
@@ -75,7 +79,7 @@ export function repeatFullDescribe(tool, result) {
|
|
|
75
79
|
sha256: digest,
|
|
76
80
|
bytes,
|
|
77
81
|
fetch: `omc artifact get ${previous.artifactId} --summary(再 --keys/--jq 取需要的键)`,
|
|
78
|
-
note: `本项目的会话里已读过同一个契约(${bytes}B,内容 sha256
|
|
82
|
+
note: `本项目的会话里已读过同一个契约(${bytes}B,内容 sha256 未变),本次因 --cache-ok 不再重发全文;要全文请去掉 --cache-ok 再 --full`,
|
|
79
83
|
};
|
|
80
84
|
}
|
|
81
85
|
catch {
|