@stalfh233/omc-cli 0.6.17 → 0.6.18
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/batch-call.js +21 -2
- package/dist/cli.js +21 -12
- package/dist/field-change.js +7 -1
- package/dist/field-families.js +64 -0
- package/dist/graph.js +31 -7
- package/dist/lanes/app.js +17 -4
- package/dist/lanes/field.js +114 -5
- package/dist/lanes/model-contract.js +1 -1
- package/dist/lanes/model.js +51 -29
- package/dist/lanes/online-js-contract.js +1 -1
- package/dist/lanes/online-js.js +41 -9
- package/dist/lanes/programming.js +67 -14
- package/dist/lanes/runtime-data.js +153 -16
- package/dist/mcp-graph.js +32 -13
- package/dist/programming-workflow.js +16 -3
- package/dist/read-shapes.js +3 -3
- package/dist/rollback.js +54 -19
- package/dist/write.js +6 -2
- package/package.json +3 -3
|
@@ -14,6 +14,7 @@ import { getReplayToken, listInstanceActivities, searchWorkItems, wireString } f
|
|
|
14
14
|
import { PLATFORM_ADMIN_START_BYPASS, resolvePermissionActor } from "../identity.js";
|
|
15
15
|
import { ENGINE_OWNED_KEYS, RUNTIME_DATA_LANE_CONTRACT, RUNTIME_DATA_WIRE, runtimeFormDataArg, } from "./runtime-data-contract.js";
|
|
16
16
|
import { SYSTEM_FIELD_CODES } from "../field-families.js";
|
|
17
|
+
import { applicationResolutionOf, resolveApplicationScope } from "../support.js";
|
|
17
18
|
/** r29b F8: keep ids/itemsPreview through governedRead's compact diet. */
|
|
18
19
|
const DATA_LIST_PREVIEW_LIMIT = 20;
|
|
19
20
|
function dataListItemPreview(items) {
|
|
@@ -371,6 +372,106 @@ function readbackWarningFor(emptyFields, toolName) {
|
|
|
371
372
|
return undefined;
|
|
372
373
|
return `${toolName} 已 DELIVERED,但即时读回里这些本次提交的字段为空/缺省:${emptyFields.join("、")}。平台 form/load 在写事务后可能先回系统字段、业务字段滞后(真机 1.1.25,r14 见过只回 modifiedTime 的一笔)。**先 omc call data.load 核对,不要重写**;若 data.load 仍为空,才按投影/字段 code 排查。`;
|
|
373
374
|
}
|
|
375
|
+
function parseOptions(value) {
|
|
376
|
+
if (typeof value === "string") {
|
|
377
|
+
try {
|
|
378
|
+
const parsed = JSON.parse(value);
|
|
379
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
380
|
+
}
|
|
381
|
+
catch {
|
|
382
|
+
return {};
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
386
|
+
}
|
|
387
|
+
function choiceCodesFromFieldOptions(field, options) {
|
|
388
|
+
const asCodes = (value) => {
|
|
389
|
+
if (!Array.isArray(value))
|
|
390
|
+
return [];
|
|
391
|
+
return value
|
|
392
|
+
.map((entry) => {
|
|
393
|
+
if (entry && typeof entry === "object" && !Array.isArray(entry)) {
|
|
394
|
+
const row = entry;
|
|
395
|
+
return String(row.code ?? row.value ?? row.key ?? "");
|
|
396
|
+
}
|
|
397
|
+
return String(entry ?? "");
|
|
398
|
+
})
|
|
399
|
+
.filter((code) => code.length > 0);
|
|
400
|
+
};
|
|
401
|
+
const direct = asCodes(field.choices);
|
|
402
|
+
if (direct.length > 0)
|
|
403
|
+
return direct;
|
|
404
|
+
for (const candidate of [options.choices, options.items]) {
|
|
405
|
+
const codes = asCodes(candidate);
|
|
406
|
+
if (codes.length > 0)
|
|
407
|
+
return codes;
|
|
408
|
+
}
|
|
409
|
+
const set = options.optionsSet;
|
|
410
|
+
if (!set || typeof set !== "object" || Array.isArray(set))
|
|
411
|
+
return [];
|
|
412
|
+
const optionSet = set;
|
|
413
|
+
for (const candidate of [optionSet.custom, optionSet.choices, optionSet.items, optionSet.useDictionariesData]) {
|
|
414
|
+
const codes = asCodes(candidate);
|
|
415
|
+
if (codes.length > 0)
|
|
416
|
+
return codes;
|
|
417
|
+
}
|
|
418
|
+
const dictionary = optionSet.dictionary;
|
|
419
|
+
if (dictionary && typeof dictionary === "object" && !Array.isArray(dictionary)) {
|
|
420
|
+
const codes = asCodes(dictionary.useDictionariesData);
|
|
421
|
+
if (codes.length > 0)
|
|
422
|
+
return codes;
|
|
423
|
+
}
|
|
424
|
+
return [];
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* Runtime/form/save deliberately remains permissive: the platform stores the
|
|
428
|
+
* row even when the portal would reject it. These are advisory findings only.
|
|
429
|
+
* In particular, never manufacture a warning for a field which is absent from
|
|
430
|
+
* the published field face (the old `code/required` ghost warning did exactly
|
|
431
|
+
* that).
|
|
432
|
+
*/
|
|
433
|
+
async function runtimeDataWarnings(session, schemaCode, sheetCode, formData, row = 1) {
|
|
434
|
+
const warnings = [];
|
|
435
|
+
let fields = [];
|
|
436
|
+
try {
|
|
437
|
+
fields = wireRows((await wireCall(session, { path: ENDPOINTS.fieldList, query: { schemaCode, isPublish: "true" } })).data);
|
|
438
|
+
}
|
|
439
|
+
catch {
|
|
440
|
+
return warnings;
|
|
441
|
+
}
|
|
442
|
+
const byCode = new Map(fields.map((field) => [String(field.code ?? field.propertyCode ?? ""), field]));
|
|
443
|
+
let required = new Set();
|
|
444
|
+
try {
|
|
445
|
+
const sheet = ((await wireCall(session, { path: ENDPOINTS.sheetGet, query: { schemaCode, sheetCode } })).data ?? {});
|
|
446
|
+
const face = typeof sheet.publishedAttributesJson === "string" ? sheet.publishedAttributesJson : typeof sheet.draftAttributesJson === "string" ? sheet.draftAttributesJson : undefined;
|
|
447
|
+
if (face) {
|
|
448
|
+
const controls = JSON.parse(face);
|
|
449
|
+
required = new Set(Object.entries(controls).filter(([, control]) => control?.options?.required === true || typeof control?.options?.requiredFormula === "string").map(([code]) => code));
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
catch {
|
|
453
|
+
// Advisory diagnostics must never affect the write.
|
|
454
|
+
}
|
|
455
|
+
for (const [field, value] of Object.entries(formData)) {
|
|
456
|
+
const meta = byCode.get(field);
|
|
457
|
+
if (!meta)
|
|
458
|
+
continue;
|
|
459
|
+
if ((required.has(field) || parseOptions(meta.options).required === true) && (value === null || value === undefined || (typeof value === "string" && value.trim() === ""))) {
|
|
460
|
+
warnings.push({ code: "required-empty", row, fieldCode: field, message: `字段 ${field} 声明必填但本行为空;平台照存,OMC 仅提示` });
|
|
461
|
+
}
|
|
462
|
+
const options = parseOptions(meta.options);
|
|
463
|
+
const optionSet = options.optionsSet && typeof options.optionsSet === "object" ? options.optionsSet : {};
|
|
464
|
+
const choiceCodes = choiceCodesFromFieldOptions(meta, options);
|
|
465
|
+
if (choiceCodes.length > 0 && value !== null && value !== undefined && value !== "") {
|
|
466
|
+
const allowed = new Set(choiceCodes);
|
|
467
|
+
const values = Array.isArray(value) ? value : [value];
|
|
468
|
+
const invalid = values.filter((entry) => !allowed.has(String(entry)));
|
|
469
|
+
if (invalid.length > 0)
|
|
470
|
+
warnings.push({ code: "choice-out-of-range", row, fieldCode: field, value: invalid.map(String), allowed: choiceCodes, message: `字段 ${field} 含未声明选项(实际值:${invalid.map(String).join("、")};合法集:${choiceCodes.join("、")});平台照存,OMC 仅提示` });
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
return warnings;
|
|
474
|
+
}
|
|
374
475
|
function projectApprovalReadback(record, submittedKeys) {
|
|
375
476
|
if (record === null || typeof record !== "object" || Array.isArray(record))
|
|
376
477
|
return {};
|
|
@@ -979,6 +1080,8 @@ export function registerRuntimeDataLane(tools) {
|
|
|
979
1080
|
throw error;
|
|
980
1081
|
}
|
|
981
1082
|
return governedRead(ctx, self, args, async (session) => {
|
|
1083
|
+
const applicationScope = await resolveApplicationScope(session);
|
|
1084
|
+
const application = { applicationCode: applicationScope.code, applicationResolution: applicationResolutionOf(applicationScope) };
|
|
982
1085
|
// A2:DSL 过滤命中的单选/字典字段(total:0 时给值写法提示)
|
|
983
1086
|
let choiceFilterFields = [];
|
|
984
1087
|
// Published field metadata is the sole source for filter propertyType
|
|
@@ -1103,7 +1206,7 @@ export function registerRuntimeDataLane(tools) {
|
|
|
1103
1206
|
const firstPage = extractBizObjectPage(first.data);
|
|
1104
1207
|
const total = Number(firstPage.total ?? 0);
|
|
1105
1208
|
if (total === 0 || firstPage.content.length === 0)
|
|
1106
|
-
return { schemaCode: args.schemaCode, total: 0, page: "all", size: 0, pagesFetched: 0, items: [], itemsCount: 0, itemsReturned: 0, resultTruncated: false, itemsExternalized: false, ...(choiceFilterFields.length > 0 ? { filterValueHint: choiceFilterValueHint(choiceFilterFields) } : {}) };
|
|
1209
|
+
return { schemaCode: args.schemaCode, ...application, total: 0, page: "all", size: 0, pagesFetched: 0, items: [], itemsCount: 0, itemsReturned: 0, resultTruncated: false, itemsExternalized: false, ...(choiceFilterFields.length > 0 ? { filterValueHint: choiceFilterValueHint(choiceFilterFields) } : {}) };
|
|
1107
1210
|
const rows = [...firstPage.content];
|
|
1108
1211
|
const target = Math.min(total, CAP);
|
|
1109
1212
|
let pageIndex = 1;
|
|
@@ -1148,6 +1251,7 @@ export function registerRuntimeDataLane(tools) {
|
|
|
1148
1251
|
const preview = dataListItemPreview(items);
|
|
1149
1252
|
return {
|
|
1150
1253
|
schemaCode: args.schemaCode,
|
|
1254
|
+
...application,
|
|
1151
1255
|
total,
|
|
1152
1256
|
page: "all",
|
|
1153
1257
|
size: collected.length,
|
|
@@ -1155,7 +1259,11 @@ export function registerRuntimeDataLane(tools) {
|
|
|
1155
1259
|
// 与普通读同一个口径(r28b 真机):草稿策略必须**从结果集实际统计**,
|
|
1156
1260
|
// 不能按"有没有查询条件"静态声明——静态声明会与 items 自相矛盾。
|
|
1157
1261
|
filter: (() => {
|
|
1158
|
-
const draftRows =
|
|
1262
|
+
const draftRows = collected.filter((row) => {
|
|
1263
|
+
const record = row;
|
|
1264
|
+
const data = record.data && typeof record.data === "object" ? record.data : record;
|
|
1265
|
+
return String(data.sequenceStatus ?? record.sequenceStatus ?? "") === "DRAFT";
|
|
1266
|
+
}).length;
|
|
1159
1267
|
return draftRows > 0
|
|
1160
1268
|
? { draftPolicy: "included", draftRows, hint: `本次结果含 ${draftRows} 条 DRAFT 行——total 含草稿,不要当"已提交数据量";判定状态读 data.load 的 sequenceStatus` }
|
|
1161
1269
|
: { draftPolicy: "unspecified", hint: "本次结果无 DRAFT 行;判定状态以 data.load 的 sequenceStatus 为准" };
|
|
@@ -1187,7 +1295,26 @@ export function registerRuntimeDataLane(tools) {
|
|
|
1187
1295
|
}
|
|
1188
1296
|
catch (error) {
|
|
1189
1297
|
if (error instanceof WireError && error.kind === "business" && error.errcode === RUNTIME_DATA_WIRE.emptyModelErrcode) {
|
|
1190
|
-
return { schemaCode: args.schemaCode, total: 0, totalPages: 0, page: args.page ?? 0, size: args.size ?? 10, items: [], ...(choiceFilterFields.length > 0 ? { filterValueHint: choiceFilterValueHint(choiceFilterFields) } : {}) };
|
|
1298
|
+
return { schemaCode: args.schemaCode, ...application, total: 0, totalPages: 0, page: args.page ?? 0, size: args.size ?? 10, items: [], ...(choiceFilterFields.length > 0 ? { filterValueHint: choiceFilterValueHint(choiceFilterFields) } : {}) };
|
|
1299
|
+
}
|
|
1300
|
+
if (error instanceof WireError && error.kind === "business" && error.errcode === 303001) {
|
|
1301
|
+
let available = [];
|
|
1302
|
+
try {
|
|
1303
|
+
const rows = wireRows((await wireCall(session, { path: ENDPOINTS.listQueryList, query: { schemaCode: args.schemaCode, clientType: "PC" } })).data);
|
|
1304
|
+
available = rows.map((row) => ({ code: row.code ?? null, name: row.name ?? null }));
|
|
1305
|
+
}
|
|
1306
|
+
catch {
|
|
1307
|
+
available = [];
|
|
1308
|
+
}
|
|
1309
|
+
return {
|
|
1310
|
+
status: "refused",
|
|
1311
|
+
blocker: "query-view-not-found",
|
|
1312
|
+
errcode: 303001,
|
|
1313
|
+
schemaCode: args.schemaCode,
|
|
1314
|
+
queryCode: args.queryCode ?? args.schemaCode,
|
|
1315
|
+
available,
|
|
1316
|
+
message: available.length > 0 ? `列表视图不存在:${String(args.queryCode ?? args.schemaCode)};可用视图见 available` : `列表视图不存在:${String(args.queryCode ?? args.schemaCode)};该模型无列表视图`,
|
|
1317
|
+
};
|
|
1191
1318
|
}
|
|
1192
1319
|
// r30: a MISSING model answers this face with a generic 50000
|
|
1193
1320
|
// 服务异常 — indistinguishable from a real outage. One field-face
|
|
@@ -1266,8 +1393,13 @@ export function registerRuntimeDataLane(tools) {
|
|
|
1266
1393
|
else if (totalPages > 0 && requestedPage >= totalPages) {
|
|
1267
1394
|
pageOutOfRange = true;
|
|
1268
1395
|
}
|
|
1396
|
+
const draftRows = page.content.filter((row) => {
|
|
1397
|
+
const data = row.data && typeof row.data === "object" ? row.data : row;
|
|
1398
|
+
return String(data.sequenceStatus ?? row.sequenceStatus ?? "") === "DRAFT";
|
|
1399
|
+
}).length;
|
|
1269
1400
|
const readback = {
|
|
1270
1401
|
schemaCode: args.schemaCode,
|
|
1402
|
+
...application,
|
|
1271
1403
|
...(projectionEmpty ? { projectionWarning: { reason: "business-columns-null", hint: "模型可能未配置列表视图:data.list 只投影列表列,业务列会全为 null;核对写入结果请用 data.load(或先建列表视图)" } } : {}),
|
|
1272
1404
|
...(args.queryCode !== undefined ? { queryCode: args.queryCode } : {}),
|
|
1273
1405
|
total,
|
|
@@ -1282,7 +1414,6 @@ export function registerRuntimeDataLane(tools) {
|
|
|
1282
1414
|
// 统一一个枚举键 `draftPolicy`(included | excluded | unspecified),
|
|
1283
1415
|
// 不再用两个不同键名表达同一件事(那要调用方写两套分支)。
|
|
1284
1416
|
...(() => {
|
|
1285
|
-
const draftRows = items.filter((item) => String(item.fields.sequenceStatus ?? "") === "DRAFT").length;
|
|
1286
1417
|
const policy = draftRows > 0
|
|
1287
1418
|
? {
|
|
1288
1419
|
draftPolicy: "included",
|
|
@@ -1456,6 +1587,8 @@ export function registerRuntimeDataLane(tools) {
|
|
|
1456
1587
|
...readbackProjectionArgs,
|
|
1457
1588
|
},
|
|
1458
1589
|
execute: async (session, args) => {
|
|
1590
|
+
const applicationScope = await resolveApplicationScope(session);
|
|
1591
|
+
const application = { applicationCode: applicationScope.code, applicationResolution: applicationResolutionOf(applicationScope) };
|
|
1459
1592
|
// F-A (self-audit): the combo used to be silently ignored (the workflow
|
|
1460
1593
|
// draft branch returns without honoring submit). Refuse before any wire.
|
|
1461
1594
|
if (args.submit === true && typeof args.workflowCode === "string" && args.workflowCode.trim())
|
|
@@ -1570,8 +1703,8 @@ export function registerRuntimeDataLane(tools) {
|
|
|
1570
1703
|
readbackData = (await wireCall(session, { path: ENDPOINTS.runtimeFormLoad, query: { schemaCode: args.schemaCode, sheetCode, objectId: returnedId } })).data;
|
|
1571
1704
|
sequenceStatus = extractSequenceStatus((readbackData ?? {}));
|
|
1572
1705
|
return {
|
|
1573
|
-
performed: { errcode: performed.errcode, bizObjectId: returnedId, workflowInstanceId, workItemId, workItemSource, sheetCode, resolvedFrom, formType: "save-draft-workflow" },
|
|
1574
|
-
readback: { authoritative: projectReadbackRecord(normalizeLoadedRecord((readbackData ?? {})), args), sequenceStatus, expectedSequenceStatus: "DRAFT" },
|
|
1706
|
+
performed: { errcode: performed.errcode, ...application, bizObjectId: returnedId, workflowInstanceId, workItemId, workItemSource, sheetCode, resolvedFrom, formType: "save-draft-workflow" },
|
|
1707
|
+
readback: { ...application, authoritative: projectReadbackRecord(normalizeLoadedRecord((readbackData ?? {})), args), sequenceStatus, expectedSequenceStatus: "DRAFT" },
|
|
1575
1708
|
};
|
|
1576
1709
|
}
|
|
1577
1710
|
let performed;
|
|
@@ -1599,9 +1732,12 @@ export function registerRuntimeDataLane(tools) {
|
|
|
1599
1732
|
// "rule didn't fire" misdiagnosis.
|
|
1600
1733
|
const savedRecord = normalizeLoadedRecord((readbackData ?? {}));
|
|
1601
1734
|
const savedEmptyFields = readbackEmptyFields(formData, savedRecord);
|
|
1735
|
+
const dataWarnings = await runtimeDataWarnings(session, args.schemaCode, sheetCode, formData, 1);
|
|
1602
1736
|
const plainReadback = {
|
|
1737
|
+
...application,
|
|
1603
1738
|
authoritative: projectReadbackRecord(savedRecord, args),
|
|
1604
1739
|
...(savedEmptyFields.length > 0 ? { readbackWarning: readbackWarningFor(savedEmptyFields, "data.save") } : {}),
|
|
1740
|
+
...(dataWarnings.length > 0 ? { warnings: dataWarnings } : {}),
|
|
1605
1741
|
draftRuleHint: "草稿态(DRAFT)保存不触发业务规则——DRAFT 行跳过 Create/Update 分发(DomainServiceImpl.java:240-248);要一调完成态本调用加 submit:true;已存草稿直提形状:data.submit {schemaCode, bizObjectId: 本读回的 bizObjectId, formData}(submit 自持久化)",
|
|
1606
1742
|
};
|
|
1607
1743
|
// r18 retro (G3): one-call COMPLETED creation — the agent goal is usually
|
|
@@ -1650,7 +1786,7 @@ export function registerRuntimeDataLane(tools) {
|
|
|
1650
1786
|
};
|
|
1651
1787
|
}
|
|
1652
1788
|
return {
|
|
1653
|
-
performed: { errcode: performed.errcode, bizObjectId, sheetCode, resolvedFrom },
|
|
1789
|
+
performed: { errcode: performed.errcode, ...application, bizObjectId, sheetCode, resolvedFrom },
|
|
1654
1790
|
readback: plainReadback,
|
|
1655
1791
|
};
|
|
1656
1792
|
},
|
|
@@ -1731,9 +1867,9 @@ export function registerRuntimeDataLane(tools) {
|
|
|
1731
1867
|
* gone, not that the model is clean — a submit can leave a separate draft row
|
|
1732
1868
|
* behind. After a delete, read the model's residue so "cleaned up" is a
|
|
1733
1869
|
* model-level fact (`total:0`). Best-effort: a query failure never fails the
|
|
1734
|
-
* delete, but it IS surfaced
|
|
1870
|
+
* delete, but it IS surfaced under `preExistingRows.error`.
|
|
1735
1871
|
*/
|
|
1736
|
-
async function
|
|
1872
|
+
async function preExistingRows(session, schemaCode, sheetCode) {
|
|
1737
1873
|
try {
|
|
1738
1874
|
const result = await wireCall(session, {
|
|
1739
1875
|
path: ENDPOINTS.runtimeQueryList,
|
|
@@ -1741,12 +1877,12 @@ export function registerRuntimeDataLane(tools) {
|
|
|
1741
1877
|
body: { schemaCode, queryCode: schemaCode, filters: [], mobile: false, page: 0, size: 1, queryVersion: 1, showTotal: true },
|
|
1742
1878
|
});
|
|
1743
1879
|
const page = extractBizObjectPage(result.data);
|
|
1744
|
-
return {
|
|
1880
|
+
return { preExistingRows: { total: page.total, clean: page.total === 0, sheetCode, note: "含历史既有数据,非本次创建" } };
|
|
1745
1881
|
}
|
|
1746
1882
|
catch (error) {
|
|
1747
1883
|
if (error instanceof WireError && error.kind === "business" && error.errcode === RUNTIME_DATA_WIRE.emptyModelErrcode)
|
|
1748
|
-
return {
|
|
1749
|
-
return {
|
|
1884
|
+
return { preExistingRows: { total: 0, clean: true, sheetCode, note: "含历史既有数据,非本次创建" } };
|
|
1885
|
+
return { preExistingRows: { total: null, clean: null, sheetCode, note: "含历史既有数据,非本次创建", error: error instanceof Error ? error.message : String(error) } };
|
|
1750
1886
|
}
|
|
1751
1887
|
}
|
|
1752
1888
|
writeTool({
|
|
@@ -1843,14 +1979,15 @@ export function registerRuntimeDataLane(tools) {
|
|
|
1843
1979
|
absent: true,
|
|
1844
1980
|
deleted: false,
|
|
1845
1981
|
noop: true,
|
|
1846
|
-
recoveryHint: "若另一进程刚刚删除,这是正常并发结果(already-absent),不是 id 拿错。否则:真实 id 必须取自 data.list 的 items[].id;工作流中心/门户页面回显的 id
|
|
1847
|
-
|
|
1982
|
+
recoveryHint: "若另一进程刚刚删除,这是正常并发结果(already-absent),不是 id 拿错。否则:真实 id 必须取自 data.list 的 items[].id;工作流中心/门户页面回显的 id 会脱敏。",
|
|
1983
|
+
thisRow: { absent: true, deleted: false },
|
|
1984
|
+
...(await preExistingRows(session, args.schemaCode, sheetCode)),
|
|
1848
1985
|
},
|
|
1849
1986
|
};
|
|
1850
1987
|
}
|
|
1851
1988
|
return {
|
|
1852
1989
|
performed: { errcode: one.errcode, deleted: one.deleted === true, noop: false, sheetCode, resolvedFrom },
|
|
1853
|
-
readback: { absent: one.absent, deleted: one.deleted === true, ...(await
|
|
1990
|
+
readback: { absent: one.absent, deleted: one.deleted === true, thisRow: { absent: one.absent, deleted: one.deleted === true }, ...(await preExistingRows(session, args.schemaCode, sheetCode)) },
|
|
1854
1991
|
};
|
|
1855
1992
|
}
|
|
1856
1993
|
const deleted = [];
|
|
@@ -1858,7 +1995,7 @@ export function registerRuntimeDataLane(tools) {
|
|
|
1858
1995
|
deleted.push(await deleteOne(id));
|
|
1859
1996
|
// `deleted`/`count` ride `readback` — the write envelope surfaces only
|
|
1860
1997
|
// performed/readback inline (extra top-level keys are artifact-only).
|
|
1861
|
-
return { performed: { sheetCode, resolvedFrom }, readback: { absent: true, count: deleted.length, deleted, ...(await
|
|
1998
|
+
return { performed: { sheetCode, resolvedFrom }, readback: { absent: true, count: deleted.length, deleted, ...(await preExistingRows(session, args.schemaCode, sheetCode)) } };
|
|
1862
1999
|
},
|
|
1863
2000
|
});
|
|
1864
2001
|
writeTool({
|
package/dist/mcp-graph.js
CHANGED
|
@@ -58,24 +58,27 @@ const EXPLORE_TOOL = {
|
|
|
58
58
|
name: "omc_graph_explore",
|
|
59
59
|
description: [
|
|
60
60
|
"一次查全一个目标的引用与影响面(本地索引,毫秒级)。",
|
|
61
|
-
"输入 query
|
|
61
|
+
"输入 query 可为业务名(实体 name+code 子串)或规则 code+name 词首;也可直接给字段/模型/服务 code。kind 可选 field|model|service,不给则三种都查。",
|
|
62
62
|
"返回 {index(freshness), references:{kind:[{schemaCode,ruleCode,nodeCode,nodeType,via}]}, impact:{kind:totals}, search:{matches:{model,field,service},rules}}。",
|
|
63
|
+
"references/impact 支持 offset+limit 分页;truncated 时按返回的 nextOffset 继续调用。",
|
|
63
64
|
"改/删/重命名任何字段、模型、服务之前必查;找\"关于 X 的规则\"拿 code 也走这里。",
|
|
64
65
|
"索引不含规则条件叶子与流程绑定;freshness=partial 时命中可能不完整(答案里有 gapWarning)。",
|
|
65
66
|
].join(" "),
|
|
66
67
|
inputSchema: {
|
|
67
68
|
type: "object",
|
|
68
69
|
properties: {
|
|
69
|
-
query: { type: "string", description: "
|
|
70
|
+
query: { type: "string", description: "业务名(实体 name+code 子串)、字段/模型/服务 code,或规则检索词(规则 code+name 词首匹配)" },
|
|
70
71
|
kind: { type: "string", enum: [...KINDS], description: "限定一种引用种类;省略则 field/model/service 全查" },
|
|
71
72
|
env: { type: "string", description: "期望的环境名;与索引所属环境不一致时会拒绝(防跨环境误答)" },
|
|
73
|
+
offset: { type: "integer", minimum: 0, description: "引用/实体结果起始偏移;truncated 时使用 nextOffset" },
|
|
74
|
+
limit: { type: "integer", minimum: 1, maximum: MATCH_CAP, description: "每类结果最多返回多少条(默认 50)" },
|
|
72
75
|
},
|
|
73
76
|
required: ["query"],
|
|
74
77
|
},
|
|
75
78
|
};
|
|
76
79
|
const STATUS_TOOL = {
|
|
77
80
|
name: "omc_graph_status",
|
|
78
|
-
description: "索引完整性与新鲜度:environmentName、freshness(complete|partial)、模型/规则计数、pulledAtIso、gaps
|
|
81
|
+
description: "索引完整性与新鲜度:environmentName、freshness(complete|partial)、模型/规则计数、pulledAtIso、ageMinutes、staleAfterMinutes、dbPath、coverage.applicationCodes、gaps。超过阈值会给 warning;回答前先看一眼,索引缺失会给出 pull 指引。",
|
|
79
82
|
inputSchema: { type: "object", properties: {} },
|
|
80
83
|
};
|
|
81
84
|
export function graphToolDefinitions() {
|
|
@@ -87,8 +90,9 @@ function toolResult(payload, isError = false) {
|
|
|
87
90
|
function toolError(message) {
|
|
88
91
|
return toolResult({ status: "error", message }, true);
|
|
89
92
|
}
|
|
90
|
-
function cap(rows) {
|
|
91
|
-
|
|
93
|
+
function cap(rows, offset = 0, limit = MATCH_CAP) {
|
|
94
|
+
const page = rows.slice(offset, offset + limit);
|
|
95
|
+
return { rows: page, truncated: offset + page.length < rows.length, offset, limit, ...(offset + page.length < rows.length ? { nextOffset: offset + page.length } : {}), total: rows.length };
|
|
92
96
|
}
|
|
93
97
|
/** Shared handler core — exported so tests drive it without a stdio pair. */
|
|
94
98
|
export async function executeGraphTool(name, rawArgs) {
|
|
@@ -111,17 +115,22 @@ export async function executeGraphTool(name, rawArgs) {
|
|
|
111
115
|
return toolError(`索引属于环境「${index.environmentName}」;要查「${args.env.trim()}」请先 omc graph pull --env ${args.env.trim()} 重建索引`);
|
|
112
116
|
}
|
|
113
117
|
const kinds = kind === undefined ? [...KINDS] : [kind];
|
|
118
|
+
const offset = typeof args.offset === "number" && Number.isSafeInteger(args.offset) && args.offset >= 0 ? args.offset : 0;
|
|
119
|
+
const limit = typeof args.limit === "number" && Number.isSafeInteger(args.limit) && args.limit > 0 && args.limit <= MATCH_CAP ? args.limit : MATCH_CAP;
|
|
114
120
|
const references = {};
|
|
115
121
|
const impact = {};
|
|
116
122
|
for (const one of kinds) {
|
|
117
123
|
const ref = queryGraphReferences(one, query);
|
|
118
|
-
const capped = cap(ref.matches);
|
|
124
|
+
const capped = cap(ref.matches, offset, limit);
|
|
119
125
|
// r35a-F4c(真机 2026-11-06):**MCP 面是第三个消费者**——CLI 的 references
|
|
120
126
|
// 与 impact 都带 coverage 了,这里若不显式透传,MCP 用户看到的仍是
|
|
121
127
|
// 「matches:[] + 一句旧 note」,照样会把「表单没扫」误读成「没人引用」。
|
|
122
128
|
references[one] = {
|
|
123
129
|
count: ref.matches.length,
|
|
124
|
-
|
|
130
|
+
rows: capped.rows,
|
|
131
|
+
offset: capped.offset,
|
|
132
|
+
limit: capped.limit,
|
|
133
|
+
...(capped.truncated ? { truncated: true, nextOffset: capped.nextOffset, note: `共 ${capped.total} 条,已给 ${capped.rows.length} 条,offset=${capped.offset} 取下一段(调用时传 offset=${capped.nextOffset}、limit=${capped.limit})` } : {}),
|
|
125
134
|
...(ref.coverage ? { coverage: ref.coverage } : {}),
|
|
126
135
|
...(ref.gapWarning ? { gapWarning: ref.gapWarning } : {}),
|
|
127
136
|
};
|
|
@@ -129,26 +138,36 @@ export async function executeGraphTool(name, rawArgs) {
|
|
|
129
138
|
const imp = impactGraph(one, query);
|
|
130
139
|
impact[one] = {
|
|
131
140
|
totals: imp.totals,
|
|
132
|
-
rules: cap(imp.rules),
|
|
133
|
-
datarules: cap(imp.datarules),
|
|
134
|
-
listviews: cap(imp.listviews),
|
|
141
|
+
rules: cap(imp.rules, offset, limit),
|
|
142
|
+
datarules: cap(imp.datarules, offset, limit),
|
|
143
|
+
listviews: cap(imp.listviews, offset, limit),
|
|
135
144
|
...(imp.coverage ? { coverage: imp.coverage } : {}),
|
|
136
145
|
};
|
|
137
146
|
}
|
|
138
147
|
}
|
|
139
|
-
const search = searchGraphRules(query);
|
|
148
|
+
const search = searchGraphRules(query, limit, offset);
|
|
149
|
+
const resolvedEntity = search.matches.model.items[0] ? { kind: "model", schemaCode: search.matches.model.items[0].schemaCode, code: search.matches.model.items[0].schemaCode } :
|
|
150
|
+
search.matches.field.items[0] ? { kind: "field", schemaCode: search.matches.field.items[0].schemaCode, code: search.matches.field.items[0].code } :
|
|
151
|
+
search.matches.service.items[0] ? { kind: "service", schemaCode: "", code: search.matches.service.items[0].code } :
|
|
152
|
+
search.rules[0] ? { kind: "rule", schemaCode: search.rules[0].schemaCode, code: search.rules[0].ruleCode } : undefined;
|
|
153
|
+
const searchNote = "实体检索:业务名/实体 name+code 使用子串匹配。\n规则检索:规则 code+name 使用词首匹配(中文子串不命中)。";
|
|
154
|
+
const entityTruncated = Object.values(search.matches).some((match) => match.truncated === true);
|
|
155
|
+
const paginationNote = entityTruncated ? `\n结果已截断:共 N 条时请使用对应 match 的 nextOffset,并在下一次调用传 offset=nextOffset、limit=${limit} 取下一段。` : "";
|
|
140
156
|
return toolResult({
|
|
141
157
|
status: "ok",
|
|
142
158
|
query,
|
|
159
|
+
found: resolvedEntity !== undefined,
|
|
160
|
+
...(resolvedEntity ? { resolved: resolvedEntity } : {}),
|
|
143
161
|
...(kind !== undefined ? { kind } : {}),
|
|
144
162
|
index,
|
|
145
163
|
references,
|
|
146
164
|
impact,
|
|
147
165
|
search: { count: search.rules.length, rules: search.rules, matches: search.matches, coverage: search.coverage },
|
|
148
|
-
|
|
166
|
+
searchNote: `${searchNote}${paginationNote}`,
|
|
167
|
+
...(search.note ? { searchNote: `${searchNote}\n${search.note}` } : {}),
|
|
149
168
|
// 旧句「索引不含规则条件叶子与流程绑定」漏了表单与流程权限(r35a-F4 实证
|
|
150
169
|
// 探针据此差点误删被表单引用的字段)。改为指向结构化 coverage,不再靠散文。
|
|
151
|
-
note:
|
|
170
|
+
note: `零命中只代表 references[].coverage.scanned 面没有命中;本索引覆盖应用:${index.coverage.applicationCodes.join("、") || "(未知)"},不代表其他应用没有结果;notScanned 面(尤其 form 与 workflow.permissions)未扫描,不可据此判定可安全删除或无影响`,
|
|
152
171
|
});
|
|
153
172
|
}
|
|
154
173
|
return toolError(`未知工具:${name}`);
|
|
@@ -17,6 +17,7 @@ import { wireCall, WireError, wireRows } from "./wire.js";
|
|
|
17
17
|
* endpoint — see .scratch/backend-online-coding/recon.md §8).
|
|
18
18
|
*
|
|
19
19
|
* Commands:
|
|
20
|
+
* ⚠ test 会 publish 并切编码模式(不可逆)
|
|
20
21
|
* pull remote → tree + manifest (environment-stamped)
|
|
21
22
|
* status local edits vs manifest, plus optional remote drift (--deep)
|
|
22
23
|
* apply changed files → programming.save (→ --publish)
|
|
@@ -801,18 +802,30 @@ export async function cmdProgrammingTest(options) {
|
|
|
801
802
|
const targetSchema = String(dataSpec.schema ?? schemaCode);
|
|
802
803
|
const filter = (dataSpec.filter ?? {});
|
|
803
804
|
const expect = (dataSpec.expect ?? {});
|
|
805
|
+
// data.list is only a locator here. Its projection is intentionally not
|
|
806
|
+
// used as evidence: omitted columns are returned as null and used to turn
|
|
807
|
+
// a correct rule into a false failure (F11).
|
|
804
808
|
const listed = await callTool("data.list", {
|
|
805
809
|
environment: session.environmentName,
|
|
806
810
|
schemaCode: targetSchema,
|
|
807
811
|
...filterOf(filter),
|
|
808
|
-
displayFields:
|
|
812
|
+
displayFields: ["bizObjectId", "id"],
|
|
809
813
|
}, context);
|
|
810
814
|
const rows = (listed.rows ?? listed.items ?? []);
|
|
811
|
-
const
|
|
815
|
+
const loadedRows = [];
|
|
816
|
+
for (const row of rows) {
|
|
817
|
+
const id = String(row.bizObjectId ?? row.id ?? "");
|
|
818
|
+
if (!id)
|
|
819
|
+
continue;
|
|
820
|
+
const loaded = await callTool("data.load", { environment: session.environmentName, schemaCode: targetSchema, bizObjectId: id, full: true }, context);
|
|
821
|
+
if (loaded.exists === true && loaded.data && typeof loaded.data === "object")
|
|
822
|
+
loadedRows.push(loaded.data);
|
|
823
|
+
}
|
|
824
|
+
const matched = loadedRows.filter((row) => matchExpectation(row, expect).ok);
|
|
812
825
|
const ok = matched.length > 0;
|
|
813
826
|
if (!ok)
|
|
814
827
|
assertsFailed += 1;
|
|
815
|
-
assertReports.push({ schema: targetSchema, filter, expect, rows:
|
|
828
|
+
assertReports.push({ schema: targetSchema, filter, expect, rows: loadedRows.length, ok, ...(ok ? {} : { sample: loadedRows.slice(0, 3) }) });
|
|
816
829
|
}
|
|
817
830
|
report.asserts = assertReports;
|
|
818
831
|
// 5. Cleanup — only rows the fixture names (seed filter), never a blanket key.
|
package/dist/read-shapes.js
CHANGED
|
@@ -71,7 +71,7 @@ const READ_SHAPES = {
|
|
|
71
71
|
keys: ["bizObjectId", "exists", "data:{...}"],
|
|
72
72
|
},
|
|
73
73
|
"app.list": {
|
|
74
|
-
keys: ["applications:array<{code,name,groupId
|
|
74
|
+
keys: ["applications:array<{code,name,groupId?,modelCount}>", "applicationsCount", "omitted"],
|
|
75
75
|
notes: ["applicationsCount + omitted 与 discover 同构;omitted:false 表示内联即全集"],
|
|
76
76
|
},
|
|
77
77
|
"model.list": {
|
|
@@ -85,8 +85,8 @@ const READ_SHAPES = {
|
|
|
85
85
|
},
|
|
86
86
|
"model.inspect": {
|
|
87
87
|
// Slim/compact face (window diet): field inventory is `fieldCodes`, not fields[].
|
|
88
|
-
keys: ["schemaCode", "fieldCodes", "form", "listview", "ruleCodes", "datarules", "workflow", "hint"],
|
|
89
|
-
notes: ["slim 面键是 fieldCodes(不是 fields[].code
|
|
88
|
+
keys: ["schemaCode", "applicationCode", "fieldCodes", "form", "listview", "ruleCodes", "datarules", "workflow", "hint"],
|
|
89
|
+
notes: ["slim 面键是 fieldCodes(不是 fields[].code);all:true 时 fields/artifact 行含 options"],
|
|
90
90
|
},
|
|
91
91
|
"field.list": {
|
|
92
92
|
keys: ["fields:array<{code,name,type,...}>", "modelPresent?"],
|
package/dist/rollback.js
CHANGED
|
@@ -8,6 +8,7 @@ import { readJournal, resolveJournalEntry, laterWritesOnTarget } from "./journal
|
|
|
8
8
|
import { canonicalForVerify, deepDiff, pruneNullish } from "./rollback-spec.js";
|
|
9
9
|
import { sha256 } from "./support.js";
|
|
10
10
|
import { emitTransportResult } from "./command-transport.js";
|
|
11
|
+
import { acquireWriteLock } from "./write-lock.js";
|
|
11
12
|
/** Pure runtime assembly: an injected context (tests) or the default one. */
|
|
12
13
|
function runtime(context) {
|
|
13
14
|
return context ?? makeToolContext({ sessions: new SessionManager(new ConfigStore(defaultPaths())) });
|
|
@@ -191,25 +192,53 @@ export async function applyRollback(ref, options, context) {
|
|
|
191
192
|
};
|
|
192
193
|
}
|
|
193
194
|
const results = [];
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
195
|
+
// The inverse plan is intentionally kept as the seam for future inverse
|
|
196
|
+
// extensions (for example onlinejs.compose remove:true): all new restore
|
|
197
|
+
// shapes still become governed calls here, without changing the evidence
|
|
198
|
+
// or lock protocol.
|
|
199
|
+
const waitMs = options.waitMs ?? 0;
|
|
200
|
+
const deadline = Date.now() + waitMs;
|
|
201
|
+
let lock;
|
|
202
|
+
for (;;) {
|
|
203
|
+
lock = await acquireWriteLock(entry.environment);
|
|
204
|
+
if (lock.held)
|
|
205
|
+
break;
|
|
206
|
+
if (Date.now() >= deadline) {
|
|
207
|
+
return {
|
|
208
|
+
status: "refused",
|
|
209
|
+
blocker: "write-lock-held",
|
|
210
|
+
rollbackOf: { seq: entry.seq, tool: entry.tool },
|
|
211
|
+
waitMs,
|
|
212
|
+
lockOwner: lock.owner ?? null,
|
|
213
|
+
message: `回滚写锁被占用;已等待 ${waitMs}ms。可用 --wait <ms> 有界排队后重试`,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
await new Promise((resolve) => setTimeout(resolve, Math.min(200, Math.max(0, deadline - Date.now()))));
|
|
217
|
+
}
|
|
218
|
+
try {
|
|
219
|
+
for (const [index, call] of plan.inverse.calls.entries()) {
|
|
220
|
+
const { exitCode, result } = await runToolCall(call.tool, {
|
|
221
|
+
args: { ...call.args, environment: entry.environment, ...(options.confirmProduction ? { confirmProduction: true } : {}) },
|
|
222
|
+
requestId: `omc-rollback-${entry.seq}-${index + 1}`,
|
|
223
|
+
context: ctx,
|
|
224
|
+
print: () => undefined,
|
|
225
|
+
});
|
|
226
|
+
results.push({
|
|
227
|
+
tool: call.tool,
|
|
228
|
+
exitCode,
|
|
229
|
+
status: result.status,
|
|
230
|
+
...(result.deliveryState ? { deliveryState: result.deliveryState } : {}),
|
|
231
|
+
...(result.summary ? { summary: result.summary } : {}),
|
|
232
|
+
// r55: exit-code-only results hid the reason a restore call failed
|
|
233
|
+
// (input-invalid showed as a bare "status=error"). Surface the shape.
|
|
234
|
+
...(exitCode === 0 ? {} : { blocker: result.blocker ?? null, message: result.message ?? null, ...(result.issues !== undefined ? { issues: result.issues } : {}), ...(result.diagnostic !== undefined ? { diagnostic: result.diagnostic } : {}) }),
|
|
235
|
+
});
|
|
236
|
+
if (exitCode !== 0)
|
|
237
|
+
return { status: "stopped", rollbackOf: { seq: entry.seq, tool: entry.tool }, step: index + 1, results, message: `恢复调用 ${call.tool} 未成功(status=${result.status});已执行的调用已各自记 journal,可对已完成步骤再 plan` };
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
finally {
|
|
241
|
+
await lock.release();
|
|
213
242
|
}
|
|
214
243
|
// Authoritative verify: re-read the target and compare against the
|
|
215
244
|
// canonicalized before. "restored" tolerates the write pipeline
|
|
@@ -232,6 +261,12 @@ export async function applyRollback(ref, options, context) {
|
|
|
232
261
|
rollbackOf: { seq: entry.seq, tool: entry.tool, target: entry.target },
|
|
233
262
|
calls: results,
|
|
234
263
|
verify,
|
|
264
|
+
// Stable evidence is present on both the restored and mismatch paths.
|
|
265
|
+
// `recoveredTo` deliberately identifies the original journal target; the
|
|
266
|
+
// replay writes are separately journaled and can be rolled back again.
|
|
267
|
+
restoredSha256: restoredSha,
|
|
268
|
+
matchesBefore: verify === "restored",
|
|
269
|
+
recoveredTo: { seq: entry.seq, ts: entry.ts },
|
|
235
270
|
...(verify === "mismatch" ? { beforeSha, restoredSha, message: "恢复调用已完成但读回与 before 存在值冲突(非默认显式化):立即人工核对目标现态(omc rollback plan <seq> 的 diff 可复看差异)" } : {}),
|
|
236
271
|
hint: "本次恢复的每次调用都走了完整写闸门并已记 journal;恢复本身可再回滚(rollback plan <新 seq>)",
|
|
237
272
|
};
|
package/dist/write.js
CHANGED
|
@@ -143,7 +143,7 @@ async function governedWrite(ctx, tool, args, execute, dryRunPayload, readOnlyEs
|
|
|
143
143
|
const artifactRef = writeArtifact(ctx, `${tool.name}-${randomUUID().slice(0, 8)}.json`, artifact);
|
|
144
144
|
return { status: executed.status, steps: executed.steps, failedStep: executed.failedStep, ...executed.envelope, ...(args.requestId ? { requestId: args.requestId } : {}), artifactFingerprint: artifactRef.detailSha256 };
|
|
145
145
|
}
|
|
146
|
-
const { performed, readback, steps, halfSuccess, irreversible } = executed;
|
|
146
|
+
const { performed, readback, steps, halfSuccess, irreversible, rollbackReceipt, runtimeVerification } = executed;
|
|
147
147
|
// Readback gate (live 2026-10-17): a tool that reports `readback.present:false`
|
|
148
148
|
// has NOT proven its own write — the platform accepted the request but the
|
|
149
149
|
// object is absent from the authoritative face. "Called without error" must
|
|
@@ -204,17 +204,21 @@ async function governedWrite(ctx, tool, args, execute, dryRunPayload, readOnlyEs
|
|
|
204
204
|
return {
|
|
205
205
|
status: "ok",
|
|
206
206
|
deliveryState,
|
|
207
|
+
...((typeof performed.applicationCode === "string" ? { applicationCode: performed.applicationCode } : typeof readback.applicationCode === "string" ? { applicationCode: readback.applicationCode } : {})),
|
|
207
208
|
...(readOnlyEscapeNote !== undefined ? { readOnlyEscape: readOnlyEscapeNote } : {}),
|
|
208
209
|
performed,
|
|
209
210
|
readback,
|
|
210
211
|
...(readbackWarning ? { readbackWarning } : {}),
|
|
211
212
|
...(steps !== undefined ? { steps } : {}),
|
|
212
213
|
...(halfSuccess !== undefined ? { halfSuccess } : {}),
|
|
214
|
+
...(runtimeVerification !== undefined ? { runtimeVerification } : {}),
|
|
213
215
|
...(typeof irreversible === "string" ? { irreversible } : {}),
|
|
214
216
|
// r30a F1: first write → replay:"none"; duplicate only on true replay.
|
|
215
217
|
...(args.requestId ? { requestId: args.requestId, idempotency: idempotencyDeclaration(tool.name, "none") } : {}),
|
|
216
218
|
...(productionConfirmed ? { productionConfirmed: true } : {}),
|
|
217
|
-
...(rollbackRef
|
|
219
|
+
...(rollbackRef
|
|
220
|
+
? { rollback: { ...(rollbackReceipt ?? {}), seq: rollbackRef.seq, ...(rollbackReceipt ? {} : { hint: rollbackRef.hint }) } }
|
|
221
|
+
: {}),
|
|
218
222
|
summary: writeSummary(tool.name, args, { performed, readback }, "done"),
|
|
219
223
|
detailFile: artifactRef.detailFile,
|
|
220
224
|
detailSha256: artifactRef.detailSha256,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stalfh233/omc-cli",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.18",
|
|
4
4
|
"description": "CloudPivot 8.6 configuration capability CLI — governed, fail-closed, coverage-manifest bounded.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
57
|
"commander": "14.0.3",
|
|
58
|
-
"omc-core": "0.6.
|
|
58
|
+
"omc-core": "0.6.18",
|
|
59
59
|
"proper-lockfile": "4.1.2",
|
|
60
60
|
"write-file-atomic": "6.0.0",
|
|
61
61
|
"zod": "^3.23.8",
|
|
@@ -73,6 +73,6 @@
|
|
|
73
73
|
"directory": "packages/omc-cli"
|
|
74
74
|
},
|
|
75
75
|
"optionalDependencies": {
|
|
76
|
-
"@stalfh233/omc-fake-platform": "0.6.
|
|
76
|
+
"@stalfh233/omc-fake-platform": "0.6.18"
|
|
77
77
|
}
|
|
78
78
|
}
|