@stalfh233/omc-cli 0.6.7 → 0.6.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/dist/approval-flow.js +5 -3
- package/dist/bizservice-config.js +26 -10
- package/dist/call.js +47 -0
- package/dist/capability-overview.js +47 -24
- package/dist/cli-contract.js +73 -26
- package/dist/cli-output.js +41 -2
- package/dist/cli-stdout.js +74 -0
- package/dist/cli.js +135 -52
- package/dist/command-transport.js +8 -1
- package/dist/config-store.js +17 -2
- package/dist/discovery.js +62 -3
- package/dist/field-families.js +35 -6
- package/dist/goal-routes.js +118 -11
- package/dist/human-summary.js +11 -0
- package/dist/lanes/app-contract.js +12 -5
- package/dist/lanes/app.js +221 -134
- package/dist/lanes/apply-changes.js +2 -1
- package/dist/lanes/b2-registration-contract.js +3 -3
- package/dist/lanes/b2-registration.js +890 -693
- package/dist/lanes/businessrule.js +75 -51
- package/dist/lanes/custom-page.js +166 -115
- package/dist/lanes/datarule-contract.js +2 -2
- package/dist/lanes/datarule.js +238 -154
- package/dist/lanes/dictionary.js +253 -110
- package/dist/lanes/environment.js +57 -9
- package/dist/lanes/field.js +160 -107
- package/dist/lanes/folder-contract.js +7 -3
- package/dist/lanes/folder.js +249 -118
- package/dist/lanes/form-contract.js +1 -1
- package/dist/lanes/form.js +188 -64
- package/dist/lanes/list-view.js +405 -369
- package/dist/lanes/meta.js +31 -6
- package/dist/lanes/model-contract.js +1 -1
- package/dist/lanes/model.js +610 -512
- package/dist/lanes/online-js-contract.js +1 -1
- package/dist/lanes/online-js.js +160 -146
- package/dist/lanes/organization.js +905 -640
- package/dist/lanes/programming.js +274 -136
- package/dist/lanes/report-contract.js +2 -2
- package/dist/lanes/report.js +354 -198
- package/dist/lanes/rule-lifecycle.js +294 -140
- package/dist/lanes/runtime-data-contract.js +4 -4
- package/dist/lanes/runtime-data.js +869 -745
- package/dist/lanes/workflow-contract.js +3 -3
- package/dist/lanes/workflow.js +732 -605
- package/dist/model-scaffold.js +26 -5
- package/dist/path-projection.js +196 -14
- package/dist/plan.js +25 -0
- package/dist/programming-workflow.js +55 -3
- package/dist/read-shapes.js +146 -0
- package/dist/read.js +40 -9
- package/dist/recipes/businessrule.js +74 -24
- package/dist/recipes/dictionary.js +7 -4
- package/dist/recipes/form.js +46 -0
- package/dist/recipes/runtime-data.js +3 -2
- package/dist/refusal-guidance.js +29 -5
- package/dist/rule-support.js +14 -1
- package/dist/rule-when.js +2 -2
- package/dist/tool-types.js +9 -3
- package/dist/version-family-gaps.js +101 -0
- package/dist/write.js +2 -1
- package/package.json +6 -4
- package/skills/omc-glossary.md +13 -0
- package/skills/omc-refusals.md +13 -5
- package/skills/omc-tool-selection.md +2 -1
- package/skills/omc-write-hazards.md +5 -2
package/dist/lanes/app.js
CHANGED
|
@@ -4,8 +4,9 @@ import { capabilityArg, environmentArg } from "../args.js";
|
|
|
4
4
|
import { governedRead } from "../read.js";
|
|
5
5
|
import { writeTool } from "../write.js";
|
|
6
6
|
import { defineTool, ToolRefusalError } from "../tool-types.js";
|
|
7
|
-
import { wireCall, wireRows } from "../wire.js";
|
|
7
|
+
import { wireCall, wireRows, WireError } from "../wire.js";
|
|
8
8
|
import { APP_LANE_CONTRACT } from "./app-contract.js";
|
|
9
|
+
import { readAppTree } from "./folder.js";
|
|
9
10
|
const APP_LANE = APP_LANE_CONTRACT.lane;
|
|
10
11
|
const APP_TOOLS = APP_LANE_CONTRACT.tools;
|
|
11
12
|
function appRollbackSpec(toolName) {
|
|
@@ -55,144 +56,230 @@ export function registerAppLane(tools) {
|
|
|
55
56
|
return { applications };
|
|
56
57
|
}),
|
|
57
58
|
}));
|
|
58
|
-
writeTool(
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
59
|
+
writeTool({
|
|
60
|
+
tools,
|
|
61
|
+
name: "app.create",
|
|
62
|
+
lane: APP_LANE,
|
|
63
|
+
scope: APP_TOOLS["app.create"].scope,
|
|
64
|
+
description: APP_TOOLS["app.create"].description,
|
|
65
|
+
inputSchema: { code: z.string().min(1), name: z.string().min(1), groupId: z.string().optional() },
|
|
66
|
+
execute: async (session, args) => {
|
|
67
|
+
const existing = wireRows((await wireCall(session, { path: ENDPOINTS.applicationList, query: {} })).data).find((row) => (row.appCode ?? row.code) === args.code);
|
|
68
|
+
if (existing)
|
|
69
|
+
throw new ToolRefusalError({ status: "refused", blocker: "app-create-duplicate", code: args.code, message: `应用 ${args.code} 已存在;拒绝重复 POST,不能用写后存在证明本次创建` });
|
|
70
|
+
let groupId = args.groupId;
|
|
71
|
+
let resolvedFrom = args.groupId ? "explicit" : undefined;
|
|
72
|
+
let warning;
|
|
73
|
+
let groups = [];
|
|
74
|
+
const readGroups = async () => wireRows((await wireCall(session, { path: ENDPOINTS.applicationGroupList, query: {} })).data);
|
|
75
|
+
if (!groupId) {
|
|
76
|
+
const configured = session.profile.applicationCode?.trim();
|
|
77
|
+
const apps = wireRows((await wireCall(session, { path: ENDPOINTS.applicationList, query: {} })).data);
|
|
78
|
+
const configuredApp = configured ? apps.find((row) => (row.appCode ?? row.code) === configured) : undefined;
|
|
79
|
+
if (configuredApp && typeof configuredApp.groupId === "string" && configuredApp.groupId) {
|
|
80
|
+
groupId = configuredApp.groupId;
|
|
81
|
+
resolvedFrom = "auto-configured-app-group";
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
groups = await readGroups();
|
|
85
|
+
const withId = groups.filter((row) => typeof row.id === "string" && String(row.id));
|
|
86
|
+
// Prefer a named non-fallback group. The tenant normally has one (e.g.
|
|
87
|
+
// code=default); landing in the fallback group `all` makes the app
|
|
88
|
+
// portal-hidden, which the user cannot see and the create cannot undo
|
|
89
|
+
// without app.update.
|
|
90
|
+
const named = withId.find((row) => String(row.code ?? "").toLowerCase() !== "all");
|
|
91
|
+
const group = named ?? withId[0];
|
|
92
|
+
if (!group)
|
|
93
|
+
throw new ToolRefusalError({
|
|
94
|
+
status: "refused",
|
|
95
|
+
blocker: "app-create-prerequisite-missing",
|
|
96
|
+
preconditions: [{ code: "application-group-required", satisfied: false }],
|
|
97
|
+
recovery: [{ code: "application-group-missing", action: "由管理侧创建应用分组后重新 discover_environment" }],
|
|
98
|
+
message: "创建应用前置条件不满足:租户没有可用应用分组;零 create wire",
|
|
99
|
+
});
|
|
100
|
+
groupId = String(group.id);
|
|
101
|
+
resolvedFrom = "auto-first-group";
|
|
102
|
+
const fallback = String(group.code ?? "").toLowerCase() === "all";
|
|
103
|
+
warning = fallback ? "resolved-to-fallback-group; portalHidden:true" : "resolved-to-first-group";
|
|
104
|
+
}
|
|
74
105
|
}
|
|
75
|
-
|
|
106
|
+
if (groups.length === 0)
|
|
76
107
|
groups = await readGroups();
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
108
|
+
const selectedGroup = groups.find((row) => String(row.id ?? "") === String(groupId));
|
|
109
|
+
if (!selectedGroup)
|
|
110
|
+
throw new ToolRefusalError({
|
|
111
|
+
status: "refused",
|
|
112
|
+
blocker: "app-create-prerequisite-missing",
|
|
113
|
+
groupId,
|
|
114
|
+
preconditions: [{ code: "application-group-required", satisfied: false, groupId }],
|
|
115
|
+
recovery: [{ code: "application-group-missing", action: "改用 discover_environment 返回的有效 groupId,或由管理侧修复分组" }],
|
|
116
|
+
message: `创建应用前置条件不满足:分组 ${String(groupId)} 无法解析;零 create wire`,
|
|
117
|
+
});
|
|
118
|
+
const performed = await wireCall(session, {
|
|
119
|
+
path: ENDPOINTS.applicationCreate,
|
|
120
|
+
method: "POST",
|
|
121
|
+
body: {
|
|
122
|
+
groupId,
|
|
123
|
+
code: args.code,
|
|
124
|
+
name: args.name,
|
|
125
|
+
name_i18n: JSON.stringify({ "zh-CN": args.name }),
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
const present = wireRows((await wireCall(session, { path: ENDPOINTS.applicationList, query: {} })).data).some((row) => (row.appCode ?? row.code) === args.code);
|
|
129
|
+
// Portal visibility is a DERIVED FACT (group membership), reported on
|
|
130
|
+
// every readback so a caller never has to infer it from `warning` and
|
|
131
|
+
// never reports `ok` while claiming visibility it did not verify.
|
|
132
|
+
const portalHidden = isFallbackGroup(selectedGroup);
|
|
133
|
+
// r28d F2: platform seeds a default Folder (name_i18n.en=defaultGroup).
|
|
134
|
+
// Surface it on create so callers don't mistake it for a user folder.
|
|
135
|
+
const tree = present ? await readAppTree(session, args.code) : [];
|
|
136
|
+
const defaultFolder = tree.find((row) => row.type === "Folder" && row.isDefault === true);
|
|
137
|
+
return {
|
|
138
|
+
performed: { errcode: performed.errcode, groupId, resolvedFrom, selectedGroup: { id: selectedGroup.id, code: selectedGroup.code, name: selectedGroup.name } },
|
|
139
|
+
readback: {
|
|
140
|
+
present,
|
|
141
|
+
selectedGroup: { id: selectedGroup.id, code: selectedGroup.code, name: selectedGroup.name },
|
|
142
|
+
...(defaultFolder
|
|
143
|
+
? {
|
|
144
|
+
defaultFolder: {
|
|
145
|
+
id: defaultFolder.id,
|
|
146
|
+
path: defaultFolder.path,
|
|
147
|
+
name: defaultFolder.name,
|
|
148
|
+
isDefault: true,
|
|
149
|
+
},
|
|
150
|
+
}
|
|
151
|
+
: {}),
|
|
152
|
+
postconditions: { created: present, published: false, portalVisible: !portalHidden, portalHidden },
|
|
153
|
+
portalHidden,
|
|
154
|
+
...(warning ? { warning } : {}),
|
|
155
|
+
publishBoundary: { inScope: false, nextAction: "publish-in-designer", reason: "app publish is outside the CLI configuration boundary" },
|
|
156
|
+
manualActions: [
|
|
157
|
+
{ action: "publish-in-designer", detail: "在设计器发布应用,否则门户不可见" },
|
|
158
|
+
{ action: "portal-visibility-check", detail: "发布后核对应用位于具名分组并在门户可见" },
|
|
159
|
+
],
|
|
160
|
+
recovery: [{ code: "publish-in-designer", action: "在设计器发布应用并核对其位于具名分组;再做门户可见性验证" }],
|
|
161
|
+
note: "应用发布属边界外:建成后需人工在设计器发布并核对该分组,否则门户不可见。" +
|
|
162
|
+
(defaultFolder
|
|
163
|
+
? ` 平台已自动生成默认分组(folderId=${String(defaultFolder.id)},isDefault:true);空目录不挡 app.delete,有模型/报表/自定义页才需先清。`
|
|
164
|
+
: ""),
|
|
165
|
+
},
|
|
166
|
+
};
|
|
167
|
+
},
|
|
168
|
+
rollback: appRollbackSpec("app.create"),
|
|
169
|
+
});
|
|
170
|
+
writeTool({
|
|
171
|
+
tools,
|
|
172
|
+
name: "app.update",
|
|
173
|
+
lane: APP_LANE,
|
|
174
|
+
scope: APP_TOOLS["app.update"].scope,
|
|
175
|
+
description: APP_TOOLS["app.update"].description,
|
|
176
|
+
inputSchema: { code: z.string().min(1), groupId: z.string().min(1) },
|
|
177
|
+
execute: async (session, args) => {
|
|
178
|
+
const record = (await wireCall(session, { path: ENDPOINTS.applicationGet, query: { appCode: args.code } })).data;
|
|
179
|
+
if (!record || (typeof record === "object" && Object.keys(record).length === 0))
|
|
180
|
+
return { performed: { skipped: "already-absent" }, readback: { absent: true } };
|
|
181
|
+
const previousGroupId = typeof record.groupId === "string" ? record.groupId : null;
|
|
182
|
+
const performed = await wireCall(session, {
|
|
183
|
+
path: ENDPOINTS.applicationUpdate,
|
|
184
|
+
method: "POST",
|
|
185
|
+
body: {
|
|
186
|
+
...record,
|
|
187
|
+
groupId: args.groupId,
|
|
188
|
+
enabled: record.enabled,
|
|
189
|
+
},
|
|
190
|
+
});
|
|
191
|
+
// Two independent reads: apppackage/list rows carry `published` (and the
|
|
192
|
+
// group), while the whole-record `get` carries `enabled`. Merge them so
|
|
193
|
+
// the readback can assert portal visibility without a second caller round.
|
|
194
|
+
const listRow = wireRows((await wireCall(session, { path: ENDPOINTS.applicationList, query: {} })).data).find((row) => (row.appCode ?? row.code) === args.code);
|
|
195
|
+
const groupRow = wireRows((await wireCall(session, { path: ENDPOINTS.applicationGroupList, query: {} })).data).find((row) => String(row.id ?? "") === String(args.groupId));
|
|
196
|
+
const after = (await wireCall(session, { path: ENDPOINTS.applicationGet, query: { appCode: args.code } })).data;
|
|
197
|
+
const portalHiddenByGroup = groupRow === undefined || isFallbackGroup(groupRow);
|
|
198
|
+
// `groupChanged` must reflect a REAL change: comparing the stored value
|
|
199
|
+
// against the requested one — an update that lands on the same group is
|
|
200
|
+
// not a change, and reporting it as one made the flag untrustworthy
|
|
201
|
+
// (dogfood R3: groupChanged:true while previousGroupId === groupId).
|
|
202
|
+
const storedGroupId = typeof after?.groupId === "string" ? after.groupId : listRow?.groupId ?? null;
|
|
203
|
+
const groupChanged = String(storedGroupId ?? "") !== String(previousGroupId ?? "");
|
|
204
|
+
return {
|
|
205
|
+
performed: { errcode: performed.errcode, code: args.code, groupId: args.groupId, previousGroupId },
|
|
206
|
+
readback: {
|
|
207
|
+
present: listRow !== undefined || after !== null,
|
|
208
|
+
groupId: storedGroupId,
|
|
209
|
+
previousGroupId,
|
|
210
|
+
groupChanged,
|
|
211
|
+
groupCode: groupRow?.code ?? null,
|
|
212
|
+
published: listRow?.published === true,
|
|
213
|
+
portalHidden: portalHiddenByGroup,
|
|
214
|
+
},
|
|
215
|
+
};
|
|
216
|
+
},
|
|
217
|
+
rollback: appRollbackSpec("app.update"),
|
|
218
|
+
});
|
|
219
|
+
writeTool({
|
|
220
|
+
tools,
|
|
221
|
+
name: "app.delete",
|
|
222
|
+
lane: APP_LANE,
|
|
223
|
+
scope: APP_TOOLS["app.delete"].scope,
|
|
224
|
+
description: APP_TOOLS["app.delete"].description,
|
|
225
|
+
inputSchema: { code: z.string().min(1) },
|
|
226
|
+
execute: async (session, args) => {
|
|
227
|
+
const readApps = async () => wireRows((await wireCall(session, { path: ENDPOINTS.applicationList, query: {} })).data);
|
|
228
|
+
const target = (await readApps()).find((row) => (row.appCode ?? row.code) === args.code);
|
|
229
|
+
if (!target)
|
|
230
|
+
return { performed: { skipped: "already-absent" }, readback: { absent: true } };
|
|
231
|
+
// r28d F8: live 600006 APP_PACKAGE_HAS_NODE fires when non-Folder nodes
|
|
232
|
+
// remain (models / reports / custom pages). Empty folders — including the
|
|
233
|
+
// platform default group — do NOT block. Pre-check with the same tree face
|
|
234
|
+
// folder.delete uses, so the refusal lists children + a runnable nextCommand
|
|
235
|
+
// instead of a bare wire-business 600006 → omc-refusals.
|
|
236
|
+
const tree = await readAppTree(session, args.code);
|
|
237
|
+
const blockers = tree.filter((row) => String(row.type ?? "") !== "Folder");
|
|
238
|
+
if (blockers.length > 0) {
|
|
239
|
+
throw new ToolRefusalError({
|
|
240
|
+
status: "refused",
|
|
241
|
+
blocker: "app-not-empty",
|
|
242
|
+
errcode: 600006,
|
|
243
|
+
code: args.code,
|
|
244
|
+
children: blockers.map((row) => ({ id: row.id, type: row.type, code: row.code ?? null, path: row.path })).slice(0, 40),
|
|
245
|
+
nextCommand: `omc call folder.tree --env <env> --args '{"applicationCode":"${args.code}"}'`,
|
|
246
|
+
message: `应用 ${args.code} 还有 ${blockers.length} 个非目录下属节点(平台 APP_PACKAGE_HAS_NODE / 600006)。` +
|
|
247
|
+
"先 model.delete / report.delete / custompage.delete 清掉这些节点;空目录(含 isDefault 默认分组)可留着,app.delete 会一并带走。" +
|
|
248
|
+
`下一步:${`omc call folder.tree --env <env> --args '{"applicationCode":"${args.code}"}'`} 看完整树。`,
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
let performed;
|
|
252
|
+
try {
|
|
253
|
+
performed = await wireCall(session, { path: ENDPOINTS.applicationDelete, method: "POST", query: { appCode: args.code } });
|
|
254
|
+
}
|
|
255
|
+
catch (error) {
|
|
256
|
+
// Race: a non-Folder node appeared between our tree read and the delete.
|
|
257
|
+
if (error instanceof WireError && error.kind === "business" && error.errcode === 600006) {
|
|
258
|
+
const late = await readAppTree(session, args.code);
|
|
259
|
+
const lateBlockers = late.filter((row) => String(row.type ?? "") !== "Folder");
|
|
85
260
|
throw new ToolRefusalError({
|
|
86
261
|
status: "refused",
|
|
87
|
-
blocker: "app-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
262
|
+
blocker: "app-not-empty",
|
|
263
|
+
errcode: 600006,
|
|
264
|
+
code: args.code,
|
|
265
|
+
children: lateBlockers.map((row) => ({ id: row.id, type: row.type, code: row.code ?? null, path: row.path })).slice(0, 40),
|
|
266
|
+
nextCommand: `omc call folder.tree --env <env> --args '{"applicationCode":"${args.code}"}'`,
|
|
267
|
+
message: `应用 ${args.code} 删除被平台拒绝(600006 应用存在下属节点)。` +
|
|
268
|
+
`当前非目录节点 ${lateBlockers.length} 个——先清模型/报表/自定义页再删应用;空目录含默认分组可留。` +
|
|
269
|
+
`下一步:omc call folder.tree --env <env> --args '{"applicationCode":"${args.code}"}'`,
|
|
91
270
|
});
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
const fallback = String(group.code ?? "").toLowerCase() === "all";
|
|
95
|
-
warning = fallback ? "resolved-to-fallback-group; portalHidden:true" : "resolved-to-first-group";
|
|
271
|
+
}
|
|
272
|
+
throw error;
|
|
96
273
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
recovery: [{ code: "application-group-missing", action: "改用 discover_environment 返回的有效 groupId,或由管理侧修复分组" }],
|
|
108
|
-
message: `创建应用前置条件不满足:分组 ${String(groupId)} 无法解析;零 create wire`,
|
|
109
|
-
});
|
|
110
|
-
const performed = await wireCall(session, {
|
|
111
|
-
path: ENDPOINTS.applicationCreate,
|
|
112
|
-
method: "POST",
|
|
113
|
-
body: {
|
|
114
|
-
groupId,
|
|
115
|
-
code: args.code,
|
|
116
|
-
name: args.name,
|
|
117
|
-
name_i18n: JSON.stringify({ "zh-CN": args.name }),
|
|
118
|
-
},
|
|
119
|
-
});
|
|
120
|
-
const present = wireRows((await wireCall(session, { path: ENDPOINTS.applicationList, query: {} })).data).some((row) => (row.appCode ?? row.code) === args.code);
|
|
121
|
-
// Portal visibility is a DERIVED FACT (group membership), reported on
|
|
122
|
-
// every readback so a caller never has to infer it from `warning` and
|
|
123
|
-
// never reports `ok` while claiming visibility it did not verify.
|
|
124
|
-
const portalHidden = isFallbackGroup(selectedGroup);
|
|
125
|
-
return {
|
|
126
|
-
performed: { errcode: performed.errcode, groupId, resolvedFrom, selectedGroup: { id: selectedGroup.id, code: selectedGroup.code, name: selectedGroup.name } },
|
|
127
|
-
readback: {
|
|
128
|
-
present,
|
|
129
|
-
selectedGroup: { id: selectedGroup.id, code: selectedGroup.code, name: selectedGroup.name },
|
|
130
|
-
postconditions: { created: present, published: false, portalVisible: !portalHidden, portalHidden },
|
|
131
|
-
portalHidden,
|
|
132
|
-
...(warning ? { warning } : {}),
|
|
133
|
-
publishBoundary: { inScope: false, nextAction: "publish-in-designer", reason: "app publish is outside the CLI configuration boundary" },
|
|
134
|
-
manualActions: [
|
|
135
|
-
{ action: "publish-in-designer", detail: "在设计器发布应用,否则门户不可见" },
|
|
136
|
-
{ action: "portal-visibility-check", detail: "发布后核对应用位于具名分组并在门户可见" },
|
|
137
|
-
],
|
|
138
|
-
recovery: [{ code: "publish-in-designer", action: "在设计器发布应用并核对其位于具名分组;再做门户可见性验证" }],
|
|
139
|
-
note: "应用发布属边界外:建成后需人工在设计器发布并核对该分组,否则门户不可见",
|
|
140
|
-
},
|
|
141
|
-
};
|
|
142
|
-
}, undefined, undefined, appRollbackSpec("app.create"));
|
|
143
|
-
writeTool(tools, "app.update", APP_LANE, APP_TOOLS["app.update"].scope, APP_TOOLS["app.update"].description, { code: z.string().min(1), groupId: z.string().min(1) }, async (session, args) => {
|
|
144
|
-
const record = (await wireCall(session, { path: ENDPOINTS.applicationGet, query: { appCode: args.code } })).data;
|
|
145
|
-
if (!record || (typeof record === "object" && Object.keys(record).length === 0))
|
|
146
|
-
return { performed: { skipped: "already-absent" }, readback: { absent: true } };
|
|
147
|
-
const previousGroupId = typeof record.groupId === "string" ? record.groupId : null;
|
|
148
|
-
const performed = await wireCall(session, {
|
|
149
|
-
path: ENDPOINTS.applicationUpdate,
|
|
150
|
-
method: "POST",
|
|
151
|
-
body: {
|
|
152
|
-
...record,
|
|
153
|
-
groupId: args.groupId,
|
|
154
|
-
enabled: record.enabled,
|
|
155
|
-
},
|
|
156
|
-
});
|
|
157
|
-
// Two independent reads: apppackage/list rows carry `published` (and the
|
|
158
|
-
// group), while the whole-record `get` carries `enabled`. Merge them so
|
|
159
|
-
// the readback can assert portal visibility without a second caller round.
|
|
160
|
-
const listRow = wireRows((await wireCall(session, { path: ENDPOINTS.applicationList, query: {} })).data).find((row) => (row.appCode ?? row.code) === args.code);
|
|
161
|
-
const groupRow = wireRows((await wireCall(session, { path: ENDPOINTS.applicationGroupList, query: {} })).data).find((row) => String(row.id ?? "") === String(args.groupId));
|
|
162
|
-
const after = (await wireCall(session, { path: ENDPOINTS.applicationGet, query: { appCode: args.code } })).data;
|
|
163
|
-
const portalHiddenByGroup = groupRow === undefined || isFallbackGroup(groupRow);
|
|
164
|
-
// `groupChanged` must reflect a REAL change: comparing the stored value
|
|
165
|
-
// against the requested one — an update that lands on the same group is
|
|
166
|
-
// not a change, and reporting it as one made the flag untrustworthy
|
|
167
|
-
// (dogfood R3: groupChanged:true while previousGroupId === groupId).
|
|
168
|
-
const storedGroupId = typeof after?.groupId === "string" ? after.groupId : listRow?.groupId ?? null;
|
|
169
|
-
const groupChanged = String(storedGroupId ?? "") !== String(previousGroupId ?? "");
|
|
170
|
-
return {
|
|
171
|
-
performed: { errcode: performed.errcode, code: args.code, groupId: args.groupId, previousGroupId },
|
|
172
|
-
readback: {
|
|
173
|
-
present: listRow !== undefined || after !== null,
|
|
174
|
-
groupId: storedGroupId,
|
|
175
|
-
previousGroupId,
|
|
176
|
-
groupChanged,
|
|
177
|
-
groupCode: groupRow?.code ?? null,
|
|
178
|
-
published: listRow?.published === true,
|
|
179
|
-
portalHidden: portalHiddenByGroup,
|
|
180
|
-
},
|
|
181
|
-
};
|
|
182
|
-
}, undefined, undefined, appRollbackSpec("app.update"));
|
|
183
|
-
writeTool(tools, "app.delete", APP_LANE, APP_TOOLS["app.delete"].scope, APP_TOOLS["app.delete"].description, { code: z.string().min(1) }, async (session, args) => {
|
|
184
|
-
const readApps = async () => wireRows((await wireCall(session, { path: ENDPOINTS.applicationList, query: {} })).data);
|
|
185
|
-
const target = (await readApps()).find((row) => (row.appCode ?? row.code) === args.code);
|
|
186
|
-
if (!target)
|
|
187
|
-
return { performed: { skipped: "already-absent" }, readback: { absent: true } };
|
|
188
|
-
const performed = await wireCall(session, { path: ENDPOINTS.applicationDelete, method: "POST", query: { appCode: args.code } });
|
|
189
|
-
let absent = false;
|
|
190
|
-
for (let attempt = 0; attempt < 12 && !absent; attempt += 1) {
|
|
191
|
-
if (attempt > 0)
|
|
192
|
-
await new Promise((resolve) => setTimeout(resolve, Math.min(1000 * attempt, 3000)));
|
|
193
|
-
absent = !(await readApps()).some((row) => (row.appCode ?? row.code) === args.code);
|
|
194
|
-
}
|
|
195
|
-
return { performed: { errcode: performed.errcode, code: args.code }, readback: { absent } };
|
|
196
|
-
}, undefined, undefined, appRollbackSpec("app.delete"));
|
|
274
|
+
let absent = false;
|
|
275
|
+
for (let attempt = 0; attempt < 12 && !absent; attempt += 1) {
|
|
276
|
+
if (attempt > 0)
|
|
277
|
+
await new Promise((resolve) => setTimeout(resolve, Math.min(1000 * attempt, 3000)));
|
|
278
|
+
absent = !(await readApps()).some((row) => (row.appCode ?? row.code) === args.code);
|
|
279
|
+
}
|
|
280
|
+
return { performed: { errcode: performed.errcode, code: args.code }, readback: { absent } };
|
|
281
|
+
},
|
|
282
|
+
rollback: appRollbackSpec("app.delete"),
|
|
283
|
+
});
|
|
197
284
|
}
|
|
198
285
|
//# sourceMappingURL=app.js.map
|
|
@@ -38,7 +38,8 @@ export function registerApplyChangesLane(tools) {
|
|
|
38
38
|
name: "apply_changes",
|
|
39
39
|
layer: "high",
|
|
40
40
|
kind: "write",
|
|
41
|
-
|
|
41
|
+
// r28k F7:选用时机写进 describe 首句——一次审批 N 个有序变更;单条调试用 omc call。
|
|
42
|
+
description: `何时用:一次审批 N 个有序变更、按序执行、首败即停(已完成步骤在 steps[],失败步在 failedStep;续跑看 progressFile/resumeFromIndex)。单条调试或需中间读回再决策 → 用 omc call,不要塞进本工具。changes:[{tool,args}](仅低层原子工具,configure_* 拒收);长批量回传 progressFile(totalTimeoutMs 设整批预算)。 ${WRITE_PROTOCOL}`,
|
|
42
43
|
inputSchema: {
|
|
43
44
|
environment: environmentArg,
|
|
44
45
|
changes: z
|
|
@@ -113,7 +113,7 @@ export const B2_LANE_CONTRACT = Object.freeze({
|
|
|
113
113
|
}),
|
|
114
114
|
"service.list": Object.freeze({
|
|
115
115
|
scope: "readback",
|
|
116
|
-
description: "列出业务集成服务(/bizservice/service/list_by_category_id;categoryId 省略或空串=全部;keyword 走 list_by_category_id_find 仅匹配 name/code 大小写无关包含)。响应是字段白名单投影(id/code/name/adapterCode/serviceCategoryId/description/shared
|
|
116
|
+
description: "列出业务集成服务(/bizservice/service/list_by_category_id;categoryId 省略或空串=全部;keyword 走 list_by_category_id_find 仅匹配 name/code 大小写无关包含)。响应是字段白名单投影(id/code/name/adapterCode/serviceCategoryId/description/shared)+ 顶栏 adapterDistribution 聚合——平台行携带完整 configJson(授权配置,可能带凭据),工具层永不回显 configJson。紧凑面仍保留 services[].adapterCode(选型闭环)。",
|
|
117
117
|
}),
|
|
118
118
|
"service.register": Object.freeze({
|
|
119
119
|
scope: B2_SCOPE,
|
|
@@ -125,7 +125,7 @@ export const B2_LANE_CONTRACT = Object.freeze({
|
|
|
125
125
|
}),
|
|
126
126
|
"method.get": Object.freeze({
|
|
127
127
|
scope: "method readback",
|
|
128
|
-
description: "按 serviceCode + methodCode 读取单个业务集成方法;解码 configJson 与 input/output
|
|
128
|
+
description: "按 serviceCode + methodCode 读取单个业务集成方法;解码 configJson 与 input/output 参数面为结构化对象。单条读面:code/name/configJson/inputParameters/outputParameters 内联(参数面是本工具存在意义),其余大字段可外置到 detailArtifactId。",
|
|
129
129
|
}),
|
|
130
130
|
"method.update": Object.freeze({
|
|
131
131
|
scope: "method update readback",
|
|
@@ -173,7 +173,7 @@ export const B2_LANE_CONTRACT = Object.freeze({
|
|
|
173
173
|
}),
|
|
174
174
|
"service.adapter_config": Object.freeze({
|
|
175
175
|
scope: "readback",
|
|
176
|
-
description: "适配器配置发现(设计器同源):`/bizservice/service/adapter_config` 返回 JSON 字符串(已解码),每个适配器 {code,name,config:{authList,commonList,serviceActionList,methodActionList}}——commonList/authList 的 code+required 就是 service.register
|
|
176
|
+
description: "适配器配置发现(设计器同源):`/bizservice/service/adapter_config` 返回 JSON 字符串(已解码),每个适配器 {code,name,config:{authList,commonList,serviceActionList,methodActionList}}——commonList/authList 的 code+required 就是 service.register 预检所依据的必需键(同源无漂移)。顶栏恒带 adapterCodes 摘要;compact:true 只回 code/name/description(选型)。RESTFUL:url 必填 + basic/oauth_password/oauth_client/customer 鉴权面;DATA_SQL:databaseConnectionPoolCode 必填、无服务级鉴权(sqlConfigEnabled 开启时平台会移除 DATA_SQL/DATA_TABLE,读回原样)。纯只读。",
|
|
177
177
|
}),
|
|
178
178
|
"service.table_list": Object.freeze({
|
|
179
179
|
scope: "readback",
|