ai-dev-requirements 0.4.0 → 0.5.0
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/index.cjs +657 -123
- package/dist/index.mjs +657 -123
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -185,7 +185,7 @@ function loadConfig(startDir) {
|
|
|
185
185
|
}
|
|
186
186
|
//#endregion
|
|
187
187
|
//#region package.json
|
|
188
|
-
var version = "0.
|
|
188
|
+
var version = "0.5.0";
|
|
189
189
|
//#endregion
|
|
190
190
|
//#region ../../src/utils/ones-issue-kind.ts
|
|
191
191
|
/**
|
|
@@ -265,18 +265,34 @@ var BaseAdapter = class {
|
|
|
265
265
|
async updateWikiPage(_params) {
|
|
266
266
|
throw new Error(`${this.sourceType}: Wiki update endpoint is not verified`);
|
|
267
267
|
}
|
|
268
|
+
/** Production Wiki deletes stay disabled until the exact provider endpoint is verified. */
|
|
269
|
+
async deleteWikiPage(_params) {
|
|
270
|
+
throw new Error(`${this.sourceType}: Wiki delete endpoint is not verified`);
|
|
271
|
+
}
|
|
268
272
|
};
|
|
269
273
|
//#endregion
|
|
270
274
|
//#region ../../src/adapters/ones/api-client.ts
|
|
271
275
|
function base64Url(buffer) {
|
|
272
276
|
return buffer.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
|
|
273
277
|
}
|
|
274
|
-
function getSetCookies(response) {
|
|
278
|
+
function getSetCookies$1(response) {
|
|
279
|
+
if (!response.headers) return [];
|
|
275
280
|
const headers = response.headers;
|
|
276
281
|
if (headers.getSetCookie) return headers.getSetCookie();
|
|
277
282
|
const raw = response.headers.get("set-cookie");
|
|
278
283
|
return raw ? [raw] : [];
|
|
279
284
|
}
|
|
285
|
+
function mergeResponseCookies(cookieJar, response) {
|
|
286
|
+
for (const cookie of getSetCookies$1(response)) {
|
|
287
|
+
const pair = cookie.split(";")[0];
|
|
288
|
+
const separator = pair.indexOf("=");
|
|
289
|
+
if (separator <= 0) continue;
|
|
290
|
+
cookieJar.set(pair.slice(0, separator), pair.slice(separator + 1));
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
function serializeCookies(cookieJar) {
|
|
294
|
+
return [...cookieJar].map(([name, value]) => `${name}=${value}`).join("; ");
|
|
295
|
+
}
|
|
280
296
|
function parseRedirectValue(location, names) {
|
|
281
297
|
try {
|
|
282
298
|
const parsed = new URL(location);
|
|
@@ -304,6 +320,7 @@ var OnesApiClient = class {
|
|
|
304
320
|
const email = this.resolvedAuth.email;
|
|
305
321
|
const password = this.resolvedAuth.password;
|
|
306
322
|
if (!email || !password) throw new Error("ONES auth requires email and password (ones-pkce auth type)");
|
|
323
|
+
const cookieJar = /* @__PURE__ */ new Map();
|
|
307
324
|
const certRes = await fetch(`${baseUrl}/identity/api/encryption_cert`, {
|
|
308
325
|
method: "POST",
|
|
309
326
|
headers: { "Content-Type": "application/json" },
|
|
@@ -324,11 +341,15 @@ var OnesApiClient = class {
|
|
|
324
341
|
})
|
|
325
342
|
});
|
|
326
343
|
if (!loginRes.ok) throw new Error(`ONES: Login failed with status ${loginRes.status}`);
|
|
327
|
-
|
|
344
|
+
mergeResponseCookies(cookieJar, loginRes);
|
|
328
345
|
const loginData = await loginRes.json();
|
|
329
346
|
const configuredOrgUuid = this.config.options?.orgUuid;
|
|
330
347
|
const orgUser = configuredOrgUuid ? loginData.org_users.find((user) => user.org_uuid === configuredOrgUuid) ?? loginData.org_users[0] : loginData.org_users[0];
|
|
331
348
|
if (!orgUser) throw new Error("ONES: No organizations found for this user");
|
|
349
|
+
cookieJar.set("ones-region-uuid", orgUser.region_uuid);
|
|
350
|
+
cookieJar.set("ones-org-uuid", orgUser.org_uuid);
|
|
351
|
+
const timezone = cookieJar.get("ones-tz");
|
|
352
|
+
if (timezone) cookieJar.set("timezone", timezone);
|
|
332
353
|
const codeVerifier = base64Url(crypto.randomBytes(32));
|
|
333
354
|
const codeChallenge = base64Url(crypto.createHash("sha256").update(codeVerifier).digest());
|
|
334
355
|
const authorizeParams = new URLSearchParams({
|
|
@@ -340,15 +361,17 @@ var OnesApiClient = class {
|
|
|
340
361
|
redirect_uri: `${baseUrl}/auth/authorize/callback`,
|
|
341
362
|
state: `org_uuid=${orgUser.org_uuid}`
|
|
342
363
|
});
|
|
343
|
-
const
|
|
364
|
+
const authorizeRes = await fetch(`${baseUrl}/identity/authorize`, {
|
|
344
365
|
method: "POST",
|
|
345
366
|
headers: {
|
|
346
367
|
"Content-Type": "application/x-www-form-urlencoded",
|
|
347
|
-
"Cookie":
|
|
368
|
+
"Cookie": serializeCookies(cookieJar)
|
|
348
369
|
},
|
|
349
370
|
body: authorizeParams.toString(),
|
|
350
371
|
redirect: "manual"
|
|
351
|
-
})
|
|
372
|
+
});
|
|
373
|
+
mergeResponseCookies(cookieJar, authorizeRes);
|
|
374
|
+
const authorizeLocation = authorizeRes.headers.get("location");
|
|
352
375
|
if (!authorizeLocation) throw new Error("ONES: Authorize response missing location header");
|
|
353
376
|
let code = parseRedirectValue(authorizeLocation, ["code"]);
|
|
354
377
|
if (!code) {
|
|
@@ -358,7 +381,7 @@ var OnesApiClient = class {
|
|
|
358
381
|
method: "POST",
|
|
359
382
|
headers: {
|
|
360
383
|
"Content-Type": "application/json;charset=UTF-8",
|
|
361
|
-
"Cookie":
|
|
384
|
+
"Cookie": serializeCookies(cookieJar)
|
|
362
385
|
},
|
|
363
386
|
body: JSON.stringify({
|
|
364
387
|
auth_request_id: authRequestId,
|
|
@@ -367,12 +390,15 @@ var OnesApiClient = class {
|
|
|
367
390
|
org_user_uuid: orgUser.org_user.org_user_uuid
|
|
368
391
|
})
|
|
369
392
|
});
|
|
393
|
+
mergeResponseCookies(cookieJar, finalizeRes);
|
|
370
394
|
if (!finalizeRes.ok) throw new Error(`ONES: Finalize failed with status ${finalizeRes.status}`);
|
|
371
|
-
const
|
|
395
|
+
const callbackRes = await fetch(`${baseUrl}/identity/authorize/callback?id=${authRequestId}&lang=zh`, {
|
|
372
396
|
method: "GET",
|
|
373
|
-
headers: { Cookie:
|
|
397
|
+
headers: { Cookie: serializeCookies(cookieJar) },
|
|
374
398
|
redirect: "manual"
|
|
375
|
-
})
|
|
399
|
+
});
|
|
400
|
+
mergeResponseCookies(cookieJar, callbackRes);
|
|
401
|
+
const callbackLocation = callbackRes.headers.get("location");
|
|
376
402
|
if (!callbackLocation) throw new Error("ONES: Callback response missing location header");
|
|
377
403
|
code = parseRedirectValue(callbackLocation, ["code"]);
|
|
378
404
|
}
|
|
@@ -381,7 +407,7 @@ var OnesApiClient = class {
|
|
|
381
407
|
method: "POST",
|
|
382
408
|
headers: {
|
|
383
409
|
"Content-Type": "application/x-www-form-urlencoded",
|
|
384
|
-
"Cookie":
|
|
410
|
+
"Cookie": serializeCookies(cookieJar)
|
|
385
411
|
},
|
|
386
412
|
body: new URLSearchParams({
|
|
387
413
|
grant_type: "authorization_code",
|
|
@@ -391,16 +417,20 @@ var OnesApiClient = class {
|
|
|
391
417
|
redirect_uri: `${baseUrl}/auth/authorize/callback`
|
|
392
418
|
}).toString()
|
|
393
419
|
});
|
|
420
|
+
mergeResponseCookies(cookieJar, tokenRes);
|
|
394
421
|
if (!tokenRes.ok) throw new Error(`ONES: Token exchange failed with status ${tokenRes.status}`);
|
|
395
422
|
const token = await tokenRes.json();
|
|
423
|
+
cookieJar.set("ones-lt", token.access_token);
|
|
396
424
|
const teamsRes = await fetch(`${baseUrl}/project/api/project/organization/${orgUser.org_uuid}/stamps/data?t=org_my_team`, {
|
|
397
425
|
method: "POST",
|
|
398
426
|
headers: {
|
|
399
427
|
"Authorization": `Bearer ${token.access_token}`,
|
|
400
|
-
"Content-Type": "application/json;charset=UTF-8"
|
|
428
|
+
"Content-Type": "application/json;charset=UTF-8",
|
|
429
|
+
"Cookie": serializeCookies(cookieJar)
|
|
401
430
|
},
|
|
402
431
|
body: JSON.stringify({ org_my_team: 0 })
|
|
403
432
|
});
|
|
433
|
+
mergeResponseCookies(cookieJar, teamsRes);
|
|
404
434
|
if (!teamsRes.ok) throw new Error(`ONES: Failed to fetch teams: ${teamsRes.status}`);
|
|
405
435
|
const teams = (await teamsRes.json()).org_my_team?.teams ?? [];
|
|
406
436
|
const configuredTeamUuid = this.config.options?.teamUuid;
|
|
@@ -412,6 +442,9 @@ var OnesApiClient = class {
|
|
|
412
442
|
orgUuid: orgUser.org_uuid,
|
|
413
443
|
userUuid: orgUser.org_user.org_user_uuid,
|
|
414
444
|
userName: orgUser.org_user.name,
|
|
445
|
+
cookieHeader: serializeCookies(cookieJar),
|
|
446
|
+
legacyAuthToken: loginData.sid,
|
|
447
|
+
legacyUserId: loginData.auth_user_uuid,
|
|
415
448
|
expiresAt: Date.now() + (token.expires_in - 60) * 1e3
|
|
416
449
|
};
|
|
417
450
|
return this.session;
|
|
@@ -644,10 +677,11 @@ function mapOnesTypeFromTask(task) {
|
|
|
644
677
|
return mapOnesType(task.subIssueType?.name ?? task.issueType?.name ?? "");
|
|
645
678
|
}
|
|
646
679
|
function toRequirement(task, description = "", attachments = []) {
|
|
680
|
+
const displayId = taskDisplayId({}, task, task.project?.identifier?.toUpperCase() ?? null);
|
|
647
681
|
return {
|
|
648
682
|
id: task.uuid,
|
|
649
683
|
source: "ones",
|
|
650
|
-
title:
|
|
684
|
+
title: `${displayId} ${task.name}`,
|
|
651
685
|
description,
|
|
652
686
|
status: mapOnesStatus(task.status?.category ?? "to_do"),
|
|
653
687
|
priority: mapOnesPriority(task.priority?.value ?? "normal"),
|
|
@@ -1166,17 +1200,43 @@ function renderWikiContent(content, context = { imageSources: [] }) {
|
|
|
1166
1200
|
return asWikiBlocks(document.blocks).map((block) => renderWikiBlock(block, document, context)).filter(Boolean).join("\n\n").replace(/[ \t]+\n/g, "\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
1167
1201
|
}
|
|
1168
1202
|
function newWikiBlockId() {
|
|
1169
|
-
|
|
1203
|
+
for (;;) {
|
|
1204
|
+
const id = randomBytes(9).toString("base64url").replace(/[^a-z0-9]/gi, "").slice(0, 9);
|
|
1205
|
+
if (/^[a-z][a-z0-9]{8}$/i.test(id)) return id;
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
function markdownTextRuns(text) {
|
|
1209
|
+
if (!text) return [];
|
|
1210
|
+
const runs = [];
|
|
1211
|
+
const inlinePattern = /\[([^\]]+)\]\((https?:\/\/[^)\s]+)\)|`([^`]+)`/g;
|
|
1212
|
+
let cursor = 0;
|
|
1213
|
+
for (const match of text.matchAll(inlinePattern)) {
|
|
1214
|
+
const index = match.index ?? 0;
|
|
1215
|
+
if (index > cursor) runs.push({ insert: text.slice(cursor, index) });
|
|
1216
|
+
if (match[1] && match[2]) runs.push({
|
|
1217
|
+
insert: match[1],
|
|
1218
|
+
attributes: { link: match[2] }
|
|
1219
|
+
});
|
|
1220
|
+
else if (match[3]) runs.push({
|
|
1221
|
+
insert: match[3],
|
|
1222
|
+
attributes: { "style-code": true }
|
|
1223
|
+
});
|
|
1224
|
+
cursor = index + match[0].length;
|
|
1225
|
+
}
|
|
1226
|
+
if (cursor < text.length) runs.push({ insert: text.slice(cursor) });
|
|
1227
|
+
return runs.length ? runs : [{ insert: text }];
|
|
1170
1228
|
}
|
|
1171
1229
|
function wikiTextBlock(text, options = {}) {
|
|
1172
1230
|
return {
|
|
1173
1231
|
id: newWikiBlockId(),
|
|
1174
1232
|
type: options.list ? "list" : "text",
|
|
1175
|
-
text: text
|
|
1233
|
+
text: markdownTextRuns(text),
|
|
1176
1234
|
...options.heading ? { heading: options.heading } : {},
|
|
1177
1235
|
...options.list ? {
|
|
1178
1236
|
ordered: options.ordered ?? false,
|
|
1179
|
-
level: 1
|
|
1237
|
+
level: options.level ?? 1,
|
|
1238
|
+
...options.start === void 0 ? {} : { start: options.start },
|
|
1239
|
+
...options.groupId ? { groupId: options.groupId } : {}
|
|
1180
1240
|
} : {}
|
|
1181
1241
|
};
|
|
1182
1242
|
}
|
|
@@ -1187,6 +1247,9 @@ function isMarkdownSeparatorRow(line) {
|
|
|
1187
1247
|
const cells = parseMarkdownRow(line);
|
|
1188
1248
|
return cells.length > 0 && cells.every((cell) => /^:?-{3,}:?$/.test(cell));
|
|
1189
1249
|
}
|
|
1250
|
+
function estimateWikiTableCellWidth(value) {
|
|
1251
|
+
return [...value].reduce((width, character) => width + (character.codePointAt(0) > 127 ? 14 : 7), 24);
|
|
1252
|
+
}
|
|
1190
1253
|
function markdownToWikiDocument(markdown) {
|
|
1191
1254
|
const document = {
|
|
1192
1255
|
blocks: [],
|
|
@@ -1195,9 +1258,15 @@ function markdownToWikiDocument(markdown) {
|
|
|
1195
1258
|
};
|
|
1196
1259
|
const blocks = document.blocks;
|
|
1197
1260
|
const lines = markdown.replace(/\r\n/g, "\n").split("\n");
|
|
1261
|
+
let activeList = null;
|
|
1198
1262
|
for (let index = 0; index < lines.length; index += 1) {
|
|
1199
1263
|
const line = lines[index];
|
|
1264
|
+
if (!line.trim()) {
|
|
1265
|
+
activeList = null;
|
|
1266
|
+
continue;
|
|
1267
|
+
}
|
|
1200
1268
|
if (line.includes("|") && lines[index + 1] && isMarkdownSeparatorRow(lines[index + 1])) {
|
|
1269
|
+
activeList = null;
|
|
1201
1270
|
const rows = [parseMarkdownRow(line)];
|
|
1202
1271
|
index += 2;
|
|
1203
1272
|
while (index < lines.length && lines[index].includes("|")) {
|
|
@@ -1215,34 +1284,120 @@ function markdownToWikiDocument(markdown) {
|
|
|
1215
1284
|
blocks.push({
|
|
1216
1285
|
id: newWikiBlockId(),
|
|
1217
1286
|
type: "table",
|
|
1218
|
-
rows: rows.length,
|
|
1219
1287
|
cols: columnCount,
|
|
1288
|
+
rows: rows.length,
|
|
1289
|
+
colsWidth: Array.from({ length: columnCount }, (_, column) => Math.max(100, ...rows.map((row) => estimateWikiTableCellWidth(row[column] ?? "")))),
|
|
1220
1290
|
children
|
|
1221
1291
|
});
|
|
1222
1292
|
continue;
|
|
1223
1293
|
}
|
|
1224
1294
|
const heading = line.match(/^(#{1,6})[ \t]/);
|
|
1225
1295
|
if (heading?.[1]) {
|
|
1296
|
+
activeList = null;
|
|
1226
1297
|
blocks.push(wikiTextBlock(line.slice(heading[0].length).trim(), { heading: heading[1].length }));
|
|
1227
1298
|
continue;
|
|
1228
1299
|
}
|
|
1229
1300
|
const unordered = line.match(/^[ \t]*[-*+][ \t]/);
|
|
1230
1301
|
if (unordered) {
|
|
1231
|
-
|
|
1302
|
+
const groupId = activeList && !activeList.ordered ? activeList.groupId : newWikiBlockId();
|
|
1303
|
+
const start = activeList && !activeList.ordered ? activeList.nextStart : 1;
|
|
1304
|
+
activeList = {
|
|
1305
|
+
ordered: false,
|
|
1306
|
+
groupId,
|
|
1307
|
+
nextStart: start + 1
|
|
1308
|
+
};
|
|
1309
|
+
blocks.push(wikiTextBlock(line.slice(unordered[0].length).trim(), {
|
|
1310
|
+
list: true,
|
|
1311
|
+
groupId,
|
|
1312
|
+
start
|
|
1313
|
+
}));
|
|
1232
1314
|
continue;
|
|
1233
1315
|
}
|
|
1234
|
-
const ordered = line.match(/^[ \t]
|
|
1235
|
-
if (ordered) {
|
|
1316
|
+
const ordered = line.match(/^[ \t]*(\d+)[.)][ \t]/);
|
|
1317
|
+
if (ordered?.[1]) {
|
|
1318
|
+
const groupId = activeList?.ordered ? activeList.groupId : newWikiBlockId();
|
|
1319
|
+
const start = Number.parseInt(ordered[1], 10);
|
|
1320
|
+
activeList = {
|
|
1321
|
+
ordered: true,
|
|
1322
|
+
groupId,
|
|
1323
|
+
nextStart: start + 1
|
|
1324
|
+
};
|
|
1236
1325
|
blocks.push(wikiTextBlock(line.slice(ordered[0].length).trim(), {
|
|
1237
1326
|
list: true,
|
|
1238
|
-
ordered: true
|
|
1327
|
+
ordered: true,
|
|
1328
|
+
start,
|
|
1329
|
+
groupId
|
|
1239
1330
|
}));
|
|
1240
1331
|
continue;
|
|
1241
1332
|
}
|
|
1333
|
+
activeList = null;
|
|
1242
1334
|
blocks.push(wikiTextBlock(line));
|
|
1243
1335
|
}
|
|
1244
1336
|
return document;
|
|
1245
1337
|
}
|
|
1338
|
+
function markdownToWikiHtml(markdown) {
|
|
1339
|
+
const renderInline = (text) => markdownTextRuns(text).map((run) => {
|
|
1340
|
+
const escaped = escapeWikiHtml(run.insert);
|
|
1341
|
+
const content = run.attributes?.["style-code"] ? `<code>${escaped}</code>` : escaped;
|
|
1342
|
+
return run.attributes?.link ? `<a href="${escapeWikiHtml(run.attributes.link)}" target="_blank" rel="noopener noreferrer">${content}</a>` : content;
|
|
1343
|
+
}).join("");
|
|
1344
|
+
const lines = markdown.replace(/\r\n/g, "\n").split("\n");
|
|
1345
|
+
const html = [];
|
|
1346
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
1347
|
+
const line = lines[index];
|
|
1348
|
+
if (!line.trim()) continue;
|
|
1349
|
+
if (line.includes("|") && lines[index + 1] && isMarkdownSeparatorRow(lines[index + 1])) {
|
|
1350
|
+
const rows = [parseMarkdownRow(line)];
|
|
1351
|
+
index += 2;
|
|
1352
|
+
while (index < lines.length && lines[index].includes("|")) {
|
|
1353
|
+
rows.push(parseMarkdownRow(lines[index]));
|
|
1354
|
+
index += 1;
|
|
1355
|
+
}
|
|
1356
|
+
index -= 1;
|
|
1357
|
+
const columnCount = Math.max(...rows.map((row) => row.length));
|
|
1358
|
+
const width = Math.floor(100 / columnCount);
|
|
1359
|
+
const header = Array.from({ length: columnCount }).map((_, column) => `<th style="width:${width}%">${renderInline(rows[0][column] ?? "")}</th>`).join("");
|
|
1360
|
+
const body = rows.slice(1).map((row) => `<tr>${Array.from({ length: columnCount }).map((_, column) => `<td>${renderInline(row[column] ?? "")}</td>`).join("")}</tr>`).join("");
|
|
1361
|
+
html.push(`<table style="width:100%"><thead><tr>${header}</tr></thead><tbody>${body}</tbody></table>`);
|
|
1362
|
+
continue;
|
|
1363
|
+
}
|
|
1364
|
+
const heading = line.match(/^(#{1,6})[ \t]/);
|
|
1365
|
+
if (heading?.[1]) {
|
|
1366
|
+
const level = heading[1].length;
|
|
1367
|
+
html.push(`<h${level}>${renderInline(line.slice(heading[0].length).trim())}</h${level}>`);
|
|
1368
|
+
continue;
|
|
1369
|
+
}
|
|
1370
|
+
if (line.match(/^[ \t]*(\d+)[.)][ \t]/)?.[1]) {
|
|
1371
|
+
const items = [];
|
|
1372
|
+
while (index < lines.length) {
|
|
1373
|
+
const item = lines[index].match(/^[ \t]*(\d+)[.)][ \t]/);
|
|
1374
|
+
if (!item?.[1]) break;
|
|
1375
|
+
items.push({
|
|
1376
|
+
value: Number.parseInt(item[1], 10),
|
|
1377
|
+
text: lines[index].slice(item[0].length).trim()
|
|
1378
|
+
});
|
|
1379
|
+
index += 1;
|
|
1380
|
+
}
|
|
1381
|
+
index -= 1;
|
|
1382
|
+
html.push(`<ol start="${items[0].value}">${items.map((item) => `<li>${renderInline(item.text)}</li>`).join("")}</ol>`);
|
|
1383
|
+
continue;
|
|
1384
|
+
}
|
|
1385
|
+
if (line.match(/^[ \t]*[-*+][ \t]/)) {
|
|
1386
|
+
const items = [];
|
|
1387
|
+
while (index < lines.length) {
|
|
1388
|
+
const item = lines[index].match(/^[ \t]*[-*+][ \t]/);
|
|
1389
|
+
if (!item) break;
|
|
1390
|
+
items.push(lines[index].slice(item[0].length).trim());
|
|
1391
|
+
index += 1;
|
|
1392
|
+
}
|
|
1393
|
+
index -= 1;
|
|
1394
|
+
html.push(`<ul>${items.map((item) => `<li>${renderInline(item)}</li>`).join("")}</ul>`);
|
|
1395
|
+
continue;
|
|
1396
|
+
}
|
|
1397
|
+
html.push(`<p>${renderInline(line.trim())}</p>`);
|
|
1398
|
+
}
|
|
1399
|
+
return html.join("\n");
|
|
1400
|
+
}
|
|
1246
1401
|
function parseWikiDocument(content) {
|
|
1247
1402
|
const document = parseJsonRecord(content);
|
|
1248
1403
|
if (!document || !Array.isArray(document.blocks)) throw new Error("ONES: Wiki content is not a supported collaborative document");
|
|
@@ -1299,6 +1454,17 @@ function appendWikiTableRow(document, operation) {
|
|
|
1299
1454
|
table.rows = (typeof table.rows === "number" ? table.rows : layout.rows.length) + 1;
|
|
1300
1455
|
}
|
|
1301
1456
|
function applyWikiUpdateOperation(content, operation) {
|
|
1457
|
+
if (operation.type === "replace_document") {
|
|
1458
|
+
const current = parseWikiDocument(content);
|
|
1459
|
+
const replacement = markdownToWikiDocument(operation.markdown);
|
|
1460
|
+
for (const key of [
|
|
1461
|
+
"comments",
|
|
1462
|
+
"meta",
|
|
1463
|
+
"authors",
|
|
1464
|
+
"commentators"
|
|
1465
|
+
]) if (Object.hasOwn(current, key)) replacement[key] = current[key];
|
|
1466
|
+
return JSON.stringify(replacement);
|
|
1467
|
+
}
|
|
1302
1468
|
const document = parseWikiDocument(content);
|
|
1303
1469
|
if (operation.type === "append_blocks") appendWikiDocument(document, markdownToWikiDocument(operation.markdown));
|
|
1304
1470
|
else if (operation.type === "replace_text") replaceWikiText(document, operation.find, operation.replace);
|
|
@@ -1337,6 +1503,26 @@ var WikiPathResolutionError = class extends Error {
|
|
|
1337
1503
|
};
|
|
1338
1504
|
//#endregion
|
|
1339
1505
|
//#region ../../src/adapters/ones/wiki-reader.ts
|
|
1506
|
+
const CURRENT_USER_PATH_ALIASES = {
|
|
1507
|
+
"i": true,
|
|
1508
|
+
"me": true,
|
|
1509
|
+
"mine": true,
|
|
1510
|
+
"my": true,
|
|
1511
|
+
"myself": true,
|
|
1512
|
+
"current user": true,
|
|
1513
|
+
"current-user": true,
|
|
1514
|
+
"current_user": true,
|
|
1515
|
+
"我": true,
|
|
1516
|
+
"我的": true,
|
|
1517
|
+
"本人": true,
|
|
1518
|
+
"当前用户": true,
|
|
1519
|
+
"当前账号": true,
|
|
1520
|
+
"自己": true
|
|
1521
|
+
};
|
|
1522
|
+
function currentUserPathAlias(segment) {
|
|
1523
|
+
const trimmed = segment.trim();
|
|
1524
|
+
return CURRENT_USER_PATH_ALIASES[trimmed.toLocaleLowerCase()] ? trimmed : null;
|
|
1525
|
+
}
|
|
1340
1526
|
var OnesWikiOpenApiError = class extends Error {
|
|
1341
1527
|
status;
|
|
1342
1528
|
reason;
|
|
@@ -1431,12 +1617,29 @@ function segmentSimilarity(left, right) {
|
|
|
1431
1617
|
var OnesWikiReader = class {
|
|
1432
1618
|
options;
|
|
1433
1619
|
treeCache = /* @__PURE__ */ new Map();
|
|
1620
|
+
currentUserNameRequest = null;
|
|
1434
1621
|
constructor(options) {
|
|
1435
1622
|
this.options = options;
|
|
1436
1623
|
}
|
|
1437
1624
|
invalidateTree(teamId, spaceId) {
|
|
1438
1625
|
this.treeCache.delete(`${teamId}:${spaceId}`);
|
|
1439
1626
|
}
|
|
1627
|
+
async fetchCurrentUserName() {
|
|
1628
|
+
const session = await this.options.getSession();
|
|
1629
|
+
const response = await fetch(new URL("/wiki/api/project/auth/token_info", this.options.apiBase).toString(), { headers: { Authorization: `Bearer ${session.accessToken}` } });
|
|
1630
|
+
if (!response.ok) throw new Error(`ONES Wiki token info error: ${response.status}`);
|
|
1631
|
+
const payload = await response.json();
|
|
1632
|
+
const name = firstNonEmptyString$1(payload.user?.name, payload.data?.user?.name, payload.data?.name, payload.name, payload.user_name, payload.userName);
|
|
1633
|
+
if (!name) throw new Error("ONES Wiki token info did not include a resolvable current account");
|
|
1634
|
+
return name;
|
|
1635
|
+
}
|
|
1636
|
+
currentUserName() {
|
|
1637
|
+
if (!this.currentUserNameRequest) this.currentUserNameRequest = this.fetchCurrentUserName().catch((error) => {
|
|
1638
|
+
this.currentUserNameRequest = null;
|
|
1639
|
+
throw error;
|
|
1640
|
+
});
|
|
1641
|
+
return this.currentUserNameRequest;
|
|
1642
|
+
}
|
|
1440
1643
|
async openApi(apiPath) {
|
|
1441
1644
|
if (!apiPath.startsWith("/wiki/")) throw new Error("ONES: Invalid Wiki Open API path");
|
|
1442
1645
|
if (!this.options.openApiToken) throw new OnesWikiOpenApiError(null, "ONES Wiki Open API credential is not configured. Set source.openApiAuth.tokenEnv to a Personal API Key with read scopes.", "missing_credential");
|
|
@@ -1696,8 +1899,19 @@ var OnesWikiReader = class {
|
|
|
1696
1899
|
return score >= MIN_PATH_MATCH_SIMILARITY ? score : null;
|
|
1697
1900
|
}
|
|
1698
1901
|
async resolvePath(params) {
|
|
1699
|
-
const
|
|
1700
|
-
if (!
|
|
1902
|
+
const requestedPath = params.path.map((segment) => segment.trim()).filter(Boolean);
|
|
1903
|
+
if (!requestedPath.length) throw new Error("ONES: Wiki path is empty");
|
|
1904
|
+
const currentUserAlias = requestedPath.map(currentUserPathAlias).find((alias) => alias !== null) ?? null;
|
|
1905
|
+
const currentUserName = currentUserAlias ? await this.currentUserName() : null;
|
|
1906
|
+
const path = currentUserName ? requestedPath.map((segment) => currentUserPathAlias(segment) ? currentUserName : segment) : requestedPath;
|
|
1907
|
+
const redactCurrentUser = (page) => {
|
|
1908
|
+
if (!currentUserName || !currentUserAlias) return page;
|
|
1909
|
+
return {
|
|
1910
|
+
...page,
|
|
1911
|
+
title: page.title === currentUserName ? currentUserAlias : page.title,
|
|
1912
|
+
breadcrumb: page.breadcrumb.map((segment) => segment === currentUserName ? currentUserAlias : segment)
|
|
1913
|
+
};
|
|
1914
|
+
};
|
|
1701
1915
|
const candidates = await this.search({
|
|
1702
1916
|
query: path.at(-1),
|
|
1703
1917
|
teamId: params.teamId,
|
|
@@ -1744,26 +1958,32 @@ var OnesWikiReader = class {
|
|
|
1744
1958
|
});
|
|
1745
1959
|
}
|
|
1746
1960
|
}
|
|
1747
|
-
if (matches.length > 1) throw new WikiPathResolutionError("ambiguous",
|
|
1961
|
+
if (matches.length > 1) throw new WikiPathResolutionError("ambiguous", requestedPath, matches.map(redactCurrentUser));
|
|
1748
1962
|
let match = matches[0];
|
|
1749
1963
|
if (!match && fuzzy.length) {
|
|
1750
1964
|
fuzzy.sort((left, right) => right.score - left.score);
|
|
1751
1965
|
const competing = fuzzy.filter((candidate) => fuzzy[0].score - candidate.score < PATH_MATCH_MARGIN);
|
|
1752
|
-
if (competing.length > 1) throw new WikiPathResolutionError("ambiguous",
|
|
1966
|
+
if (competing.length > 1) throw new WikiPathResolutionError("ambiguous", requestedPath, competing.map((candidate) => redactCurrentUser(candidate.page)));
|
|
1753
1967
|
match = fuzzy[0].page;
|
|
1754
1968
|
}
|
|
1755
1969
|
if (!match) {
|
|
1756
1970
|
const inspectedIds = new Set(inspected.map((page) => page.pageId));
|
|
1757
|
-
throw new WikiPathResolutionError("not_found",
|
|
1971
|
+
throw new WikiPathResolutionError("not_found", requestedPath, [...inspected, ...candidates.filter((page) => !inspectedIds.has(page.pageId))].map(redactCurrentUser));
|
|
1758
1972
|
}
|
|
1759
1973
|
if (!match.spaceId) throw new Error("ONES: Wiki space ID could not be verified");
|
|
1760
|
-
|
|
1974
|
+
const publicMatch = redactCurrentUser(match);
|
|
1975
|
+
const resolution = {
|
|
1761
1976
|
teamId: match.teamId,
|
|
1762
1977
|
spaceId: match.spaceId,
|
|
1763
1978
|
pageId: match.pageId,
|
|
1764
|
-
title:
|
|
1765
|
-
breadcrumb:
|
|
1979
|
+
title: publicMatch.title,
|
|
1980
|
+
breadcrumb: publicMatch.breadcrumb
|
|
1766
1981
|
};
|
|
1982
|
+
if (currentUserName && currentUserAlias) Object.defineProperty(resolution, "redactPrivateValues", {
|
|
1983
|
+
enumerable: false,
|
|
1984
|
+
value: (value) => value.split(currentUserName).join(currentUserAlias)
|
|
1985
|
+
});
|
|
1986
|
+
return resolution;
|
|
1767
1987
|
}
|
|
1768
1988
|
};
|
|
1769
1989
|
//#endregion
|
|
@@ -1965,8 +2185,9 @@ var OnesTaskContent = class {
|
|
|
1965
2185
|
attachments: rendered.attachments
|
|
1966
2186
|
};
|
|
1967
2187
|
})), containsInlineTaskImages(task) ? this.getTaskImageAttachments(task) : Promise.resolve([])]);
|
|
2188
|
+
const projectIdentifier = task.project?.identifier?.toUpperCase() ?? null;
|
|
1968
2189
|
const parts = [
|
|
1969
|
-
`#
|
|
2190
|
+
`# ${taskDisplayId({}, task, projectIdentifier)} ${task.name}`,
|
|
1970
2191
|
"",
|
|
1971
2192
|
`- **Type**: ${task.issueType?.name ?? "Unknown"}`,
|
|
1972
2193
|
"- **Work Item Kind**: requirement",
|
|
@@ -1978,7 +2199,7 @@ var OnesTaskContent = class {
|
|
|
1978
2199
|
parts.push(`- **UUID**: ${task.uuid}`);
|
|
1979
2200
|
if (task.relatedTasks?.length) {
|
|
1980
2201
|
parts.push("", "## Related Tasks");
|
|
1981
|
-
for (const related of task.relatedTasks) parts.push(`-
|
|
2202
|
+
for (const related of task.relatedTasks) parts.push(`- ${taskDisplayId({}, related, related.project?.identifier?.toUpperCase() ?? projectIdentifier)} ${related.name} [${related.issueType?.name}] (${related.status?.name}) — ${related.assign?.name ?? "Unassigned"}`);
|
|
1982
2203
|
}
|
|
1983
2204
|
if (relatedActivities.length) {
|
|
1984
2205
|
parts.push("", "## Related Work Items");
|
|
@@ -1993,7 +2214,7 @@ var OnesTaskContent = class {
|
|
|
1993
2214
|
}
|
|
1994
2215
|
if (task.parent?.uuid) {
|
|
1995
2216
|
parts.push("", "## Parent Task", `- UUID: ${task.parent.uuid}`);
|
|
1996
|
-
if (task.parent.number) parts.push(`- Number:
|
|
2217
|
+
if (task.parent.number) parts.push(`- Number: ${projectIdentifier ? `${projectIdentifier}-${task.parent.number}` : `#${task.parent.number}`}`);
|
|
1997
2218
|
}
|
|
1998
2219
|
if (wikiContents.length > 0) {
|
|
1999
2220
|
parts.push("", "---", "", "## Requirement Documents");
|
|
@@ -2016,8 +2237,9 @@ var OnesTaskContent = class {
|
|
|
2016
2237
|
}
|
|
2017
2238
|
buildWorkItemSummary(task, kind) {
|
|
2018
2239
|
const nextTool = kind === "defect" ? "get_issue_detail" : "get_related_issues / get_testcases";
|
|
2240
|
+
const projectIdentifier = task.project?.identifier?.toUpperCase() ?? null;
|
|
2019
2241
|
const parts = [
|
|
2020
|
-
`#
|
|
2242
|
+
`# ${taskDisplayId({}, task, projectIdentifier)} ${task.name}`,
|
|
2021
2243
|
"",
|
|
2022
2244
|
`- **Type**: ${task.subIssueType?.name ?? task.issueType?.name ?? "Unknown"}`,
|
|
2023
2245
|
`- **Work Item Kind**: ${kind}`,
|
|
@@ -2029,14 +2251,14 @@ var OnesTaskContent = class {
|
|
|
2029
2251
|
parts.push(`- **UUID**: ${task.uuid}`);
|
|
2030
2252
|
if (task.parent?.uuid) {
|
|
2031
2253
|
parts.push("", "## Parent Task", `- UUID: ${task.parent.uuid}`);
|
|
2032
|
-
if (task.parent.number) parts.push(`- Number:
|
|
2254
|
+
if (task.parent.number) parts.push(`- Number: ${projectIdentifier ? `${projectIdentifier}-${task.parent.number}` : `#${task.parent.number}`}`);
|
|
2033
2255
|
}
|
|
2034
2256
|
const detailText = getTaskDetailText(task);
|
|
2035
2257
|
if (detailText) parts.push("", "---", "", kind === "defect" ? "## Defect Detail" : "## Task Detail", "", detailText);
|
|
2036
2258
|
parts.push("", "## Next Tool", "", `This ID is a ${workItemKindLabel(kind)}, not a requirement document.`, `Do not treat wiki/requirement docs as the source of truth. Use \`${nextTool}\` for the next lookup.`);
|
|
2037
2259
|
if (task.relatedTasks?.length) {
|
|
2038
2260
|
parts.push("", "## Related Tasks");
|
|
2039
|
-
for (const related of task.relatedTasks) parts.push(`-
|
|
2261
|
+
for (const related of task.relatedTasks) parts.push(`- ${taskDisplayId({}, related, related.project?.identifier?.toUpperCase() ?? projectIdentifier)} ${related.name} [${related.issueType?.name}] (${related.status?.name}) — ${related.assign?.name ?? "Unassigned"}`);
|
|
2040
2262
|
}
|
|
2041
2263
|
const requirement = toRequirement(task, parts.join("\n"));
|
|
2042
2264
|
requirement.raw = {
|
|
@@ -2098,7 +2320,7 @@ const TASK_DETAIL_QUERY = `
|
|
|
2098
2320
|
priority { value }
|
|
2099
2321
|
assign { uuid name }
|
|
2100
2322
|
owner { uuid name }
|
|
2101
|
-
project { uuid name }
|
|
2323
|
+
project { uuid name identifier }
|
|
2102
2324
|
parent { uuid number issueType { uuid name } }
|
|
2103
2325
|
relatedTasks {
|
|
2104
2326
|
key uuid number name
|
|
@@ -2109,6 +2331,7 @@ const TASK_DETAIL_QUERY = `
|
|
|
2109
2331
|
subIssueType { uuid name detailType }
|
|
2110
2332
|
status { uuid name category }
|
|
2111
2333
|
assign { uuid name }
|
|
2334
|
+
project { uuid name identifier }
|
|
2112
2335
|
}
|
|
2113
2336
|
relatedWikiPages {
|
|
2114
2337
|
uuid title referenceType subReferenceType errorMessage
|
|
@@ -2245,7 +2468,9 @@ var OnesTaskPlanning = class {
|
|
|
2245
2468
|
if (!Number.isInteger(raw.number)) throw new TypeError("ONES: Standalone wiki pages cannot be decomposed into requirement tasks");
|
|
2246
2469
|
const parsedDisplayId = parseDisplayId(params.requirementId);
|
|
2247
2470
|
const requirementInfo = await this.options.fetchTaskInfo(workItem.id);
|
|
2248
|
-
|
|
2471
|
+
let projectIdentifier = parsedDisplayId?.identifier ?? firstString(requirementInfo, ["projectIdentifier", "project_identifier"]) ?? raw.project?.identifier ?? null;
|
|
2472
|
+
if (!projectIdentifier && raw.project?.uuid) projectIdentifier = await this.options.resolveProjectIdentifier(raw.project.uuid);
|
|
2473
|
+
projectIdentifier = projectIdentifier?.toUpperCase() ?? null;
|
|
2249
2474
|
const displayId = firstString(requirementInfo, ["displayId", "display_id"]) ?? (projectIdentifier ? `${projectIdentifier}-${raw.number}` : `#${raw.number}`);
|
|
2250
2475
|
const relatedTasks = (raw.relatedTasks ?? []).filter((task) => classifyOnesWorkItem(task.issueType, task.subIssueType) === "task");
|
|
2251
2476
|
const relatedInfos = await Promise.all(relatedTasks.map((task) => this.options.fetchTaskInfo(task.uuid)));
|
|
@@ -2574,6 +2799,12 @@ var OnesTestcaseReader = class {
|
|
|
2574
2799
|
const JSON0_URI = "http://sharejs.org/types/JSONv0";
|
|
2575
2800
|
const JSON1_URI = "http://sharejs.org/types/JSONv1";
|
|
2576
2801
|
const DEFAULT_TIMEOUT_MS$1 = 15e3;
|
|
2802
|
+
function getSetCookies(response) {
|
|
2803
|
+
const headers = response.headers;
|
|
2804
|
+
if (headers.getSetCookie) return headers.getSetCookie();
|
|
2805
|
+
const raw = headers.get("set-cookie");
|
|
2806
|
+
return raw ? [raw] : [];
|
|
2807
|
+
}
|
|
2577
2808
|
function isRecord(value) {
|
|
2578
2809
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
2579
2810
|
}
|
|
@@ -2586,28 +2817,44 @@ function asDoc(value) {
|
|
|
2586
2817
|
}
|
|
2587
2818
|
function createTopLevelJson1Operation(current, next) {
|
|
2588
2819
|
let operation = null;
|
|
2820
|
+
const appendComponent = (component) => {
|
|
2821
|
+
operation = operation === null ? component : type.compose(operation, component);
|
|
2822
|
+
};
|
|
2589
2823
|
const keys = [.../* @__PURE__ */ new Set([...Object.keys(current), ...Object.keys(next)])].sort();
|
|
2590
2824
|
for (const key of keys) {
|
|
2825
|
+
if (key === "blocks") continue;
|
|
2591
2826
|
const hasCurrent = Object.hasOwn(current, key);
|
|
2592
2827
|
const hasNext = Object.hasOwn(next, key);
|
|
2593
|
-
|
|
2594
|
-
if (
|
|
2595
|
-
else if (!
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2828
|
+
if (!hasCurrent && hasNext) appendComponent(insertOp([key], asDoc(next[key])));
|
|
2829
|
+
else if (hasCurrent && !hasNext) appendComponent(removeOp([key]));
|
|
2830
|
+
else if (hasCurrent && hasNext && !isDeepStrictEqual(current[key], next[key])) appendComponent(replaceOp([key], asDoc(current[key]), asDoc(next[key])));
|
|
2831
|
+
}
|
|
2832
|
+
const currentBlocks = Array.isArray(current.blocks) ? current.blocks : [];
|
|
2833
|
+
const nextBlocks = Array.isArray(next.blocks) ? next.blocks : [];
|
|
2834
|
+
const sharedBlockCount = Math.min(currentBlocks.length, nextBlocks.length);
|
|
2835
|
+
for (let index = 0; index < sharedBlockCount; index += 1) if (!isDeepStrictEqual(currentBlocks[index], nextBlocks[index])) {
|
|
2836
|
+
appendComponent(removeOp(["blocks", index]));
|
|
2837
|
+
appendComponent(insertOp(["blocks", index], asDoc(nextBlocks[index])));
|
|
2838
|
+
}
|
|
2839
|
+
for (let index = currentBlocks.length - 1; index >= nextBlocks.length; index -= 1) appendComponent(removeOp(["blocks", index]));
|
|
2840
|
+
for (let index = currentBlocks.length; index < nextBlocks.length; index += 1) appendComponent(insertOp(["blocks", index], asDoc(nextBlocks[index])));
|
|
2600
2841
|
if (operation !== null) type.checkValidOp(operation);
|
|
2601
2842
|
return operation;
|
|
2602
2843
|
}
|
|
2844
|
+
function createTopLevelJson1Operations(current, next) {
|
|
2845
|
+
const operation = createTopLevelJson1Operation(current, next);
|
|
2846
|
+
return operation === null ? [] : [operation];
|
|
2847
|
+
}
|
|
2603
2848
|
function wikiEditorUrls(baseUrl, teamId, documentId) {
|
|
2604
2849
|
const url = new URL(baseUrl);
|
|
2605
2850
|
if (url.protocol !== "https:" && url.protocol !== "http:") throw new Error("ONES Wiki collaboration requires an HTTP(S) source URL");
|
|
2606
2851
|
const path = `/wiki/api/wiki/editor/${encodeURIComponent(teamId)}/${encodeURIComponent(documentId)}`;
|
|
2607
|
-
const
|
|
2852
|
+
const editorBaseUrl = new URL(path, url);
|
|
2853
|
+
const socketUrl = new URL(editorBaseUrl);
|
|
2608
2854
|
socketUrl.protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
2609
2855
|
return {
|
|
2610
|
-
authUrl:
|
|
2856
|
+
authUrl: `${editorBaseUrl.toString()}/auth`,
|
|
2857
|
+
editorBaseUrl: editorBaseUrl.toString(),
|
|
2611
2858
|
socketUrl: socketUrl.toString()
|
|
2612
2859
|
};
|
|
2613
2860
|
}
|
|
@@ -2624,39 +2871,52 @@ function closeSocket(socket) {
|
|
|
2624
2871
|
}
|
|
2625
2872
|
async function replaceOnesWikiDocument(options, update, dependencies = {
|
|
2626
2873
|
fetch,
|
|
2627
|
-
openWebSocket: (url) => new WebSocket(url)
|
|
2874
|
+
openWebSocket: (url, headers) => new WebSocket(url, { headers })
|
|
2628
2875
|
}) {
|
|
2629
|
-
const { authUrl, socketUrl } = wikiEditorUrls(options.baseUrl, options.teamId, options.documentId);
|
|
2876
|
+
const { authUrl, editorBaseUrl, socketUrl } = wikiEditorUrls(options.baseUrl, options.teamId, options.documentId);
|
|
2630
2877
|
const authResponse = await dependencies.fetch(authUrl, {
|
|
2631
2878
|
method: "GET",
|
|
2632
2879
|
headers: {
|
|
2633
2880
|
"Authorization": `Bearer ${options.accessToken}`,
|
|
2634
2881
|
"x-live-editor-token": options.editorToken,
|
|
2635
|
-
"x-live-editor-base-url": Buffer.from(
|
|
2882
|
+
"x-live-editor-base-url": Buffer.from(editorBaseUrl).toString("base64url"),
|
|
2883
|
+
...options.cookieHeader ? { Cookie: options.cookieHeader } : {}
|
|
2636
2884
|
}
|
|
2637
2885
|
});
|
|
2638
2886
|
if (!authResponse.ok) throw new Error(`ONES Wiki collaboration auth failed with status ${authResponse.status}`);
|
|
2639
2887
|
const editorAuth = await authResponse.json();
|
|
2640
2888
|
if (typeof editorAuth.read !== "string" || !editorAuth.read) throw new Error("ONES Wiki collaboration auth response did not include a read token");
|
|
2889
|
+
const cookies = [options.cookieHeader, ...getSetCookies(authResponse).map((cookie) => cookie.split(";")[0])].filter((cookie) => Boolean(cookie)).join("; ");
|
|
2890
|
+
const socketHeaders = {
|
|
2891
|
+
"Accept-Language": "zh-CN,zh;q=0.9",
|
|
2892
|
+
"Cache-Control": "no-cache",
|
|
2893
|
+
"Pragma": "no-cache",
|
|
2894
|
+
"User-Agent": "Mozilla/5.0 AppleWebKit/537.36 Chrome/128.0.0.0 Safari/537.36",
|
|
2895
|
+
"Origin": new URL(options.baseUrl).origin,
|
|
2896
|
+
...cookies ? { Cookie: cookies } : {}
|
|
2897
|
+
};
|
|
2641
2898
|
return new Promise((resolve, reject) => {
|
|
2642
|
-
const socket = dependencies.openWebSocket(socketUrl);
|
|
2643
|
-
|
|
2644
|
-
let clientId = "";
|
|
2899
|
+
const socket = dependencies.openWebSocket(socketUrl, socketHeaders);
|
|
2900
|
+
let sequence = 1;
|
|
2645
2901
|
let state = "init";
|
|
2646
2902
|
let settled = false;
|
|
2647
2903
|
let snapshotVersion = 0;
|
|
2904
|
+
let currentVersion = 0;
|
|
2905
|
+
let pendingOperations = [];
|
|
2906
|
+
const presenceChannel = `${options.teamId}:${options.documentId}`;
|
|
2907
|
+
const presenceId = randomBytes(7).toString("base64url").slice(0, 9);
|
|
2648
2908
|
let timeout;
|
|
2649
2909
|
const finish = (result) => {
|
|
2650
2910
|
if (settled) return;
|
|
2651
2911
|
settled = true;
|
|
2652
|
-
|
|
2912
|
+
clearTimeout(timeout);
|
|
2653
2913
|
resolve(result);
|
|
2654
2914
|
closeSocket(socket);
|
|
2655
2915
|
};
|
|
2656
2916
|
const fail = (error) => {
|
|
2657
2917
|
if (settled) return;
|
|
2658
2918
|
settled = true;
|
|
2659
|
-
|
|
2919
|
+
clearTimeout(timeout);
|
|
2660
2920
|
reject(error);
|
|
2661
2921
|
closeSocket(socket);
|
|
2662
2922
|
};
|
|
@@ -2665,9 +2925,38 @@ async function replaceOnesWikiDocument(options, update, dependencies = {
|
|
|
2665
2925
|
if (error) fail(/* @__PURE__ */ new Error(`ONES Wiki collaboration send failed: ${error.message}`));
|
|
2666
2926
|
});
|
|
2667
2927
|
};
|
|
2928
|
+
const sendHandshake = () => {
|
|
2929
|
+
send({
|
|
2930
|
+
a: "hs",
|
|
2931
|
+
id: null,
|
|
2932
|
+
auth: {
|
|
2933
|
+
appId: options.teamId,
|
|
2934
|
+
docId: options.documentId,
|
|
2935
|
+
userId: options.userId,
|
|
2936
|
+
permission: "w",
|
|
2937
|
+
token: options.editorToken,
|
|
2938
|
+
displayName: options.displayName ?? "",
|
|
2939
|
+
avatarUrl: options.avatarUrl ?? ""
|
|
2940
|
+
}
|
|
2941
|
+
});
|
|
2942
|
+
};
|
|
2943
|
+
const sendNextOperation = () => {
|
|
2944
|
+
const operation = pendingOperations[0];
|
|
2945
|
+
if (operation === void 0) return;
|
|
2946
|
+
send({
|
|
2947
|
+
a: "op",
|
|
2948
|
+
c: options.teamId,
|
|
2949
|
+
d: options.documentId,
|
|
2950
|
+
v: currentVersion,
|
|
2951
|
+
seq: sequence,
|
|
2952
|
+
x: {},
|
|
2953
|
+
op: operation
|
|
2954
|
+
});
|
|
2955
|
+
};
|
|
2668
2956
|
timeout = setTimeout(() => {
|
|
2669
2957
|
fail(/* @__PURE__ */ new Error(`ONES Wiki collaboration timed out while waiting for ${state}`));
|
|
2670
2958
|
}, options.timeoutMs ?? DEFAULT_TIMEOUT_MS$1);
|
|
2959
|
+
socket.on("open", sendHandshake);
|
|
2671
2960
|
socket.on("error", () => fail(/* @__PURE__ */ new Error("ONES Wiki collaboration WebSocket failed")));
|
|
2672
2961
|
socket.on("close", (code) => {
|
|
2673
2962
|
if (!settled) fail(/* @__PURE__ */ new Error(`ONES Wiki collaboration WebSocket closed before ${state} (code ${code})`));
|
|
@@ -2694,58 +2983,64 @@ async function replaceOnesWikiDocument(options, update, dependencies = {
|
|
|
2694
2983
|
fail(/* @__PURE__ */ new Error("ONES Wiki collaboration returned an unsupported init frame"));
|
|
2695
2984
|
return;
|
|
2696
2985
|
}
|
|
2697
|
-
clientId = message.id;
|
|
2698
2986
|
state = "handshake";
|
|
2699
|
-
|
|
2700
|
-
a: "hs",
|
|
2701
|
-
id: clientId,
|
|
2702
|
-
auth: {
|
|
2703
|
-
appId: options.teamId,
|
|
2704
|
-
docId: options.documentId,
|
|
2705
|
-
userId: options.userId,
|
|
2706
|
-
permission: "w",
|
|
2707
|
-
token: editorAuth.read,
|
|
2708
|
-
displayName: options.displayName ?? "",
|
|
2709
|
-
avatarUrl: options.avatarUrl ?? ""
|
|
2710
|
-
},
|
|
2711
|
-
options: { ping: {
|
|
2712
|
-
interval: 5e4,
|
|
2713
|
-
timeout: 15e4
|
|
2714
|
-
} }
|
|
2715
|
-
});
|
|
2987
|
+
sendHandshake();
|
|
2716
2988
|
return;
|
|
2717
2989
|
}
|
|
2718
2990
|
if (state === "handshake") {
|
|
2719
|
-
if (message.a !== "hs" || message.id !==
|
|
2991
|
+
if (message.a !== "hs" || typeof message.id !== "string" || message.protocol !== 1 || message.protocolMinor !== 1 || message.type !== JSON0_URI) {
|
|
2720
2992
|
fail(/* @__PURE__ */ new Error("ONES Wiki collaboration returned an unsupported handshake frame"));
|
|
2721
2993
|
return;
|
|
2722
2994
|
}
|
|
2723
|
-
state = "
|
|
2995
|
+
state = "fetch";
|
|
2724
2996
|
send({
|
|
2725
|
-
a: "
|
|
2997
|
+
a: "f",
|
|
2726
2998
|
c: options.teamId,
|
|
2727
2999
|
d: options.documentId
|
|
2728
3000
|
});
|
|
2729
3001
|
return;
|
|
2730
3002
|
}
|
|
2731
|
-
if (state === "
|
|
3003
|
+
if (state === "fetch") {
|
|
2732
3004
|
const snapshot = message.data;
|
|
2733
|
-
if (message.a !== "
|
|
2734
|
-
fail(/* @__PURE__ */ new Error("ONES Wiki collaboration returned an unsupported snapshot frame"));
|
|
2735
|
-
return;
|
|
2736
|
-
}
|
|
3005
|
+
if (message.a !== "f" || message.c !== options.teamId || message.d !== options.documentId || typeof snapshot?.v !== "number" || snapshot.type !== JSON1_URI) return;
|
|
2737
3006
|
const current = asWikiSnapshot(snapshot.data);
|
|
2738
|
-
let next;
|
|
2739
|
-
let operation;
|
|
2740
3007
|
try {
|
|
2741
|
-
|
|
2742
|
-
operation = createTopLevelJson1Operation(current, next);
|
|
3008
|
+
pendingOperations = createTopLevelJson1Operations(current, asJsonDocument(update(structuredClone(current)), "update"));
|
|
2743
3009
|
} catch (error) {
|
|
2744
3010
|
fail(error instanceof Error ? error : /* @__PURE__ */ new Error("ONES Wiki collaboration update failed"));
|
|
2745
3011
|
return;
|
|
2746
3012
|
}
|
|
2747
3013
|
snapshotVersion = snapshot.v;
|
|
2748
|
-
|
|
3014
|
+
currentVersion = snapshotVersion;
|
|
3015
|
+
state = "presence";
|
|
3016
|
+
send({
|
|
3017
|
+
a: "p",
|
|
3018
|
+
ch: presenceChannel,
|
|
3019
|
+
id: presenceId,
|
|
3020
|
+
p: null,
|
|
3021
|
+
pv: 2
|
|
3022
|
+
});
|
|
3023
|
+
send({
|
|
3024
|
+
a: "ps",
|
|
3025
|
+
ch: presenceChannel,
|
|
3026
|
+
seq: 1
|
|
3027
|
+
});
|
|
3028
|
+
return;
|
|
3029
|
+
}
|
|
3030
|
+
if (state === "presence") {
|
|
3031
|
+
if (message.a !== "ps" || message.ch !== presenceChannel || message.seq !== 1) return;
|
|
3032
|
+
state = "subscribe";
|
|
3033
|
+
send({
|
|
3034
|
+
a: "s",
|
|
3035
|
+
c: options.teamId,
|
|
3036
|
+
d: options.documentId,
|
|
3037
|
+
v: snapshotVersion
|
|
3038
|
+
});
|
|
3039
|
+
return;
|
|
3040
|
+
}
|
|
3041
|
+
if (state === "subscribe") {
|
|
3042
|
+
if (message.a !== "s" || message.c !== options.teamId || message.d !== options.documentId) return;
|
|
3043
|
+
if (!pendingOperations.length) {
|
|
2749
3044
|
finish({
|
|
2750
3045
|
snapshotVersion,
|
|
2751
3046
|
version: snapshotVersion,
|
|
@@ -2754,21 +3049,23 @@ async function replaceOnesWikiDocument(options, update, dependencies = {
|
|
|
2754
3049
|
return;
|
|
2755
3050
|
}
|
|
2756
3051
|
state = "ack";
|
|
2757
|
-
|
|
2758
|
-
a: "op",
|
|
2759
|
-
c: options.teamId,
|
|
2760
|
-
d: options.documentId,
|
|
2761
|
-
v: snapshotVersion,
|
|
2762
|
-
seq: sequence,
|
|
2763
|
-
op: operation
|
|
2764
|
-
});
|
|
3052
|
+
sendNextOperation();
|
|
2765
3053
|
return;
|
|
2766
3054
|
}
|
|
2767
|
-
if (state === "ack" && message.a === "op" && message.c === options.teamId && message.d === options.documentId && message.seq === sequence)
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
3055
|
+
if (state === "ack" && message.a === "op" && message.c === options.teamId && message.d === options.documentId && message.seq === sequence) {
|
|
3056
|
+
currentVersion = typeof message.v === "number" ? message.v : currentVersion + 1;
|
|
3057
|
+
pendingOperations.shift();
|
|
3058
|
+
if (!pendingOperations.length) {
|
|
3059
|
+
finish({
|
|
3060
|
+
snapshotVersion,
|
|
3061
|
+
version: currentVersion,
|
|
3062
|
+
changed: true
|
|
3063
|
+
});
|
|
3064
|
+
return;
|
|
3065
|
+
}
|
|
3066
|
+
sequence += 1;
|
|
3067
|
+
sendNextOperation();
|
|
3068
|
+
}
|
|
2772
3069
|
});
|
|
2773
3070
|
});
|
|
2774
3071
|
}
|
|
@@ -2806,6 +3103,10 @@ var OnesWikiProductWriter = class {
|
|
|
2806
3103
|
const encodedTeamId = encodeIdentifier(teamId, "team UUID");
|
|
2807
3104
|
const headers = new Headers(init.headers);
|
|
2808
3105
|
headers.set("Authorization", `Bearer ${session.accessToken}`);
|
|
3106
|
+
headers.set("Ones-Auth-Token", session.legacyAuthToken);
|
|
3107
|
+
headers.set("Ones-User-Id", session.legacyUserId);
|
|
3108
|
+
headers.set("Referer", this.options.apiBase);
|
|
3109
|
+
if (session.cookieHeader) headers.set("Cookie", session.cookieHeader);
|
|
2809
3110
|
if (typeof init.body === "string" && !headers.has("Content-Type")) headers.set("Content-Type", "application/json;charset=UTF-8");
|
|
2810
3111
|
const response = await fetch(`${this.options.apiBase}/wiki/api/wiki/team/${encodedTeamId}${apiPath}`, {
|
|
2811
3112
|
...init,
|
|
@@ -2828,9 +3129,9 @@ var OnesWikiProductWriter = class {
|
|
|
2828
3129
|
}
|
|
2829
3130
|
throw new Error("ONES Wiki editor token endpoint was not found");
|
|
2830
3131
|
}
|
|
2831
|
-
async replaceDocument(teamId, resourceId, documentId, update, preferDraft = false) {
|
|
3132
|
+
async replaceDocument(teamId, resourceId, documentId, update, preferDraft = false, editorTokenOverride) {
|
|
2832
3133
|
const session = await this.options.getSession();
|
|
2833
|
-
const editorToken = await this.fetchEditorToken(teamId, resourceId, preferDraft);
|
|
3134
|
+
const editorToken = editorTokenOverride ?? await this.fetchEditorToken(teamId, resourceId, preferDraft);
|
|
2834
3135
|
return { version: (await replaceOnesWikiDocument({
|
|
2835
3136
|
baseUrl: this.options.apiBase,
|
|
2836
3137
|
teamId,
|
|
@@ -2838,6 +3139,7 @@ var OnesWikiProductWriter = class {
|
|
|
2838
3139
|
accessToken: session.accessToken,
|
|
2839
3140
|
editorToken,
|
|
2840
3141
|
userId: session.userUuid,
|
|
3142
|
+
cookieHeader: session.cookieHeader,
|
|
2841
3143
|
displayName: session.userName
|
|
2842
3144
|
}, update)).version };
|
|
2843
3145
|
}
|
|
@@ -2891,20 +3193,120 @@ var OnesWikiProductWriter = class {
|
|
|
2891
3193
|
};
|
|
2892
3194
|
}
|
|
2893
3195
|
async update(params) {
|
|
2894
|
-
const
|
|
2895
|
-
const
|
|
2896
|
-
|
|
2897
|
-
const
|
|
3196
|
+
const encodedPageId = encodeIdentifier(params.pageId, "Wiki page UUID");
|
|
3197
|
+
const encodedSpaceId = params.spaceId ? encodeIdentifier(params.spaceId, "Wiki space UUID") : null;
|
|
3198
|
+
let detail = await this.request(params.teamId, `/page/${encodedPageId}?action=edit`);
|
|
3199
|
+
const title = firstNonEmptyString(detail.title, detail.name, detail.page_title) ?? `Wiki ${params.pageId}`;
|
|
3200
|
+
if (detail.ref_type === 6 && detail.ref_uuid) {
|
|
3201
|
+
const editorToken = await this.fetchEditorToken(params.teamId, params.pageId);
|
|
3202
|
+
const write = await this.replaceDocument(params.teamId, params.pageId, detail.ref_uuid, (snapshot) => {
|
|
3203
|
+
return parseWikiDocument(applyWikiUpdateOperation(JSON.stringify(snapshot), params.operation));
|
|
3204
|
+
}, false, editorToken);
|
|
3205
|
+
const published = await this.request(params.teamId, `/online_page/${encodedPageId}/publish`, {
|
|
3206
|
+
method: "POST",
|
|
3207
|
+
body: JSON.stringify({ title })
|
|
3208
|
+
});
|
|
3209
|
+
if (params.spaceId) this.options.invalidateTree(params.teamId, params.spaceId);
|
|
3210
|
+
return {
|
|
3211
|
+
pageId: params.pageId,
|
|
3212
|
+
title,
|
|
3213
|
+
version: String(published.version ?? published.updated_time ?? write.version),
|
|
3214
|
+
url: params.spaceId ? `${this.options.apiBase}/wiki/#/team/${encodeURIComponent(params.teamId)}/space/${encodeURIComponent(params.spaceId)}/page/${encodeURIComponent(params.pageId)}` : null
|
|
3215
|
+
};
|
|
3216
|
+
}
|
|
3217
|
+
let draftId = firstNonEmptyString(detail.online_draft_uuid, detail.onlineDraftUuid, detail.online_draft?.uuid, detail.draft_uuid, detail.draftUuid, detail.draft?.uuid);
|
|
3218
|
+
let documentId = firstNonEmptyString(detail.online_draft_ref_uuid, detail.onlineDraftRefUuid, detail.online_draft?.ref_uuid, detail.draft_ref_uuid, detail.draftRefUuid, detail.draft?.ref_uuid, detail.ref_uuid);
|
|
3219
|
+
if (!draftId) {
|
|
3220
|
+
if (!encodedSpaceId) throw new Error("ONES Wiki page space is required to create an edit draft");
|
|
3221
|
+
try {
|
|
3222
|
+
const createdDraft = await this.request(params.teamId, `/space/${encodedSpaceId}/drafts/add`, {
|
|
3223
|
+
method: "POST",
|
|
3224
|
+
body: JSON.stringify({
|
|
3225
|
+
page_uuid: params.pageId,
|
|
3226
|
+
status: 2,
|
|
3227
|
+
title
|
|
3228
|
+
})
|
|
3229
|
+
});
|
|
3230
|
+
draftId = firstNonEmptyString(createdDraft.draft_uuid, createdDraft.draftUuid, createdDraft.uuid, createdDraft.id);
|
|
3231
|
+
documentId = firstNonEmptyString(createdDraft.ref_uuid, createdDraft.refUuid, documentId);
|
|
3232
|
+
} catch (error) {
|
|
3233
|
+
const refreshedDetail = await this.request(params.teamId, `/space/${encodedSpaceId}/page/${encodedPageId}`);
|
|
3234
|
+
draftId = firstNonEmptyString(refreshedDetail.draft_uuid, refreshedDetail.draftUuid);
|
|
3235
|
+
if (!draftId) throw error;
|
|
3236
|
+
detail = refreshedDetail;
|
|
3237
|
+
documentId = firstNonEmptyString(refreshedDetail.draft_ref_uuid, refreshedDetail.draftRefUuid, refreshedDetail.ref_uuid, documentId);
|
|
3238
|
+
}
|
|
3239
|
+
}
|
|
3240
|
+
if (!draftId || !encodedSpaceId) throw new Error("ONES Wiki edit draft did not include the required resource and space IDs");
|
|
3241
|
+
const encodedDraftId = encodeIdentifier(draftId, "Wiki draft UUID");
|
|
3242
|
+
const draftDetail = await this.request(params.teamId, `/space/${encodedSpaceId}/draft/${encodedDraftId}`);
|
|
3243
|
+
documentId = firstNonEmptyString(draftDetail.ref_uuid, draftDetail.refUuid, documentId);
|
|
3244
|
+
if (!documentId) throw new Error("ONES Wiki edit draft did not include a collaborative document ID");
|
|
3245
|
+
if (typeof draftDetail.content === "string") {
|
|
3246
|
+
let content;
|
|
3247
|
+
if (params.operation.type === "replace_document") content = markdownToWikiHtml(params.operation.markdown);
|
|
3248
|
+
else if (params.operation.type === "append_blocks") content = `${draftDetail.content}\n${markdownToWikiHtml(params.operation.markdown)}`;
|
|
3249
|
+
else if (params.operation.type === "replace_text") {
|
|
3250
|
+
const occurrences = draftDetail.content.split(params.operation.find).length - 1;
|
|
3251
|
+
if (occurrences !== 1) throw new Error(`ONES Wiki draft replace_text requires exactly one match; found ${occurrences}`);
|
|
3252
|
+
content = draftDetail.content.replace(params.operation.find, params.operation.replace);
|
|
3253
|
+
} else throw new Error("ONES Wiki append_table_row is not supported for legacy page drafts");
|
|
3254
|
+
const published = await this.request(params.teamId, `/space/${encodedSpaceId}/draft/${encodedDraftId}/update`, {
|
|
3255
|
+
method: "POST",
|
|
3256
|
+
body: JSON.stringify({
|
|
3257
|
+
...draftDetail,
|
|
3258
|
+
content,
|
|
3259
|
+
title,
|
|
3260
|
+
page_uuid: params.pageId,
|
|
3261
|
+
space_uuid: params.spaceId,
|
|
3262
|
+
from_version: detail.version ?? draftDetail.from_version,
|
|
3263
|
+
is_published: true,
|
|
3264
|
+
is_forced: true
|
|
3265
|
+
})
|
|
3266
|
+
});
|
|
3267
|
+
this.options.invalidateTree(params.teamId, params.spaceId);
|
|
3268
|
+
const publishedVersion = published.version ?? published.updated_time ?? draftDetail.version ?? draftDetail.updated_time;
|
|
3269
|
+
return {
|
|
3270
|
+
pageId: params.pageId,
|
|
3271
|
+
title,
|
|
3272
|
+
version: publishedVersion === void 0 ? null : String(publishedVersion),
|
|
3273
|
+
url: `${this.options.apiBase}/wiki/#/team/${encodeURIComponent(params.teamId)}/space/${encodeURIComponent(params.spaceId)}/page/${encodeURIComponent(params.pageId)}`
|
|
3274
|
+
};
|
|
3275
|
+
}
|
|
3276
|
+
const write = await this.replaceDocument(params.teamId, draftId, documentId, (snapshot) => {
|
|
2898
3277
|
return parseWikiDocument(applyWikiUpdateOperation(JSON.stringify(snapshot), params.operation));
|
|
3278
|
+
}, true);
|
|
3279
|
+
const refreshedDraft = await this.request(params.teamId, `/space/${encodedSpaceId}/draft/${encodedDraftId}`);
|
|
3280
|
+
await this.request(params.teamId, `/space/${encodedSpaceId}/draft/${encodedDraftId}/update`, {
|
|
3281
|
+
method: "POST",
|
|
3282
|
+
body: JSON.stringify({
|
|
3283
|
+
...refreshedDraft,
|
|
3284
|
+
title,
|
|
3285
|
+
page_uuid: params.pageId,
|
|
3286
|
+
space_uuid: params.spaceId,
|
|
3287
|
+
from_version: detail.version ?? refreshedDraft.from_version,
|
|
3288
|
+
is_published: true,
|
|
3289
|
+
is_forced: true
|
|
3290
|
+
})
|
|
2899
3291
|
});
|
|
2900
3292
|
if (params.spaceId) this.options.invalidateTree(params.teamId, params.spaceId);
|
|
2901
3293
|
return {
|
|
2902
3294
|
pageId: params.pageId,
|
|
2903
|
-
title
|
|
3295
|
+
title,
|
|
2904
3296
|
version: String(write.version),
|
|
2905
3297
|
url: params.spaceId ? `${this.options.apiBase}/wiki/#/team/${encodeURIComponent(params.teamId)}/space/${encodeURIComponent(params.spaceId)}/page/${encodeURIComponent(params.pageId)}` : null
|
|
2906
3298
|
};
|
|
2907
3299
|
}
|
|
3300
|
+
async delete(params) {
|
|
3301
|
+
const encodedSpaceId = encodeIdentifier(params.spaceId, "Wiki space UUID");
|
|
3302
|
+
const encodedPageId = encodeIdentifier(params.pageId, "Wiki page UUID");
|
|
3303
|
+
await this.request(params.teamId, `/space/${encodedSpaceId}/page/${encodedPageId}/delete`, { method: "POST" });
|
|
3304
|
+
this.options.invalidateTree(params.teamId, params.spaceId);
|
|
3305
|
+
return {
|
|
3306
|
+
pageId: params.pageId,
|
|
3307
|
+
deleted: true
|
|
3308
|
+
};
|
|
3309
|
+
}
|
|
2908
3310
|
};
|
|
2909
3311
|
//#endregion
|
|
2910
3312
|
//#region ../../src/adapters/ones/task-query.ts
|
|
@@ -2991,7 +3393,6 @@ var OnesTaskAdapter = class extends BaseAdapter {
|
|
|
2991
3393
|
this.wikiProductWriter = new OnesWikiProductWriter({
|
|
2992
3394
|
apiBase: config.apiBase,
|
|
2993
3395
|
getSession: () => this.login(),
|
|
2994
|
-
fetchPageDetail: (pageId, teamId) => this.wikiReader.fetchPageDetail(pageId, teamId, true),
|
|
2995
3396
|
invalidateTree: (teamId, spaceId) => this.wikiReader.invalidateTree(teamId, spaceId)
|
|
2996
3397
|
});
|
|
2997
3398
|
this.content = new OnesTaskContent({
|
|
@@ -3007,7 +3408,10 @@ var OnesTaskAdapter = class extends BaseAdapter {
|
|
|
3007
3408
|
this.planning = new OnesTaskPlanning({
|
|
3008
3409
|
api: this.api,
|
|
3009
3410
|
getRequirement: (id) => this.getRequirement({ id }),
|
|
3010
|
-
fetchTaskInfo: (taskUuid) => this.fetchTaskInfo(taskUuid)
|
|
3411
|
+
fetchTaskInfo: (taskUuid) => this.fetchTaskInfo(taskUuid),
|
|
3412
|
+
resolveProjectIdentifier: async (projectUuid) => {
|
|
3413
|
+
return (await this.fetchProjects()).find((candidate) => candidate.uuid === projectUuid)?.identifier?.toUpperCase() ?? null;
|
|
3414
|
+
}
|
|
3011
3415
|
});
|
|
3012
3416
|
this.issueReader = new OnesIssueReader({
|
|
3013
3417
|
api: this.api,
|
|
@@ -3225,6 +3629,9 @@ var OnesTaskAdapter = class extends BaseAdapter {
|
|
|
3225
3629
|
async updateWikiPage(params) {
|
|
3226
3630
|
return this.wikiProductWriter.update(params);
|
|
3227
3631
|
}
|
|
3632
|
+
async deleteWikiPage(params) {
|
|
3633
|
+
return this.wikiProductWriter.delete(params);
|
|
3634
|
+
}
|
|
3228
3635
|
/**
|
|
3229
3636
|
* Fetch a work item by UUID, number, display id, or wiki URL.
|
|
3230
3637
|
* Routes by issueType.detailType: requirements (1 and 5) load wiki docs;
|
|
@@ -3967,7 +4374,7 @@ function formatWorkItem(req) {
|
|
|
3967
4374
|
//#endregion
|
|
3968
4375
|
//#region ../../src/tools/list-pending-work-items.ts
|
|
3969
4376
|
const ListPendingWorkItemsSchema = z.object({ source: z.string().optional().describe("Source to read. If omitted, uses the default source.") });
|
|
3970
|
-
function resolveAdapter$
|
|
4377
|
+
function resolveAdapter$4(source, adapters, defaultSource) {
|
|
3971
4378
|
const sourceType = source ?? defaultSource;
|
|
3972
4379
|
if (!sourceType) throw new Error("No source specified and no default source configured");
|
|
3973
4380
|
const adapter = adapters.get(sourceType);
|
|
@@ -4011,7 +4418,7 @@ function formatResult(result) {
|
|
|
4011
4418
|
return lines.join("\n");
|
|
4012
4419
|
}
|
|
4013
4420
|
async function handleListPendingWorkItems(input, adapters, defaultSource) {
|
|
4014
|
-
const result = await resolveAdapter$
|
|
4421
|
+
const result = await resolveAdapter$4(input.source, adapters, defaultSource).listPendingWorkItems();
|
|
4015
4422
|
const safeResult = {
|
|
4016
4423
|
...result,
|
|
4017
4424
|
items: result.items.map(sanitizeItem)
|
|
@@ -4125,7 +4532,7 @@ var RequirementDecompositionApprovalStore = class {
|
|
|
4125
4532
|
return record;
|
|
4126
4533
|
}
|
|
4127
4534
|
};
|
|
4128
|
-
function resolveAdapter$
|
|
4535
|
+
function resolveAdapter$3(source, adapters, defaultSource) {
|
|
4129
4536
|
const sourceType = source ?? defaultSource;
|
|
4130
4537
|
if (!sourceType) throw new Error("No source specified and no default source configured");
|
|
4131
4538
|
const adapter = adapters.get(sourceType);
|
|
@@ -4228,7 +4635,7 @@ function buildOperations(displayId, tasks) {
|
|
|
4228
4635
|
});
|
|
4229
4636
|
}
|
|
4230
4637
|
async function handleInspectRequirementDecomposition(input, adapters, defaultSource) {
|
|
4231
|
-
const { adapter } = resolveAdapter$
|
|
4638
|
+
const { adapter } = resolveAdapter$3(input.source, adapters, defaultSource);
|
|
4232
4639
|
const context = sanitizedContext(await adapter.getRequirementDecompositionContext({ requirementId: input.requirementId }));
|
|
4233
4640
|
return {
|
|
4234
4641
|
content: [{
|
|
@@ -4239,7 +4646,7 @@ async function handleInspectRequirementDecomposition(input, adapters, defaultSou
|
|
|
4239
4646
|
};
|
|
4240
4647
|
}
|
|
4241
4648
|
async function handlePrepareRequirementDecomposition(input, adapters, approvals, defaultSource) {
|
|
4242
|
-
const { sourceType, adapter } = resolveAdapter$
|
|
4649
|
+
const { sourceType, adapter } = resolveAdapter$3(input.source, adapters, defaultSource);
|
|
4243
4650
|
const context = await adapter.getRequirementDecompositionContext({ requirementId: input.requirementId });
|
|
4244
4651
|
if (context.requirement.workItemKind !== "requirement") throw new Error("Only requirements can be decomposed");
|
|
4245
4652
|
if (!context.decompositionRelation.verified || !context.decompositionRelation.uuid) throw new Error("The \"requirement decomposition task\" relationship could not be verified from the read response. No plan or write was prepared.");
|
|
@@ -4289,7 +4696,7 @@ async function handleApplyRequirementDecomposition(input, adapters, approvals, o
|
|
|
4289
4696
|
if (!record) throw new Error("Approval token is invalid, expired, or already used. Prepare the decomposition again.");
|
|
4290
4697
|
if ((input.source ?? options.defaultSource) !== record.source) throw new Error("Approval token source does not match the requested source");
|
|
4291
4698
|
if (input.planHash !== record.planHash) throw new Error("Plan hash does not match the approved decomposition");
|
|
4292
|
-
const { adapter } = resolveAdapter$
|
|
4699
|
+
const { adapter } = resolveAdapter$3(record.source, adapters, options.defaultSource);
|
|
4293
4700
|
const current = await adapter.getRequirementDecompositionContext({ requirementId: record.requirementId });
|
|
4294
4701
|
if (current.requirement.uuid !== record.requirementUuid || !isSameRequirementBaseline(current.baseline, record.baseline)) throw new Error("Requirement or related tasks changed after preparation. Prepare and confirm a new decomposition.");
|
|
4295
4702
|
if (!current.decompositionRelation.verified || current.decompositionRelation.uuid !== record.decompositionRelation.uuid) throw new Error("The requirement decomposition relationship changed or is no longer verified. Prepare and confirm again.");
|
|
@@ -4395,12 +4802,82 @@ function formatUpdateTaskPlanDatesResult(result) {
|
|
|
4395
4802
|
return lines.join("\n");
|
|
4396
4803
|
}
|
|
4397
4804
|
//#endregion
|
|
4805
|
+
//#region ../../src/tools/wiki-delete.ts
|
|
4806
|
+
const SourceSchema$1 = z.string().trim().min(1).optional();
|
|
4807
|
+
const DeleteEmptyWikiDuplicatesSchema = z.object({
|
|
4808
|
+
keepPageId: z.string().trim().min(1),
|
|
4809
|
+
duplicatePageIds: z.array(z.string().trim().min(1)).min(1).max(20),
|
|
4810
|
+
expectedTitle: z.string().trim().min(1),
|
|
4811
|
+
confirmed: z.literal(true).describe("Set true only after the user confirms the exact keep and delete page IDs immediately before submission."),
|
|
4812
|
+
source: SourceSchema$1
|
|
4813
|
+
});
|
|
4814
|
+
const DeleteEmptyWikiDuplicatesOutputSchema = z.object({
|
|
4815
|
+
keptPageId: z.string(),
|
|
4816
|
+
deletedPageIds: z.array(z.string())
|
|
4817
|
+
});
|
|
4818
|
+
function resolveAdapter$2(source, adapters, defaultSource) {
|
|
4819
|
+
const sourceType = source ?? defaultSource;
|
|
4820
|
+
if (!sourceType) throw new Error("No source specified and no default source configured");
|
|
4821
|
+
const adapter = adapters.get(sourceType);
|
|
4822
|
+
if (!adapter) throw new Error(`Source "${sourceType}" is not configured`);
|
|
4823
|
+
return {
|
|
4824
|
+
sourceType,
|
|
4825
|
+
adapter
|
|
4826
|
+
};
|
|
4827
|
+
}
|
|
4828
|
+
function assertCleanupTarget(keep, duplicates, expectedTitle) {
|
|
4829
|
+
if (keep.title !== expectedTitle) throw new Error("The retained Wiki page title no longer matches the confirmed title");
|
|
4830
|
+
if (keep.content.trim() === `# ${keep.title}`) throw new Error("The retained Wiki page does not contain a body");
|
|
4831
|
+
for (const duplicate of duplicates) {
|
|
4832
|
+
if (duplicate.title !== expectedTitle) throw new Error(`Wiki duplicate ${duplicate.pageId} title no longer matches the confirmed title`);
|
|
4833
|
+
if (duplicate.teamId !== keep.teamId || duplicate.spaceId !== keep.spaceId || duplicate.parentPageId !== keep.parentPageId) throw new Error(`Wiki duplicate ${duplicate.pageId} is not a sibling of the retained page`);
|
|
4834
|
+
if (duplicate.content.trim() !== `# ${duplicate.title}`) throw new Error(`Wiki duplicate ${duplicate.pageId} is not empty and will not be deleted`);
|
|
4835
|
+
}
|
|
4836
|
+
}
|
|
4837
|
+
async function handleDeleteEmptyWikiDuplicates(input, adapters, options) {
|
|
4838
|
+
if (!options.writesEnabled) throw new Error("Wiki writes are disabled. Enable both ONES_WIKI_ENABLE_WRITES=true and source option wikiWrites=true.");
|
|
4839
|
+
const uniqueDuplicateIds = [...new Set(input.duplicatePageIds)];
|
|
4840
|
+
if (uniqueDuplicateIds.length !== input.duplicatePageIds.length) throw new Error("duplicatePageIds contains repeated page IDs");
|
|
4841
|
+
if (uniqueDuplicateIds.includes(input.keepPageId)) throw new Error("The retained Wiki page cannot also be deleted");
|
|
4842
|
+
const { adapter } = resolveAdapter$2(input.source, adapters, options.defaultSource);
|
|
4843
|
+
const [keep, ...duplicates] = await Promise.all([adapter.getWikiPage({ pageId: input.keepPageId }), ...uniqueDuplicateIds.map((pageId) => adapter.getWikiPage({ pageId }))]);
|
|
4844
|
+
assertCleanupTarget(keep, duplicates, input.expectedTitle);
|
|
4845
|
+
const spaceId = keep.spaceId;
|
|
4846
|
+
if (!spaceId) throw new Error("The retained Wiki page space could not be verified");
|
|
4847
|
+
const operationHash = crypto.createHash("sha256").update(JSON.stringify({
|
|
4848
|
+
keepPageId: input.keepPageId,
|
|
4849
|
+
duplicatePageIds: uniqueDuplicateIds,
|
|
4850
|
+
expectedTitle: input.expectedTitle,
|
|
4851
|
+
baselines: [keep, ...duplicates].map((page) => ({
|
|
4852
|
+
pageId: page.pageId,
|
|
4853
|
+
version: page.version,
|
|
4854
|
+
contentHash: page.contentHash
|
|
4855
|
+
}))
|
|
4856
|
+
})).digest("hex");
|
|
4857
|
+
for (const duplicate of duplicates) await adapter.deleteWikiPage({
|
|
4858
|
+
teamId: duplicate.teamId,
|
|
4859
|
+
spaceId,
|
|
4860
|
+
pageId: duplicate.pageId
|
|
4861
|
+
});
|
|
4862
|
+
const result = {
|
|
4863
|
+
keptPageId: keep.pageId,
|
|
4864
|
+
deletedPageIds: duplicates.map((page) => page.pageId)
|
|
4865
|
+
};
|
|
4866
|
+
return {
|
|
4867
|
+
content: [{
|
|
4868
|
+
type: "text",
|
|
4869
|
+
text: `Kept Wiki page ${result.keptPageId} and deleted empty duplicates: ${result.deletedPageIds.join(", ")}.\noperationHash: ${operationHash}`
|
|
4870
|
+
}],
|
|
4871
|
+
structuredContent: result
|
|
4872
|
+
};
|
|
4873
|
+
}
|
|
4874
|
+
//#endregion
|
|
4398
4875
|
//#region ../../src/tools/wiki-read.ts
|
|
4399
4876
|
const WikiSourceSchema = z.string().trim().min(1).optional();
|
|
4400
4877
|
const GetOnesWikiPageSchema = z.object({
|
|
4401
4878
|
pageId: z.string().trim().min(1).optional(),
|
|
4402
4879
|
url: z.string().url().optional(),
|
|
4403
|
-
path: z.union([z.string().trim().min(1), z.array(z.string().trim().min(1)).min(1)]).optional().describe("Wiki path such as \"Department/Annual Plans/2026\". Resolves a unique exact, title-prefix, or confidently close match; otherwise returns candidate pages for confirmation."),
|
|
4880
|
+
path: z.union([z.string().trim().min(1), z.array(z.string().trim().min(1)).min(1)]).optional().describe("Wiki path such as \"Department/Annual Plans/2026\". Exact self-reference segments such as \"我的\", \"我\", \"me\", or \"my\" resolve the authenticated user through ONES token info without exposing the display name. Resolves a unique exact, title-prefix, or confidently close match; otherwise returns candidate pages for confirmation."),
|
|
4404
4881
|
teamId: z.string().trim().min(1).optional(),
|
|
4405
4882
|
spaceId: z.string().trim().min(1).optional(),
|
|
4406
4883
|
revealSensitiveSecrets: z.boolean().default(false).describe("Default false. Set true only when the user explicitly asks to reveal secrets."),
|
|
@@ -4502,6 +4979,19 @@ async function handleGetOnesWikiPage(input, adapters, defaultSource) {
|
|
|
4502
4979
|
teamId: resolved?.teamId ?? input.teamId,
|
|
4503
4980
|
spaceId: resolved?.spaceId ?? input.spaceId
|
|
4504
4981
|
}), input.revealSensitiveSecrets);
|
|
4982
|
+
if (resolved) {
|
|
4983
|
+
page.title = resolved.title;
|
|
4984
|
+
page.breadcrumb = resolved.breadcrumb;
|
|
4985
|
+
const redactPrivateValues = resolved.redactPrivateValues;
|
|
4986
|
+
if (redactPrivateValues) {
|
|
4987
|
+
page.content = redactPrivateValues(page.content);
|
|
4988
|
+
page.attachments = page.attachments.map((attachment) => ({
|
|
4989
|
+
...attachment,
|
|
4990
|
+
name: redactPrivateValues(attachment.name),
|
|
4991
|
+
url: redactPrivateValues(attachment.url)
|
|
4992
|
+
}));
|
|
4993
|
+
}
|
|
4994
|
+
}
|
|
4505
4995
|
return {
|
|
4506
4996
|
content: [{
|
|
4507
4997
|
type: "text",
|
|
@@ -4649,7 +5139,7 @@ async function handleLookupEnvironmentAccess(input, adapters, defaultSource) {
|
|
|
4649
5139
|
//#region ../../src/tools/wiki-write.ts
|
|
4650
5140
|
const APPROVAL_TTL_MS = 1800 * 1e3;
|
|
4651
5141
|
const SourceSchema = z.string().trim().min(1).optional();
|
|
4652
|
-
const PathSchema = z.union([z.string().trim().min(1), z.array(z.string().trim().min(1)).min(1)]);
|
|
5142
|
+
const PathSchema = z.union([z.string().trim().min(1), z.array(z.string().trim().min(1)).min(1)]).describe("Wiki path. Exact self-reference segments such as \"我的\", \"我\", \"me\", or \"my\" resolve the authenticated user through ONES token info without exposing the display name.");
|
|
4653
5143
|
const PrepareWikiCreateSchema = z.object({
|
|
4654
5144
|
parentPageId: z.string().trim().min(1).optional(),
|
|
4655
5145
|
parentPath: PathSchema.optional(),
|
|
@@ -4673,6 +5163,10 @@ const ReplaceTextSchema = z.object({
|
|
|
4673
5163
|
find: z.string().min(1),
|
|
4674
5164
|
replace: z.string()
|
|
4675
5165
|
});
|
|
5166
|
+
const ReplaceDocumentSchema = z.object({
|
|
5167
|
+
type: z.literal("replace_document"),
|
|
5168
|
+
markdown: z.string().trim().min(1)
|
|
5169
|
+
});
|
|
4676
5170
|
const PrepareWikiUpdateSchema = z.object({
|
|
4677
5171
|
pageId: z.string().trim().min(1).optional(),
|
|
4678
5172
|
url: z.string().url().optional(),
|
|
@@ -4682,7 +5176,8 @@ const PrepareWikiUpdateSchema = z.object({
|
|
|
4682
5176
|
operation: z.discriminatedUnion("type", [
|
|
4683
5177
|
AppendBlocksSchema,
|
|
4684
5178
|
AppendTableRowSchema,
|
|
4685
|
-
ReplaceTextSchema
|
|
5179
|
+
ReplaceTextSchema,
|
|
5180
|
+
ReplaceDocumentSchema
|
|
4686
5181
|
]),
|
|
4687
5182
|
source: SourceSchema
|
|
4688
5183
|
}).refine((value) => [
|
|
@@ -4723,6 +5218,10 @@ const WikiUpdateOperationSchema = z.discriminatedUnion("type", [
|
|
|
4723
5218
|
type: z.literal("replace_text"),
|
|
4724
5219
|
find: z.string(),
|
|
4725
5220
|
replace: z.string()
|
|
5221
|
+
}),
|
|
5222
|
+
z.object({
|
|
5223
|
+
type: z.literal("replace_document"),
|
|
5224
|
+
markdown: z.string()
|
|
4726
5225
|
})
|
|
4727
5226
|
]);
|
|
4728
5227
|
const WikiUpdateRequestSchema = z.object({
|
|
@@ -4852,11 +5351,24 @@ async function handlePrepareWikiCreate(input, adapters, approvals, defaultSource
|
|
|
4852
5351
|
const teamId = resolved?.teamId ?? parent.teamId;
|
|
4853
5352
|
const spaceId = resolved?.spaceId ?? parent.spaceId;
|
|
4854
5353
|
if (!spaceId) throw new Error("The target space could not be verified");
|
|
5354
|
+
const title = input.title.trim();
|
|
5355
|
+
const titleCandidates = await adapter.searchWikiPages({
|
|
5356
|
+
query: title,
|
|
5357
|
+
teamId,
|
|
5358
|
+
spaceId,
|
|
5359
|
+
limit: 50
|
|
5360
|
+
});
|
|
5361
|
+
const siblingConflicts = (await Promise.all(titleCandidates.filter((candidate) => candidate.title === title).map((candidate) => adapter.getWikiPage({
|
|
5362
|
+
pageId: candidate.pageId,
|
|
5363
|
+
teamId: candidate.teamId,
|
|
5364
|
+
spaceId: candidate.spaceId ?? spaceId
|
|
5365
|
+
})))).filter((page) => page.parentPageId === parent.pageId);
|
|
5366
|
+
if (siblingConflicts.length) throw new Error(`Wiki title already exists under the selected parent (${siblingConflicts.map((page) => page.pageId).join(", ")}). Ask the user to choose one action: edit the existing page, delete it and recreate, or create with a new title.`);
|
|
4855
5367
|
const requestWithoutKey = {
|
|
4856
5368
|
teamId,
|
|
4857
5369
|
spaceId,
|
|
4858
5370
|
parentPageId: parent.pageId,
|
|
4859
|
-
title
|
|
5371
|
+
title,
|
|
4860
5372
|
markdown: input.markdown
|
|
4861
5373
|
};
|
|
4862
5374
|
const operationHash = hashOperation({
|
|
@@ -4878,7 +5390,7 @@ async function handlePrepareWikiCreate(input, adapters, approvals, defaultSource
|
|
|
4878
5390
|
});
|
|
4879
5391
|
const plan = {
|
|
4880
5392
|
kind: "create",
|
|
4881
|
-
targetBreadcrumb: [...parent.breadcrumb, input.title.trim()],
|
|
5393
|
+
targetBreadcrumb: [...resolved?.breadcrumb ?? parent.breadcrumb, input.title.trim()],
|
|
4882
5394
|
request,
|
|
4883
5395
|
parentBaseline: baseline(parent),
|
|
4884
5396
|
operationHash,
|
|
@@ -4935,7 +5447,7 @@ async function handlePrepareWikiUpdate(input, adapters, approvals, defaultSource
|
|
|
4935
5447
|
});
|
|
4936
5448
|
const plan = {
|
|
4937
5449
|
kind: "update",
|
|
4938
|
-
targetBreadcrumb: page.breadcrumb,
|
|
5450
|
+
targetBreadcrumb: resolved?.breadcrumb ?? page.breadcrumb,
|
|
4939
5451
|
request,
|
|
4940
5452
|
operationHash,
|
|
4941
5453
|
approvalToken: approval.token,
|
|
@@ -4945,7 +5457,7 @@ async function handlePrepareWikiUpdate(input, adapters, approvals, defaultSource
|
|
|
4945
5457
|
content: [{
|
|
4946
5458
|
type: "text",
|
|
4947
5459
|
text: [
|
|
4948
|
-
`Prepared Wiki update for ${
|
|
5460
|
+
`Prepared Wiki update for ${plan.targetBreadcrumb.join(" / ") || page.title}.`,
|
|
4949
5461
|
"No write was performed. Ask the user to confirm this exact operation immediately before apply.",
|
|
4950
5462
|
`operationHash: ${plan.operationHash}`,
|
|
4951
5463
|
`approvalToken: ${plan.approvalToken}`,
|
|
@@ -5058,7 +5570,7 @@ function createRequirementsServer(config, adapterOverrides) {
|
|
|
5058
5570
|
});
|
|
5059
5571
|
server.registerTool("get_ones_wiki_page", {
|
|
5060
5572
|
title: "Get ONES Wiki Page",
|
|
5061
|
-
description: "Read one ONES Wiki page as Markdown by page ID, URL, or hierarchical path. Prefer path for requests like \"Department/Annual Plans/2026\". Unique confidently close paths are corrected automatically; unresolved or ambiguous paths return candidate pages for confirmation. Sensitive values are redacted by default; revealing them requires an explicit argument based on an explicit user request.",
|
|
5573
|
+
description: "Read one ONES Wiki page as Markdown by page ID, URL, or hierarchical path. Prefer path for requests like \"Department/Annual Plans/2026\". Exact self-reference path segments such as \"我的\", \"我\", \"me\", or \"my\" resolve the authenticated user through ONES token info; never infer or expose the display name from local Git or machine configuration. Unique confidently close paths are corrected automatically; unresolved or ambiguous paths return candidate pages for confirmation. Sensitive values are redacted by default; revealing them requires an explicit argument based on an explicit user request.",
|
|
5062
5574
|
inputSchema: GetOnesWikiPageSchema,
|
|
5063
5575
|
annotations: {
|
|
5064
5576
|
readOnlyHint: true,
|
|
@@ -5120,7 +5632,7 @@ function createRequirementsServer(config, adapterOverrides) {
|
|
|
5120
5632
|
});
|
|
5121
5633
|
server.registerTool("prepare_wiki_create", {
|
|
5122
5634
|
title: "Prepare ONES Wiki Create",
|
|
5123
|
-
description: "Resolve an exact parent page and prepare one exact Wiki create operation. Never writes. Returns a one-time 30-minute approval token.",
|
|
5635
|
+
description: "Resolve an exact parent page and prepare one exact Wiki create operation. If a sibling page already has the requested title, fail closed and require the user to choose: edit the existing page, delete it and recreate, or use a new title. Exact self-reference path segments such as \"我的\", \"我\", \"me\", or \"my\" resolve the authenticated user through ONES token info; never infer or expose the display name from local Git or machine configuration. Never writes. Returns a one-time 30-minute approval token.",
|
|
5124
5636
|
inputSchema: PrepareWikiCreateSchema,
|
|
5125
5637
|
outputSchema: PrepareWikiCreateOutputSchema,
|
|
5126
5638
|
annotations: {
|
|
@@ -5159,7 +5671,7 @@ function createRequirementsServer(config, adapterOverrides) {
|
|
|
5159
5671
|
});
|
|
5160
5672
|
server.registerTool("prepare_wiki_update", {
|
|
5161
5673
|
title: "Prepare ONES Wiki Update",
|
|
5162
|
-
description: "Prepare an exact minimal Wiki update, including exact table-row targeting. Never writes. Ambiguous pages, tables, or text matches fail closed.",
|
|
5674
|
+
description: "Prepare an exact minimal Wiki update, including exact table-row targeting. Exact self-reference path segments such as \"我的\", \"我\", \"me\", or \"my\" resolve the authenticated user through ONES token info; never infer or expose the display name from local Git or machine configuration. Never writes. Ambiguous pages, tables, or text matches fail closed.",
|
|
5163
5675
|
inputSchema: PrepareWikiUpdateSchema,
|
|
5164
5676
|
outputSchema: PrepareWikiUpdateOutputSchema,
|
|
5165
5677
|
annotations: {
|
|
@@ -5196,6 +5708,28 @@ function createRequirementsServer(config, adapterOverrides) {
|
|
|
5196
5708
|
return toolError(err);
|
|
5197
5709
|
}
|
|
5198
5710
|
});
|
|
5711
|
+
server.registerTool("delete_empty_wiki_duplicates", {
|
|
5712
|
+
title: "Delete Empty ONES Wiki Duplicates",
|
|
5713
|
+
description: "Delete only explicitly confirmed, title-only duplicate sibling pages while retaining one verified page with a body. Revalidates every page immediately before deletion and fails closed if any duplicate contains additional content.",
|
|
5714
|
+
inputSchema: DeleteEmptyWikiDuplicatesSchema,
|
|
5715
|
+
outputSchema: DeleteEmptyWikiDuplicatesOutputSchema,
|
|
5716
|
+
annotations: {
|
|
5717
|
+
readOnlyHint: false,
|
|
5718
|
+
destructiveHint: true,
|
|
5719
|
+
idempotentHint: false,
|
|
5720
|
+
openWorldHint: true
|
|
5721
|
+
}
|
|
5722
|
+
}, async (params) => {
|
|
5723
|
+
try {
|
|
5724
|
+
const sourceType = params.source ?? defaultSource;
|
|
5725
|
+
return await handleDeleteEmptyWikiDuplicates(params, adapters, {
|
|
5726
|
+
defaultSource,
|
|
5727
|
+
writesEnabled: wikiWritesEnabled(sourceType)
|
|
5728
|
+
});
|
|
5729
|
+
} catch (err) {
|
|
5730
|
+
return toolError(err);
|
|
5731
|
+
}
|
|
5732
|
+
});
|
|
5199
5733
|
server.registerTool("list_sources", {
|
|
5200
5734
|
title: "List Sources",
|
|
5201
5735
|
description: "List all configured requirement sources and their status",
|