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.cjs
CHANGED
|
@@ -211,7 +211,7 @@ function loadConfig(startDir) {
|
|
|
211
211
|
}
|
|
212
212
|
//#endregion
|
|
213
213
|
//#region package.json
|
|
214
|
-
var version = "0.
|
|
214
|
+
var version = "0.5.0";
|
|
215
215
|
//#endregion
|
|
216
216
|
//#region ../../src/utils/ones-issue-kind.ts
|
|
217
217
|
/**
|
|
@@ -291,18 +291,34 @@ var BaseAdapter = class {
|
|
|
291
291
|
async updateWikiPage(_params) {
|
|
292
292
|
throw new Error(`${this.sourceType}: Wiki update endpoint is not verified`);
|
|
293
293
|
}
|
|
294
|
+
/** Production Wiki deletes stay disabled until the exact provider endpoint is verified. */
|
|
295
|
+
async deleteWikiPage(_params) {
|
|
296
|
+
throw new Error(`${this.sourceType}: Wiki delete endpoint is not verified`);
|
|
297
|
+
}
|
|
294
298
|
};
|
|
295
299
|
//#endregion
|
|
296
300
|
//#region ../../src/adapters/ones/api-client.ts
|
|
297
301
|
function base64Url(buffer) {
|
|
298
302
|
return buffer.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
|
|
299
303
|
}
|
|
300
|
-
function getSetCookies(response) {
|
|
304
|
+
function getSetCookies$1(response) {
|
|
305
|
+
if (!response.headers) return [];
|
|
301
306
|
const headers = response.headers;
|
|
302
307
|
if (headers.getSetCookie) return headers.getSetCookie();
|
|
303
308
|
const raw = response.headers.get("set-cookie");
|
|
304
309
|
return raw ? [raw] : [];
|
|
305
310
|
}
|
|
311
|
+
function mergeResponseCookies(cookieJar, response) {
|
|
312
|
+
for (const cookie of getSetCookies$1(response)) {
|
|
313
|
+
const pair = cookie.split(";")[0];
|
|
314
|
+
const separator = pair.indexOf("=");
|
|
315
|
+
if (separator <= 0) continue;
|
|
316
|
+
cookieJar.set(pair.slice(0, separator), pair.slice(separator + 1));
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
function serializeCookies(cookieJar) {
|
|
320
|
+
return [...cookieJar].map(([name, value]) => `${name}=${value}`).join("; ");
|
|
321
|
+
}
|
|
306
322
|
function parseRedirectValue(location, names) {
|
|
307
323
|
try {
|
|
308
324
|
const parsed = new URL(location);
|
|
@@ -330,6 +346,7 @@ var OnesApiClient = class {
|
|
|
330
346
|
const email = this.resolvedAuth.email;
|
|
331
347
|
const password = this.resolvedAuth.password;
|
|
332
348
|
if (!email || !password) throw new Error("ONES auth requires email and password (ones-pkce auth type)");
|
|
349
|
+
const cookieJar = /* @__PURE__ */ new Map();
|
|
333
350
|
const certRes = await fetch(`${baseUrl}/identity/api/encryption_cert`, {
|
|
334
351
|
method: "POST",
|
|
335
352
|
headers: { "Content-Type": "application/json" },
|
|
@@ -350,11 +367,15 @@ var OnesApiClient = class {
|
|
|
350
367
|
})
|
|
351
368
|
});
|
|
352
369
|
if (!loginRes.ok) throw new Error(`ONES: Login failed with status ${loginRes.status}`);
|
|
353
|
-
|
|
370
|
+
mergeResponseCookies(cookieJar, loginRes);
|
|
354
371
|
const loginData = await loginRes.json();
|
|
355
372
|
const configuredOrgUuid = this.config.options?.orgUuid;
|
|
356
373
|
const orgUser = configuredOrgUuid ? loginData.org_users.find((user) => user.org_uuid === configuredOrgUuid) ?? loginData.org_users[0] : loginData.org_users[0];
|
|
357
374
|
if (!orgUser) throw new Error("ONES: No organizations found for this user");
|
|
375
|
+
cookieJar.set("ones-region-uuid", orgUser.region_uuid);
|
|
376
|
+
cookieJar.set("ones-org-uuid", orgUser.org_uuid);
|
|
377
|
+
const timezone = cookieJar.get("ones-tz");
|
|
378
|
+
if (timezone) cookieJar.set("timezone", timezone);
|
|
358
379
|
const codeVerifier = base64Url(node_crypto.default.randomBytes(32));
|
|
359
380
|
const codeChallenge = base64Url(node_crypto.default.createHash("sha256").update(codeVerifier).digest());
|
|
360
381
|
const authorizeParams = new URLSearchParams({
|
|
@@ -366,15 +387,17 @@ var OnesApiClient = class {
|
|
|
366
387
|
redirect_uri: `${baseUrl}/auth/authorize/callback`,
|
|
367
388
|
state: `org_uuid=${orgUser.org_uuid}`
|
|
368
389
|
});
|
|
369
|
-
const
|
|
390
|
+
const authorizeRes = await fetch(`${baseUrl}/identity/authorize`, {
|
|
370
391
|
method: "POST",
|
|
371
392
|
headers: {
|
|
372
393
|
"Content-Type": "application/x-www-form-urlencoded",
|
|
373
|
-
"Cookie":
|
|
394
|
+
"Cookie": serializeCookies(cookieJar)
|
|
374
395
|
},
|
|
375
396
|
body: authorizeParams.toString(),
|
|
376
397
|
redirect: "manual"
|
|
377
|
-
})
|
|
398
|
+
});
|
|
399
|
+
mergeResponseCookies(cookieJar, authorizeRes);
|
|
400
|
+
const authorizeLocation = authorizeRes.headers.get("location");
|
|
378
401
|
if (!authorizeLocation) throw new Error("ONES: Authorize response missing location header");
|
|
379
402
|
let code = parseRedirectValue(authorizeLocation, ["code"]);
|
|
380
403
|
if (!code) {
|
|
@@ -384,7 +407,7 @@ var OnesApiClient = class {
|
|
|
384
407
|
method: "POST",
|
|
385
408
|
headers: {
|
|
386
409
|
"Content-Type": "application/json;charset=UTF-8",
|
|
387
|
-
"Cookie":
|
|
410
|
+
"Cookie": serializeCookies(cookieJar)
|
|
388
411
|
},
|
|
389
412
|
body: JSON.stringify({
|
|
390
413
|
auth_request_id: authRequestId,
|
|
@@ -393,12 +416,15 @@ var OnesApiClient = class {
|
|
|
393
416
|
org_user_uuid: orgUser.org_user.org_user_uuid
|
|
394
417
|
})
|
|
395
418
|
});
|
|
419
|
+
mergeResponseCookies(cookieJar, finalizeRes);
|
|
396
420
|
if (!finalizeRes.ok) throw new Error(`ONES: Finalize failed with status ${finalizeRes.status}`);
|
|
397
|
-
const
|
|
421
|
+
const callbackRes = await fetch(`${baseUrl}/identity/authorize/callback?id=${authRequestId}&lang=zh`, {
|
|
398
422
|
method: "GET",
|
|
399
|
-
headers: { Cookie:
|
|
423
|
+
headers: { Cookie: serializeCookies(cookieJar) },
|
|
400
424
|
redirect: "manual"
|
|
401
|
-
})
|
|
425
|
+
});
|
|
426
|
+
mergeResponseCookies(cookieJar, callbackRes);
|
|
427
|
+
const callbackLocation = callbackRes.headers.get("location");
|
|
402
428
|
if (!callbackLocation) throw new Error("ONES: Callback response missing location header");
|
|
403
429
|
code = parseRedirectValue(callbackLocation, ["code"]);
|
|
404
430
|
}
|
|
@@ -407,7 +433,7 @@ var OnesApiClient = class {
|
|
|
407
433
|
method: "POST",
|
|
408
434
|
headers: {
|
|
409
435
|
"Content-Type": "application/x-www-form-urlencoded",
|
|
410
|
-
"Cookie":
|
|
436
|
+
"Cookie": serializeCookies(cookieJar)
|
|
411
437
|
},
|
|
412
438
|
body: new URLSearchParams({
|
|
413
439
|
grant_type: "authorization_code",
|
|
@@ -417,16 +443,20 @@ var OnesApiClient = class {
|
|
|
417
443
|
redirect_uri: `${baseUrl}/auth/authorize/callback`
|
|
418
444
|
}).toString()
|
|
419
445
|
});
|
|
446
|
+
mergeResponseCookies(cookieJar, tokenRes);
|
|
420
447
|
if (!tokenRes.ok) throw new Error(`ONES: Token exchange failed with status ${tokenRes.status}`);
|
|
421
448
|
const token = await tokenRes.json();
|
|
449
|
+
cookieJar.set("ones-lt", token.access_token);
|
|
422
450
|
const teamsRes = await fetch(`${baseUrl}/project/api/project/organization/${orgUser.org_uuid}/stamps/data?t=org_my_team`, {
|
|
423
451
|
method: "POST",
|
|
424
452
|
headers: {
|
|
425
453
|
"Authorization": `Bearer ${token.access_token}`,
|
|
426
|
-
"Content-Type": "application/json;charset=UTF-8"
|
|
454
|
+
"Content-Type": "application/json;charset=UTF-8",
|
|
455
|
+
"Cookie": serializeCookies(cookieJar)
|
|
427
456
|
},
|
|
428
457
|
body: JSON.stringify({ org_my_team: 0 })
|
|
429
458
|
});
|
|
459
|
+
mergeResponseCookies(cookieJar, teamsRes);
|
|
430
460
|
if (!teamsRes.ok) throw new Error(`ONES: Failed to fetch teams: ${teamsRes.status}`);
|
|
431
461
|
const teams = (await teamsRes.json()).org_my_team?.teams ?? [];
|
|
432
462
|
const configuredTeamUuid = this.config.options?.teamUuid;
|
|
@@ -438,6 +468,9 @@ var OnesApiClient = class {
|
|
|
438
468
|
orgUuid: orgUser.org_uuid,
|
|
439
469
|
userUuid: orgUser.org_user.org_user_uuid,
|
|
440
470
|
userName: orgUser.org_user.name,
|
|
471
|
+
cookieHeader: serializeCookies(cookieJar),
|
|
472
|
+
legacyAuthToken: loginData.sid,
|
|
473
|
+
legacyUserId: loginData.auth_user_uuid,
|
|
441
474
|
expiresAt: Date.now() + (token.expires_in - 60) * 1e3
|
|
442
475
|
};
|
|
443
476
|
return this.session;
|
|
@@ -670,10 +703,11 @@ function mapOnesTypeFromTask(task) {
|
|
|
670
703
|
return mapOnesType(task.subIssueType?.name ?? task.issueType?.name ?? "");
|
|
671
704
|
}
|
|
672
705
|
function toRequirement(task, description = "", attachments = []) {
|
|
706
|
+
const displayId = taskDisplayId({}, task, task.project?.identifier?.toUpperCase() ?? null);
|
|
673
707
|
return {
|
|
674
708
|
id: task.uuid,
|
|
675
709
|
source: "ones",
|
|
676
|
-
title:
|
|
710
|
+
title: `${displayId} ${task.name}`,
|
|
677
711
|
description,
|
|
678
712
|
status: mapOnesStatus(task.status?.category ?? "to_do"),
|
|
679
713
|
priority: mapOnesPriority(task.priority?.value ?? "normal"),
|
|
@@ -1192,17 +1226,43 @@ function renderWikiContent(content, context = { imageSources: [] }) {
|
|
|
1192
1226
|
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();
|
|
1193
1227
|
}
|
|
1194
1228
|
function newWikiBlockId() {
|
|
1195
|
-
|
|
1229
|
+
for (;;) {
|
|
1230
|
+
const id = (0, node_crypto.randomBytes)(9).toString("base64url").replace(/[^a-z0-9]/gi, "").slice(0, 9);
|
|
1231
|
+
if (/^[a-z][a-z0-9]{8}$/i.test(id)) return id;
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
function markdownTextRuns(text) {
|
|
1235
|
+
if (!text) return [];
|
|
1236
|
+
const runs = [];
|
|
1237
|
+
const inlinePattern = /\[([^\]]+)\]\((https?:\/\/[^)\s]+)\)|`([^`]+)`/g;
|
|
1238
|
+
let cursor = 0;
|
|
1239
|
+
for (const match of text.matchAll(inlinePattern)) {
|
|
1240
|
+
const index = match.index ?? 0;
|
|
1241
|
+
if (index > cursor) runs.push({ insert: text.slice(cursor, index) });
|
|
1242
|
+
if (match[1] && match[2]) runs.push({
|
|
1243
|
+
insert: match[1],
|
|
1244
|
+
attributes: { link: match[2] }
|
|
1245
|
+
});
|
|
1246
|
+
else if (match[3]) runs.push({
|
|
1247
|
+
insert: match[3],
|
|
1248
|
+
attributes: { "style-code": true }
|
|
1249
|
+
});
|
|
1250
|
+
cursor = index + match[0].length;
|
|
1251
|
+
}
|
|
1252
|
+
if (cursor < text.length) runs.push({ insert: text.slice(cursor) });
|
|
1253
|
+
return runs.length ? runs : [{ insert: text }];
|
|
1196
1254
|
}
|
|
1197
1255
|
function wikiTextBlock(text, options = {}) {
|
|
1198
1256
|
return {
|
|
1199
1257
|
id: newWikiBlockId(),
|
|
1200
1258
|
type: options.list ? "list" : "text",
|
|
1201
|
-
text: text
|
|
1259
|
+
text: markdownTextRuns(text),
|
|
1202
1260
|
...options.heading ? { heading: options.heading } : {},
|
|
1203
1261
|
...options.list ? {
|
|
1204
1262
|
ordered: options.ordered ?? false,
|
|
1205
|
-
level: 1
|
|
1263
|
+
level: options.level ?? 1,
|
|
1264
|
+
...options.start === void 0 ? {} : { start: options.start },
|
|
1265
|
+
...options.groupId ? { groupId: options.groupId } : {}
|
|
1206
1266
|
} : {}
|
|
1207
1267
|
};
|
|
1208
1268
|
}
|
|
@@ -1213,6 +1273,9 @@ function isMarkdownSeparatorRow(line) {
|
|
|
1213
1273
|
const cells = parseMarkdownRow(line);
|
|
1214
1274
|
return cells.length > 0 && cells.every((cell) => /^:?-{3,}:?$/.test(cell));
|
|
1215
1275
|
}
|
|
1276
|
+
function estimateWikiTableCellWidth(value) {
|
|
1277
|
+
return [...value].reduce((width, character) => width + (character.codePointAt(0) > 127 ? 14 : 7), 24);
|
|
1278
|
+
}
|
|
1216
1279
|
function markdownToWikiDocument(markdown) {
|
|
1217
1280
|
const document = {
|
|
1218
1281
|
blocks: [],
|
|
@@ -1221,9 +1284,15 @@ function markdownToWikiDocument(markdown) {
|
|
|
1221
1284
|
};
|
|
1222
1285
|
const blocks = document.blocks;
|
|
1223
1286
|
const lines = markdown.replace(/\r\n/g, "\n").split("\n");
|
|
1287
|
+
let activeList = null;
|
|
1224
1288
|
for (let index = 0; index < lines.length; index += 1) {
|
|
1225
1289
|
const line = lines[index];
|
|
1290
|
+
if (!line.trim()) {
|
|
1291
|
+
activeList = null;
|
|
1292
|
+
continue;
|
|
1293
|
+
}
|
|
1226
1294
|
if (line.includes("|") && lines[index + 1] && isMarkdownSeparatorRow(lines[index + 1])) {
|
|
1295
|
+
activeList = null;
|
|
1227
1296
|
const rows = [parseMarkdownRow(line)];
|
|
1228
1297
|
index += 2;
|
|
1229
1298
|
while (index < lines.length && lines[index].includes("|")) {
|
|
@@ -1241,34 +1310,120 @@ function markdownToWikiDocument(markdown) {
|
|
|
1241
1310
|
blocks.push({
|
|
1242
1311
|
id: newWikiBlockId(),
|
|
1243
1312
|
type: "table",
|
|
1244
|
-
rows: rows.length,
|
|
1245
1313
|
cols: columnCount,
|
|
1314
|
+
rows: rows.length,
|
|
1315
|
+
colsWidth: Array.from({ length: columnCount }, (_, column) => Math.max(100, ...rows.map((row) => estimateWikiTableCellWidth(row[column] ?? "")))),
|
|
1246
1316
|
children
|
|
1247
1317
|
});
|
|
1248
1318
|
continue;
|
|
1249
1319
|
}
|
|
1250
1320
|
const heading = line.match(/^(#{1,6})[ \t]/);
|
|
1251
1321
|
if (heading?.[1]) {
|
|
1322
|
+
activeList = null;
|
|
1252
1323
|
blocks.push(wikiTextBlock(line.slice(heading[0].length).trim(), { heading: heading[1].length }));
|
|
1253
1324
|
continue;
|
|
1254
1325
|
}
|
|
1255
1326
|
const unordered = line.match(/^[ \t]*[-*+][ \t]/);
|
|
1256
1327
|
if (unordered) {
|
|
1257
|
-
|
|
1328
|
+
const groupId = activeList && !activeList.ordered ? activeList.groupId : newWikiBlockId();
|
|
1329
|
+
const start = activeList && !activeList.ordered ? activeList.nextStart : 1;
|
|
1330
|
+
activeList = {
|
|
1331
|
+
ordered: false,
|
|
1332
|
+
groupId,
|
|
1333
|
+
nextStart: start + 1
|
|
1334
|
+
};
|
|
1335
|
+
blocks.push(wikiTextBlock(line.slice(unordered[0].length).trim(), {
|
|
1336
|
+
list: true,
|
|
1337
|
+
groupId,
|
|
1338
|
+
start
|
|
1339
|
+
}));
|
|
1258
1340
|
continue;
|
|
1259
1341
|
}
|
|
1260
|
-
const ordered = line.match(/^[ \t]
|
|
1261
|
-
if (ordered) {
|
|
1342
|
+
const ordered = line.match(/^[ \t]*(\d+)[.)][ \t]/);
|
|
1343
|
+
if (ordered?.[1]) {
|
|
1344
|
+
const groupId = activeList?.ordered ? activeList.groupId : newWikiBlockId();
|
|
1345
|
+
const start = Number.parseInt(ordered[1], 10);
|
|
1346
|
+
activeList = {
|
|
1347
|
+
ordered: true,
|
|
1348
|
+
groupId,
|
|
1349
|
+
nextStart: start + 1
|
|
1350
|
+
};
|
|
1262
1351
|
blocks.push(wikiTextBlock(line.slice(ordered[0].length).trim(), {
|
|
1263
1352
|
list: true,
|
|
1264
|
-
ordered: true
|
|
1353
|
+
ordered: true,
|
|
1354
|
+
start,
|
|
1355
|
+
groupId
|
|
1265
1356
|
}));
|
|
1266
1357
|
continue;
|
|
1267
1358
|
}
|
|
1359
|
+
activeList = null;
|
|
1268
1360
|
blocks.push(wikiTextBlock(line));
|
|
1269
1361
|
}
|
|
1270
1362
|
return document;
|
|
1271
1363
|
}
|
|
1364
|
+
function markdownToWikiHtml(markdown) {
|
|
1365
|
+
const renderInline = (text) => markdownTextRuns(text).map((run) => {
|
|
1366
|
+
const escaped = escapeWikiHtml(run.insert);
|
|
1367
|
+
const content = run.attributes?.["style-code"] ? `<code>${escaped}</code>` : escaped;
|
|
1368
|
+
return run.attributes?.link ? `<a href="${escapeWikiHtml(run.attributes.link)}" target="_blank" rel="noopener noreferrer">${content}</a>` : content;
|
|
1369
|
+
}).join("");
|
|
1370
|
+
const lines = markdown.replace(/\r\n/g, "\n").split("\n");
|
|
1371
|
+
const html = [];
|
|
1372
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
1373
|
+
const line = lines[index];
|
|
1374
|
+
if (!line.trim()) continue;
|
|
1375
|
+
if (line.includes("|") && lines[index + 1] && isMarkdownSeparatorRow(lines[index + 1])) {
|
|
1376
|
+
const rows = [parseMarkdownRow(line)];
|
|
1377
|
+
index += 2;
|
|
1378
|
+
while (index < lines.length && lines[index].includes("|")) {
|
|
1379
|
+
rows.push(parseMarkdownRow(lines[index]));
|
|
1380
|
+
index += 1;
|
|
1381
|
+
}
|
|
1382
|
+
index -= 1;
|
|
1383
|
+
const columnCount = Math.max(...rows.map((row) => row.length));
|
|
1384
|
+
const width = Math.floor(100 / columnCount);
|
|
1385
|
+
const header = Array.from({ length: columnCount }).map((_, column) => `<th style="width:${width}%">${renderInline(rows[0][column] ?? "")}</th>`).join("");
|
|
1386
|
+
const body = rows.slice(1).map((row) => `<tr>${Array.from({ length: columnCount }).map((_, column) => `<td>${renderInline(row[column] ?? "")}</td>`).join("")}</tr>`).join("");
|
|
1387
|
+
html.push(`<table style="width:100%"><thead><tr>${header}</tr></thead><tbody>${body}</tbody></table>`);
|
|
1388
|
+
continue;
|
|
1389
|
+
}
|
|
1390
|
+
const heading = line.match(/^(#{1,6})[ \t]/);
|
|
1391
|
+
if (heading?.[1]) {
|
|
1392
|
+
const level = heading[1].length;
|
|
1393
|
+
html.push(`<h${level}>${renderInline(line.slice(heading[0].length).trim())}</h${level}>`);
|
|
1394
|
+
continue;
|
|
1395
|
+
}
|
|
1396
|
+
if (line.match(/^[ \t]*(\d+)[.)][ \t]/)?.[1]) {
|
|
1397
|
+
const items = [];
|
|
1398
|
+
while (index < lines.length) {
|
|
1399
|
+
const item = lines[index].match(/^[ \t]*(\d+)[.)][ \t]/);
|
|
1400
|
+
if (!item?.[1]) break;
|
|
1401
|
+
items.push({
|
|
1402
|
+
value: Number.parseInt(item[1], 10),
|
|
1403
|
+
text: lines[index].slice(item[0].length).trim()
|
|
1404
|
+
});
|
|
1405
|
+
index += 1;
|
|
1406
|
+
}
|
|
1407
|
+
index -= 1;
|
|
1408
|
+
html.push(`<ol start="${items[0].value}">${items.map((item) => `<li>${renderInline(item.text)}</li>`).join("")}</ol>`);
|
|
1409
|
+
continue;
|
|
1410
|
+
}
|
|
1411
|
+
if (line.match(/^[ \t]*[-*+][ \t]/)) {
|
|
1412
|
+
const items = [];
|
|
1413
|
+
while (index < lines.length) {
|
|
1414
|
+
const item = lines[index].match(/^[ \t]*[-*+][ \t]/);
|
|
1415
|
+
if (!item) break;
|
|
1416
|
+
items.push(lines[index].slice(item[0].length).trim());
|
|
1417
|
+
index += 1;
|
|
1418
|
+
}
|
|
1419
|
+
index -= 1;
|
|
1420
|
+
html.push(`<ul>${items.map((item) => `<li>${renderInline(item)}</li>`).join("")}</ul>`);
|
|
1421
|
+
continue;
|
|
1422
|
+
}
|
|
1423
|
+
html.push(`<p>${renderInline(line.trim())}</p>`);
|
|
1424
|
+
}
|
|
1425
|
+
return html.join("\n");
|
|
1426
|
+
}
|
|
1272
1427
|
function parseWikiDocument(content) {
|
|
1273
1428
|
const document = parseJsonRecord(content);
|
|
1274
1429
|
if (!document || !Array.isArray(document.blocks)) throw new Error("ONES: Wiki content is not a supported collaborative document");
|
|
@@ -1325,6 +1480,17 @@ function appendWikiTableRow(document, operation) {
|
|
|
1325
1480
|
table.rows = (typeof table.rows === "number" ? table.rows : layout.rows.length) + 1;
|
|
1326
1481
|
}
|
|
1327
1482
|
function applyWikiUpdateOperation(content, operation) {
|
|
1483
|
+
if (operation.type === "replace_document") {
|
|
1484
|
+
const current = parseWikiDocument(content);
|
|
1485
|
+
const replacement = markdownToWikiDocument(operation.markdown);
|
|
1486
|
+
for (const key of [
|
|
1487
|
+
"comments",
|
|
1488
|
+
"meta",
|
|
1489
|
+
"authors",
|
|
1490
|
+
"commentators"
|
|
1491
|
+
]) if (Object.hasOwn(current, key)) replacement[key] = current[key];
|
|
1492
|
+
return JSON.stringify(replacement);
|
|
1493
|
+
}
|
|
1328
1494
|
const document = parseWikiDocument(content);
|
|
1329
1495
|
if (operation.type === "append_blocks") appendWikiDocument(document, markdownToWikiDocument(operation.markdown));
|
|
1330
1496
|
else if (operation.type === "replace_text") replaceWikiText(document, operation.find, operation.replace);
|
|
@@ -1363,6 +1529,26 @@ var WikiPathResolutionError = class extends Error {
|
|
|
1363
1529
|
};
|
|
1364
1530
|
//#endregion
|
|
1365
1531
|
//#region ../../src/adapters/ones/wiki-reader.ts
|
|
1532
|
+
const CURRENT_USER_PATH_ALIASES = {
|
|
1533
|
+
"i": true,
|
|
1534
|
+
"me": true,
|
|
1535
|
+
"mine": true,
|
|
1536
|
+
"my": true,
|
|
1537
|
+
"myself": true,
|
|
1538
|
+
"current user": true,
|
|
1539
|
+
"current-user": true,
|
|
1540
|
+
"current_user": true,
|
|
1541
|
+
"我": true,
|
|
1542
|
+
"我的": true,
|
|
1543
|
+
"本人": true,
|
|
1544
|
+
"当前用户": true,
|
|
1545
|
+
"当前账号": true,
|
|
1546
|
+
"自己": true
|
|
1547
|
+
};
|
|
1548
|
+
function currentUserPathAlias(segment) {
|
|
1549
|
+
const trimmed = segment.trim();
|
|
1550
|
+
return CURRENT_USER_PATH_ALIASES[trimmed.toLocaleLowerCase()] ? trimmed : null;
|
|
1551
|
+
}
|
|
1366
1552
|
var OnesWikiOpenApiError = class extends Error {
|
|
1367
1553
|
status;
|
|
1368
1554
|
reason;
|
|
@@ -1457,12 +1643,29 @@ function segmentSimilarity(left, right) {
|
|
|
1457
1643
|
var OnesWikiReader = class {
|
|
1458
1644
|
options;
|
|
1459
1645
|
treeCache = /* @__PURE__ */ new Map();
|
|
1646
|
+
currentUserNameRequest = null;
|
|
1460
1647
|
constructor(options) {
|
|
1461
1648
|
this.options = options;
|
|
1462
1649
|
}
|
|
1463
1650
|
invalidateTree(teamId, spaceId) {
|
|
1464
1651
|
this.treeCache.delete(`${teamId}:${spaceId}`);
|
|
1465
1652
|
}
|
|
1653
|
+
async fetchCurrentUserName() {
|
|
1654
|
+
const session = await this.options.getSession();
|
|
1655
|
+
const response = await fetch(new URL("/wiki/api/project/auth/token_info", this.options.apiBase).toString(), { headers: { Authorization: `Bearer ${session.accessToken}` } });
|
|
1656
|
+
if (!response.ok) throw new Error(`ONES Wiki token info error: ${response.status}`);
|
|
1657
|
+
const payload = await response.json();
|
|
1658
|
+
const name = firstNonEmptyString$1(payload.user?.name, payload.data?.user?.name, payload.data?.name, payload.name, payload.user_name, payload.userName);
|
|
1659
|
+
if (!name) throw new Error("ONES Wiki token info did not include a resolvable current account");
|
|
1660
|
+
return name;
|
|
1661
|
+
}
|
|
1662
|
+
currentUserName() {
|
|
1663
|
+
if (!this.currentUserNameRequest) this.currentUserNameRequest = this.fetchCurrentUserName().catch((error) => {
|
|
1664
|
+
this.currentUserNameRequest = null;
|
|
1665
|
+
throw error;
|
|
1666
|
+
});
|
|
1667
|
+
return this.currentUserNameRequest;
|
|
1668
|
+
}
|
|
1466
1669
|
async openApi(apiPath) {
|
|
1467
1670
|
if (!apiPath.startsWith("/wiki/")) throw new Error("ONES: Invalid Wiki Open API path");
|
|
1468
1671
|
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");
|
|
@@ -1722,8 +1925,19 @@ var OnesWikiReader = class {
|
|
|
1722
1925
|
return score >= MIN_PATH_MATCH_SIMILARITY ? score : null;
|
|
1723
1926
|
}
|
|
1724
1927
|
async resolvePath(params) {
|
|
1725
|
-
const
|
|
1726
|
-
if (!
|
|
1928
|
+
const requestedPath = params.path.map((segment) => segment.trim()).filter(Boolean);
|
|
1929
|
+
if (!requestedPath.length) throw new Error("ONES: Wiki path is empty");
|
|
1930
|
+
const currentUserAlias = requestedPath.map(currentUserPathAlias).find((alias) => alias !== null) ?? null;
|
|
1931
|
+
const currentUserName = currentUserAlias ? await this.currentUserName() : null;
|
|
1932
|
+
const path = currentUserName ? requestedPath.map((segment) => currentUserPathAlias(segment) ? currentUserName : segment) : requestedPath;
|
|
1933
|
+
const redactCurrentUser = (page) => {
|
|
1934
|
+
if (!currentUserName || !currentUserAlias) return page;
|
|
1935
|
+
return {
|
|
1936
|
+
...page,
|
|
1937
|
+
title: page.title === currentUserName ? currentUserAlias : page.title,
|
|
1938
|
+
breadcrumb: page.breadcrumb.map((segment) => segment === currentUserName ? currentUserAlias : segment)
|
|
1939
|
+
};
|
|
1940
|
+
};
|
|
1727
1941
|
const candidates = await this.search({
|
|
1728
1942
|
query: path.at(-1),
|
|
1729
1943
|
teamId: params.teamId,
|
|
@@ -1770,26 +1984,32 @@ var OnesWikiReader = class {
|
|
|
1770
1984
|
});
|
|
1771
1985
|
}
|
|
1772
1986
|
}
|
|
1773
|
-
if (matches.length > 1) throw new WikiPathResolutionError("ambiguous",
|
|
1987
|
+
if (matches.length > 1) throw new WikiPathResolutionError("ambiguous", requestedPath, matches.map(redactCurrentUser));
|
|
1774
1988
|
let match = matches[0];
|
|
1775
1989
|
if (!match && fuzzy.length) {
|
|
1776
1990
|
fuzzy.sort((left, right) => right.score - left.score);
|
|
1777
1991
|
const competing = fuzzy.filter((candidate) => fuzzy[0].score - candidate.score < PATH_MATCH_MARGIN);
|
|
1778
|
-
if (competing.length > 1) throw new WikiPathResolutionError("ambiguous",
|
|
1992
|
+
if (competing.length > 1) throw new WikiPathResolutionError("ambiguous", requestedPath, competing.map((candidate) => redactCurrentUser(candidate.page)));
|
|
1779
1993
|
match = fuzzy[0].page;
|
|
1780
1994
|
}
|
|
1781
1995
|
if (!match) {
|
|
1782
1996
|
const inspectedIds = new Set(inspected.map((page) => page.pageId));
|
|
1783
|
-
throw new WikiPathResolutionError("not_found",
|
|
1997
|
+
throw new WikiPathResolutionError("not_found", requestedPath, [...inspected, ...candidates.filter((page) => !inspectedIds.has(page.pageId))].map(redactCurrentUser));
|
|
1784
1998
|
}
|
|
1785
1999
|
if (!match.spaceId) throw new Error("ONES: Wiki space ID could not be verified");
|
|
1786
|
-
|
|
2000
|
+
const publicMatch = redactCurrentUser(match);
|
|
2001
|
+
const resolution = {
|
|
1787
2002
|
teamId: match.teamId,
|
|
1788
2003
|
spaceId: match.spaceId,
|
|
1789
2004
|
pageId: match.pageId,
|
|
1790
|
-
title:
|
|
1791
|
-
breadcrumb:
|
|
2005
|
+
title: publicMatch.title,
|
|
2006
|
+
breadcrumb: publicMatch.breadcrumb
|
|
1792
2007
|
};
|
|
2008
|
+
if (currentUserName && currentUserAlias) Object.defineProperty(resolution, "redactPrivateValues", {
|
|
2009
|
+
enumerable: false,
|
|
2010
|
+
value: (value) => value.split(currentUserName).join(currentUserAlias)
|
|
2011
|
+
});
|
|
2012
|
+
return resolution;
|
|
1793
2013
|
}
|
|
1794
2014
|
};
|
|
1795
2015
|
//#endregion
|
|
@@ -1991,8 +2211,9 @@ var OnesTaskContent = class {
|
|
|
1991
2211
|
attachments: rendered.attachments
|
|
1992
2212
|
};
|
|
1993
2213
|
})), containsInlineTaskImages(task) ? this.getTaskImageAttachments(task) : Promise.resolve([])]);
|
|
2214
|
+
const projectIdentifier = task.project?.identifier?.toUpperCase() ?? null;
|
|
1994
2215
|
const parts = [
|
|
1995
|
-
`#
|
|
2216
|
+
`# ${taskDisplayId({}, task, projectIdentifier)} ${task.name}`,
|
|
1996
2217
|
"",
|
|
1997
2218
|
`- **Type**: ${task.issueType?.name ?? "Unknown"}`,
|
|
1998
2219
|
"- **Work Item Kind**: requirement",
|
|
@@ -2004,7 +2225,7 @@ var OnesTaskContent = class {
|
|
|
2004
2225
|
parts.push(`- **UUID**: ${task.uuid}`);
|
|
2005
2226
|
if (task.relatedTasks?.length) {
|
|
2006
2227
|
parts.push("", "## Related Tasks");
|
|
2007
|
-
for (const related of task.relatedTasks) parts.push(`-
|
|
2228
|
+
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"}`);
|
|
2008
2229
|
}
|
|
2009
2230
|
if (relatedActivities.length) {
|
|
2010
2231
|
parts.push("", "## Related Work Items");
|
|
@@ -2019,7 +2240,7 @@ var OnesTaskContent = class {
|
|
|
2019
2240
|
}
|
|
2020
2241
|
if (task.parent?.uuid) {
|
|
2021
2242
|
parts.push("", "## Parent Task", `- UUID: ${task.parent.uuid}`);
|
|
2022
|
-
if (task.parent.number) parts.push(`- Number:
|
|
2243
|
+
if (task.parent.number) parts.push(`- Number: ${projectIdentifier ? `${projectIdentifier}-${task.parent.number}` : `#${task.parent.number}`}`);
|
|
2023
2244
|
}
|
|
2024
2245
|
if (wikiContents.length > 0) {
|
|
2025
2246
|
parts.push("", "---", "", "## Requirement Documents");
|
|
@@ -2042,8 +2263,9 @@ var OnesTaskContent = class {
|
|
|
2042
2263
|
}
|
|
2043
2264
|
buildWorkItemSummary(task, kind) {
|
|
2044
2265
|
const nextTool = kind === "defect" ? "get_issue_detail" : "get_related_issues / get_testcases";
|
|
2266
|
+
const projectIdentifier = task.project?.identifier?.toUpperCase() ?? null;
|
|
2045
2267
|
const parts = [
|
|
2046
|
-
`#
|
|
2268
|
+
`# ${taskDisplayId({}, task, projectIdentifier)} ${task.name}`,
|
|
2047
2269
|
"",
|
|
2048
2270
|
`- **Type**: ${task.subIssueType?.name ?? task.issueType?.name ?? "Unknown"}`,
|
|
2049
2271
|
`- **Work Item Kind**: ${kind}`,
|
|
@@ -2055,14 +2277,14 @@ var OnesTaskContent = class {
|
|
|
2055
2277
|
parts.push(`- **UUID**: ${task.uuid}`);
|
|
2056
2278
|
if (task.parent?.uuid) {
|
|
2057
2279
|
parts.push("", "## Parent Task", `- UUID: ${task.parent.uuid}`);
|
|
2058
|
-
if (task.parent.number) parts.push(`- Number:
|
|
2280
|
+
if (task.parent.number) parts.push(`- Number: ${projectIdentifier ? `${projectIdentifier}-${task.parent.number}` : `#${task.parent.number}`}`);
|
|
2059
2281
|
}
|
|
2060
2282
|
const detailText = getTaskDetailText(task);
|
|
2061
2283
|
if (detailText) parts.push("", "---", "", kind === "defect" ? "## Defect Detail" : "## Task Detail", "", detailText);
|
|
2062
2284
|
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.`);
|
|
2063
2285
|
if (task.relatedTasks?.length) {
|
|
2064
2286
|
parts.push("", "## Related Tasks");
|
|
2065
|
-
for (const related of task.relatedTasks) parts.push(`-
|
|
2287
|
+
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"}`);
|
|
2066
2288
|
}
|
|
2067
2289
|
const requirement = toRequirement(task, parts.join("\n"));
|
|
2068
2290
|
requirement.raw = {
|
|
@@ -2124,7 +2346,7 @@ const TASK_DETAIL_QUERY = `
|
|
|
2124
2346
|
priority { value }
|
|
2125
2347
|
assign { uuid name }
|
|
2126
2348
|
owner { uuid name }
|
|
2127
|
-
project { uuid name }
|
|
2349
|
+
project { uuid name identifier }
|
|
2128
2350
|
parent { uuid number issueType { uuid name } }
|
|
2129
2351
|
relatedTasks {
|
|
2130
2352
|
key uuid number name
|
|
@@ -2135,6 +2357,7 @@ const TASK_DETAIL_QUERY = `
|
|
|
2135
2357
|
subIssueType { uuid name detailType }
|
|
2136
2358
|
status { uuid name category }
|
|
2137
2359
|
assign { uuid name }
|
|
2360
|
+
project { uuid name identifier }
|
|
2138
2361
|
}
|
|
2139
2362
|
relatedWikiPages {
|
|
2140
2363
|
uuid title referenceType subReferenceType errorMessage
|
|
@@ -2271,7 +2494,9 @@ var OnesTaskPlanning = class {
|
|
|
2271
2494
|
if (!Number.isInteger(raw.number)) throw new TypeError("ONES: Standalone wiki pages cannot be decomposed into requirement tasks");
|
|
2272
2495
|
const parsedDisplayId = parseDisplayId(params.requirementId);
|
|
2273
2496
|
const requirementInfo = await this.options.fetchTaskInfo(workItem.id);
|
|
2274
|
-
|
|
2497
|
+
let projectIdentifier = parsedDisplayId?.identifier ?? firstString(requirementInfo, ["projectIdentifier", "project_identifier"]) ?? raw.project?.identifier ?? null;
|
|
2498
|
+
if (!projectIdentifier && raw.project?.uuid) projectIdentifier = await this.options.resolveProjectIdentifier(raw.project.uuid);
|
|
2499
|
+
projectIdentifier = projectIdentifier?.toUpperCase() ?? null;
|
|
2275
2500
|
const displayId = firstString(requirementInfo, ["displayId", "display_id"]) ?? (projectIdentifier ? `${projectIdentifier}-${raw.number}` : `#${raw.number}`);
|
|
2276
2501
|
const relatedTasks = (raw.relatedTasks ?? []).filter((task) => classifyOnesWorkItem(task.issueType, task.subIssueType) === "task");
|
|
2277
2502
|
const relatedInfos = await Promise.all(relatedTasks.map((task) => this.options.fetchTaskInfo(task.uuid)));
|
|
@@ -2600,6 +2825,12 @@ var OnesTestcaseReader = class {
|
|
|
2600
2825
|
const JSON0_URI = "http://sharejs.org/types/JSONv0";
|
|
2601
2826
|
const JSON1_URI = "http://sharejs.org/types/JSONv1";
|
|
2602
2827
|
const DEFAULT_TIMEOUT_MS$1 = 15e3;
|
|
2828
|
+
function getSetCookies(response) {
|
|
2829
|
+
const headers = response.headers;
|
|
2830
|
+
if (headers.getSetCookie) return headers.getSetCookie();
|
|
2831
|
+
const raw = headers.get("set-cookie");
|
|
2832
|
+
return raw ? [raw] : [];
|
|
2833
|
+
}
|
|
2603
2834
|
function isRecord(value) {
|
|
2604
2835
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
2605
2836
|
}
|
|
@@ -2612,28 +2843,44 @@ function asDoc(value) {
|
|
|
2612
2843
|
}
|
|
2613
2844
|
function createTopLevelJson1Operation(current, next) {
|
|
2614
2845
|
let operation = null;
|
|
2846
|
+
const appendComponent = (component) => {
|
|
2847
|
+
operation = operation === null ? component : ot_json1.type.compose(operation, component);
|
|
2848
|
+
};
|
|
2615
2849
|
const keys = [.../* @__PURE__ */ new Set([...Object.keys(current), ...Object.keys(next)])].sort();
|
|
2616
2850
|
for (const key of keys) {
|
|
2851
|
+
if (key === "blocks") continue;
|
|
2617
2852
|
const hasCurrent = Object.hasOwn(current, key);
|
|
2618
2853
|
const hasNext = Object.hasOwn(next, key);
|
|
2619
|
-
|
|
2620
|
-
if (
|
|
2621
|
-
else if (
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2854
|
+
if (!hasCurrent && hasNext) appendComponent((0, ot_json1.insertOp)([key], asDoc(next[key])));
|
|
2855
|
+
else if (hasCurrent && !hasNext) appendComponent((0, ot_json1.removeOp)([key]));
|
|
2856
|
+
else if (hasCurrent && hasNext && !(0, node_util.isDeepStrictEqual)(current[key], next[key])) appendComponent((0, ot_json1.replaceOp)([key], asDoc(current[key]), asDoc(next[key])));
|
|
2857
|
+
}
|
|
2858
|
+
const currentBlocks = Array.isArray(current.blocks) ? current.blocks : [];
|
|
2859
|
+
const nextBlocks = Array.isArray(next.blocks) ? next.blocks : [];
|
|
2860
|
+
const sharedBlockCount = Math.min(currentBlocks.length, nextBlocks.length);
|
|
2861
|
+
for (let index = 0; index < sharedBlockCount; index += 1) if (!(0, node_util.isDeepStrictEqual)(currentBlocks[index], nextBlocks[index])) {
|
|
2862
|
+
appendComponent((0, ot_json1.removeOp)(["blocks", index]));
|
|
2863
|
+
appendComponent((0, ot_json1.insertOp)(["blocks", index], asDoc(nextBlocks[index])));
|
|
2864
|
+
}
|
|
2865
|
+
for (let index = currentBlocks.length - 1; index >= nextBlocks.length; index -= 1) appendComponent((0, ot_json1.removeOp)(["blocks", index]));
|
|
2866
|
+
for (let index = currentBlocks.length; index < nextBlocks.length; index += 1) appendComponent((0, ot_json1.insertOp)(["blocks", index], asDoc(nextBlocks[index])));
|
|
2626
2867
|
if (operation !== null) ot_json1.type.checkValidOp(operation);
|
|
2627
2868
|
return operation;
|
|
2628
2869
|
}
|
|
2870
|
+
function createTopLevelJson1Operations(current, next) {
|
|
2871
|
+
const operation = createTopLevelJson1Operation(current, next);
|
|
2872
|
+
return operation === null ? [] : [operation];
|
|
2873
|
+
}
|
|
2629
2874
|
function wikiEditorUrls(baseUrl, teamId, documentId) {
|
|
2630
2875
|
const url = new URL(baseUrl);
|
|
2631
2876
|
if (url.protocol !== "https:" && url.protocol !== "http:") throw new Error("ONES Wiki collaboration requires an HTTP(S) source URL");
|
|
2632
2877
|
const path = `/wiki/api/wiki/editor/${encodeURIComponent(teamId)}/${encodeURIComponent(documentId)}`;
|
|
2633
|
-
const
|
|
2878
|
+
const editorBaseUrl = new URL(path, url);
|
|
2879
|
+
const socketUrl = new URL(editorBaseUrl);
|
|
2634
2880
|
socketUrl.protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
2635
2881
|
return {
|
|
2636
|
-
authUrl:
|
|
2882
|
+
authUrl: `${editorBaseUrl.toString()}/auth`,
|
|
2883
|
+
editorBaseUrl: editorBaseUrl.toString(),
|
|
2637
2884
|
socketUrl: socketUrl.toString()
|
|
2638
2885
|
};
|
|
2639
2886
|
}
|
|
@@ -2650,39 +2897,52 @@ function closeSocket(socket) {
|
|
|
2650
2897
|
}
|
|
2651
2898
|
async function replaceOnesWikiDocument(options, update, dependencies = {
|
|
2652
2899
|
fetch,
|
|
2653
|
-
openWebSocket: (url) => new ws.default(url)
|
|
2900
|
+
openWebSocket: (url, headers) => new ws.default(url, { headers })
|
|
2654
2901
|
}) {
|
|
2655
|
-
const { authUrl, socketUrl } = wikiEditorUrls(options.baseUrl, options.teamId, options.documentId);
|
|
2902
|
+
const { authUrl, editorBaseUrl, socketUrl } = wikiEditorUrls(options.baseUrl, options.teamId, options.documentId);
|
|
2656
2903
|
const authResponse = await dependencies.fetch(authUrl, {
|
|
2657
2904
|
method: "GET",
|
|
2658
2905
|
headers: {
|
|
2659
2906
|
"Authorization": `Bearer ${options.accessToken}`,
|
|
2660
2907
|
"x-live-editor-token": options.editorToken,
|
|
2661
|
-
"x-live-editor-base-url": Buffer.from(
|
|
2908
|
+
"x-live-editor-base-url": Buffer.from(editorBaseUrl).toString("base64url"),
|
|
2909
|
+
...options.cookieHeader ? { Cookie: options.cookieHeader } : {}
|
|
2662
2910
|
}
|
|
2663
2911
|
});
|
|
2664
2912
|
if (!authResponse.ok) throw new Error(`ONES Wiki collaboration auth failed with status ${authResponse.status}`);
|
|
2665
2913
|
const editorAuth = await authResponse.json();
|
|
2666
2914
|
if (typeof editorAuth.read !== "string" || !editorAuth.read) throw new Error("ONES Wiki collaboration auth response did not include a read token");
|
|
2915
|
+
const cookies = [options.cookieHeader, ...getSetCookies(authResponse).map((cookie) => cookie.split(";")[0])].filter((cookie) => Boolean(cookie)).join("; ");
|
|
2916
|
+
const socketHeaders = {
|
|
2917
|
+
"Accept-Language": "zh-CN,zh;q=0.9",
|
|
2918
|
+
"Cache-Control": "no-cache",
|
|
2919
|
+
"Pragma": "no-cache",
|
|
2920
|
+
"User-Agent": "Mozilla/5.0 AppleWebKit/537.36 Chrome/128.0.0.0 Safari/537.36",
|
|
2921
|
+
"Origin": new URL(options.baseUrl).origin,
|
|
2922
|
+
...cookies ? { Cookie: cookies } : {}
|
|
2923
|
+
};
|
|
2667
2924
|
return new Promise((resolve, reject) => {
|
|
2668
|
-
const socket = dependencies.openWebSocket(socketUrl);
|
|
2669
|
-
|
|
2670
|
-
let clientId = "";
|
|
2925
|
+
const socket = dependencies.openWebSocket(socketUrl, socketHeaders);
|
|
2926
|
+
let sequence = 1;
|
|
2671
2927
|
let state = "init";
|
|
2672
2928
|
let settled = false;
|
|
2673
2929
|
let snapshotVersion = 0;
|
|
2930
|
+
let currentVersion = 0;
|
|
2931
|
+
let pendingOperations = [];
|
|
2932
|
+
const presenceChannel = `${options.teamId}:${options.documentId}`;
|
|
2933
|
+
const presenceId = (0, node_crypto.randomBytes)(7).toString("base64url").slice(0, 9);
|
|
2674
2934
|
let timeout;
|
|
2675
2935
|
const finish = (result) => {
|
|
2676
2936
|
if (settled) return;
|
|
2677
2937
|
settled = true;
|
|
2678
|
-
|
|
2938
|
+
clearTimeout(timeout);
|
|
2679
2939
|
resolve(result);
|
|
2680
2940
|
closeSocket(socket);
|
|
2681
2941
|
};
|
|
2682
2942
|
const fail = (error) => {
|
|
2683
2943
|
if (settled) return;
|
|
2684
2944
|
settled = true;
|
|
2685
|
-
|
|
2945
|
+
clearTimeout(timeout);
|
|
2686
2946
|
reject(error);
|
|
2687
2947
|
closeSocket(socket);
|
|
2688
2948
|
};
|
|
@@ -2691,9 +2951,38 @@ async function replaceOnesWikiDocument(options, update, dependencies = {
|
|
|
2691
2951
|
if (error) fail(/* @__PURE__ */ new Error(`ONES Wiki collaboration send failed: ${error.message}`));
|
|
2692
2952
|
});
|
|
2693
2953
|
};
|
|
2954
|
+
const sendHandshake = () => {
|
|
2955
|
+
send({
|
|
2956
|
+
a: "hs",
|
|
2957
|
+
id: null,
|
|
2958
|
+
auth: {
|
|
2959
|
+
appId: options.teamId,
|
|
2960
|
+
docId: options.documentId,
|
|
2961
|
+
userId: options.userId,
|
|
2962
|
+
permission: "w",
|
|
2963
|
+
token: options.editorToken,
|
|
2964
|
+
displayName: options.displayName ?? "",
|
|
2965
|
+
avatarUrl: options.avatarUrl ?? ""
|
|
2966
|
+
}
|
|
2967
|
+
});
|
|
2968
|
+
};
|
|
2969
|
+
const sendNextOperation = () => {
|
|
2970
|
+
const operation = pendingOperations[0];
|
|
2971
|
+
if (operation === void 0) return;
|
|
2972
|
+
send({
|
|
2973
|
+
a: "op",
|
|
2974
|
+
c: options.teamId,
|
|
2975
|
+
d: options.documentId,
|
|
2976
|
+
v: currentVersion,
|
|
2977
|
+
seq: sequence,
|
|
2978
|
+
x: {},
|
|
2979
|
+
op: operation
|
|
2980
|
+
});
|
|
2981
|
+
};
|
|
2694
2982
|
timeout = setTimeout(() => {
|
|
2695
2983
|
fail(/* @__PURE__ */ new Error(`ONES Wiki collaboration timed out while waiting for ${state}`));
|
|
2696
2984
|
}, options.timeoutMs ?? DEFAULT_TIMEOUT_MS$1);
|
|
2985
|
+
socket.on("open", sendHandshake);
|
|
2697
2986
|
socket.on("error", () => fail(/* @__PURE__ */ new Error("ONES Wiki collaboration WebSocket failed")));
|
|
2698
2987
|
socket.on("close", (code) => {
|
|
2699
2988
|
if (!settled) fail(/* @__PURE__ */ new Error(`ONES Wiki collaboration WebSocket closed before ${state} (code ${code})`));
|
|
@@ -2720,58 +3009,64 @@ async function replaceOnesWikiDocument(options, update, dependencies = {
|
|
|
2720
3009
|
fail(/* @__PURE__ */ new Error("ONES Wiki collaboration returned an unsupported init frame"));
|
|
2721
3010
|
return;
|
|
2722
3011
|
}
|
|
2723
|
-
clientId = message.id;
|
|
2724
3012
|
state = "handshake";
|
|
2725
|
-
|
|
2726
|
-
a: "hs",
|
|
2727
|
-
id: clientId,
|
|
2728
|
-
auth: {
|
|
2729
|
-
appId: options.teamId,
|
|
2730
|
-
docId: options.documentId,
|
|
2731
|
-
userId: options.userId,
|
|
2732
|
-
permission: "w",
|
|
2733
|
-
token: editorAuth.read,
|
|
2734
|
-
displayName: options.displayName ?? "",
|
|
2735
|
-
avatarUrl: options.avatarUrl ?? ""
|
|
2736
|
-
},
|
|
2737
|
-
options: { ping: {
|
|
2738
|
-
interval: 5e4,
|
|
2739
|
-
timeout: 15e4
|
|
2740
|
-
} }
|
|
2741
|
-
});
|
|
3013
|
+
sendHandshake();
|
|
2742
3014
|
return;
|
|
2743
3015
|
}
|
|
2744
3016
|
if (state === "handshake") {
|
|
2745
|
-
if (message.a !== "hs" || message.id !==
|
|
3017
|
+
if (message.a !== "hs" || typeof message.id !== "string" || message.protocol !== 1 || message.protocolMinor !== 1 || message.type !== JSON0_URI) {
|
|
2746
3018
|
fail(/* @__PURE__ */ new Error("ONES Wiki collaboration returned an unsupported handshake frame"));
|
|
2747
3019
|
return;
|
|
2748
3020
|
}
|
|
2749
|
-
state = "
|
|
3021
|
+
state = "fetch";
|
|
2750
3022
|
send({
|
|
2751
|
-
a: "
|
|
3023
|
+
a: "f",
|
|
2752
3024
|
c: options.teamId,
|
|
2753
3025
|
d: options.documentId
|
|
2754
3026
|
});
|
|
2755
3027
|
return;
|
|
2756
3028
|
}
|
|
2757
|
-
if (state === "
|
|
3029
|
+
if (state === "fetch") {
|
|
2758
3030
|
const snapshot = message.data;
|
|
2759
|
-
if (message.a !== "
|
|
2760
|
-
fail(/* @__PURE__ */ new Error("ONES Wiki collaboration returned an unsupported snapshot frame"));
|
|
2761
|
-
return;
|
|
2762
|
-
}
|
|
3031
|
+
if (message.a !== "f" || message.c !== options.teamId || message.d !== options.documentId || typeof snapshot?.v !== "number" || snapshot.type !== JSON1_URI) return;
|
|
2763
3032
|
const current = asWikiSnapshot(snapshot.data);
|
|
2764
|
-
let next;
|
|
2765
|
-
let operation;
|
|
2766
3033
|
try {
|
|
2767
|
-
|
|
2768
|
-
operation = createTopLevelJson1Operation(current, next);
|
|
3034
|
+
pendingOperations = createTopLevelJson1Operations(current, asJsonDocument(update(structuredClone(current)), "update"));
|
|
2769
3035
|
} catch (error) {
|
|
2770
3036
|
fail(error instanceof Error ? error : /* @__PURE__ */ new Error("ONES Wiki collaboration update failed"));
|
|
2771
3037
|
return;
|
|
2772
3038
|
}
|
|
2773
3039
|
snapshotVersion = snapshot.v;
|
|
2774
|
-
|
|
3040
|
+
currentVersion = snapshotVersion;
|
|
3041
|
+
state = "presence";
|
|
3042
|
+
send({
|
|
3043
|
+
a: "p",
|
|
3044
|
+
ch: presenceChannel,
|
|
3045
|
+
id: presenceId,
|
|
3046
|
+
p: null,
|
|
3047
|
+
pv: 2
|
|
3048
|
+
});
|
|
3049
|
+
send({
|
|
3050
|
+
a: "ps",
|
|
3051
|
+
ch: presenceChannel,
|
|
3052
|
+
seq: 1
|
|
3053
|
+
});
|
|
3054
|
+
return;
|
|
3055
|
+
}
|
|
3056
|
+
if (state === "presence") {
|
|
3057
|
+
if (message.a !== "ps" || message.ch !== presenceChannel || message.seq !== 1) return;
|
|
3058
|
+
state = "subscribe";
|
|
3059
|
+
send({
|
|
3060
|
+
a: "s",
|
|
3061
|
+
c: options.teamId,
|
|
3062
|
+
d: options.documentId,
|
|
3063
|
+
v: snapshotVersion
|
|
3064
|
+
});
|
|
3065
|
+
return;
|
|
3066
|
+
}
|
|
3067
|
+
if (state === "subscribe") {
|
|
3068
|
+
if (message.a !== "s" || message.c !== options.teamId || message.d !== options.documentId) return;
|
|
3069
|
+
if (!pendingOperations.length) {
|
|
2775
3070
|
finish({
|
|
2776
3071
|
snapshotVersion,
|
|
2777
3072
|
version: snapshotVersion,
|
|
@@ -2780,21 +3075,23 @@ async function replaceOnesWikiDocument(options, update, dependencies = {
|
|
|
2780
3075
|
return;
|
|
2781
3076
|
}
|
|
2782
3077
|
state = "ack";
|
|
2783
|
-
|
|
2784
|
-
a: "op",
|
|
2785
|
-
c: options.teamId,
|
|
2786
|
-
d: options.documentId,
|
|
2787
|
-
v: snapshotVersion,
|
|
2788
|
-
seq: sequence,
|
|
2789
|
-
op: operation
|
|
2790
|
-
});
|
|
3078
|
+
sendNextOperation();
|
|
2791
3079
|
return;
|
|
2792
3080
|
}
|
|
2793
|
-
if (state === "ack" && message.a === "op" && message.c === options.teamId && message.d === options.documentId && message.seq === sequence)
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
3081
|
+
if (state === "ack" && message.a === "op" && message.c === options.teamId && message.d === options.documentId && message.seq === sequence) {
|
|
3082
|
+
currentVersion = typeof message.v === "number" ? message.v : currentVersion + 1;
|
|
3083
|
+
pendingOperations.shift();
|
|
3084
|
+
if (!pendingOperations.length) {
|
|
3085
|
+
finish({
|
|
3086
|
+
snapshotVersion,
|
|
3087
|
+
version: currentVersion,
|
|
3088
|
+
changed: true
|
|
3089
|
+
});
|
|
3090
|
+
return;
|
|
3091
|
+
}
|
|
3092
|
+
sequence += 1;
|
|
3093
|
+
sendNextOperation();
|
|
3094
|
+
}
|
|
2798
3095
|
});
|
|
2799
3096
|
});
|
|
2800
3097
|
}
|
|
@@ -2832,6 +3129,10 @@ var OnesWikiProductWriter = class {
|
|
|
2832
3129
|
const encodedTeamId = encodeIdentifier(teamId, "team UUID");
|
|
2833
3130
|
const headers = new Headers(init.headers);
|
|
2834
3131
|
headers.set("Authorization", `Bearer ${session.accessToken}`);
|
|
3132
|
+
headers.set("Ones-Auth-Token", session.legacyAuthToken);
|
|
3133
|
+
headers.set("Ones-User-Id", session.legacyUserId);
|
|
3134
|
+
headers.set("Referer", this.options.apiBase);
|
|
3135
|
+
if (session.cookieHeader) headers.set("Cookie", session.cookieHeader);
|
|
2835
3136
|
if (typeof init.body === "string" && !headers.has("Content-Type")) headers.set("Content-Type", "application/json;charset=UTF-8");
|
|
2836
3137
|
const response = await fetch(`${this.options.apiBase}/wiki/api/wiki/team/${encodedTeamId}${apiPath}`, {
|
|
2837
3138
|
...init,
|
|
@@ -2854,9 +3155,9 @@ var OnesWikiProductWriter = class {
|
|
|
2854
3155
|
}
|
|
2855
3156
|
throw new Error("ONES Wiki editor token endpoint was not found");
|
|
2856
3157
|
}
|
|
2857
|
-
async replaceDocument(teamId, resourceId, documentId, update, preferDraft = false) {
|
|
3158
|
+
async replaceDocument(teamId, resourceId, documentId, update, preferDraft = false, editorTokenOverride) {
|
|
2858
3159
|
const session = await this.options.getSession();
|
|
2859
|
-
const editorToken = await this.fetchEditorToken(teamId, resourceId, preferDraft);
|
|
3160
|
+
const editorToken = editorTokenOverride ?? await this.fetchEditorToken(teamId, resourceId, preferDraft);
|
|
2860
3161
|
return { version: (await replaceOnesWikiDocument({
|
|
2861
3162
|
baseUrl: this.options.apiBase,
|
|
2862
3163
|
teamId,
|
|
@@ -2864,6 +3165,7 @@ var OnesWikiProductWriter = class {
|
|
|
2864
3165
|
accessToken: session.accessToken,
|
|
2865
3166
|
editorToken,
|
|
2866
3167
|
userId: session.userUuid,
|
|
3168
|
+
cookieHeader: session.cookieHeader,
|
|
2867
3169
|
displayName: session.userName
|
|
2868
3170
|
}, update)).version };
|
|
2869
3171
|
}
|
|
@@ -2917,20 +3219,120 @@ var OnesWikiProductWriter = class {
|
|
|
2917
3219
|
};
|
|
2918
3220
|
}
|
|
2919
3221
|
async update(params) {
|
|
2920
|
-
const
|
|
2921
|
-
const
|
|
2922
|
-
|
|
2923
|
-
const
|
|
3222
|
+
const encodedPageId = encodeIdentifier(params.pageId, "Wiki page UUID");
|
|
3223
|
+
const encodedSpaceId = params.spaceId ? encodeIdentifier(params.spaceId, "Wiki space UUID") : null;
|
|
3224
|
+
let detail = await this.request(params.teamId, `/page/${encodedPageId}?action=edit`);
|
|
3225
|
+
const title = firstNonEmptyString(detail.title, detail.name, detail.page_title) ?? `Wiki ${params.pageId}`;
|
|
3226
|
+
if (detail.ref_type === 6 && detail.ref_uuid) {
|
|
3227
|
+
const editorToken = await this.fetchEditorToken(params.teamId, params.pageId);
|
|
3228
|
+
const write = await this.replaceDocument(params.teamId, params.pageId, detail.ref_uuid, (snapshot) => {
|
|
3229
|
+
return parseWikiDocument(applyWikiUpdateOperation(JSON.stringify(snapshot), params.operation));
|
|
3230
|
+
}, false, editorToken);
|
|
3231
|
+
const published = await this.request(params.teamId, `/online_page/${encodedPageId}/publish`, {
|
|
3232
|
+
method: "POST",
|
|
3233
|
+
body: JSON.stringify({ title })
|
|
3234
|
+
});
|
|
3235
|
+
if (params.spaceId) this.options.invalidateTree(params.teamId, params.spaceId);
|
|
3236
|
+
return {
|
|
3237
|
+
pageId: params.pageId,
|
|
3238
|
+
title,
|
|
3239
|
+
version: String(published.version ?? published.updated_time ?? write.version),
|
|
3240
|
+
url: params.spaceId ? `${this.options.apiBase}/wiki/#/team/${encodeURIComponent(params.teamId)}/space/${encodeURIComponent(params.spaceId)}/page/${encodeURIComponent(params.pageId)}` : null
|
|
3241
|
+
};
|
|
3242
|
+
}
|
|
3243
|
+
let draftId = firstNonEmptyString(detail.online_draft_uuid, detail.onlineDraftUuid, detail.online_draft?.uuid, detail.draft_uuid, detail.draftUuid, detail.draft?.uuid);
|
|
3244
|
+
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);
|
|
3245
|
+
if (!draftId) {
|
|
3246
|
+
if (!encodedSpaceId) throw new Error("ONES Wiki page space is required to create an edit draft");
|
|
3247
|
+
try {
|
|
3248
|
+
const createdDraft = await this.request(params.teamId, `/space/${encodedSpaceId}/drafts/add`, {
|
|
3249
|
+
method: "POST",
|
|
3250
|
+
body: JSON.stringify({
|
|
3251
|
+
page_uuid: params.pageId,
|
|
3252
|
+
status: 2,
|
|
3253
|
+
title
|
|
3254
|
+
})
|
|
3255
|
+
});
|
|
3256
|
+
draftId = firstNonEmptyString(createdDraft.draft_uuid, createdDraft.draftUuid, createdDraft.uuid, createdDraft.id);
|
|
3257
|
+
documentId = firstNonEmptyString(createdDraft.ref_uuid, createdDraft.refUuid, documentId);
|
|
3258
|
+
} catch (error) {
|
|
3259
|
+
const refreshedDetail = await this.request(params.teamId, `/space/${encodedSpaceId}/page/${encodedPageId}`);
|
|
3260
|
+
draftId = firstNonEmptyString(refreshedDetail.draft_uuid, refreshedDetail.draftUuid);
|
|
3261
|
+
if (!draftId) throw error;
|
|
3262
|
+
detail = refreshedDetail;
|
|
3263
|
+
documentId = firstNonEmptyString(refreshedDetail.draft_ref_uuid, refreshedDetail.draftRefUuid, refreshedDetail.ref_uuid, documentId);
|
|
3264
|
+
}
|
|
3265
|
+
}
|
|
3266
|
+
if (!draftId || !encodedSpaceId) throw new Error("ONES Wiki edit draft did not include the required resource and space IDs");
|
|
3267
|
+
const encodedDraftId = encodeIdentifier(draftId, "Wiki draft UUID");
|
|
3268
|
+
const draftDetail = await this.request(params.teamId, `/space/${encodedSpaceId}/draft/${encodedDraftId}`);
|
|
3269
|
+
documentId = firstNonEmptyString(draftDetail.ref_uuid, draftDetail.refUuid, documentId);
|
|
3270
|
+
if (!documentId) throw new Error("ONES Wiki edit draft did not include a collaborative document ID");
|
|
3271
|
+
if (typeof draftDetail.content === "string") {
|
|
3272
|
+
let content;
|
|
3273
|
+
if (params.operation.type === "replace_document") content = markdownToWikiHtml(params.operation.markdown);
|
|
3274
|
+
else if (params.operation.type === "append_blocks") content = `${draftDetail.content}\n${markdownToWikiHtml(params.operation.markdown)}`;
|
|
3275
|
+
else if (params.operation.type === "replace_text") {
|
|
3276
|
+
const occurrences = draftDetail.content.split(params.operation.find).length - 1;
|
|
3277
|
+
if (occurrences !== 1) throw new Error(`ONES Wiki draft replace_text requires exactly one match; found ${occurrences}`);
|
|
3278
|
+
content = draftDetail.content.replace(params.operation.find, params.operation.replace);
|
|
3279
|
+
} else throw new Error("ONES Wiki append_table_row is not supported for legacy page drafts");
|
|
3280
|
+
const published = await this.request(params.teamId, `/space/${encodedSpaceId}/draft/${encodedDraftId}/update`, {
|
|
3281
|
+
method: "POST",
|
|
3282
|
+
body: JSON.stringify({
|
|
3283
|
+
...draftDetail,
|
|
3284
|
+
content,
|
|
3285
|
+
title,
|
|
3286
|
+
page_uuid: params.pageId,
|
|
3287
|
+
space_uuid: params.spaceId,
|
|
3288
|
+
from_version: detail.version ?? draftDetail.from_version,
|
|
3289
|
+
is_published: true,
|
|
3290
|
+
is_forced: true
|
|
3291
|
+
})
|
|
3292
|
+
});
|
|
3293
|
+
this.options.invalidateTree(params.teamId, params.spaceId);
|
|
3294
|
+
const publishedVersion = published.version ?? published.updated_time ?? draftDetail.version ?? draftDetail.updated_time;
|
|
3295
|
+
return {
|
|
3296
|
+
pageId: params.pageId,
|
|
3297
|
+
title,
|
|
3298
|
+
version: publishedVersion === void 0 ? null : String(publishedVersion),
|
|
3299
|
+
url: `${this.options.apiBase}/wiki/#/team/${encodeURIComponent(params.teamId)}/space/${encodeURIComponent(params.spaceId)}/page/${encodeURIComponent(params.pageId)}`
|
|
3300
|
+
};
|
|
3301
|
+
}
|
|
3302
|
+
const write = await this.replaceDocument(params.teamId, draftId, documentId, (snapshot) => {
|
|
2924
3303
|
return parseWikiDocument(applyWikiUpdateOperation(JSON.stringify(snapshot), params.operation));
|
|
3304
|
+
}, true);
|
|
3305
|
+
const refreshedDraft = await this.request(params.teamId, `/space/${encodedSpaceId}/draft/${encodedDraftId}`);
|
|
3306
|
+
await this.request(params.teamId, `/space/${encodedSpaceId}/draft/${encodedDraftId}/update`, {
|
|
3307
|
+
method: "POST",
|
|
3308
|
+
body: JSON.stringify({
|
|
3309
|
+
...refreshedDraft,
|
|
3310
|
+
title,
|
|
3311
|
+
page_uuid: params.pageId,
|
|
3312
|
+
space_uuid: params.spaceId,
|
|
3313
|
+
from_version: detail.version ?? refreshedDraft.from_version,
|
|
3314
|
+
is_published: true,
|
|
3315
|
+
is_forced: true
|
|
3316
|
+
})
|
|
2925
3317
|
});
|
|
2926
3318
|
if (params.spaceId) this.options.invalidateTree(params.teamId, params.spaceId);
|
|
2927
3319
|
return {
|
|
2928
3320
|
pageId: params.pageId,
|
|
2929
|
-
title
|
|
3321
|
+
title,
|
|
2930
3322
|
version: String(write.version),
|
|
2931
3323
|
url: params.spaceId ? `${this.options.apiBase}/wiki/#/team/${encodeURIComponent(params.teamId)}/space/${encodeURIComponent(params.spaceId)}/page/${encodeURIComponent(params.pageId)}` : null
|
|
2932
3324
|
};
|
|
2933
3325
|
}
|
|
3326
|
+
async delete(params) {
|
|
3327
|
+
const encodedSpaceId = encodeIdentifier(params.spaceId, "Wiki space UUID");
|
|
3328
|
+
const encodedPageId = encodeIdentifier(params.pageId, "Wiki page UUID");
|
|
3329
|
+
await this.request(params.teamId, `/space/${encodedSpaceId}/page/${encodedPageId}/delete`, { method: "POST" });
|
|
3330
|
+
this.options.invalidateTree(params.teamId, params.spaceId);
|
|
3331
|
+
return {
|
|
3332
|
+
pageId: params.pageId,
|
|
3333
|
+
deleted: true
|
|
3334
|
+
};
|
|
3335
|
+
}
|
|
2934
3336
|
};
|
|
2935
3337
|
//#endregion
|
|
2936
3338
|
//#region ../../src/adapters/ones/task-query.ts
|
|
@@ -3017,7 +3419,6 @@ var OnesTaskAdapter = class extends BaseAdapter {
|
|
|
3017
3419
|
this.wikiProductWriter = new OnesWikiProductWriter({
|
|
3018
3420
|
apiBase: config.apiBase,
|
|
3019
3421
|
getSession: () => this.login(),
|
|
3020
|
-
fetchPageDetail: (pageId, teamId) => this.wikiReader.fetchPageDetail(pageId, teamId, true),
|
|
3021
3422
|
invalidateTree: (teamId, spaceId) => this.wikiReader.invalidateTree(teamId, spaceId)
|
|
3022
3423
|
});
|
|
3023
3424
|
this.content = new OnesTaskContent({
|
|
@@ -3033,7 +3434,10 @@ var OnesTaskAdapter = class extends BaseAdapter {
|
|
|
3033
3434
|
this.planning = new OnesTaskPlanning({
|
|
3034
3435
|
api: this.api,
|
|
3035
3436
|
getRequirement: (id) => this.getRequirement({ id }),
|
|
3036
|
-
fetchTaskInfo: (taskUuid) => this.fetchTaskInfo(taskUuid)
|
|
3437
|
+
fetchTaskInfo: (taskUuid) => this.fetchTaskInfo(taskUuid),
|
|
3438
|
+
resolveProjectIdentifier: async (projectUuid) => {
|
|
3439
|
+
return (await this.fetchProjects()).find((candidate) => candidate.uuid === projectUuid)?.identifier?.toUpperCase() ?? null;
|
|
3440
|
+
}
|
|
3037
3441
|
});
|
|
3038
3442
|
this.issueReader = new OnesIssueReader({
|
|
3039
3443
|
api: this.api,
|
|
@@ -3251,6 +3655,9 @@ var OnesTaskAdapter = class extends BaseAdapter {
|
|
|
3251
3655
|
async updateWikiPage(params) {
|
|
3252
3656
|
return this.wikiProductWriter.update(params);
|
|
3253
3657
|
}
|
|
3658
|
+
async deleteWikiPage(params) {
|
|
3659
|
+
return this.wikiProductWriter.delete(params);
|
|
3660
|
+
}
|
|
3254
3661
|
/**
|
|
3255
3662
|
* Fetch a work item by UUID, number, display id, or wiki URL.
|
|
3256
3663
|
* Routes by issueType.detailType: requirements (1 and 5) load wiki docs;
|
|
@@ -3993,7 +4400,7 @@ function formatWorkItem(req) {
|
|
|
3993
4400
|
//#endregion
|
|
3994
4401
|
//#region ../../src/tools/list-pending-work-items.ts
|
|
3995
4402
|
const ListPendingWorkItemsSchema = zod_v4.z.object({ source: zod_v4.z.string().optional().describe("Source to read. If omitted, uses the default source.") });
|
|
3996
|
-
function resolveAdapter$
|
|
4403
|
+
function resolveAdapter$4(source, adapters, defaultSource) {
|
|
3997
4404
|
const sourceType = source ?? defaultSource;
|
|
3998
4405
|
if (!sourceType) throw new Error("No source specified and no default source configured");
|
|
3999
4406
|
const adapter = adapters.get(sourceType);
|
|
@@ -4037,7 +4444,7 @@ function formatResult(result) {
|
|
|
4037
4444
|
return lines.join("\n");
|
|
4038
4445
|
}
|
|
4039
4446
|
async function handleListPendingWorkItems(input, adapters, defaultSource) {
|
|
4040
|
-
const result = await resolveAdapter$
|
|
4447
|
+
const result = await resolveAdapter$4(input.source, adapters, defaultSource).listPendingWorkItems();
|
|
4041
4448
|
const safeResult = {
|
|
4042
4449
|
...result,
|
|
4043
4450
|
items: result.items.map(sanitizeItem)
|
|
@@ -4151,7 +4558,7 @@ var RequirementDecompositionApprovalStore = class {
|
|
|
4151
4558
|
return record;
|
|
4152
4559
|
}
|
|
4153
4560
|
};
|
|
4154
|
-
function resolveAdapter$
|
|
4561
|
+
function resolveAdapter$3(source, adapters, defaultSource) {
|
|
4155
4562
|
const sourceType = source ?? defaultSource;
|
|
4156
4563
|
if (!sourceType) throw new Error("No source specified and no default source configured");
|
|
4157
4564
|
const adapter = adapters.get(sourceType);
|
|
@@ -4254,7 +4661,7 @@ function buildOperations(displayId, tasks) {
|
|
|
4254
4661
|
});
|
|
4255
4662
|
}
|
|
4256
4663
|
async function handleInspectRequirementDecomposition(input, adapters, defaultSource) {
|
|
4257
|
-
const { adapter } = resolveAdapter$
|
|
4664
|
+
const { adapter } = resolveAdapter$3(input.source, adapters, defaultSource);
|
|
4258
4665
|
const context = sanitizedContext(await adapter.getRequirementDecompositionContext({ requirementId: input.requirementId }));
|
|
4259
4666
|
return {
|
|
4260
4667
|
content: [{
|
|
@@ -4265,7 +4672,7 @@ async function handleInspectRequirementDecomposition(input, adapters, defaultSou
|
|
|
4265
4672
|
};
|
|
4266
4673
|
}
|
|
4267
4674
|
async function handlePrepareRequirementDecomposition(input, adapters, approvals, defaultSource) {
|
|
4268
|
-
const { sourceType, adapter } = resolveAdapter$
|
|
4675
|
+
const { sourceType, adapter } = resolveAdapter$3(input.source, adapters, defaultSource);
|
|
4269
4676
|
const context = await adapter.getRequirementDecompositionContext({ requirementId: input.requirementId });
|
|
4270
4677
|
if (context.requirement.workItemKind !== "requirement") throw new Error("Only requirements can be decomposed");
|
|
4271
4678
|
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.");
|
|
@@ -4315,7 +4722,7 @@ async function handleApplyRequirementDecomposition(input, adapters, approvals, o
|
|
|
4315
4722
|
if (!record) throw new Error("Approval token is invalid, expired, or already used. Prepare the decomposition again.");
|
|
4316
4723
|
if ((input.source ?? options.defaultSource) !== record.source) throw new Error("Approval token source does not match the requested source");
|
|
4317
4724
|
if (input.planHash !== record.planHash) throw new Error("Plan hash does not match the approved decomposition");
|
|
4318
|
-
const { adapter } = resolveAdapter$
|
|
4725
|
+
const { adapter } = resolveAdapter$3(record.source, adapters, options.defaultSource);
|
|
4319
4726
|
const current = await adapter.getRequirementDecompositionContext({ requirementId: record.requirementId });
|
|
4320
4727
|
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.");
|
|
4321
4728
|
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.");
|
|
@@ -4421,12 +4828,82 @@ function formatUpdateTaskPlanDatesResult(result) {
|
|
|
4421
4828
|
return lines.join("\n");
|
|
4422
4829
|
}
|
|
4423
4830
|
//#endregion
|
|
4831
|
+
//#region ../../src/tools/wiki-delete.ts
|
|
4832
|
+
const SourceSchema$1 = zod_v4.z.string().trim().min(1).optional();
|
|
4833
|
+
const DeleteEmptyWikiDuplicatesSchema = zod_v4.z.object({
|
|
4834
|
+
keepPageId: zod_v4.z.string().trim().min(1),
|
|
4835
|
+
duplicatePageIds: zod_v4.z.array(zod_v4.z.string().trim().min(1)).min(1).max(20),
|
|
4836
|
+
expectedTitle: zod_v4.z.string().trim().min(1),
|
|
4837
|
+
confirmed: zod_v4.z.literal(true).describe("Set true only after the user confirms the exact keep and delete page IDs immediately before submission."),
|
|
4838
|
+
source: SourceSchema$1
|
|
4839
|
+
});
|
|
4840
|
+
const DeleteEmptyWikiDuplicatesOutputSchema = zod_v4.z.object({
|
|
4841
|
+
keptPageId: zod_v4.z.string(),
|
|
4842
|
+
deletedPageIds: zod_v4.z.array(zod_v4.z.string())
|
|
4843
|
+
});
|
|
4844
|
+
function resolveAdapter$2(source, adapters, defaultSource) {
|
|
4845
|
+
const sourceType = source ?? defaultSource;
|
|
4846
|
+
if (!sourceType) throw new Error("No source specified and no default source configured");
|
|
4847
|
+
const adapter = adapters.get(sourceType);
|
|
4848
|
+
if (!adapter) throw new Error(`Source "${sourceType}" is not configured`);
|
|
4849
|
+
return {
|
|
4850
|
+
sourceType,
|
|
4851
|
+
adapter
|
|
4852
|
+
};
|
|
4853
|
+
}
|
|
4854
|
+
function assertCleanupTarget(keep, duplicates, expectedTitle) {
|
|
4855
|
+
if (keep.title !== expectedTitle) throw new Error("The retained Wiki page title no longer matches the confirmed title");
|
|
4856
|
+
if (keep.content.trim() === `# ${keep.title}`) throw new Error("The retained Wiki page does not contain a body");
|
|
4857
|
+
for (const duplicate of duplicates) {
|
|
4858
|
+
if (duplicate.title !== expectedTitle) throw new Error(`Wiki duplicate ${duplicate.pageId} title no longer matches the confirmed title`);
|
|
4859
|
+
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`);
|
|
4860
|
+
if (duplicate.content.trim() !== `# ${duplicate.title}`) throw new Error(`Wiki duplicate ${duplicate.pageId} is not empty and will not be deleted`);
|
|
4861
|
+
}
|
|
4862
|
+
}
|
|
4863
|
+
async function handleDeleteEmptyWikiDuplicates(input, adapters, options) {
|
|
4864
|
+
if (!options.writesEnabled) throw new Error("Wiki writes are disabled. Enable both ONES_WIKI_ENABLE_WRITES=true and source option wikiWrites=true.");
|
|
4865
|
+
const uniqueDuplicateIds = [...new Set(input.duplicatePageIds)];
|
|
4866
|
+
if (uniqueDuplicateIds.length !== input.duplicatePageIds.length) throw new Error("duplicatePageIds contains repeated page IDs");
|
|
4867
|
+
if (uniqueDuplicateIds.includes(input.keepPageId)) throw new Error("The retained Wiki page cannot also be deleted");
|
|
4868
|
+
const { adapter } = resolveAdapter$2(input.source, adapters, options.defaultSource);
|
|
4869
|
+
const [keep, ...duplicates] = await Promise.all([adapter.getWikiPage({ pageId: input.keepPageId }), ...uniqueDuplicateIds.map((pageId) => adapter.getWikiPage({ pageId }))]);
|
|
4870
|
+
assertCleanupTarget(keep, duplicates, input.expectedTitle);
|
|
4871
|
+
const spaceId = keep.spaceId;
|
|
4872
|
+
if (!spaceId) throw new Error("The retained Wiki page space could not be verified");
|
|
4873
|
+
const operationHash = node_crypto.default.createHash("sha256").update(JSON.stringify({
|
|
4874
|
+
keepPageId: input.keepPageId,
|
|
4875
|
+
duplicatePageIds: uniqueDuplicateIds,
|
|
4876
|
+
expectedTitle: input.expectedTitle,
|
|
4877
|
+
baselines: [keep, ...duplicates].map((page) => ({
|
|
4878
|
+
pageId: page.pageId,
|
|
4879
|
+
version: page.version,
|
|
4880
|
+
contentHash: page.contentHash
|
|
4881
|
+
}))
|
|
4882
|
+
})).digest("hex");
|
|
4883
|
+
for (const duplicate of duplicates) await adapter.deleteWikiPage({
|
|
4884
|
+
teamId: duplicate.teamId,
|
|
4885
|
+
spaceId,
|
|
4886
|
+
pageId: duplicate.pageId
|
|
4887
|
+
});
|
|
4888
|
+
const result = {
|
|
4889
|
+
keptPageId: keep.pageId,
|
|
4890
|
+
deletedPageIds: duplicates.map((page) => page.pageId)
|
|
4891
|
+
};
|
|
4892
|
+
return {
|
|
4893
|
+
content: [{
|
|
4894
|
+
type: "text",
|
|
4895
|
+
text: `Kept Wiki page ${result.keptPageId} and deleted empty duplicates: ${result.deletedPageIds.join(", ")}.\noperationHash: ${operationHash}`
|
|
4896
|
+
}],
|
|
4897
|
+
structuredContent: result
|
|
4898
|
+
};
|
|
4899
|
+
}
|
|
4900
|
+
//#endregion
|
|
4424
4901
|
//#region ../../src/tools/wiki-read.ts
|
|
4425
4902
|
const WikiSourceSchema = zod_v4.z.string().trim().min(1).optional();
|
|
4426
4903
|
const GetOnesWikiPageSchema = zod_v4.z.object({
|
|
4427
4904
|
pageId: zod_v4.z.string().trim().min(1).optional(),
|
|
4428
4905
|
url: zod_v4.z.string().url().optional(),
|
|
4429
|
-
path: zod_v4.z.union([zod_v4.z.string().trim().min(1), zod_v4.z.array(zod_v4.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."),
|
|
4906
|
+
path: zod_v4.z.union([zod_v4.z.string().trim().min(1), zod_v4.z.array(zod_v4.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."),
|
|
4430
4907
|
teamId: zod_v4.z.string().trim().min(1).optional(),
|
|
4431
4908
|
spaceId: zod_v4.z.string().trim().min(1).optional(),
|
|
4432
4909
|
revealSensitiveSecrets: zod_v4.z.boolean().default(false).describe("Default false. Set true only when the user explicitly asks to reveal secrets."),
|
|
@@ -4528,6 +5005,19 @@ async function handleGetOnesWikiPage(input, adapters, defaultSource) {
|
|
|
4528
5005
|
teamId: resolved?.teamId ?? input.teamId,
|
|
4529
5006
|
spaceId: resolved?.spaceId ?? input.spaceId
|
|
4530
5007
|
}), input.revealSensitiveSecrets);
|
|
5008
|
+
if (resolved) {
|
|
5009
|
+
page.title = resolved.title;
|
|
5010
|
+
page.breadcrumb = resolved.breadcrumb;
|
|
5011
|
+
const redactPrivateValues = resolved.redactPrivateValues;
|
|
5012
|
+
if (redactPrivateValues) {
|
|
5013
|
+
page.content = redactPrivateValues(page.content);
|
|
5014
|
+
page.attachments = page.attachments.map((attachment) => ({
|
|
5015
|
+
...attachment,
|
|
5016
|
+
name: redactPrivateValues(attachment.name),
|
|
5017
|
+
url: redactPrivateValues(attachment.url)
|
|
5018
|
+
}));
|
|
5019
|
+
}
|
|
5020
|
+
}
|
|
4531
5021
|
return {
|
|
4532
5022
|
content: [{
|
|
4533
5023
|
type: "text",
|
|
@@ -4675,7 +5165,7 @@ async function handleLookupEnvironmentAccess(input, adapters, defaultSource) {
|
|
|
4675
5165
|
//#region ../../src/tools/wiki-write.ts
|
|
4676
5166
|
const APPROVAL_TTL_MS = 1800 * 1e3;
|
|
4677
5167
|
const SourceSchema = zod_v4.z.string().trim().min(1).optional();
|
|
4678
|
-
const PathSchema = zod_v4.z.union([zod_v4.z.string().trim().min(1), zod_v4.z.array(zod_v4.z.string().trim().min(1)).min(1)]);
|
|
5168
|
+
const PathSchema = zod_v4.z.union([zod_v4.z.string().trim().min(1), zod_v4.z.array(zod_v4.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.");
|
|
4679
5169
|
const PrepareWikiCreateSchema = zod_v4.z.object({
|
|
4680
5170
|
parentPageId: zod_v4.z.string().trim().min(1).optional(),
|
|
4681
5171
|
parentPath: PathSchema.optional(),
|
|
@@ -4699,6 +5189,10 @@ const ReplaceTextSchema = zod_v4.z.object({
|
|
|
4699
5189
|
find: zod_v4.z.string().min(1),
|
|
4700
5190
|
replace: zod_v4.z.string()
|
|
4701
5191
|
});
|
|
5192
|
+
const ReplaceDocumentSchema = zod_v4.z.object({
|
|
5193
|
+
type: zod_v4.z.literal("replace_document"),
|
|
5194
|
+
markdown: zod_v4.z.string().trim().min(1)
|
|
5195
|
+
});
|
|
4702
5196
|
const PrepareWikiUpdateSchema = zod_v4.z.object({
|
|
4703
5197
|
pageId: zod_v4.z.string().trim().min(1).optional(),
|
|
4704
5198
|
url: zod_v4.z.string().url().optional(),
|
|
@@ -4708,7 +5202,8 @@ const PrepareWikiUpdateSchema = zod_v4.z.object({
|
|
|
4708
5202
|
operation: zod_v4.z.discriminatedUnion("type", [
|
|
4709
5203
|
AppendBlocksSchema,
|
|
4710
5204
|
AppendTableRowSchema,
|
|
4711
|
-
ReplaceTextSchema
|
|
5205
|
+
ReplaceTextSchema,
|
|
5206
|
+
ReplaceDocumentSchema
|
|
4712
5207
|
]),
|
|
4713
5208
|
source: SourceSchema
|
|
4714
5209
|
}).refine((value) => [
|
|
@@ -4749,6 +5244,10 @@ const WikiUpdateOperationSchema = zod_v4.z.discriminatedUnion("type", [
|
|
|
4749
5244
|
type: zod_v4.z.literal("replace_text"),
|
|
4750
5245
|
find: zod_v4.z.string(),
|
|
4751
5246
|
replace: zod_v4.z.string()
|
|
5247
|
+
}),
|
|
5248
|
+
zod_v4.z.object({
|
|
5249
|
+
type: zod_v4.z.literal("replace_document"),
|
|
5250
|
+
markdown: zod_v4.z.string()
|
|
4752
5251
|
})
|
|
4753
5252
|
]);
|
|
4754
5253
|
const WikiUpdateRequestSchema = zod_v4.z.object({
|
|
@@ -4878,11 +5377,24 @@ async function handlePrepareWikiCreate(input, adapters, approvals, defaultSource
|
|
|
4878
5377
|
const teamId = resolved?.teamId ?? parent.teamId;
|
|
4879
5378
|
const spaceId = resolved?.spaceId ?? parent.spaceId;
|
|
4880
5379
|
if (!spaceId) throw new Error("The target space could not be verified");
|
|
5380
|
+
const title = input.title.trim();
|
|
5381
|
+
const titleCandidates = await adapter.searchWikiPages({
|
|
5382
|
+
query: title,
|
|
5383
|
+
teamId,
|
|
5384
|
+
spaceId,
|
|
5385
|
+
limit: 50
|
|
5386
|
+
});
|
|
5387
|
+
const siblingConflicts = (await Promise.all(titleCandidates.filter((candidate) => candidate.title === title).map((candidate) => adapter.getWikiPage({
|
|
5388
|
+
pageId: candidate.pageId,
|
|
5389
|
+
teamId: candidate.teamId,
|
|
5390
|
+
spaceId: candidate.spaceId ?? spaceId
|
|
5391
|
+
})))).filter((page) => page.parentPageId === parent.pageId);
|
|
5392
|
+
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.`);
|
|
4881
5393
|
const requestWithoutKey = {
|
|
4882
5394
|
teamId,
|
|
4883
5395
|
spaceId,
|
|
4884
5396
|
parentPageId: parent.pageId,
|
|
4885
|
-
title
|
|
5397
|
+
title,
|
|
4886
5398
|
markdown: input.markdown
|
|
4887
5399
|
};
|
|
4888
5400
|
const operationHash = hashOperation({
|
|
@@ -4904,7 +5416,7 @@ async function handlePrepareWikiCreate(input, adapters, approvals, defaultSource
|
|
|
4904
5416
|
});
|
|
4905
5417
|
const plan = {
|
|
4906
5418
|
kind: "create",
|
|
4907
|
-
targetBreadcrumb: [...parent.breadcrumb, input.title.trim()],
|
|
5419
|
+
targetBreadcrumb: [...resolved?.breadcrumb ?? parent.breadcrumb, input.title.trim()],
|
|
4908
5420
|
request,
|
|
4909
5421
|
parentBaseline: baseline(parent),
|
|
4910
5422
|
operationHash,
|
|
@@ -4961,7 +5473,7 @@ async function handlePrepareWikiUpdate(input, adapters, approvals, defaultSource
|
|
|
4961
5473
|
});
|
|
4962
5474
|
const plan = {
|
|
4963
5475
|
kind: "update",
|
|
4964
|
-
targetBreadcrumb: page.breadcrumb,
|
|
5476
|
+
targetBreadcrumb: resolved?.breadcrumb ?? page.breadcrumb,
|
|
4965
5477
|
request,
|
|
4966
5478
|
operationHash,
|
|
4967
5479
|
approvalToken: approval.token,
|
|
@@ -4971,7 +5483,7 @@ async function handlePrepareWikiUpdate(input, adapters, approvals, defaultSource
|
|
|
4971
5483
|
content: [{
|
|
4972
5484
|
type: "text",
|
|
4973
5485
|
text: [
|
|
4974
|
-
`Prepared Wiki update for ${
|
|
5486
|
+
`Prepared Wiki update for ${plan.targetBreadcrumb.join(" / ") || page.title}.`,
|
|
4975
5487
|
"No write was performed. Ask the user to confirm this exact operation immediately before apply.",
|
|
4976
5488
|
`operationHash: ${plan.operationHash}`,
|
|
4977
5489
|
`approvalToken: ${plan.approvalToken}`,
|
|
@@ -5084,7 +5596,7 @@ function createRequirementsServer(config, adapterOverrides) {
|
|
|
5084
5596
|
});
|
|
5085
5597
|
server.registerTool("get_ones_wiki_page", {
|
|
5086
5598
|
title: "Get ONES Wiki Page",
|
|
5087
|
-
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.",
|
|
5599
|
+
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.",
|
|
5088
5600
|
inputSchema: GetOnesWikiPageSchema,
|
|
5089
5601
|
annotations: {
|
|
5090
5602
|
readOnlyHint: true,
|
|
@@ -5146,7 +5658,7 @@ function createRequirementsServer(config, adapterOverrides) {
|
|
|
5146
5658
|
});
|
|
5147
5659
|
server.registerTool("prepare_wiki_create", {
|
|
5148
5660
|
title: "Prepare ONES Wiki Create",
|
|
5149
|
-
description: "Resolve an exact parent page and prepare one exact Wiki create operation. Never writes. Returns a one-time 30-minute approval token.",
|
|
5661
|
+
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.",
|
|
5150
5662
|
inputSchema: PrepareWikiCreateSchema,
|
|
5151
5663
|
outputSchema: PrepareWikiCreateOutputSchema,
|
|
5152
5664
|
annotations: {
|
|
@@ -5185,7 +5697,7 @@ function createRequirementsServer(config, adapterOverrides) {
|
|
|
5185
5697
|
});
|
|
5186
5698
|
server.registerTool("prepare_wiki_update", {
|
|
5187
5699
|
title: "Prepare ONES Wiki Update",
|
|
5188
|
-
description: "Prepare an exact minimal Wiki update, including exact table-row targeting. Never writes. Ambiguous pages, tables, or text matches fail closed.",
|
|
5700
|
+
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.",
|
|
5189
5701
|
inputSchema: PrepareWikiUpdateSchema,
|
|
5190
5702
|
outputSchema: PrepareWikiUpdateOutputSchema,
|
|
5191
5703
|
annotations: {
|
|
@@ -5222,6 +5734,28 @@ function createRequirementsServer(config, adapterOverrides) {
|
|
|
5222
5734
|
return toolError(err);
|
|
5223
5735
|
}
|
|
5224
5736
|
});
|
|
5737
|
+
server.registerTool("delete_empty_wiki_duplicates", {
|
|
5738
|
+
title: "Delete Empty ONES Wiki Duplicates",
|
|
5739
|
+
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.",
|
|
5740
|
+
inputSchema: DeleteEmptyWikiDuplicatesSchema,
|
|
5741
|
+
outputSchema: DeleteEmptyWikiDuplicatesOutputSchema,
|
|
5742
|
+
annotations: {
|
|
5743
|
+
readOnlyHint: false,
|
|
5744
|
+
destructiveHint: true,
|
|
5745
|
+
idempotentHint: false,
|
|
5746
|
+
openWorldHint: true
|
|
5747
|
+
}
|
|
5748
|
+
}, async (params) => {
|
|
5749
|
+
try {
|
|
5750
|
+
const sourceType = params.source ?? defaultSource;
|
|
5751
|
+
return await handleDeleteEmptyWikiDuplicates(params, adapters, {
|
|
5752
|
+
defaultSource,
|
|
5753
|
+
writesEnabled: wikiWritesEnabled(sourceType)
|
|
5754
|
+
});
|
|
5755
|
+
} catch (err) {
|
|
5756
|
+
return toolError(err);
|
|
5757
|
+
}
|
|
5758
|
+
});
|
|
5225
5759
|
server.registerTool("list_sources", {
|
|
5226
5760
|
title: "List Sources",
|
|
5227
5761
|
description: "List all configured requirement sources and their status",
|