@stalfh233/omc-cli 0.4.18 → 0.4.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +18 -0
- package/dist/lanes/b2-registration.js +25 -13
- package/dist/lanes/businessrule-contract.js +1 -1
- package/dist/lanes/businessrule.js +21 -11
- package/dist/lanes/field-contract.js +1 -1
- package/dist/lanes/field.js +51 -12
- package/dist/lanes/model.js +13 -6
- package/dist/script.js +65 -0
- package/dist/support.js +22 -0
- package/package.json +2 -2
- package/skills/omc-start-here.md +1 -0
package/dist/cli.js
CHANGED
|
@@ -1375,6 +1375,24 @@ async function main() {
|
|
|
1375
1375
|
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.maxOutputBytes !== undefined ? ["--max-output-bytes", options.maxOutputBytes] : []), ...(options.requestId ? ["--request-id", options.requestId] : []), ...(options.timeoutMs ? ["--timeout-ms", options.timeoutMs] : []), ...(options.wait !== undefined ? ["--wait", options.wait] : []), ...(options.dryRun ? ["--dry-run"] : [])];
|
|
1376
1376
|
commandExitCode = await cmdCall(new ConfigStore(defaultPaths()), rest);
|
|
1377
1377
|
});
|
|
1378
|
+
command.command("script [file]").option("--env <environment>", "行内无 env 时的默认环境").option("--continue", "某行失败后继续执行剩余行(默认停在首个失败)").description("NDJSON 多调脚本:每行 {\"tool\":\"<名称>\",\"args\":{...},\"env\":\"<环境>\",\"dryRun\":true}(# 注释/空行忽略;file 为 - 读 stdin);一个进程一次会话跑完全部,逐行输出 {index,tool,exitCode,result}").action(async (file, options) => {
|
|
1379
|
+
const { runScript, parseScript, readScriptInput } = await import("./script.js");
|
|
1380
|
+
let lines;
|
|
1381
|
+
try {
|
|
1382
|
+
lines = parseScript(readScriptInput(file ?? "-"));
|
|
1383
|
+
}
|
|
1384
|
+
catch (error) {
|
|
1385
|
+
console.error(String(error instanceof Error ? error.message : error));
|
|
1386
|
+
commandExitCode = CLI_EXIT_CODES.invalidInput;
|
|
1387
|
+
return;
|
|
1388
|
+
}
|
|
1389
|
+
if (lines.length === 0) {
|
|
1390
|
+
console.error("脚本为空:每行一个 {\"tool\":\"<名称>\",\"args\":{...}} 调用");
|
|
1391
|
+
commandExitCode = CLI_EXIT_CODES.invalidInput;
|
|
1392
|
+
return;
|
|
1393
|
+
}
|
|
1394
|
+
commandExitCode = await runScript(lines, { defaultEnv: options.env, continueOnError: options.continue === true });
|
|
1395
|
+
});
|
|
1378
1396
|
command.command("artifact").command("get <id>").option("--jq <path>").option("--path-only").option("--summary").option("--keys [a,b]").option("--max-output-bytes <bytes>").action(async (id, options) => {
|
|
1379
1397
|
commandExitCode = await withTransport(options, request, (transport) => cmdArtifactGet(id, transport, { ...(options.jq ? { jq: options.jq } : {}), pathOnly: options.pathOnly === true, summary: options.summary === true || options.keys === true, ...(typeof options.keys === "string" ? { keys: options.keys } : {}) }));
|
|
1380
1398
|
});
|
|
@@ -158,9 +158,15 @@ export function registerB2Lane(tools) {
|
|
|
158
158
|
description: B2_TOOLS["service.get"].description,
|
|
159
159
|
inputSchema: { environment: environmentArg, serviceCode: z.string(), capability: capabilityArg },
|
|
160
160
|
handler: async (ctx, args, self) => governedRead(ctx, self, args, async (session) => {
|
|
161
|
+
// F5: the service row and its method list are independent reads keyed
|
|
162
|
+
// by the same serviceCode — fire both at once. The absent path
|
|
163
|
+
// short-circuits AFTER the pair settles (the method read's result is
|
|
164
|
+
// simply discarded; absence is a fact either way).
|
|
165
|
+
const serviceRead = wireCall(session, { path: ENDPOINTS.bizServiceGetByCode, query: { serviceCode: args.serviceCode } });
|
|
166
|
+
const methodRead = wireCall(session, { path: ENDPOINTS.bizServiceMethodList, query: { serviceCode: args.serviceCode, filterFlag: "false" } }).catch((error) => error);
|
|
161
167
|
let service;
|
|
162
168
|
try {
|
|
163
|
-
service = await
|
|
169
|
+
service = await serviceRead;
|
|
164
170
|
}
|
|
165
171
|
catch (error) {
|
|
166
172
|
// A deleted service answers the wire-business error 500001; reading a
|
|
@@ -173,19 +179,25 @@ export function registerB2Lane(tools) {
|
|
|
173
179
|
const serviceRow = service.data;
|
|
174
180
|
let methods = null;
|
|
175
181
|
let methodsError = null;
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
name: row.name,
|
|
180
|
-
serviceCode: row.serviceCode ?? args.serviceCode,
|
|
181
|
-
configJson: maskServiceConfigSecrets(normalizeMethodConfigReadback(adapterCodeOf(serviceRow), decodeJsonColumn(row.configJson))),
|
|
182
|
-
inputParameters: normalizeParameterFaceReadback(adapterCodeOf(serviceRow), decodeJsonColumn(row.inputParametersJson)),
|
|
183
|
-
outputParameters: normalizeParameterFaceReadback(adapterCodeOf(serviceRow), decodeJsonColumn(row.outputParametersJson)),
|
|
184
|
-
referenced: row.referenced === true,
|
|
185
|
-
}));
|
|
182
|
+
const methodOutcome = await methodRead;
|
|
183
|
+
if (methodOutcome instanceof Error) {
|
|
184
|
+
methodsError = methodOutcome.message;
|
|
186
185
|
}
|
|
187
|
-
|
|
188
|
-
|
|
186
|
+
else {
|
|
187
|
+
try {
|
|
188
|
+
methods = wireRows(methodOutcome.data).map((row) => ({
|
|
189
|
+
code: row.code,
|
|
190
|
+
name: row.name,
|
|
191
|
+
serviceCode: row.serviceCode ?? args.serviceCode,
|
|
192
|
+
configJson: maskServiceConfigSecrets(normalizeMethodConfigReadback(adapterCodeOf(serviceRow), decodeJsonColumn(row.configJson))),
|
|
193
|
+
inputParameters: normalizeParameterFaceReadback(adapterCodeOf(serviceRow), decodeJsonColumn(row.inputParametersJson)),
|
|
194
|
+
outputParameters: normalizeParameterFaceReadback(adapterCodeOf(serviceRow), decodeJsonColumn(row.outputParametersJson)),
|
|
195
|
+
referenced: row.referenced === true,
|
|
196
|
+
}));
|
|
197
|
+
}
|
|
198
|
+
catch (error) {
|
|
199
|
+
methodsError = error instanceof Error ? error.message : String(error);
|
|
200
|
+
}
|
|
189
201
|
}
|
|
190
202
|
return {
|
|
191
203
|
serviceCode: args.serviceCode,
|
|
@@ -144,7 +144,7 @@ export const BUSINESSRULE_LANE_CONTRACT = Object.freeze({
|
|
|
144
144
|
tools: Object.freeze({
|
|
145
145
|
"rule.list": Object.freeze({
|
|
146
146
|
scope: "latest rule business-field listing",
|
|
147
|
-
description: "
|
|
147
|
+
description: "列出某模型的最新业务规则版本。默认行=清单面(code/name/enabled/status/ruleVersion/defaultRule——defaultRule:true 是平台预置触发行,客户端自滤);id/createdTime/creator/developmentMode 等审计列走 full:true。服务端不过滤。",
|
|
148
148
|
}),
|
|
149
149
|
"rule.get": Object.freeze({
|
|
150
150
|
scope: "rule node route readback",
|
|
@@ -51,28 +51,38 @@ export function registerBusinessRuleLane(tools) {
|
|
|
51
51
|
inputSchema: {
|
|
52
52
|
environment: environmentArg,
|
|
53
53
|
schemaCode: z.string().min(1),
|
|
54
|
-
full: z.boolean().optional().describe("
|
|
54
|
+
full: z.boolean().optional().describe("行内联完整字段(id/createdTime/creator/…,并跳过 detailFile 外置);默认行只有 code/name/enabled/status/ruleVersion/defaultRule——平台预置触发行(defaultRule:true)客户端自滤"),
|
|
55
55
|
capability: capabilityArg,
|
|
56
56
|
},
|
|
57
57
|
handler: async (ctx, args, self) => governedRead(ctx, self, args, async (session) => {
|
|
58
58
|
const rows = wireRows((await wireCall(session, { path: ENDPOINTS.runtimeBusinessRuleListBySchemaCode, query: { schemaCode: args.schemaCode } })).data);
|
|
59
|
+
// N2 (r10 noise pass): the default row is the inventory face —
|
|
60
|
+
// code/name/state/publish-version/is-preset. The audit columns
|
|
61
|
+
// (id/createdTime/creator/developmentMode/templateType/…) only ride
|
|
62
|
+
// with full:true (the old 14-key row). rule.get resolves by business
|
|
63
|
+
// key (schemaCode+ruleCode), so the row id is rarely needed.
|
|
64
|
+
const fullRows = args.full === true;
|
|
59
65
|
return {
|
|
60
66
|
schemaCode: args.schemaCode,
|
|
61
67
|
rules: rows.map((row) => ({
|
|
62
|
-
id: row.id,
|
|
63
68
|
code: row.code,
|
|
64
69
|
name: row.name,
|
|
65
|
-
schemaCode: row.schemaCode,
|
|
66
|
-
bizRuleType: row.bizRuleType,
|
|
67
70
|
enabled: row.enabled,
|
|
68
71
|
status: row.status,
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
72
|
+
...(row.ruleVersion !== undefined ? { ruleVersion: row.ruleVersion } : {}),
|
|
73
|
+
...(row.defaultRule !== undefined ? { defaultRule: row.defaultRule } : {}),
|
|
74
|
+
...(fullRows
|
|
75
|
+
? {
|
|
76
|
+
id: row.id,
|
|
77
|
+
schemaCode: row.schemaCode,
|
|
78
|
+
bizRuleType: row.bizRuleType,
|
|
79
|
+
developmentMode: row.developmentMode,
|
|
80
|
+
latestPublished: row.latestPublished,
|
|
81
|
+
templateType: row.templateType,
|
|
82
|
+
createdTime: row.createdTime,
|
|
83
|
+
creator: row.creater ?? row.creator,
|
|
84
|
+
}
|
|
85
|
+
: {}),
|
|
76
86
|
})),
|
|
77
87
|
};
|
|
78
88
|
}, { full: args.full === true, target: `rule.list:${args.schemaCode}` }),
|
|
@@ -161,7 +161,7 @@ export const FIELD_LANE_CONTRACT = Object.freeze({
|
|
|
161
161
|
tools: Object.freeze({
|
|
162
162
|
"field.list": Object.freeze({
|
|
163
163
|
scope: "create publish readback",
|
|
164
|
-
description: "List draft/published fields of a model
|
|
164
|
+
description: "List draft/published fields of a model. 默认清单面:code/name/type(+typeName)/conditions 信号/shapeWarning——选项族整包 choices 不内联(实测选择字段 ~500B/行);验证 options/subSchema 用 code/fields 选择器或 full:true(readback face of the field family chain)。",
|
|
165
165
|
}),
|
|
166
166
|
"field.create": Object.freeze({
|
|
167
167
|
scope: "create publish readback",
|
package/dist/lanes/field.js
CHANGED
|
@@ -12,6 +12,7 @@ import { defineTool, ToolRefusalError } from "../tool-types.js";
|
|
|
12
12
|
import { wireCall, wireRows, WireError } from "../wire.js";
|
|
13
13
|
import { executeFormDraftWrite, executeFormPublishWrite } from "../form-support.js";
|
|
14
14
|
import { FIELD_LANE_CONTRACT } from "./field-contract.js";
|
|
15
|
+
import { mapBounded } from "../support.js";
|
|
15
16
|
const FIELD_LANE = FIELD_LANE_CONTRACT.lane;
|
|
16
17
|
const FIELD_TOOLS = FIELD_LANE_CONTRACT.tools;
|
|
17
18
|
/** Parse a raw options JSON string for readback; policy: non-strings and unparseable strings pass through verbatim. */
|
|
@@ -74,7 +75,7 @@ export function registerFieldLane(tools) {
|
|
|
74
75
|
lane: FIELD_LANE,
|
|
75
76
|
scope: FIELD_TOOLS["field.list"].scope,
|
|
76
77
|
description: FIELD_TOOLS["field.list"].description,
|
|
77
|
-
inputSchema: { environment: environmentArg, schemaCode: z.string(), published: z.boolean().default(false), code: z.string().optional(), fields: z.array(z.string()).optional(), all: z.boolean().optional().describe("默认只返业务字段(过滤 SYSTEM_FIELD_CODES 的 13 个平台字段:id/name/sequenceNo/createdTime/creater/sequenceStatus/…);true 连系统字段一起返"), capability: capabilityArg },
|
|
78
|
+
inputSchema: { environment: environmentArg, schemaCode: z.string(), published: z.boolean().default(false), code: z.string().optional(), fields: z.array(z.string()).optional(), full: z.boolean().optional().describe("行内联完整 options/subSchema;默认清单面只有 code/name/type/typeName(+conditions 信号/shapeWarning),验证 options 用 code/fields 选择器或 full:true"), all: z.boolean().optional().describe("默认只返业务字段(过滤 SYSTEM_FIELD_CODES 的 13 个平台字段:id/name/sequenceNo/createdTime/creater/sequenceStatus/…);true 连系统字段一起返"), capability: capabilityArg },
|
|
78
79
|
handler: async (ctx, args, self) => governedRead(ctx, self, args, async (session) => {
|
|
79
80
|
const rows = wireRows((await wireCall(session, { path: ENDPOINTS.fieldList, query: { schemaCode: args.schemaCode, isPublish: args.published ? "true" : "false" } })).data);
|
|
80
81
|
const mapped = rows.map((row) => {
|
|
@@ -102,13 +103,44 @@ export function registerFieldLane(tools) {
|
|
|
102
103
|
// (which token-diets to {count,sha256} past 4 KB) through an artifact.
|
|
103
104
|
const selector = [args.code, ...(args.fields ?? [])].filter((entry) => typeof entry === "string" && entry.length > 0);
|
|
104
105
|
const selected = selector.length > 0 ? mapped.filter((field) => selector.includes(String(field.code))) : mapped;
|
|
106
|
+
// N1 (r10 noise pass): the DEFAULT face is the inventory — code/name/
|
|
107
|
+
// type(+condition signal/shapeWarning). Options arrays (choices carry
|
|
108
|
+
// their full custom lists inline, ~500B per selection field measured)
|
|
109
|
+
// only echo for a code/fields selector or full:true. Same shape the
|
|
110
|
+
// >4KB window diet already served; promoted to the default so small
|
|
111
|
+
// models stop paying option bytes on inventory reads.
|
|
112
|
+
const detailed = selector.length > 0 || args.full === true;
|
|
105
113
|
// Round 10: the default face is BUSINESS fields only — 13 of 21 fields on a
|
|
106
114
|
// typical model are platform system fields, so a caller asking "what fields
|
|
107
115
|
// does this model have" paid for noise it then had to filter. `all:true`
|
|
108
116
|
// restores the previous full list (uses the SAME SYSTEM_FIELD_CODES set the
|
|
109
117
|
// list-view/form scaffolds already filter with).
|
|
110
118
|
const visible = args.all === true ? selected : selected.filter((field) => !SYSTEM_FIELD_CODES.has(String(field.code)));
|
|
111
|
-
|
|
119
|
+
const projected = detailed
|
|
120
|
+
? visible
|
|
121
|
+
: visible.map((field) => {
|
|
122
|
+
const options = (field.options ?? {});
|
|
123
|
+
const conditions = Object.keys(FORM_CONDITION_OPTION_KEYS).filter((key) => {
|
|
124
|
+
const value = options[key];
|
|
125
|
+
return key === "readonlyFormula" ? value === true : typeof value === "string" && value.trim() !== "";
|
|
126
|
+
});
|
|
127
|
+
return {
|
|
128
|
+
code: field.code,
|
|
129
|
+
type: field.type,
|
|
130
|
+
...(field.typeName !== undefined ? { typeName: field.typeName } : {}),
|
|
131
|
+
...(field.name !== undefined ? { name: field.name } : {}),
|
|
132
|
+
...(conditions.length > 0 ? { conditions } : {}),
|
|
133
|
+
...(field.shapeWarning !== undefined ? { shapeWarning: field.shapeWarning } : {}),
|
|
134
|
+
};
|
|
135
|
+
});
|
|
136
|
+
return {
|
|
137
|
+
schemaCode: args.schemaCode,
|
|
138
|
+
published: args.published === true,
|
|
139
|
+
...(selector.length > 0 ? { selected: selector } : {}),
|
|
140
|
+
...(args.all !== true ? { businessOnly: true } : {}),
|
|
141
|
+
fields: projected,
|
|
142
|
+
...(detailed ? {} : { inventory: true, hint: '字段详情(options/subSchema)用 code/fields 选择器或 full:true' }),
|
|
143
|
+
};
|
|
112
144
|
}, {
|
|
113
145
|
// Round 4: the default diet reduced the whole list to a count, so agents
|
|
114
146
|
// fetched the 28 KB artifact just to read field codes (benchmark: 4×28 KB).
|
|
@@ -116,6 +148,11 @@ export function registerFieldLane(tools) {
|
|
|
116
148
|
// warning). `options` stays out — use code/fields selector for that.
|
|
117
149
|
target: `field.list:${args.schemaCode}:${args.published === true ? "published" : "draft"}`,
|
|
118
150
|
compact: (result) => {
|
|
151
|
+
// N1: the handler's DEFAULT face is already the inventory projection;
|
|
152
|
+
// re-projecting here would strip the conditions signal (options are
|
|
153
|
+
// gone by design). Pass it through untouched.
|
|
154
|
+
if (result.inventory === true)
|
|
155
|
+
return result;
|
|
119
156
|
const fields = Array.isArray(result.fields) ? result.fields : [];
|
|
120
157
|
return {
|
|
121
158
|
schemaCode: result.schemaCode,
|
|
@@ -304,24 +341,26 @@ export async function executeFieldPublish(session, args) {
|
|
|
304
341
|
.map((row) => [codeOf(row), { name: String(row.name ?? ""), propertyType: Number(row.type ?? row.propertyType) }]));
|
|
305
342
|
const verifyFieldDetails = async (targets) => {
|
|
306
343
|
const bounded = targets.slice(0, 25);
|
|
307
|
-
const
|
|
308
|
-
for (const code of bounded) {
|
|
344
|
+
const checked = await mapBounded(bounded, 5, async (code) => {
|
|
309
345
|
const expected = draftSnapshot.get(code);
|
|
310
346
|
if (!expected)
|
|
311
|
-
|
|
312
|
-
let detail;
|
|
347
|
+
return { kind: "skip" };
|
|
313
348
|
try {
|
|
314
|
-
detail = (await wireCall(session, { path: ENDPOINTS.fieldGet, query: { schemaCode: args.schemaCode, propertyCode: code } })).data;
|
|
349
|
+
const detail = (await wireCall(session, { path: ENDPOINTS.fieldGet, query: { schemaCode: args.schemaCode, propertyCode: code } })).data;
|
|
350
|
+
const actual = { name: String(detail.name ?? ""), propertyType: Number(detail.propertyType ?? detail.type) };
|
|
351
|
+
if (actual.name !== expected.name || actual.propertyType !== expected.propertyType)
|
|
352
|
+
return { kind: "mismatch", code, expected, actual };
|
|
353
|
+
return { kind: "ok" };
|
|
315
354
|
}
|
|
316
355
|
catch {
|
|
317
356
|
// The detail endpoint is optional across builds: report the
|
|
318
357
|
// verification as unavailable rather than failing the publish.
|
|
319
|
-
return {
|
|
358
|
+
return { kind: "unavailable" };
|
|
320
359
|
}
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
360
|
+
});
|
|
361
|
+
if (checked.some((entry) => entry.kind === "unavailable"))
|
|
362
|
+
return { mode: "unavailable", verified: false, note: "bizproperty/get 不可用,字段详情未核对(仅列表面存在性)" };
|
|
363
|
+
const mismatches = checked.filter((entry) => entry.kind === "mismatch");
|
|
325
364
|
return { mode: "detail", verified: true, checked: bounded.length, ok: mismatches.length === 0, ...(mismatches.length > 0 ? { mismatches } : {}) };
|
|
326
365
|
};
|
|
327
366
|
const preRead = await wireCall(session, { path: ENDPOINTS.fieldList, query: { schemaCode: args.schemaCode, isPublish: "true" } });
|
package/dist/lanes/model.js
CHANGED
|
@@ -3,6 +3,7 @@ import { ENDPOINTS } from "../endpoints.js";
|
|
|
3
3
|
import { assertFieldFamilyAllowed, choicesArg, fieldOptionsArg, nameI18nJson, propertyTypeArg, relationTargetOf, relativeCodeArg, SYSTEM_FIELD_CODES, subFieldArg, } from "../field-families.js";
|
|
4
4
|
import { fieldChangeWireBody, prepareFieldChange, sameFieldChange } from "../field-change.js";
|
|
5
5
|
import { executeFormDraftWrite, executeFormPublishWrite } from "../form-support.js";
|
|
6
|
+
import { mapBounded } from "../support.js";
|
|
6
7
|
import { validateBusinessCode } from "../code-rules.js";
|
|
7
8
|
import { writeSummary } from "../human-summary.js";
|
|
8
9
|
import { capabilityArg, environmentArg } from "../args.js";
|
|
@@ -188,16 +189,25 @@ export function registerModelLane(tools) {
|
|
|
188
189
|
}
|
|
189
190
|
};
|
|
190
191
|
const inspectOne = async (schemaCode) => {
|
|
191
|
-
|
|
192
|
+
// F5: the six read faces are INDEPENDENT wire reads; fan them out in
|
|
193
|
+
// parallel (bounded) instead of paying six sequential round-trips. Only
|
|
194
|
+
// workflow.get_published stays chained (it needs workflowCode from the
|
|
195
|
+
// draft face). Output ordering is unchanged (results land by index).
|
|
196
|
+
const [fieldFace, formFace, listviewFace, ruleFace, dataruleFace, draftFace] = await mapBounded([
|
|
197
|
+
() => face("field.list", { schemaCode, published: true, ...(all ? { all: true } : {}) }),
|
|
198
|
+
() => face("form.get", { schemaCode }),
|
|
199
|
+
() => face("listview.get", { schemaCode }),
|
|
200
|
+
() => face("rule.list", { schemaCode }),
|
|
201
|
+
() => face("datarule.list", { schemaCode }),
|
|
202
|
+
() => face("workflow.get_draft", { schemaCode }),
|
|
203
|
+
], 6, (task) => task());
|
|
192
204
|
const fieldRows = Array.isArray(fieldFace?.fields) ? fieldFace.fields : [];
|
|
193
205
|
const fields = fieldRows
|
|
194
206
|
.filter((field) => all || !SYSTEM_FIELD_CODES.has(String(field.code)))
|
|
195
207
|
.map((field) => ({ code: field.code, type: field.type, ...(field.typeName !== undefined ? { typeName: field.typeName } : {}), name: field.name, ...(field.conditions ? { conditions: field.conditions } : {}) }));
|
|
196
|
-
const formFace = await face("form.get", { schemaCode });
|
|
197
208
|
const form = formFace && formFace.present !== undefined
|
|
198
209
|
? { present: formFace.present === true, published: formFace.published === true, controlCount: Array.isArray(formFace.draftControlCodes) ? formFace.draftControlCodes.length : 0 }
|
|
199
210
|
: undefined;
|
|
200
|
-
const listviewFace = await face("listview.get", { schemaCode });
|
|
201
211
|
const listviewColumns = Array.isArray(listviewFace?.columns) ? listviewFace.columns : [];
|
|
202
212
|
// Round 13: the default view is platform-generated and lists the system
|
|
203
213
|
// columns (createdTime/creater/name/sequenceNo/…). In a design-time survey
|
|
@@ -208,14 +218,12 @@ export function registerModelLane(tools) {
|
|
|
208
218
|
const listview = listviewFace && listviewFace.columns
|
|
209
219
|
? { code: listviewFace.code ?? schemaCode, columnCount: visibleColumns.length, columns: visibleColumns.map((column) => ({ propertyCode: column.propertyCode, propertyType: column.propertyType, visible: column.visible })) }
|
|
210
220
|
: undefined;
|
|
211
|
-
const ruleFace = await face("rule.list", { schemaCode });
|
|
212
221
|
const ruleRows = Array.isArray(ruleFace?.rules) ? ruleFace.rules : [];
|
|
213
222
|
// All rules are kept: the platform pre-creates trigger rows (Create/Update/
|
|
214
223
|
// …) but user logic is AUTHORED INTO them, so `defaultRule` cannot be used
|
|
215
224
|
// to hide "system" rules without dropping authored ones. `defaultRule` is
|
|
216
225
|
// reported per rule so a caller can decide.
|
|
217
226
|
const rules = ruleRows.map((rule) => ({ code: rule.code, name: rule.name, enabled: rule.enabled, status: rule.status, ruleVersion: rule.ruleVersion, defaultRule: rule.defaultRule }));
|
|
218
|
-
const dataruleFace = await face("datarule.list", { schemaCode });
|
|
219
227
|
const dataruleRows = Array.isArray(dataruleFace?.rules) ? dataruleFace.rules : [];
|
|
220
228
|
const datarules = dataruleRows.map((rule) => ({ propertyCode: rule.propertyCode, propertyCodes: rule.propertyCodes, dataRuleType: rule.dataRuleType, name: rule.name, checkType: rule.checkType }));
|
|
221
229
|
// Workflow summary: use the PUBLISHED face for the `published` flag. The
|
|
@@ -224,7 +232,6 @@ export function registerModelLane(tools) {
|
|
|
224
232
|
// `published` from get_draft reported published:false forever (live round
|
|
225
233
|
// 10: an agent's flow read as published:false while get_published was
|
|
226
234
|
// templateType:PUBLISH). Activities come from whichever face exists.
|
|
227
|
-
const draftFace = await face("workflow.get_draft", { schemaCode });
|
|
228
235
|
const workflowCode = draftFace?.workflowSummary?.workflowCode ?? draftFace?.workflowCode;
|
|
229
236
|
const publishedFace = workflowCode ? await face("workflow.get_published", { schemaCode }) : undefined;
|
|
230
237
|
const draftSummary = draftFace?.workflowSummary;
|
package/dist/script.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { ConfigStore, defaultPaths } from "./config-store.js";
|
|
4
|
+
import { SessionManager } from "./session-manager.js";
|
|
5
|
+
import { makeToolContext } from "./tools.js";
|
|
6
|
+
import { runToolCall } from "./call.js";
|
|
7
|
+
export function parseScript(text) {
|
|
8
|
+
const lines = [];
|
|
9
|
+
const raw = text.split("\n");
|
|
10
|
+
for (const [index, untrimmed] of raw.entries()) {
|
|
11
|
+
const line = untrimmed.trim();
|
|
12
|
+
if (line.length === 0 || line.startsWith("#"))
|
|
13
|
+
continue;
|
|
14
|
+
let parsed;
|
|
15
|
+
try {
|
|
16
|
+
parsed = JSON.parse(line);
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
throw new Error(`第 ${index + 1} 行不是合法 JSON:${error instanceof Error ? error.message : String(error)}`);
|
|
20
|
+
}
|
|
21
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed) || typeof parsed.tool !== "string") {
|
|
22
|
+
throw new Error(`第 ${index + 1} 行必须是 {"tool":"<名称>","args":{...},"env":"<环境>","dryRun":true} 形状(tool 必填)`);
|
|
23
|
+
}
|
|
24
|
+
const entry = parsed;
|
|
25
|
+
if (entry.args !== undefined && (entry.args === null || typeof entry.args !== "object" || Array.isArray(entry.args)))
|
|
26
|
+
throw new Error(`第 ${index + 1} 行 args 必须是 JSON 对象`);
|
|
27
|
+
lines.push({
|
|
28
|
+
tool: entry.tool,
|
|
29
|
+
...(typeof entry.args === "object" && entry.args !== null ? { args: entry.args } : {}),
|
|
30
|
+
...(typeof entry.env === "string" ? { env: entry.env } : {}),
|
|
31
|
+
...(entry.dryRun === true ? { dryRun: true } : {}),
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
return lines;
|
|
35
|
+
}
|
|
36
|
+
export async function runScript(lines, options = {}) {
|
|
37
|
+
const print = options.print ?? ((line) => console.log(line));
|
|
38
|
+
const context = options.context ?? makeToolContext({ sessions: new SessionManager(new ConfigStore(defaultPaths())) });
|
|
39
|
+
let failed = false;
|
|
40
|
+
for (const [index, line] of lines.entries()) {
|
|
41
|
+
const { exitCode, result } = await runToolCall(line.tool, {
|
|
42
|
+
args: line.args,
|
|
43
|
+
environment: line.env ?? options.defaultEnv,
|
|
44
|
+
dryRun: line.dryRun === true,
|
|
45
|
+
requestId: randomUUID(),
|
|
46
|
+
context,
|
|
47
|
+
print: () => undefined,
|
|
48
|
+
});
|
|
49
|
+
print(JSON.stringify({ index, tool: line.tool, exitCode, result }));
|
|
50
|
+
if (exitCode !== 0) {
|
|
51
|
+
failed = true;
|
|
52
|
+
if (options.continueOnError !== true) {
|
|
53
|
+
print(JSON.stringify({ index, tool: line.tool, stopped: true, note: `第 ${index + 1} 行失败,后续 ${lines.length - index - 1} 行未执行;加 --continue 跑完全部` }));
|
|
54
|
+
return exitCode;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return failed ? 1 : 0;
|
|
59
|
+
}
|
|
60
|
+
export function readScriptInput(path) {
|
|
61
|
+
if (path === "-")
|
|
62
|
+
return readFileSync(0, "utf8");
|
|
63
|
+
return readFileSync(path, "utf8");
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=script.js.map
|
package/dist/support.js
CHANGED
|
@@ -95,4 +95,26 @@ export function flattenTreeRows(rows) {
|
|
|
95
95
|
visit(row);
|
|
96
96
|
return flat;
|
|
97
97
|
}
|
|
98
|
+
/**
|
|
99
|
+
* F5 (r10-friction): order-preserving map with BOUNDED concurrency. The read
|
|
100
|
+
* lanes fan out independent wire faces (model.inspect's six faces, field
|
|
101
|
+
* publish verification's detail reads) that used to pay full per-call
|
|
102
|
+
* latency sequentially. The bound keeps the fan-out polite to the small
|
|
103
|
+
* environments (ticket 79 documented DB contention under 30 parallel
|
|
104
|
+
* publishes; reads are lighter but still bounded).
|
|
105
|
+
*/
|
|
106
|
+
export async function mapBounded(items, limit, worker) {
|
|
107
|
+
const results = new Array(items.length);
|
|
108
|
+
let next = 0;
|
|
109
|
+
const runners = Array.from({ length: Math.max(1, Math.min(limit, items.length)) }, async () => {
|
|
110
|
+
while (true) {
|
|
111
|
+
const index = next++;
|
|
112
|
+
if (index >= items.length)
|
|
113
|
+
return;
|
|
114
|
+
results[index] = await worker(items[index], index);
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
await Promise.all(runners);
|
|
118
|
+
return results;
|
|
119
|
+
}
|
|
98
120
|
//# sourceMappingURL=support.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stalfh233/omc-cli",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.20",
|
|
4
4
|
"description": "CloudPivot 8.6 configuration capability CLI — governed, fail-closed, coverage-manifest bounded.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
53
|
"commander": "14.0.3",
|
|
54
|
-
"omc-core": "0.4.
|
|
54
|
+
"omc-core": "0.4.20",
|
|
55
55
|
"proper-lockfile": "4.1.2",
|
|
56
56
|
"write-file-atomic": "6.0.0",
|
|
57
57
|
"zod": "^3.23.8",
|
package/skills/omc-start-here.md
CHANGED
|
@@ -37,6 +37,7 @@ Run in a **clean project directory** (not the OMC dev repo). Start from the goal
|
|
|
37
37
|
omc next "<目标>" # goal → ordered route (offline)
|
|
38
38
|
omc doctor # environment name and readiness
|
|
39
39
|
omc call discover_environment --args '{}' # 单环境项目(.omc-env 在位)可省 --env;多环境才显式传
|
|
40
|
+
omc script - --env <env> <<< '{"tool":"model.list"}' # 多调链:NDJSON 每行一调,一进程一次会话跑完
|
|
40
41
|
omc capability <selected-id> # only the selected capability
|
|
41
42
|
omc describe <tool> --example <recipe-id> # recipe first; default describe otherwise
|
|
42
43
|
omc help <topic> --section "<heading>" # one methodology section, not all
|