ai-dev-requirements 0.4.0 → 0.5.1
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 +672 -133
- package/dist/index.mjs +671 -132
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -16,7 +16,7 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
16
16
|
}
|
|
17
17
|
return to;
|
|
18
18
|
};
|
|
19
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
19
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", {
|
|
20
20
|
value: mod,
|
|
21
21
|
enumerable: true
|
|
22
22
|
}) : target, mod));
|
|
@@ -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.1";
|
|
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"),
|
|
@@ -814,7 +848,8 @@ function removeControlCharacters(value) {
|
|
|
814
848
|
return output;
|
|
815
849
|
}
|
|
816
850
|
function sanitizeExternalText(value) {
|
|
817
|
-
|
|
851
|
+
const withoutActiveContent = value.slice(0, MAX_EXTERNAL_TEXT_CHARS).replace(/<(?:script|style|iframe|object|embed)\b[^>]*>[\s\S]*?<\/(?:script|style|iframe|object|embed)>/gi, "").replace(/<img\b[^>]*>/gi, "[Image omitted]").replace(/<br\s*\/?>/gi, "\n").replace(/<\/p\s*>/gi, "\n").replace(/<\/(?:td|th)\s*>/gi, " | ").replace(/<\/tr\s*>/gi, "\n").replace(/<[^>]+>/g, "");
|
|
852
|
+
return removeControlCharacters(removeUrlCredentials((0, entities.decodeHTML)(withoutActiveContent))).replace(/[ \t]+\n/g, "\n").replace(/\n[ \t]+/g, "\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
818
853
|
}
|
|
819
854
|
function sanitizeExternalInline(value) {
|
|
820
855
|
return sanitizeExternalText(value).replace(/\s+/g, " ").slice(0, MAX_EXTERNAL_INLINE_CHARS);
|
|
@@ -1192,17 +1227,43 @@ function renderWikiContent(content, context = { imageSources: [] }) {
|
|
|
1192
1227
|
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
1228
|
}
|
|
1194
1229
|
function newWikiBlockId() {
|
|
1195
|
-
|
|
1230
|
+
for (;;) {
|
|
1231
|
+
const id = (0, node_crypto.randomBytes)(9).toString("base64url").replace(/[^a-z0-9]/gi, "").slice(0, 9);
|
|
1232
|
+
if (/^[a-z][a-z0-9]{8}$/i.test(id)) return id;
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
function markdownTextRuns(text) {
|
|
1236
|
+
if (!text) return [];
|
|
1237
|
+
const runs = [];
|
|
1238
|
+
const inlinePattern = /\[([^\]]+)\]\((https?:\/\/[^)\s]+)\)|`([^`]+)`/g;
|
|
1239
|
+
let cursor = 0;
|
|
1240
|
+
for (const match of text.matchAll(inlinePattern)) {
|
|
1241
|
+
const index = match.index ?? 0;
|
|
1242
|
+
if (index > cursor) runs.push({ insert: text.slice(cursor, index) });
|
|
1243
|
+
if (match[1] && match[2]) runs.push({
|
|
1244
|
+
insert: match[1],
|
|
1245
|
+
attributes: { link: match[2] }
|
|
1246
|
+
});
|
|
1247
|
+
else if (match[3]) runs.push({
|
|
1248
|
+
insert: match[3],
|
|
1249
|
+
attributes: { "style-code": true }
|
|
1250
|
+
});
|
|
1251
|
+
cursor = index + match[0].length;
|
|
1252
|
+
}
|
|
1253
|
+
if (cursor < text.length) runs.push({ insert: text.slice(cursor) });
|
|
1254
|
+
return runs.length ? runs : [{ insert: text }];
|
|
1196
1255
|
}
|
|
1197
1256
|
function wikiTextBlock(text, options = {}) {
|
|
1198
1257
|
return {
|
|
1199
1258
|
id: newWikiBlockId(),
|
|
1200
1259
|
type: options.list ? "list" : "text",
|
|
1201
|
-
text: text
|
|
1260
|
+
text: markdownTextRuns(text),
|
|
1202
1261
|
...options.heading ? { heading: options.heading } : {},
|
|
1203
1262
|
...options.list ? {
|
|
1204
1263
|
ordered: options.ordered ?? false,
|
|
1205
|
-
level: 1
|
|
1264
|
+
level: options.level ?? 1,
|
|
1265
|
+
...options.start === void 0 ? {} : { start: options.start },
|
|
1266
|
+
...options.groupId ? { groupId: options.groupId } : {}
|
|
1206
1267
|
} : {}
|
|
1207
1268
|
};
|
|
1208
1269
|
}
|
|
@@ -1213,6 +1274,9 @@ function isMarkdownSeparatorRow(line) {
|
|
|
1213
1274
|
const cells = parseMarkdownRow(line);
|
|
1214
1275
|
return cells.length > 0 && cells.every((cell) => /^:?-{3,}:?$/.test(cell));
|
|
1215
1276
|
}
|
|
1277
|
+
function estimateWikiTableCellWidth(value) {
|
|
1278
|
+
return [...value].reduce((width, character) => width + (character.codePointAt(0) > 127 ? 14 : 7), 24);
|
|
1279
|
+
}
|
|
1216
1280
|
function markdownToWikiDocument(markdown) {
|
|
1217
1281
|
const document = {
|
|
1218
1282
|
blocks: [],
|
|
@@ -1221,9 +1285,15 @@ function markdownToWikiDocument(markdown) {
|
|
|
1221
1285
|
};
|
|
1222
1286
|
const blocks = document.blocks;
|
|
1223
1287
|
const lines = markdown.replace(/\r\n/g, "\n").split("\n");
|
|
1288
|
+
let activeList = null;
|
|
1224
1289
|
for (let index = 0; index < lines.length; index += 1) {
|
|
1225
1290
|
const line = lines[index];
|
|
1291
|
+
if (!line.trim()) {
|
|
1292
|
+
activeList = null;
|
|
1293
|
+
continue;
|
|
1294
|
+
}
|
|
1226
1295
|
if (line.includes("|") && lines[index + 1] && isMarkdownSeparatorRow(lines[index + 1])) {
|
|
1296
|
+
activeList = null;
|
|
1227
1297
|
const rows = [parseMarkdownRow(line)];
|
|
1228
1298
|
index += 2;
|
|
1229
1299
|
while (index < lines.length && lines[index].includes("|")) {
|
|
@@ -1241,34 +1311,120 @@ function markdownToWikiDocument(markdown) {
|
|
|
1241
1311
|
blocks.push({
|
|
1242
1312
|
id: newWikiBlockId(),
|
|
1243
1313
|
type: "table",
|
|
1244
|
-
rows: rows.length,
|
|
1245
1314
|
cols: columnCount,
|
|
1315
|
+
rows: rows.length,
|
|
1316
|
+
colsWidth: Array.from({ length: columnCount }, (_, column) => Math.max(100, ...rows.map((row) => estimateWikiTableCellWidth(row[column] ?? "")))),
|
|
1246
1317
|
children
|
|
1247
1318
|
});
|
|
1248
1319
|
continue;
|
|
1249
1320
|
}
|
|
1250
1321
|
const heading = line.match(/^(#{1,6})[ \t]/);
|
|
1251
1322
|
if (heading?.[1]) {
|
|
1323
|
+
activeList = null;
|
|
1252
1324
|
blocks.push(wikiTextBlock(line.slice(heading[0].length).trim(), { heading: heading[1].length }));
|
|
1253
1325
|
continue;
|
|
1254
1326
|
}
|
|
1255
1327
|
const unordered = line.match(/^[ \t]*[-*+][ \t]/);
|
|
1256
1328
|
if (unordered) {
|
|
1257
|
-
|
|
1329
|
+
const groupId = activeList && !activeList.ordered ? activeList.groupId : newWikiBlockId();
|
|
1330
|
+
const start = activeList && !activeList.ordered ? activeList.nextStart : 1;
|
|
1331
|
+
activeList = {
|
|
1332
|
+
ordered: false,
|
|
1333
|
+
groupId,
|
|
1334
|
+
nextStart: start + 1
|
|
1335
|
+
};
|
|
1336
|
+
blocks.push(wikiTextBlock(line.slice(unordered[0].length).trim(), {
|
|
1337
|
+
list: true,
|
|
1338
|
+
groupId,
|
|
1339
|
+
start
|
|
1340
|
+
}));
|
|
1258
1341
|
continue;
|
|
1259
1342
|
}
|
|
1260
|
-
const ordered = line.match(/^[ \t]
|
|
1261
|
-
if (ordered) {
|
|
1343
|
+
const ordered = line.match(/^[ \t]*(\d+)[.)][ \t]/);
|
|
1344
|
+
if (ordered?.[1]) {
|
|
1345
|
+
const groupId = activeList?.ordered ? activeList.groupId : newWikiBlockId();
|
|
1346
|
+
const start = Number.parseInt(ordered[1], 10);
|
|
1347
|
+
activeList = {
|
|
1348
|
+
ordered: true,
|
|
1349
|
+
groupId,
|
|
1350
|
+
nextStart: start + 1
|
|
1351
|
+
};
|
|
1262
1352
|
blocks.push(wikiTextBlock(line.slice(ordered[0].length).trim(), {
|
|
1263
1353
|
list: true,
|
|
1264
|
-
ordered: true
|
|
1354
|
+
ordered: true,
|
|
1355
|
+
start,
|
|
1356
|
+
groupId
|
|
1265
1357
|
}));
|
|
1266
1358
|
continue;
|
|
1267
1359
|
}
|
|
1360
|
+
activeList = null;
|
|
1268
1361
|
blocks.push(wikiTextBlock(line));
|
|
1269
1362
|
}
|
|
1270
1363
|
return document;
|
|
1271
1364
|
}
|
|
1365
|
+
function markdownToWikiHtml(markdown) {
|
|
1366
|
+
const renderInline = (text) => markdownTextRuns(text).map((run) => {
|
|
1367
|
+
const escaped = escapeWikiHtml(run.insert);
|
|
1368
|
+
const content = run.attributes?.["style-code"] ? `<code>${escaped}</code>` : escaped;
|
|
1369
|
+
return run.attributes?.link ? `<a href="${escapeWikiHtml(run.attributes.link)}" target="_blank" rel="noopener noreferrer">${content}</a>` : content;
|
|
1370
|
+
}).join("");
|
|
1371
|
+
const lines = markdown.replace(/\r\n/g, "\n").split("\n");
|
|
1372
|
+
const html = [];
|
|
1373
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
1374
|
+
const line = lines[index];
|
|
1375
|
+
if (!line.trim()) continue;
|
|
1376
|
+
if (line.includes("|") && lines[index + 1] && isMarkdownSeparatorRow(lines[index + 1])) {
|
|
1377
|
+
const rows = [parseMarkdownRow(line)];
|
|
1378
|
+
index += 2;
|
|
1379
|
+
while (index < lines.length && lines[index].includes("|")) {
|
|
1380
|
+
rows.push(parseMarkdownRow(lines[index]));
|
|
1381
|
+
index += 1;
|
|
1382
|
+
}
|
|
1383
|
+
index -= 1;
|
|
1384
|
+
const columnCount = Math.max(...rows.map((row) => row.length));
|
|
1385
|
+
const width = Math.floor(100 / columnCount);
|
|
1386
|
+
const header = Array.from({ length: columnCount }).map((_, column) => `<th style="width:${width}%">${renderInline(rows[0][column] ?? "")}</th>`).join("");
|
|
1387
|
+
const body = rows.slice(1).map((row) => `<tr>${Array.from({ length: columnCount }).map((_, column) => `<td>${renderInline(row[column] ?? "")}</td>`).join("")}</tr>`).join("");
|
|
1388
|
+
html.push(`<table style="width:100%"><thead><tr>${header}</tr></thead><tbody>${body}</tbody></table>`);
|
|
1389
|
+
continue;
|
|
1390
|
+
}
|
|
1391
|
+
const heading = line.match(/^(#{1,6})[ \t]/);
|
|
1392
|
+
if (heading?.[1]) {
|
|
1393
|
+
const level = heading[1].length;
|
|
1394
|
+
html.push(`<h${level}>${renderInline(line.slice(heading[0].length).trim())}</h${level}>`);
|
|
1395
|
+
continue;
|
|
1396
|
+
}
|
|
1397
|
+
if (line.match(/^[ \t]*(\d+)[.)][ \t]/)?.[1]) {
|
|
1398
|
+
const items = [];
|
|
1399
|
+
while (index < lines.length) {
|
|
1400
|
+
const item = lines[index].match(/^[ \t]*(\d+)[.)][ \t]/);
|
|
1401
|
+
if (!item?.[1]) break;
|
|
1402
|
+
items.push({
|
|
1403
|
+
value: Number.parseInt(item[1], 10),
|
|
1404
|
+
text: lines[index].slice(item[0].length).trim()
|
|
1405
|
+
});
|
|
1406
|
+
index += 1;
|
|
1407
|
+
}
|
|
1408
|
+
index -= 1;
|
|
1409
|
+
html.push(`<ol start="${items[0].value}">${items.map((item) => `<li>${renderInline(item.text)}</li>`).join("")}</ol>`);
|
|
1410
|
+
continue;
|
|
1411
|
+
}
|
|
1412
|
+
if (line.match(/^[ \t]*[-*+][ \t]/)) {
|
|
1413
|
+
const items = [];
|
|
1414
|
+
while (index < lines.length) {
|
|
1415
|
+
const item = lines[index].match(/^[ \t]*[-*+][ \t]/);
|
|
1416
|
+
if (!item) break;
|
|
1417
|
+
items.push(lines[index].slice(item[0].length).trim());
|
|
1418
|
+
index += 1;
|
|
1419
|
+
}
|
|
1420
|
+
index -= 1;
|
|
1421
|
+
html.push(`<ul>${items.map((item) => `<li>${renderInline(item)}</li>`).join("")}</ul>`);
|
|
1422
|
+
continue;
|
|
1423
|
+
}
|
|
1424
|
+
html.push(`<p>${renderInline(line.trim())}</p>`);
|
|
1425
|
+
}
|
|
1426
|
+
return html.join("\n");
|
|
1427
|
+
}
|
|
1272
1428
|
function parseWikiDocument(content) {
|
|
1273
1429
|
const document = parseJsonRecord(content);
|
|
1274
1430
|
if (!document || !Array.isArray(document.blocks)) throw new Error("ONES: Wiki content is not a supported collaborative document");
|
|
@@ -1325,6 +1481,17 @@ function appendWikiTableRow(document, operation) {
|
|
|
1325
1481
|
table.rows = (typeof table.rows === "number" ? table.rows : layout.rows.length) + 1;
|
|
1326
1482
|
}
|
|
1327
1483
|
function applyWikiUpdateOperation(content, operation) {
|
|
1484
|
+
if (operation.type === "replace_document") {
|
|
1485
|
+
const current = parseWikiDocument(content);
|
|
1486
|
+
const replacement = markdownToWikiDocument(operation.markdown);
|
|
1487
|
+
for (const key of [
|
|
1488
|
+
"comments",
|
|
1489
|
+
"meta",
|
|
1490
|
+
"authors",
|
|
1491
|
+
"commentators"
|
|
1492
|
+
]) if (Object.hasOwn(current, key)) replacement[key] = current[key];
|
|
1493
|
+
return JSON.stringify(replacement);
|
|
1494
|
+
}
|
|
1328
1495
|
const document = parseWikiDocument(content);
|
|
1329
1496
|
if (operation.type === "append_blocks") appendWikiDocument(document, markdownToWikiDocument(operation.markdown));
|
|
1330
1497
|
else if (operation.type === "replace_text") replaceWikiText(document, operation.find, operation.replace);
|
|
@@ -1363,6 +1530,30 @@ var WikiPathResolutionError = class extends Error {
|
|
|
1363
1530
|
};
|
|
1364
1531
|
//#endregion
|
|
1365
1532
|
//#region ../../src/adapters/ones/wiki-reader.ts
|
|
1533
|
+
function isWikiAttachmentPath(source) {
|
|
1534
|
+
if (/^[a-z][a-z\d+.-]*:/i.test(source)) return false;
|
|
1535
|
+
return source.split("/").every((part) => Boolean(part) && part !== "." && part !== ".." && !part.includes("\\"));
|
|
1536
|
+
}
|
|
1537
|
+
const CURRENT_USER_PATH_ALIASES = {
|
|
1538
|
+
"i": true,
|
|
1539
|
+
"me": true,
|
|
1540
|
+
"mine": true,
|
|
1541
|
+
"my": true,
|
|
1542
|
+
"myself": true,
|
|
1543
|
+
"current user": true,
|
|
1544
|
+
"current-user": true,
|
|
1545
|
+
"current_user": true,
|
|
1546
|
+
"我": true,
|
|
1547
|
+
"我的": true,
|
|
1548
|
+
"本人": true,
|
|
1549
|
+
"当前用户": true,
|
|
1550
|
+
"当前账号": true,
|
|
1551
|
+
"自己": true
|
|
1552
|
+
};
|
|
1553
|
+
function currentUserPathAlias(segment) {
|
|
1554
|
+
const trimmed = segment.trim();
|
|
1555
|
+
return CURRENT_USER_PATH_ALIASES[trimmed.toLocaleLowerCase()] ? trimmed : null;
|
|
1556
|
+
}
|
|
1366
1557
|
var OnesWikiOpenApiError = class extends Error {
|
|
1367
1558
|
status;
|
|
1368
1559
|
reason;
|
|
@@ -1457,12 +1648,29 @@ function segmentSimilarity(left, right) {
|
|
|
1457
1648
|
var OnesWikiReader = class {
|
|
1458
1649
|
options;
|
|
1459
1650
|
treeCache = /* @__PURE__ */ new Map();
|
|
1651
|
+
currentUserNameRequest = null;
|
|
1460
1652
|
constructor(options) {
|
|
1461
1653
|
this.options = options;
|
|
1462
1654
|
}
|
|
1463
1655
|
invalidateTree(teamId, spaceId) {
|
|
1464
1656
|
this.treeCache.delete(`${teamId}:${spaceId}`);
|
|
1465
1657
|
}
|
|
1658
|
+
async fetchCurrentUserName() {
|
|
1659
|
+
const session = await this.options.getSession();
|
|
1660
|
+
const response = await fetch(new URL("/wiki/api/project/auth/token_info", this.options.apiBase).toString(), { headers: { Authorization: `Bearer ${session.accessToken}` } });
|
|
1661
|
+
if (!response.ok) throw new Error(`ONES Wiki token info error: ${response.status}`);
|
|
1662
|
+
const payload = await response.json();
|
|
1663
|
+
const name = firstNonEmptyString$1(payload.user?.name, payload.data?.user?.name, payload.data?.name, payload.name, payload.user_name, payload.userName);
|
|
1664
|
+
if (!name) throw new Error("ONES Wiki token info did not include a resolvable current account");
|
|
1665
|
+
return name;
|
|
1666
|
+
}
|
|
1667
|
+
currentUserName() {
|
|
1668
|
+
if (!this.currentUserNameRequest) this.currentUserNameRequest = this.fetchCurrentUserName().catch((error) => {
|
|
1669
|
+
this.currentUserNameRequest = null;
|
|
1670
|
+
throw error;
|
|
1671
|
+
});
|
|
1672
|
+
return this.currentUserNameRequest;
|
|
1673
|
+
}
|
|
1466
1674
|
async openApi(apiPath) {
|
|
1467
1675
|
if (!apiPath.startsWith("/wiki/")) throw new Error("ONES: Invalid Wiki Open API path");
|
|
1468
1676
|
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");
|
|
@@ -1488,9 +1696,8 @@ var OnesWikiReader = class {
|
|
|
1488
1696
|
}
|
|
1489
1697
|
buildImageUrl(session, refUuid, source, token, teamUuid) {
|
|
1490
1698
|
const encodedRefUuid = encodeIdentifier$1(refUuid, "wiki reference UUID");
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
const encodedSource = sourceParts.map((part) => encodeURIComponent(part)).join("/");
|
|
1699
|
+
if (!isWikiAttachmentPath(source)) throw new Error("ONES: Invalid wiki attachment path");
|
|
1700
|
+
const encodedSource = source.split("/").map((part) => encodeURIComponent(part)).join("/");
|
|
1494
1701
|
const encodedTeamUuid = encodeIdentifier$1(teamUuid ?? session.teamUuid, "team UUID");
|
|
1495
1702
|
return `${this.options.apiBase}/wiki/api/wiki/editor/${encodedTeamUuid}/${encodedRefUuid}/resources/${encodedSource}?token=${encodeURIComponent(token)}`;
|
|
1496
1703
|
}
|
|
@@ -1508,7 +1715,8 @@ var OnesWikiReader = class {
|
|
|
1508
1715
|
const context = { imageSources: [] };
|
|
1509
1716
|
const content = renderWikiContent(typeof data.content === "string" ? data.content : "", context);
|
|
1510
1717
|
const token = typeof data.token === "string" ? data.token : "";
|
|
1511
|
-
|
|
1718
|
+
const attachmentSources = context.imageSources.filter(isWikiAttachmentPath);
|
|
1719
|
+
if (!attachmentSources.length || !token) return {
|
|
1512
1720
|
content,
|
|
1513
1721
|
attachments: []
|
|
1514
1722
|
};
|
|
@@ -1520,7 +1728,7 @@ var OnesWikiReader = class {
|
|
|
1520
1728
|
};
|
|
1521
1729
|
return {
|
|
1522
1730
|
content,
|
|
1523
|
-
attachments:
|
|
1731
|
+
attachments: attachmentSources.map((source, index) => ({
|
|
1524
1732
|
id: `${wikiUuid}-image-${index + 1}`,
|
|
1525
1733
|
name: attachmentNameFromPath(source),
|
|
1526
1734
|
url: this.buildImageUrl(session, refUuid, source, token, wikiTeamUuid),
|
|
@@ -1722,8 +1930,19 @@ var OnesWikiReader = class {
|
|
|
1722
1930
|
return score >= MIN_PATH_MATCH_SIMILARITY ? score : null;
|
|
1723
1931
|
}
|
|
1724
1932
|
async resolvePath(params) {
|
|
1725
|
-
const
|
|
1726
|
-
if (!
|
|
1933
|
+
const requestedPath = params.path.map((segment) => segment.trim()).filter(Boolean);
|
|
1934
|
+
if (!requestedPath.length) throw new Error("ONES: Wiki path is empty");
|
|
1935
|
+
const currentUserAlias = requestedPath.map(currentUserPathAlias).find((alias) => alias !== null) ?? null;
|
|
1936
|
+
const currentUserName = currentUserAlias ? await this.currentUserName() : null;
|
|
1937
|
+
const path = currentUserName ? requestedPath.map((segment) => currentUserPathAlias(segment) ? currentUserName : segment) : requestedPath;
|
|
1938
|
+
const redactCurrentUser = (page) => {
|
|
1939
|
+
if (!currentUserName || !currentUserAlias) return page;
|
|
1940
|
+
return {
|
|
1941
|
+
...page,
|
|
1942
|
+
title: page.title === currentUserName ? currentUserAlias : page.title,
|
|
1943
|
+
breadcrumb: page.breadcrumb.map((segment) => segment === currentUserName ? currentUserAlias : segment)
|
|
1944
|
+
};
|
|
1945
|
+
};
|
|
1727
1946
|
const candidates = await this.search({
|
|
1728
1947
|
query: path.at(-1),
|
|
1729
1948
|
teamId: params.teamId,
|
|
@@ -1770,26 +1989,32 @@ var OnesWikiReader = class {
|
|
|
1770
1989
|
});
|
|
1771
1990
|
}
|
|
1772
1991
|
}
|
|
1773
|
-
if (matches.length > 1) throw new WikiPathResolutionError("ambiguous",
|
|
1992
|
+
if (matches.length > 1) throw new WikiPathResolutionError("ambiguous", requestedPath, matches.map(redactCurrentUser));
|
|
1774
1993
|
let match = matches[0];
|
|
1775
1994
|
if (!match && fuzzy.length) {
|
|
1776
1995
|
fuzzy.sort((left, right) => right.score - left.score);
|
|
1777
1996
|
const competing = fuzzy.filter((candidate) => fuzzy[0].score - candidate.score < PATH_MATCH_MARGIN);
|
|
1778
|
-
if (competing.length > 1) throw new WikiPathResolutionError("ambiguous",
|
|
1997
|
+
if (competing.length > 1) throw new WikiPathResolutionError("ambiguous", requestedPath, competing.map((candidate) => redactCurrentUser(candidate.page)));
|
|
1779
1998
|
match = fuzzy[0].page;
|
|
1780
1999
|
}
|
|
1781
2000
|
if (!match) {
|
|
1782
2001
|
const inspectedIds = new Set(inspected.map((page) => page.pageId));
|
|
1783
|
-
throw new WikiPathResolutionError("not_found",
|
|
2002
|
+
throw new WikiPathResolutionError("not_found", requestedPath, [...inspected, ...candidates.filter((page) => !inspectedIds.has(page.pageId))].map(redactCurrentUser));
|
|
1784
2003
|
}
|
|
1785
2004
|
if (!match.spaceId) throw new Error("ONES: Wiki space ID could not be verified");
|
|
1786
|
-
|
|
2005
|
+
const publicMatch = redactCurrentUser(match);
|
|
2006
|
+
const resolution = {
|
|
1787
2007
|
teamId: match.teamId,
|
|
1788
2008
|
spaceId: match.spaceId,
|
|
1789
2009
|
pageId: match.pageId,
|
|
1790
|
-
title:
|
|
1791
|
-
breadcrumb:
|
|
2010
|
+
title: publicMatch.title,
|
|
2011
|
+
breadcrumb: publicMatch.breadcrumb
|
|
1792
2012
|
};
|
|
2013
|
+
if (currentUserName && currentUserAlias) Object.defineProperty(resolution, "redactPrivateValues", {
|
|
2014
|
+
enumerable: false,
|
|
2015
|
+
value: (value) => value.split(currentUserName).join(currentUserAlias)
|
|
2016
|
+
});
|
|
2017
|
+
return resolution;
|
|
1793
2018
|
}
|
|
1794
2019
|
};
|
|
1795
2020
|
//#endregion
|
|
@@ -1991,8 +2216,9 @@ var OnesTaskContent = class {
|
|
|
1991
2216
|
attachments: rendered.attachments
|
|
1992
2217
|
};
|
|
1993
2218
|
})), containsInlineTaskImages(task) ? this.getTaskImageAttachments(task) : Promise.resolve([])]);
|
|
2219
|
+
const projectIdentifier = task.project?.identifier?.toUpperCase() ?? null;
|
|
1994
2220
|
const parts = [
|
|
1995
|
-
`#
|
|
2221
|
+
`# ${taskDisplayId({}, task, projectIdentifier)} ${task.name}`,
|
|
1996
2222
|
"",
|
|
1997
2223
|
`- **Type**: ${task.issueType?.name ?? "Unknown"}`,
|
|
1998
2224
|
"- **Work Item Kind**: requirement",
|
|
@@ -2004,7 +2230,7 @@ var OnesTaskContent = class {
|
|
|
2004
2230
|
parts.push(`- **UUID**: ${task.uuid}`);
|
|
2005
2231
|
if (task.relatedTasks?.length) {
|
|
2006
2232
|
parts.push("", "## Related Tasks");
|
|
2007
|
-
for (const related of task.relatedTasks) parts.push(`-
|
|
2233
|
+
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
2234
|
}
|
|
2009
2235
|
if (relatedActivities.length) {
|
|
2010
2236
|
parts.push("", "## Related Work Items");
|
|
@@ -2019,7 +2245,7 @@ var OnesTaskContent = class {
|
|
|
2019
2245
|
}
|
|
2020
2246
|
if (task.parent?.uuid) {
|
|
2021
2247
|
parts.push("", "## Parent Task", `- UUID: ${task.parent.uuid}`);
|
|
2022
|
-
if (task.parent.number) parts.push(`- Number:
|
|
2248
|
+
if (task.parent.number) parts.push(`- Number: ${projectIdentifier ? `${projectIdentifier}-${task.parent.number}` : `#${task.parent.number}`}`);
|
|
2023
2249
|
}
|
|
2024
2250
|
if (wikiContents.length > 0) {
|
|
2025
2251
|
parts.push("", "---", "", "## Requirement Documents");
|
|
@@ -2042,8 +2268,9 @@ var OnesTaskContent = class {
|
|
|
2042
2268
|
}
|
|
2043
2269
|
buildWorkItemSummary(task, kind) {
|
|
2044
2270
|
const nextTool = kind === "defect" ? "get_issue_detail" : "get_related_issues / get_testcases";
|
|
2271
|
+
const projectIdentifier = task.project?.identifier?.toUpperCase() ?? null;
|
|
2045
2272
|
const parts = [
|
|
2046
|
-
`#
|
|
2273
|
+
`# ${taskDisplayId({}, task, projectIdentifier)} ${task.name}`,
|
|
2047
2274
|
"",
|
|
2048
2275
|
`- **Type**: ${task.subIssueType?.name ?? task.issueType?.name ?? "Unknown"}`,
|
|
2049
2276
|
`- **Work Item Kind**: ${kind}`,
|
|
@@ -2055,14 +2282,14 @@ var OnesTaskContent = class {
|
|
|
2055
2282
|
parts.push(`- **UUID**: ${task.uuid}`);
|
|
2056
2283
|
if (task.parent?.uuid) {
|
|
2057
2284
|
parts.push("", "## Parent Task", `- UUID: ${task.parent.uuid}`);
|
|
2058
|
-
if (task.parent.number) parts.push(`- Number:
|
|
2285
|
+
if (task.parent.number) parts.push(`- Number: ${projectIdentifier ? `${projectIdentifier}-${task.parent.number}` : `#${task.parent.number}`}`);
|
|
2059
2286
|
}
|
|
2060
2287
|
const detailText = getTaskDetailText(task);
|
|
2061
2288
|
if (detailText) parts.push("", "---", "", kind === "defect" ? "## Defect Detail" : "## Task Detail", "", detailText);
|
|
2062
2289
|
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
2290
|
if (task.relatedTasks?.length) {
|
|
2064
2291
|
parts.push("", "## Related Tasks");
|
|
2065
|
-
for (const related of task.relatedTasks) parts.push(`-
|
|
2292
|
+
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
2293
|
}
|
|
2067
2294
|
const requirement = toRequirement(task, parts.join("\n"));
|
|
2068
2295
|
requirement.raw = {
|
|
@@ -2124,7 +2351,7 @@ const TASK_DETAIL_QUERY = `
|
|
|
2124
2351
|
priority { value }
|
|
2125
2352
|
assign { uuid name }
|
|
2126
2353
|
owner { uuid name }
|
|
2127
|
-
project { uuid name }
|
|
2354
|
+
project { uuid name identifier }
|
|
2128
2355
|
parent { uuid number issueType { uuid name } }
|
|
2129
2356
|
relatedTasks {
|
|
2130
2357
|
key uuid number name
|
|
@@ -2135,6 +2362,7 @@ const TASK_DETAIL_QUERY = `
|
|
|
2135
2362
|
subIssueType { uuid name detailType }
|
|
2136
2363
|
status { uuid name category }
|
|
2137
2364
|
assign { uuid name }
|
|
2365
|
+
project { uuid name identifier }
|
|
2138
2366
|
}
|
|
2139
2367
|
relatedWikiPages {
|
|
2140
2368
|
uuid title referenceType subReferenceType errorMessage
|
|
@@ -2271,7 +2499,9 @@ var OnesTaskPlanning = class {
|
|
|
2271
2499
|
if (!Number.isInteger(raw.number)) throw new TypeError("ONES: Standalone wiki pages cannot be decomposed into requirement tasks");
|
|
2272
2500
|
const parsedDisplayId = parseDisplayId(params.requirementId);
|
|
2273
2501
|
const requirementInfo = await this.options.fetchTaskInfo(workItem.id);
|
|
2274
|
-
|
|
2502
|
+
let projectIdentifier = parsedDisplayId?.identifier ?? firstString(requirementInfo, ["projectIdentifier", "project_identifier"]) ?? raw.project?.identifier ?? null;
|
|
2503
|
+
if (!projectIdentifier && raw.project?.uuid) projectIdentifier = await this.options.resolveProjectIdentifier(raw.project.uuid);
|
|
2504
|
+
projectIdentifier = projectIdentifier?.toUpperCase() ?? null;
|
|
2275
2505
|
const displayId = firstString(requirementInfo, ["displayId", "display_id"]) ?? (projectIdentifier ? `${projectIdentifier}-${raw.number}` : `#${raw.number}`);
|
|
2276
2506
|
const relatedTasks = (raw.relatedTasks ?? []).filter((task) => classifyOnesWorkItem(task.issueType, task.subIssueType) === "task");
|
|
2277
2507
|
const relatedInfos = await Promise.all(relatedTasks.map((task) => this.options.fetchTaskInfo(task.uuid)));
|
|
@@ -2600,6 +2830,12 @@ var OnesTestcaseReader = class {
|
|
|
2600
2830
|
const JSON0_URI = "http://sharejs.org/types/JSONv0";
|
|
2601
2831
|
const JSON1_URI = "http://sharejs.org/types/JSONv1";
|
|
2602
2832
|
const DEFAULT_TIMEOUT_MS$1 = 15e3;
|
|
2833
|
+
function getSetCookies(response) {
|
|
2834
|
+
const headers = response.headers;
|
|
2835
|
+
if (headers.getSetCookie) return headers.getSetCookie();
|
|
2836
|
+
const raw = headers.get("set-cookie");
|
|
2837
|
+
return raw ? [raw] : [];
|
|
2838
|
+
}
|
|
2603
2839
|
function isRecord(value) {
|
|
2604
2840
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
2605
2841
|
}
|
|
@@ -2612,28 +2848,44 @@ function asDoc(value) {
|
|
|
2612
2848
|
}
|
|
2613
2849
|
function createTopLevelJson1Operation(current, next) {
|
|
2614
2850
|
let operation = null;
|
|
2851
|
+
const appendComponent = (component) => {
|
|
2852
|
+
operation = operation === null ? component : ot_json1.type.compose(operation, component);
|
|
2853
|
+
};
|
|
2615
2854
|
const keys = [.../* @__PURE__ */ new Set([...Object.keys(current), ...Object.keys(next)])].sort();
|
|
2616
2855
|
for (const key of keys) {
|
|
2856
|
+
if (key === "blocks") continue;
|
|
2617
2857
|
const hasCurrent = Object.hasOwn(current, key);
|
|
2618
2858
|
const hasNext = Object.hasOwn(next, key);
|
|
2619
|
-
|
|
2620
|
-
if (
|
|
2621
|
-
else if (
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2859
|
+
if (!hasCurrent && hasNext) appendComponent((0, ot_json1.insertOp)([key], asDoc(next[key])));
|
|
2860
|
+
else if (hasCurrent && !hasNext) appendComponent((0, ot_json1.removeOp)([key]));
|
|
2861
|
+
else if (hasCurrent && hasNext && !(0, node_util.isDeepStrictEqual)(current[key], next[key])) appendComponent((0, ot_json1.replaceOp)([key], asDoc(current[key]), asDoc(next[key])));
|
|
2862
|
+
}
|
|
2863
|
+
const currentBlocks = Array.isArray(current.blocks) ? current.blocks : [];
|
|
2864
|
+
const nextBlocks = Array.isArray(next.blocks) ? next.blocks : [];
|
|
2865
|
+
const sharedBlockCount = Math.min(currentBlocks.length, nextBlocks.length);
|
|
2866
|
+
for (let index = 0; index < sharedBlockCount; index += 1) if (!(0, node_util.isDeepStrictEqual)(currentBlocks[index], nextBlocks[index])) {
|
|
2867
|
+
appendComponent((0, ot_json1.removeOp)(["blocks", index]));
|
|
2868
|
+
appendComponent((0, ot_json1.insertOp)(["blocks", index], asDoc(nextBlocks[index])));
|
|
2869
|
+
}
|
|
2870
|
+
for (let index = currentBlocks.length - 1; index >= nextBlocks.length; index -= 1) appendComponent((0, ot_json1.removeOp)(["blocks", index]));
|
|
2871
|
+
for (let index = currentBlocks.length; index < nextBlocks.length; index += 1) appendComponent((0, ot_json1.insertOp)(["blocks", index], asDoc(nextBlocks[index])));
|
|
2626
2872
|
if (operation !== null) ot_json1.type.checkValidOp(operation);
|
|
2627
2873
|
return operation;
|
|
2628
2874
|
}
|
|
2875
|
+
function createTopLevelJson1Operations(current, next) {
|
|
2876
|
+
const operation = createTopLevelJson1Operation(current, next);
|
|
2877
|
+
return operation === null ? [] : [operation];
|
|
2878
|
+
}
|
|
2629
2879
|
function wikiEditorUrls(baseUrl, teamId, documentId) {
|
|
2630
2880
|
const url = new URL(baseUrl);
|
|
2631
2881
|
if (url.protocol !== "https:" && url.protocol !== "http:") throw new Error("ONES Wiki collaboration requires an HTTP(S) source URL");
|
|
2632
2882
|
const path = `/wiki/api/wiki/editor/${encodeURIComponent(teamId)}/${encodeURIComponent(documentId)}`;
|
|
2633
|
-
const
|
|
2883
|
+
const editorBaseUrl = new URL(path, url);
|
|
2884
|
+
const socketUrl = new URL(editorBaseUrl);
|
|
2634
2885
|
socketUrl.protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
2635
2886
|
return {
|
|
2636
|
-
authUrl:
|
|
2887
|
+
authUrl: `${editorBaseUrl.toString()}/auth`,
|
|
2888
|
+
editorBaseUrl: editorBaseUrl.toString(),
|
|
2637
2889
|
socketUrl: socketUrl.toString()
|
|
2638
2890
|
};
|
|
2639
2891
|
}
|
|
@@ -2650,39 +2902,52 @@ function closeSocket(socket) {
|
|
|
2650
2902
|
}
|
|
2651
2903
|
async function replaceOnesWikiDocument(options, update, dependencies = {
|
|
2652
2904
|
fetch,
|
|
2653
|
-
openWebSocket: (url) => new ws.default(url)
|
|
2905
|
+
openWebSocket: (url, headers) => new ws.default(url, { headers })
|
|
2654
2906
|
}) {
|
|
2655
|
-
const { authUrl, socketUrl } = wikiEditorUrls(options.baseUrl, options.teamId, options.documentId);
|
|
2907
|
+
const { authUrl, editorBaseUrl, socketUrl } = wikiEditorUrls(options.baseUrl, options.teamId, options.documentId);
|
|
2656
2908
|
const authResponse = await dependencies.fetch(authUrl, {
|
|
2657
2909
|
method: "GET",
|
|
2658
2910
|
headers: {
|
|
2659
2911
|
"Authorization": `Bearer ${options.accessToken}`,
|
|
2660
2912
|
"x-live-editor-token": options.editorToken,
|
|
2661
|
-
"x-live-editor-base-url": Buffer.from(
|
|
2913
|
+
"x-live-editor-base-url": Buffer.from(editorBaseUrl).toString("base64url"),
|
|
2914
|
+
...options.cookieHeader ? { Cookie: options.cookieHeader } : {}
|
|
2662
2915
|
}
|
|
2663
2916
|
});
|
|
2664
2917
|
if (!authResponse.ok) throw new Error(`ONES Wiki collaboration auth failed with status ${authResponse.status}`);
|
|
2665
2918
|
const editorAuth = await authResponse.json();
|
|
2666
2919
|
if (typeof editorAuth.read !== "string" || !editorAuth.read) throw new Error("ONES Wiki collaboration auth response did not include a read token");
|
|
2920
|
+
const cookies = [options.cookieHeader, ...getSetCookies(authResponse).map((cookie) => cookie.split(";")[0])].filter((cookie) => Boolean(cookie)).join("; ");
|
|
2921
|
+
const socketHeaders = {
|
|
2922
|
+
"Accept-Language": "zh-CN,zh;q=0.9",
|
|
2923
|
+
"Cache-Control": "no-cache",
|
|
2924
|
+
"Pragma": "no-cache",
|
|
2925
|
+
"User-Agent": "Mozilla/5.0 AppleWebKit/537.36 Chrome/128.0.0.0 Safari/537.36",
|
|
2926
|
+
"Origin": new URL(options.baseUrl).origin,
|
|
2927
|
+
...cookies ? { Cookie: cookies } : {}
|
|
2928
|
+
};
|
|
2667
2929
|
return new Promise((resolve, reject) => {
|
|
2668
|
-
const socket = dependencies.openWebSocket(socketUrl);
|
|
2669
|
-
|
|
2670
|
-
let clientId = "";
|
|
2930
|
+
const socket = dependencies.openWebSocket(socketUrl, socketHeaders);
|
|
2931
|
+
let sequence = 1;
|
|
2671
2932
|
let state = "init";
|
|
2672
2933
|
let settled = false;
|
|
2673
2934
|
let snapshotVersion = 0;
|
|
2935
|
+
let currentVersion = 0;
|
|
2936
|
+
let pendingOperations = [];
|
|
2937
|
+
const presenceChannel = `${options.teamId}:${options.documentId}`;
|
|
2938
|
+
const presenceId = (0, node_crypto.randomBytes)(7).toString("base64url").slice(0, 9);
|
|
2674
2939
|
let timeout;
|
|
2675
2940
|
const finish = (result) => {
|
|
2676
2941
|
if (settled) return;
|
|
2677
2942
|
settled = true;
|
|
2678
|
-
|
|
2943
|
+
clearTimeout(timeout);
|
|
2679
2944
|
resolve(result);
|
|
2680
2945
|
closeSocket(socket);
|
|
2681
2946
|
};
|
|
2682
2947
|
const fail = (error) => {
|
|
2683
2948
|
if (settled) return;
|
|
2684
2949
|
settled = true;
|
|
2685
|
-
|
|
2950
|
+
clearTimeout(timeout);
|
|
2686
2951
|
reject(error);
|
|
2687
2952
|
closeSocket(socket);
|
|
2688
2953
|
};
|
|
@@ -2691,9 +2956,38 @@ async function replaceOnesWikiDocument(options, update, dependencies = {
|
|
|
2691
2956
|
if (error) fail(/* @__PURE__ */ new Error(`ONES Wiki collaboration send failed: ${error.message}`));
|
|
2692
2957
|
});
|
|
2693
2958
|
};
|
|
2959
|
+
const sendHandshake = () => {
|
|
2960
|
+
send({
|
|
2961
|
+
a: "hs",
|
|
2962
|
+
id: null,
|
|
2963
|
+
auth: {
|
|
2964
|
+
appId: options.teamId,
|
|
2965
|
+
docId: options.documentId,
|
|
2966
|
+
userId: options.userId,
|
|
2967
|
+
permission: "w",
|
|
2968
|
+
token: options.editorToken,
|
|
2969
|
+
displayName: options.displayName ?? "",
|
|
2970
|
+
avatarUrl: options.avatarUrl ?? ""
|
|
2971
|
+
}
|
|
2972
|
+
});
|
|
2973
|
+
};
|
|
2974
|
+
const sendNextOperation = () => {
|
|
2975
|
+
const operation = pendingOperations[0];
|
|
2976
|
+
if (operation === void 0) return;
|
|
2977
|
+
send({
|
|
2978
|
+
a: "op",
|
|
2979
|
+
c: options.teamId,
|
|
2980
|
+
d: options.documentId,
|
|
2981
|
+
v: currentVersion,
|
|
2982
|
+
seq: sequence,
|
|
2983
|
+
x: {},
|
|
2984
|
+
op: operation
|
|
2985
|
+
});
|
|
2986
|
+
};
|
|
2694
2987
|
timeout = setTimeout(() => {
|
|
2695
2988
|
fail(/* @__PURE__ */ new Error(`ONES Wiki collaboration timed out while waiting for ${state}`));
|
|
2696
2989
|
}, options.timeoutMs ?? DEFAULT_TIMEOUT_MS$1);
|
|
2990
|
+
socket.on("open", sendHandshake);
|
|
2697
2991
|
socket.on("error", () => fail(/* @__PURE__ */ new Error("ONES Wiki collaboration WebSocket failed")));
|
|
2698
2992
|
socket.on("close", (code) => {
|
|
2699
2993
|
if (!settled) fail(/* @__PURE__ */ new Error(`ONES Wiki collaboration WebSocket closed before ${state} (code ${code})`));
|
|
@@ -2720,58 +3014,64 @@ async function replaceOnesWikiDocument(options, update, dependencies = {
|
|
|
2720
3014
|
fail(/* @__PURE__ */ new Error("ONES Wiki collaboration returned an unsupported init frame"));
|
|
2721
3015
|
return;
|
|
2722
3016
|
}
|
|
2723
|
-
clientId = message.id;
|
|
2724
3017
|
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
|
-
});
|
|
3018
|
+
sendHandshake();
|
|
2742
3019
|
return;
|
|
2743
3020
|
}
|
|
2744
3021
|
if (state === "handshake") {
|
|
2745
|
-
if (message.a !== "hs" || message.id !==
|
|
3022
|
+
if (message.a !== "hs" || typeof message.id !== "string" || message.protocol !== 1 || message.protocolMinor !== 1 || message.type !== JSON0_URI) {
|
|
2746
3023
|
fail(/* @__PURE__ */ new Error("ONES Wiki collaboration returned an unsupported handshake frame"));
|
|
2747
3024
|
return;
|
|
2748
3025
|
}
|
|
2749
|
-
state = "
|
|
3026
|
+
state = "fetch";
|
|
2750
3027
|
send({
|
|
2751
|
-
a: "
|
|
3028
|
+
a: "f",
|
|
2752
3029
|
c: options.teamId,
|
|
2753
3030
|
d: options.documentId
|
|
2754
3031
|
});
|
|
2755
3032
|
return;
|
|
2756
3033
|
}
|
|
2757
|
-
if (state === "
|
|
3034
|
+
if (state === "fetch") {
|
|
2758
3035
|
const snapshot = message.data;
|
|
2759
|
-
if (message.a !== "
|
|
2760
|
-
fail(/* @__PURE__ */ new Error("ONES Wiki collaboration returned an unsupported snapshot frame"));
|
|
2761
|
-
return;
|
|
2762
|
-
}
|
|
3036
|
+
if (message.a !== "f" || message.c !== options.teamId || message.d !== options.documentId || typeof snapshot?.v !== "number" || snapshot.type !== JSON1_URI) return;
|
|
2763
3037
|
const current = asWikiSnapshot(snapshot.data);
|
|
2764
|
-
let next;
|
|
2765
|
-
let operation;
|
|
2766
3038
|
try {
|
|
2767
|
-
|
|
2768
|
-
operation = createTopLevelJson1Operation(current, next);
|
|
3039
|
+
pendingOperations = createTopLevelJson1Operations(current, asJsonDocument(update(structuredClone(current)), "update"));
|
|
2769
3040
|
} catch (error) {
|
|
2770
3041
|
fail(error instanceof Error ? error : /* @__PURE__ */ new Error("ONES Wiki collaboration update failed"));
|
|
2771
3042
|
return;
|
|
2772
3043
|
}
|
|
2773
3044
|
snapshotVersion = snapshot.v;
|
|
2774
|
-
|
|
3045
|
+
currentVersion = snapshotVersion;
|
|
3046
|
+
state = "presence";
|
|
3047
|
+
send({
|
|
3048
|
+
a: "p",
|
|
3049
|
+
ch: presenceChannel,
|
|
3050
|
+
id: presenceId,
|
|
3051
|
+
p: null,
|
|
3052
|
+
pv: 2
|
|
3053
|
+
});
|
|
3054
|
+
send({
|
|
3055
|
+
a: "ps",
|
|
3056
|
+
ch: presenceChannel,
|
|
3057
|
+
seq: 1
|
|
3058
|
+
});
|
|
3059
|
+
return;
|
|
3060
|
+
}
|
|
3061
|
+
if (state === "presence") {
|
|
3062
|
+
if (message.a !== "ps" || message.ch !== presenceChannel || message.seq !== 1) return;
|
|
3063
|
+
state = "subscribe";
|
|
3064
|
+
send({
|
|
3065
|
+
a: "s",
|
|
3066
|
+
c: options.teamId,
|
|
3067
|
+
d: options.documentId,
|
|
3068
|
+
v: snapshotVersion
|
|
3069
|
+
});
|
|
3070
|
+
return;
|
|
3071
|
+
}
|
|
3072
|
+
if (state === "subscribe") {
|
|
3073
|
+
if (message.a !== "s" || message.c !== options.teamId || message.d !== options.documentId) return;
|
|
3074
|
+
if (!pendingOperations.length) {
|
|
2775
3075
|
finish({
|
|
2776
3076
|
snapshotVersion,
|
|
2777
3077
|
version: snapshotVersion,
|
|
@@ -2780,21 +3080,23 @@ async function replaceOnesWikiDocument(options, update, dependencies = {
|
|
|
2780
3080
|
return;
|
|
2781
3081
|
}
|
|
2782
3082
|
state = "ack";
|
|
2783
|
-
|
|
2784
|
-
a: "op",
|
|
2785
|
-
c: options.teamId,
|
|
2786
|
-
d: options.documentId,
|
|
2787
|
-
v: snapshotVersion,
|
|
2788
|
-
seq: sequence,
|
|
2789
|
-
op: operation
|
|
2790
|
-
});
|
|
3083
|
+
sendNextOperation();
|
|
2791
3084
|
return;
|
|
2792
3085
|
}
|
|
2793
|
-
if (state === "ack" && message.a === "op" && message.c === options.teamId && message.d === options.documentId && message.seq === sequence)
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
3086
|
+
if (state === "ack" && message.a === "op" && message.c === options.teamId && message.d === options.documentId && message.seq === sequence) {
|
|
3087
|
+
currentVersion = typeof message.v === "number" ? message.v : currentVersion + 1;
|
|
3088
|
+
pendingOperations.shift();
|
|
3089
|
+
if (!pendingOperations.length) {
|
|
3090
|
+
finish({
|
|
3091
|
+
snapshotVersion,
|
|
3092
|
+
version: currentVersion,
|
|
3093
|
+
changed: true
|
|
3094
|
+
});
|
|
3095
|
+
return;
|
|
3096
|
+
}
|
|
3097
|
+
sequence += 1;
|
|
3098
|
+
sendNextOperation();
|
|
3099
|
+
}
|
|
2798
3100
|
});
|
|
2799
3101
|
});
|
|
2800
3102
|
}
|
|
@@ -2832,6 +3134,10 @@ var OnesWikiProductWriter = class {
|
|
|
2832
3134
|
const encodedTeamId = encodeIdentifier(teamId, "team UUID");
|
|
2833
3135
|
const headers = new Headers(init.headers);
|
|
2834
3136
|
headers.set("Authorization", `Bearer ${session.accessToken}`);
|
|
3137
|
+
headers.set("Ones-Auth-Token", session.legacyAuthToken);
|
|
3138
|
+
headers.set("Ones-User-Id", session.legacyUserId);
|
|
3139
|
+
headers.set("Referer", this.options.apiBase);
|
|
3140
|
+
if (session.cookieHeader) headers.set("Cookie", session.cookieHeader);
|
|
2835
3141
|
if (typeof init.body === "string" && !headers.has("Content-Type")) headers.set("Content-Type", "application/json;charset=UTF-8");
|
|
2836
3142
|
const response = await fetch(`${this.options.apiBase}/wiki/api/wiki/team/${encodedTeamId}${apiPath}`, {
|
|
2837
3143
|
...init,
|
|
@@ -2854,9 +3160,9 @@ var OnesWikiProductWriter = class {
|
|
|
2854
3160
|
}
|
|
2855
3161
|
throw new Error("ONES Wiki editor token endpoint was not found");
|
|
2856
3162
|
}
|
|
2857
|
-
async replaceDocument(teamId, resourceId, documentId, update, preferDraft = false) {
|
|
3163
|
+
async replaceDocument(teamId, resourceId, documentId, update, preferDraft = false, editorTokenOverride) {
|
|
2858
3164
|
const session = await this.options.getSession();
|
|
2859
|
-
const editorToken = await this.fetchEditorToken(teamId, resourceId, preferDraft);
|
|
3165
|
+
const editorToken = editorTokenOverride ?? await this.fetchEditorToken(teamId, resourceId, preferDraft);
|
|
2860
3166
|
return { version: (await replaceOnesWikiDocument({
|
|
2861
3167
|
baseUrl: this.options.apiBase,
|
|
2862
3168
|
teamId,
|
|
@@ -2864,6 +3170,7 @@ var OnesWikiProductWriter = class {
|
|
|
2864
3170
|
accessToken: session.accessToken,
|
|
2865
3171
|
editorToken,
|
|
2866
3172
|
userId: session.userUuid,
|
|
3173
|
+
cookieHeader: session.cookieHeader,
|
|
2867
3174
|
displayName: session.userName
|
|
2868
3175
|
}, update)).version };
|
|
2869
3176
|
}
|
|
@@ -2917,20 +3224,120 @@ var OnesWikiProductWriter = class {
|
|
|
2917
3224
|
};
|
|
2918
3225
|
}
|
|
2919
3226
|
async update(params) {
|
|
2920
|
-
const
|
|
2921
|
-
const
|
|
2922
|
-
|
|
2923
|
-
const
|
|
3227
|
+
const encodedPageId = encodeIdentifier(params.pageId, "Wiki page UUID");
|
|
3228
|
+
const encodedSpaceId = params.spaceId ? encodeIdentifier(params.spaceId, "Wiki space UUID") : null;
|
|
3229
|
+
let detail = await this.request(params.teamId, `/page/${encodedPageId}?action=edit`);
|
|
3230
|
+
const title = firstNonEmptyString(detail.title, detail.name, detail.page_title) ?? `Wiki ${params.pageId}`;
|
|
3231
|
+
if (detail.ref_type === 6 && detail.ref_uuid) {
|
|
3232
|
+
const editorToken = await this.fetchEditorToken(params.teamId, params.pageId);
|
|
3233
|
+
const write = await this.replaceDocument(params.teamId, params.pageId, detail.ref_uuid, (snapshot) => {
|
|
3234
|
+
return parseWikiDocument(applyWikiUpdateOperation(JSON.stringify(snapshot), params.operation));
|
|
3235
|
+
}, false, editorToken);
|
|
3236
|
+
const published = await this.request(params.teamId, `/online_page/${encodedPageId}/publish`, {
|
|
3237
|
+
method: "POST",
|
|
3238
|
+
body: JSON.stringify({ title })
|
|
3239
|
+
});
|
|
3240
|
+
if (params.spaceId) this.options.invalidateTree(params.teamId, params.spaceId);
|
|
3241
|
+
return {
|
|
3242
|
+
pageId: params.pageId,
|
|
3243
|
+
title,
|
|
3244
|
+
version: String(published.version ?? published.updated_time ?? write.version),
|
|
3245
|
+
url: params.spaceId ? `${this.options.apiBase}/wiki/#/team/${encodeURIComponent(params.teamId)}/space/${encodeURIComponent(params.spaceId)}/page/${encodeURIComponent(params.pageId)}` : null
|
|
3246
|
+
};
|
|
3247
|
+
}
|
|
3248
|
+
let draftId = firstNonEmptyString(detail.online_draft_uuid, detail.onlineDraftUuid, detail.online_draft?.uuid, detail.draft_uuid, detail.draftUuid, detail.draft?.uuid);
|
|
3249
|
+
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);
|
|
3250
|
+
if (!draftId) {
|
|
3251
|
+
if (!encodedSpaceId) throw new Error("ONES Wiki page space is required to create an edit draft");
|
|
3252
|
+
try {
|
|
3253
|
+
const createdDraft = await this.request(params.teamId, `/space/${encodedSpaceId}/drafts/add`, {
|
|
3254
|
+
method: "POST",
|
|
3255
|
+
body: JSON.stringify({
|
|
3256
|
+
page_uuid: params.pageId,
|
|
3257
|
+
status: 2,
|
|
3258
|
+
title
|
|
3259
|
+
})
|
|
3260
|
+
});
|
|
3261
|
+
draftId = firstNonEmptyString(createdDraft.draft_uuid, createdDraft.draftUuid, createdDraft.uuid, createdDraft.id);
|
|
3262
|
+
documentId = firstNonEmptyString(createdDraft.ref_uuid, createdDraft.refUuid, documentId);
|
|
3263
|
+
} catch (error) {
|
|
3264
|
+
const refreshedDetail = await this.request(params.teamId, `/space/${encodedSpaceId}/page/${encodedPageId}`);
|
|
3265
|
+
draftId = firstNonEmptyString(refreshedDetail.draft_uuid, refreshedDetail.draftUuid);
|
|
3266
|
+
if (!draftId) throw error;
|
|
3267
|
+
detail = refreshedDetail;
|
|
3268
|
+
documentId = firstNonEmptyString(refreshedDetail.draft_ref_uuid, refreshedDetail.draftRefUuid, refreshedDetail.ref_uuid, documentId);
|
|
3269
|
+
}
|
|
3270
|
+
}
|
|
3271
|
+
if (!draftId || !encodedSpaceId) throw new Error("ONES Wiki edit draft did not include the required resource and space IDs");
|
|
3272
|
+
const encodedDraftId = encodeIdentifier(draftId, "Wiki draft UUID");
|
|
3273
|
+
const draftDetail = await this.request(params.teamId, `/space/${encodedSpaceId}/draft/${encodedDraftId}`);
|
|
3274
|
+
documentId = firstNonEmptyString(draftDetail.ref_uuid, draftDetail.refUuid, documentId);
|
|
3275
|
+
if (!documentId) throw new Error("ONES Wiki edit draft did not include a collaborative document ID");
|
|
3276
|
+
if (typeof draftDetail.content === "string") {
|
|
3277
|
+
let content;
|
|
3278
|
+
if (params.operation.type === "replace_document") content = markdownToWikiHtml(params.operation.markdown);
|
|
3279
|
+
else if (params.operation.type === "append_blocks") content = `${draftDetail.content}\n${markdownToWikiHtml(params.operation.markdown)}`;
|
|
3280
|
+
else if (params.operation.type === "replace_text") {
|
|
3281
|
+
const occurrences = draftDetail.content.split(params.operation.find).length - 1;
|
|
3282
|
+
if (occurrences !== 1) throw new Error(`ONES Wiki draft replace_text requires exactly one match; found ${occurrences}`);
|
|
3283
|
+
content = draftDetail.content.replace(params.operation.find, params.operation.replace);
|
|
3284
|
+
} else throw new Error("ONES Wiki append_table_row is not supported for legacy page drafts");
|
|
3285
|
+
const published = await this.request(params.teamId, `/space/${encodedSpaceId}/draft/${encodedDraftId}/update`, {
|
|
3286
|
+
method: "POST",
|
|
3287
|
+
body: JSON.stringify({
|
|
3288
|
+
...draftDetail,
|
|
3289
|
+
content,
|
|
3290
|
+
title,
|
|
3291
|
+
page_uuid: params.pageId,
|
|
3292
|
+
space_uuid: params.spaceId,
|
|
3293
|
+
from_version: detail.version ?? draftDetail.from_version,
|
|
3294
|
+
is_published: true,
|
|
3295
|
+
is_forced: true
|
|
3296
|
+
})
|
|
3297
|
+
});
|
|
3298
|
+
this.options.invalidateTree(params.teamId, params.spaceId);
|
|
3299
|
+
const publishedVersion = published.version ?? published.updated_time ?? draftDetail.version ?? draftDetail.updated_time;
|
|
3300
|
+
return {
|
|
3301
|
+
pageId: params.pageId,
|
|
3302
|
+
title,
|
|
3303
|
+
version: publishedVersion === void 0 ? null : String(publishedVersion),
|
|
3304
|
+
url: `${this.options.apiBase}/wiki/#/team/${encodeURIComponent(params.teamId)}/space/${encodeURIComponent(params.spaceId)}/page/${encodeURIComponent(params.pageId)}`
|
|
3305
|
+
};
|
|
3306
|
+
}
|
|
3307
|
+
const write = await this.replaceDocument(params.teamId, draftId, documentId, (snapshot) => {
|
|
2924
3308
|
return parseWikiDocument(applyWikiUpdateOperation(JSON.stringify(snapshot), params.operation));
|
|
3309
|
+
}, true);
|
|
3310
|
+
const refreshedDraft = await this.request(params.teamId, `/space/${encodedSpaceId}/draft/${encodedDraftId}`);
|
|
3311
|
+
await this.request(params.teamId, `/space/${encodedSpaceId}/draft/${encodedDraftId}/update`, {
|
|
3312
|
+
method: "POST",
|
|
3313
|
+
body: JSON.stringify({
|
|
3314
|
+
...refreshedDraft,
|
|
3315
|
+
title,
|
|
3316
|
+
page_uuid: params.pageId,
|
|
3317
|
+
space_uuid: params.spaceId,
|
|
3318
|
+
from_version: detail.version ?? refreshedDraft.from_version,
|
|
3319
|
+
is_published: true,
|
|
3320
|
+
is_forced: true
|
|
3321
|
+
})
|
|
2925
3322
|
});
|
|
2926
3323
|
if (params.spaceId) this.options.invalidateTree(params.teamId, params.spaceId);
|
|
2927
3324
|
return {
|
|
2928
3325
|
pageId: params.pageId,
|
|
2929
|
-
title
|
|
3326
|
+
title,
|
|
2930
3327
|
version: String(write.version),
|
|
2931
3328
|
url: params.spaceId ? `${this.options.apiBase}/wiki/#/team/${encodeURIComponent(params.teamId)}/space/${encodeURIComponent(params.spaceId)}/page/${encodeURIComponent(params.pageId)}` : null
|
|
2932
3329
|
};
|
|
2933
3330
|
}
|
|
3331
|
+
async delete(params) {
|
|
3332
|
+
const encodedSpaceId = encodeIdentifier(params.spaceId, "Wiki space UUID");
|
|
3333
|
+
const encodedPageId = encodeIdentifier(params.pageId, "Wiki page UUID");
|
|
3334
|
+
await this.request(params.teamId, `/space/${encodedSpaceId}/page/${encodedPageId}/delete`, { method: "POST" });
|
|
3335
|
+
this.options.invalidateTree(params.teamId, params.spaceId);
|
|
3336
|
+
return {
|
|
3337
|
+
pageId: params.pageId,
|
|
3338
|
+
deleted: true
|
|
3339
|
+
};
|
|
3340
|
+
}
|
|
2934
3341
|
};
|
|
2935
3342
|
//#endregion
|
|
2936
3343
|
//#region ../../src/adapters/ones/task-query.ts
|
|
@@ -3017,7 +3424,6 @@ var OnesTaskAdapter = class extends BaseAdapter {
|
|
|
3017
3424
|
this.wikiProductWriter = new OnesWikiProductWriter({
|
|
3018
3425
|
apiBase: config.apiBase,
|
|
3019
3426
|
getSession: () => this.login(),
|
|
3020
|
-
fetchPageDetail: (pageId, teamId) => this.wikiReader.fetchPageDetail(pageId, teamId, true),
|
|
3021
3427
|
invalidateTree: (teamId, spaceId) => this.wikiReader.invalidateTree(teamId, spaceId)
|
|
3022
3428
|
});
|
|
3023
3429
|
this.content = new OnesTaskContent({
|
|
@@ -3033,7 +3439,10 @@ var OnesTaskAdapter = class extends BaseAdapter {
|
|
|
3033
3439
|
this.planning = new OnesTaskPlanning({
|
|
3034
3440
|
api: this.api,
|
|
3035
3441
|
getRequirement: (id) => this.getRequirement({ id }),
|
|
3036
|
-
fetchTaskInfo: (taskUuid) => this.fetchTaskInfo(taskUuid)
|
|
3442
|
+
fetchTaskInfo: (taskUuid) => this.fetchTaskInfo(taskUuid),
|
|
3443
|
+
resolveProjectIdentifier: async (projectUuid) => {
|
|
3444
|
+
return (await this.fetchProjects()).find((candidate) => candidate.uuid === projectUuid)?.identifier?.toUpperCase() ?? null;
|
|
3445
|
+
}
|
|
3037
3446
|
});
|
|
3038
3447
|
this.issueReader = new OnesIssueReader({
|
|
3039
3448
|
api: this.api,
|
|
@@ -3251,6 +3660,9 @@ var OnesTaskAdapter = class extends BaseAdapter {
|
|
|
3251
3660
|
async updateWikiPage(params) {
|
|
3252
3661
|
return this.wikiProductWriter.update(params);
|
|
3253
3662
|
}
|
|
3663
|
+
async deleteWikiPage(params) {
|
|
3664
|
+
return this.wikiProductWriter.delete(params);
|
|
3665
|
+
}
|
|
3254
3666
|
/**
|
|
3255
3667
|
* Fetch a work item by UUID, number, display id, or wiki URL.
|
|
3256
3668
|
* Routes by issueType.detailType: requirements (1 and 5) load wiki docs;
|
|
@@ -3645,7 +4057,7 @@ async function handleGetGrillingBrief(input, adapters, defaultSource) {
|
|
|
3645
4057
|
}
|
|
3646
4058
|
//#endregion
|
|
3647
4059
|
//#region ../../src/utils/safe-image.ts
|
|
3648
|
-
const DEFAULT_MAX_BYTES =
|
|
4060
|
+
const DEFAULT_MAX_BYTES = 8388608;
|
|
3649
4061
|
const DEFAULT_MAX_REDIRECTS = 3;
|
|
3650
4062
|
const DEFAULT_TIMEOUT_MS = 1e4;
|
|
3651
4063
|
const MAX_IMAGES = 8;
|
|
@@ -3993,7 +4405,7 @@ function formatWorkItem(req) {
|
|
|
3993
4405
|
//#endregion
|
|
3994
4406
|
//#region ../../src/tools/list-pending-work-items.ts
|
|
3995
4407
|
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$
|
|
4408
|
+
function resolveAdapter$4(source, adapters, defaultSource) {
|
|
3997
4409
|
const sourceType = source ?? defaultSource;
|
|
3998
4410
|
if (!sourceType) throw new Error("No source specified and no default source configured");
|
|
3999
4411
|
const adapter = adapters.get(sourceType);
|
|
@@ -4037,7 +4449,7 @@ function formatResult(result) {
|
|
|
4037
4449
|
return lines.join("\n");
|
|
4038
4450
|
}
|
|
4039
4451
|
async function handleListPendingWorkItems(input, adapters, defaultSource) {
|
|
4040
|
-
const result = await resolveAdapter$
|
|
4452
|
+
const result = await resolveAdapter$4(input.source, adapters, defaultSource).listPendingWorkItems();
|
|
4041
4453
|
const safeResult = {
|
|
4042
4454
|
...result,
|
|
4043
4455
|
items: result.items.map(sanitizeItem)
|
|
@@ -4075,7 +4487,7 @@ async function handleListSources(adapters, config) {
|
|
|
4075
4487
|
}
|
|
4076
4488
|
//#endregion
|
|
4077
4489
|
//#region ../../src/tools/requirement-decomposition.ts
|
|
4078
|
-
const APPROVAL_TTL_MS$1 =
|
|
4490
|
+
const APPROVAL_TTL_MS$1 = 18e5;
|
|
4079
4491
|
const MAX_CREATE_OPERATIONS = 10;
|
|
4080
4492
|
function isValidDate(value) {
|
|
4081
4493
|
if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) return false;
|
|
@@ -4151,7 +4563,7 @@ var RequirementDecompositionApprovalStore = class {
|
|
|
4151
4563
|
return record;
|
|
4152
4564
|
}
|
|
4153
4565
|
};
|
|
4154
|
-
function resolveAdapter$
|
|
4566
|
+
function resolveAdapter$3(source, adapters, defaultSource) {
|
|
4155
4567
|
const sourceType = source ?? defaultSource;
|
|
4156
4568
|
if (!sourceType) throw new Error("No source specified and no default source configured");
|
|
4157
4569
|
const adapter = adapters.get(sourceType);
|
|
@@ -4254,7 +4666,7 @@ function buildOperations(displayId, tasks) {
|
|
|
4254
4666
|
});
|
|
4255
4667
|
}
|
|
4256
4668
|
async function handleInspectRequirementDecomposition(input, adapters, defaultSource) {
|
|
4257
|
-
const { adapter } = resolveAdapter$
|
|
4669
|
+
const { adapter } = resolveAdapter$3(input.source, adapters, defaultSource);
|
|
4258
4670
|
const context = sanitizedContext(await adapter.getRequirementDecompositionContext({ requirementId: input.requirementId }));
|
|
4259
4671
|
return {
|
|
4260
4672
|
content: [{
|
|
@@ -4265,7 +4677,7 @@ async function handleInspectRequirementDecomposition(input, adapters, defaultSou
|
|
|
4265
4677
|
};
|
|
4266
4678
|
}
|
|
4267
4679
|
async function handlePrepareRequirementDecomposition(input, adapters, approvals, defaultSource) {
|
|
4268
|
-
const { sourceType, adapter } = resolveAdapter$
|
|
4680
|
+
const { sourceType, adapter } = resolveAdapter$3(input.source, adapters, defaultSource);
|
|
4269
4681
|
const context = await adapter.getRequirementDecompositionContext({ requirementId: input.requirementId });
|
|
4270
4682
|
if (context.requirement.workItemKind !== "requirement") throw new Error("Only requirements can be decomposed");
|
|
4271
4683
|
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 +4727,7 @@ async function handleApplyRequirementDecomposition(input, adapters, approvals, o
|
|
|
4315
4727
|
if (!record) throw new Error("Approval token is invalid, expired, or already used. Prepare the decomposition again.");
|
|
4316
4728
|
if ((input.source ?? options.defaultSource) !== record.source) throw new Error("Approval token source does not match the requested source");
|
|
4317
4729
|
if (input.planHash !== record.planHash) throw new Error("Plan hash does not match the approved decomposition");
|
|
4318
|
-
const { adapter } = resolveAdapter$
|
|
4730
|
+
const { adapter } = resolveAdapter$3(record.source, adapters, options.defaultSource);
|
|
4319
4731
|
const current = await adapter.getRequirementDecompositionContext({ requirementId: record.requirementId });
|
|
4320
4732
|
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
4733
|
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 +4833,82 @@ function formatUpdateTaskPlanDatesResult(result) {
|
|
|
4421
4833
|
return lines.join("\n");
|
|
4422
4834
|
}
|
|
4423
4835
|
//#endregion
|
|
4836
|
+
//#region ../../src/tools/wiki-delete.ts
|
|
4837
|
+
const SourceSchema$1 = zod_v4.z.string().trim().min(1).optional();
|
|
4838
|
+
const DeleteEmptyWikiDuplicatesSchema = zod_v4.z.object({
|
|
4839
|
+
keepPageId: zod_v4.z.string().trim().min(1),
|
|
4840
|
+
duplicatePageIds: zod_v4.z.array(zod_v4.z.string().trim().min(1)).min(1).max(20),
|
|
4841
|
+
expectedTitle: zod_v4.z.string().trim().min(1),
|
|
4842
|
+
confirmed: zod_v4.z.literal(true).describe("Set true only after the user confirms the exact keep and delete page IDs immediately before submission."),
|
|
4843
|
+
source: SourceSchema$1
|
|
4844
|
+
});
|
|
4845
|
+
const DeleteEmptyWikiDuplicatesOutputSchema = zod_v4.z.object({
|
|
4846
|
+
keptPageId: zod_v4.z.string(),
|
|
4847
|
+
deletedPageIds: zod_v4.z.array(zod_v4.z.string())
|
|
4848
|
+
});
|
|
4849
|
+
function resolveAdapter$2(source, adapters, defaultSource) {
|
|
4850
|
+
const sourceType = source ?? defaultSource;
|
|
4851
|
+
if (!sourceType) throw new Error("No source specified and no default source configured");
|
|
4852
|
+
const adapter = adapters.get(sourceType);
|
|
4853
|
+
if (!adapter) throw new Error(`Source "${sourceType}" is not configured`);
|
|
4854
|
+
return {
|
|
4855
|
+
sourceType,
|
|
4856
|
+
adapter
|
|
4857
|
+
};
|
|
4858
|
+
}
|
|
4859
|
+
function assertCleanupTarget(keep, duplicates, expectedTitle) {
|
|
4860
|
+
if (keep.title !== expectedTitle) throw new Error("The retained Wiki page title no longer matches the confirmed title");
|
|
4861
|
+
if (keep.content.trim() === `# ${keep.title}`) throw new Error("The retained Wiki page does not contain a body");
|
|
4862
|
+
for (const duplicate of duplicates) {
|
|
4863
|
+
if (duplicate.title !== expectedTitle) throw new Error(`Wiki duplicate ${duplicate.pageId} title no longer matches the confirmed title`);
|
|
4864
|
+
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`);
|
|
4865
|
+
if (duplicate.content.trim() !== `# ${duplicate.title}`) throw new Error(`Wiki duplicate ${duplicate.pageId} is not empty and will not be deleted`);
|
|
4866
|
+
}
|
|
4867
|
+
}
|
|
4868
|
+
async function handleDeleteEmptyWikiDuplicates(input, adapters, options) {
|
|
4869
|
+
if (!options.writesEnabled) throw new Error("Wiki writes are disabled. Enable both ONES_WIKI_ENABLE_WRITES=true and source option wikiWrites=true.");
|
|
4870
|
+
const uniqueDuplicateIds = [...new Set(input.duplicatePageIds)];
|
|
4871
|
+
if (uniqueDuplicateIds.length !== input.duplicatePageIds.length) throw new Error("duplicatePageIds contains repeated page IDs");
|
|
4872
|
+
if (uniqueDuplicateIds.includes(input.keepPageId)) throw new Error("The retained Wiki page cannot also be deleted");
|
|
4873
|
+
const { adapter } = resolveAdapter$2(input.source, adapters, options.defaultSource);
|
|
4874
|
+
const [keep, ...duplicates] = await Promise.all([adapter.getWikiPage({ pageId: input.keepPageId }), ...uniqueDuplicateIds.map((pageId) => adapter.getWikiPage({ pageId }))]);
|
|
4875
|
+
assertCleanupTarget(keep, duplicates, input.expectedTitle);
|
|
4876
|
+
const spaceId = keep.spaceId;
|
|
4877
|
+
if (!spaceId) throw new Error("The retained Wiki page space could not be verified");
|
|
4878
|
+
const operationHash = node_crypto.default.createHash("sha256").update(JSON.stringify({
|
|
4879
|
+
keepPageId: input.keepPageId,
|
|
4880
|
+
duplicatePageIds: uniqueDuplicateIds,
|
|
4881
|
+
expectedTitle: input.expectedTitle,
|
|
4882
|
+
baselines: [keep, ...duplicates].map((page) => ({
|
|
4883
|
+
pageId: page.pageId,
|
|
4884
|
+
version: page.version,
|
|
4885
|
+
contentHash: page.contentHash
|
|
4886
|
+
}))
|
|
4887
|
+
})).digest("hex");
|
|
4888
|
+
for (const duplicate of duplicates) await adapter.deleteWikiPage({
|
|
4889
|
+
teamId: duplicate.teamId,
|
|
4890
|
+
spaceId,
|
|
4891
|
+
pageId: duplicate.pageId
|
|
4892
|
+
});
|
|
4893
|
+
const result = {
|
|
4894
|
+
keptPageId: keep.pageId,
|
|
4895
|
+
deletedPageIds: duplicates.map((page) => page.pageId)
|
|
4896
|
+
};
|
|
4897
|
+
return {
|
|
4898
|
+
content: [{
|
|
4899
|
+
type: "text",
|
|
4900
|
+
text: `Kept Wiki page ${result.keptPageId} and deleted empty duplicates: ${result.deletedPageIds.join(", ")}.\noperationHash: ${operationHash}`
|
|
4901
|
+
}],
|
|
4902
|
+
structuredContent: result
|
|
4903
|
+
};
|
|
4904
|
+
}
|
|
4905
|
+
//#endregion
|
|
4424
4906
|
//#region ../../src/tools/wiki-read.ts
|
|
4425
4907
|
const WikiSourceSchema = zod_v4.z.string().trim().min(1).optional();
|
|
4426
4908
|
const GetOnesWikiPageSchema = zod_v4.z.object({
|
|
4427
4909
|
pageId: zod_v4.z.string().trim().min(1).optional(),
|
|
4428
4910
|
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."),
|
|
4911
|
+
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
4912
|
teamId: zod_v4.z.string().trim().min(1).optional(),
|
|
4431
4913
|
spaceId: zod_v4.z.string().trim().min(1).optional(),
|
|
4432
4914
|
revealSensitiveSecrets: zod_v4.z.boolean().default(false).describe("Default false. Set true only when the user explicitly asks to reveal secrets."),
|
|
@@ -4528,6 +5010,19 @@ async function handleGetOnesWikiPage(input, adapters, defaultSource) {
|
|
|
4528
5010
|
teamId: resolved?.teamId ?? input.teamId,
|
|
4529
5011
|
spaceId: resolved?.spaceId ?? input.spaceId
|
|
4530
5012
|
}), input.revealSensitiveSecrets);
|
|
5013
|
+
if (resolved) {
|
|
5014
|
+
page.title = resolved.title;
|
|
5015
|
+
page.breadcrumb = resolved.breadcrumb;
|
|
5016
|
+
const redactPrivateValues = resolved.redactPrivateValues;
|
|
5017
|
+
if (redactPrivateValues) {
|
|
5018
|
+
page.content = redactPrivateValues(page.content);
|
|
5019
|
+
page.attachments = page.attachments.map((attachment) => ({
|
|
5020
|
+
...attachment,
|
|
5021
|
+
name: redactPrivateValues(attachment.name),
|
|
5022
|
+
url: redactPrivateValues(attachment.url)
|
|
5023
|
+
}));
|
|
5024
|
+
}
|
|
5025
|
+
}
|
|
4531
5026
|
return {
|
|
4532
5027
|
content: [{
|
|
4533
5028
|
type: "text",
|
|
@@ -4673,9 +5168,9 @@ async function handleLookupEnvironmentAccess(input, adapters, defaultSource) {
|
|
|
4673
5168
|
}
|
|
4674
5169
|
//#endregion
|
|
4675
5170
|
//#region ../../src/tools/wiki-write.ts
|
|
4676
|
-
const APPROVAL_TTL_MS =
|
|
5171
|
+
const APPROVAL_TTL_MS = 18e5;
|
|
4677
5172
|
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)]);
|
|
5173
|
+
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
5174
|
const PrepareWikiCreateSchema = zod_v4.z.object({
|
|
4680
5175
|
parentPageId: zod_v4.z.string().trim().min(1).optional(),
|
|
4681
5176
|
parentPath: PathSchema.optional(),
|
|
@@ -4699,6 +5194,10 @@ const ReplaceTextSchema = zod_v4.z.object({
|
|
|
4699
5194
|
find: zod_v4.z.string().min(1),
|
|
4700
5195
|
replace: zod_v4.z.string()
|
|
4701
5196
|
});
|
|
5197
|
+
const ReplaceDocumentSchema = zod_v4.z.object({
|
|
5198
|
+
type: zod_v4.z.literal("replace_document"),
|
|
5199
|
+
markdown: zod_v4.z.string().trim().min(1)
|
|
5200
|
+
});
|
|
4702
5201
|
const PrepareWikiUpdateSchema = zod_v4.z.object({
|
|
4703
5202
|
pageId: zod_v4.z.string().trim().min(1).optional(),
|
|
4704
5203
|
url: zod_v4.z.string().url().optional(),
|
|
@@ -4708,7 +5207,8 @@ const PrepareWikiUpdateSchema = zod_v4.z.object({
|
|
|
4708
5207
|
operation: zod_v4.z.discriminatedUnion("type", [
|
|
4709
5208
|
AppendBlocksSchema,
|
|
4710
5209
|
AppendTableRowSchema,
|
|
4711
|
-
ReplaceTextSchema
|
|
5210
|
+
ReplaceTextSchema,
|
|
5211
|
+
ReplaceDocumentSchema
|
|
4712
5212
|
]),
|
|
4713
5213
|
source: SourceSchema
|
|
4714
5214
|
}).refine((value) => [
|
|
@@ -4749,6 +5249,10 @@ const WikiUpdateOperationSchema = zod_v4.z.discriminatedUnion("type", [
|
|
|
4749
5249
|
type: zod_v4.z.literal("replace_text"),
|
|
4750
5250
|
find: zod_v4.z.string(),
|
|
4751
5251
|
replace: zod_v4.z.string()
|
|
5252
|
+
}),
|
|
5253
|
+
zod_v4.z.object({
|
|
5254
|
+
type: zod_v4.z.literal("replace_document"),
|
|
5255
|
+
markdown: zod_v4.z.string()
|
|
4752
5256
|
})
|
|
4753
5257
|
]);
|
|
4754
5258
|
const WikiUpdateRequestSchema = zod_v4.z.object({
|
|
@@ -4878,11 +5382,24 @@ async function handlePrepareWikiCreate(input, adapters, approvals, defaultSource
|
|
|
4878
5382
|
const teamId = resolved?.teamId ?? parent.teamId;
|
|
4879
5383
|
const spaceId = resolved?.spaceId ?? parent.spaceId;
|
|
4880
5384
|
if (!spaceId) throw new Error("The target space could not be verified");
|
|
5385
|
+
const title = input.title.trim();
|
|
5386
|
+
const titleCandidates = await adapter.searchWikiPages({
|
|
5387
|
+
query: title,
|
|
5388
|
+
teamId,
|
|
5389
|
+
spaceId,
|
|
5390
|
+
limit: 50
|
|
5391
|
+
});
|
|
5392
|
+
const siblingConflicts = (await Promise.all(titleCandidates.filter((candidate) => candidate.title === title).map((candidate) => adapter.getWikiPage({
|
|
5393
|
+
pageId: candidate.pageId,
|
|
5394
|
+
teamId: candidate.teamId,
|
|
5395
|
+
spaceId: candidate.spaceId ?? spaceId
|
|
5396
|
+
})))).filter((page) => page.parentPageId === parent.pageId);
|
|
5397
|
+
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
5398
|
const requestWithoutKey = {
|
|
4882
5399
|
teamId,
|
|
4883
5400
|
spaceId,
|
|
4884
5401
|
parentPageId: parent.pageId,
|
|
4885
|
-
title
|
|
5402
|
+
title,
|
|
4886
5403
|
markdown: input.markdown
|
|
4887
5404
|
};
|
|
4888
5405
|
const operationHash = hashOperation({
|
|
@@ -4904,7 +5421,7 @@ async function handlePrepareWikiCreate(input, adapters, approvals, defaultSource
|
|
|
4904
5421
|
});
|
|
4905
5422
|
const plan = {
|
|
4906
5423
|
kind: "create",
|
|
4907
|
-
targetBreadcrumb: [...parent.breadcrumb, input.title.trim()],
|
|
5424
|
+
targetBreadcrumb: [...resolved?.breadcrumb ?? parent.breadcrumb, input.title.trim()],
|
|
4908
5425
|
request,
|
|
4909
5426
|
parentBaseline: baseline(parent),
|
|
4910
5427
|
operationHash,
|
|
@@ -4961,7 +5478,7 @@ async function handlePrepareWikiUpdate(input, adapters, approvals, defaultSource
|
|
|
4961
5478
|
});
|
|
4962
5479
|
const plan = {
|
|
4963
5480
|
kind: "update",
|
|
4964
|
-
targetBreadcrumb: page.breadcrumb,
|
|
5481
|
+
targetBreadcrumb: resolved?.breadcrumb ?? page.breadcrumb,
|
|
4965
5482
|
request,
|
|
4966
5483
|
operationHash,
|
|
4967
5484
|
approvalToken: approval.token,
|
|
@@ -4971,7 +5488,7 @@ async function handlePrepareWikiUpdate(input, adapters, approvals, defaultSource
|
|
|
4971
5488
|
content: [{
|
|
4972
5489
|
type: "text",
|
|
4973
5490
|
text: [
|
|
4974
|
-
`Prepared Wiki update for ${
|
|
5491
|
+
`Prepared Wiki update for ${plan.targetBreadcrumb.join(" / ") || page.title}.`,
|
|
4975
5492
|
"No write was performed. Ask the user to confirm this exact operation immediately before apply.",
|
|
4976
5493
|
`operationHash: ${plan.operationHash}`,
|
|
4977
5494
|
`approvalToken: ${plan.approvalToken}`,
|
|
@@ -5084,7 +5601,7 @@ function createRequirementsServer(config, adapterOverrides) {
|
|
|
5084
5601
|
});
|
|
5085
5602
|
server.registerTool("get_ones_wiki_page", {
|
|
5086
5603
|
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.",
|
|
5604
|
+
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
5605
|
inputSchema: GetOnesWikiPageSchema,
|
|
5089
5606
|
annotations: {
|
|
5090
5607
|
readOnlyHint: true,
|
|
@@ -5146,7 +5663,7 @@ function createRequirementsServer(config, adapterOverrides) {
|
|
|
5146
5663
|
});
|
|
5147
5664
|
server.registerTool("prepare_wiki_create", {
|
|
5148
5665
|
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.",
|
|
5666
|
+
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
5667
|
inputSchema: PrepareWikiCreateSchema,
|
|
5151
5668
|
outputSchema: PrepareWikiCreateOutputSchema,
|
|
5152
5669
|
annotations: {
|
|
@@ -5185,7 +5702,7 @@ function createRequirementsServer(config, adapterOverrides) {
|
|
|
5185
5702
|
});
|
|
5186
5703
|
server.registerTool("prepare_wiki_update", {
|
|
5187
5704
|
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.",
|
|
5705
|
+
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
5706
|
inputSchema: PrepareWikiUpdateSchema,
|
|
5190
5707
|
outputSchema: PrepareWikiUpdateOutputSchema,
|
|
5191
5708
|
annotations: {
|
|
@@ -5222,6 +5739,28 @@ function createRequirementsServer(config, adapterOverrides) {
|
|
|
5222
5739
|
return toolError(err);
|
|
5223
5740
|
}
|
|
5224
5741
|
});
|
|
5742
|
+
server.registerTool("delete_empty_wiki_duplicates", {
|
|
5743
|
+
title: "Delete Empty ONES Wiki Duplicates",
|
|
5744
|
+
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.",
|
|
5745
|
+
inputSchema: DeleteEmptyWikiDuplicatesSchema,
|
|
5746
|
+
outputSchema: DeleteEmptyWikiDuplicatesOutputSchema,
|
|
5747
|
+
annotations: {
|
|
5748
|
+
readOnlyHint: false,
|
|
5749
|
+
destructiveHint: true,
|
|
5750
|
+
idempotentHint: false,
|
|
5751
|
+
openWorldHint: true
|
|
5752
|
+
}
|
|
5753
|
+
}, async (params) => {
|
|
5754
|
+
try {
|
|
5755
|
+
const sourceType = params.source ?? defaultSource;
|
|
5756
|
+
return await handleDeleteEmptyWikiDuplicates(params, adapters, {
|
|
5757
|
+
defaultSource,
|
|
5758
|
+
writesEnabled: wikiWritesEnabled(sourceType)
|
|
5759
|
+
});
|
|
5760
|
+
} catch (err) {
|
|
5761
|
+
return toolError(err);
|
|
5762
|
+
}
|
|
5763
|
+
});
|
|
5225
5764
|
server.registerTool("list_sources", {
|
|
5226
5765
|
title: "List Sources",
|
|
5227
5766
|
description: "List all configured requirement sources and their status",
|