@michengai/dsh-codex-ui 1.1.8 → 1.1.9
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/CHANGELOG.md +26 -0
- package/CHANGELOG.zh-CN.md +26 -0
- package/lib/client.js +121 -18
- package/lib/index.mjs +961 -76
- package/package.json +1 -1
package/lib/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { apply as apply$1 } from "@deepseek-ai/dsh-client-ui-settings-general";
|
|
2
|
-
import { lstat, mkdir, open, readFile, realpath, rename, rm, unlink, writeFile } from "node:fs/promises";
|
|
2
|
+
import { lstat, mkdir, open, readFile, readdir, realpath, rename, rm, unlink, writeFile } from "node:fs/promises";
|
|
3
3
|
import { basename, dirname, isAbsolute, join, relative, resolve, sep, win32 } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import { spawn } from "node:child_process";
|
|
@@ -1192,6 +1192,173 @@ var SessionMoveError = class extends Error {
|
|
|
1192
1192
|
}
|
|
1193
1193
|
};
|
|
1194
1194
|
const movingSessions = /* @__PURE__ */ new Set();
|
|
1195
|
+
const ZSTD_MAGIC = 4247762216;
|
|
1196
|
+
/** 旧宿主 list() 直接给会话头;0.1.6 起返回 { header, revision }。 */
|
|
1197
|
+
function persistedSessionHeader(item) {
|
|
1198
|
+
if (item === null || typeof item !== "object") return void 0;
|
|
1199
|
+
const record = item;
|
|
1200
|
+
if (typeof record.id === "string" && record.id.length > 0) return record;
|
|
1201
|
+
const nested = record.header;
|
|
1202
|
+
if (nested !== null && typeof nested === "object") {
|
|
1203
|
+
const header = nested;
|
|
1204
|
+
if (typeof header.id === "string" && header.id.length > 0) return header;
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
async function listPersistedHeader(persistence, sessionId) {
|
|
1208
|
+
for (const item of await persistence.list()) {
|
|
1209
|
+
const header = persistedSessionHeader(item);
|
|
1210
|
+
if (header?.id === sessionId) return header;
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1213
|
+
function isMissingArtifact(error) {
|
|
1214
|
+
if (error.code === "ENOENT") return true;
|
|
1215
|
+
return error.name === "SessionPersistenceNotFoundError";
|
|
1216
|
+
}
|
|
1217
|
+
function missingArtifactError(error) {
|
|
1218
|
+
return new SessionMoveError("session-move/session-not-found", "读取会话持久化记录失败。", error === void 0 ? void 0 : { cause: error });
|
|
1219
|
+
}
|
|
1220
|
+
async function loadStoredSession(persistence, sessionId) {
|
|
1221
|
+
try {
|
|
1222
|
+
if (typeof persistence.loadStored === "function") return persistence.loadStored(sessionId);
|
|
1223
|
+
if (typeof persistence.open !== "function") return void 0;
|
|
1224
|
+
const handle = await persistence.open(sessionId, "read");
|
|
1225
|
+
try {
|
|
1226
|
+
const { events } = await handle.read();
|
|
1227
|
+
return {
|
|
1228
|
+
meta: handle.header,
|
|
1229
|
+
events,
|
|
1230
|
+
inheritedEventCount: handle.inheritedEventCount
|
|
1231
|
+
};
|
|
1232
|
+
} finally {
|
|
1233
|
+
await handle.close();
|
|
1234
|
+
}
|
|
1235
|
+
} catch (error) {
|
|
1236
|
+
if (isMissingArtifact(error)) throw missingArtifactError(error);
|
|
1237
|
+
throw error;
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
function locateArtifact(persistence, meta) {
|
|
1241
|
+
if (typeof persistence.locate !== "function") return void 0;
|
|
1242
|
+
return persistence.locate(meta);
|
|
1243
|
+
}
|
|
1244
|
+
async function resolveArtifactPath(persistence, sessionId, meta) {
|
|
1245
|
+
const located = locateArtifact(persistence, meta);
|
|
1246
|
+
if (located?.path !== void 0 && located.path !== "") return located.path;
|
|
1247
|
+
if (typeof persistence.resolveCurrentLog === "function") return persistence.resolveCurrentLog(sessionId);
|
|
1248
|
+
}
|
|
1249
|
+
function firstZstdFrameEnd(buffer) {
|
|
1250
|
+
if (buffer.length < 4 || buffer.readUInt32LE(0) !== ZSTD_MAGIC) return void 0;
|
|
1251
|
+
let offset = 4;
|
|
1252
|
+
if (offset >= buffer.length) return void 0;
|
|
1253
|
+
const descriptor = buffer.readUInt8(offset);
|
|
1254
|
+
offset += 1;
|
|
1255
|
+
if ((descriptor & 24) !== 0) return void 0;
|
|
1256
|
+
const contentSizeFlag = descriptor >>> 6;
|
|
1257
|
+
const singleSegment = (descriptor & 32) !== 0;
|
|
1258
|
+
const checksum = (descriptor & 4) !== 0;
|
|
1259
|
+
const dictionaryFlag = descriptor & 3;
|
|
1260
|
+
const dictionaryBytes = dictionaryFlag === 3 ? 4 : dictionaryFlag;
|
|
1261
|
+
const contentSizeBytes = contentSizeFlag === 0 ? singleSegment ? 1 : 0 : 1 << contentSizeFlag;
|
|
1262
|
+
offset += (singleSegment ? 0 : 1) + dictionaryBytes + contentSizeBytes;
|
|
1263
|
+
for (;;) {
|
|
1264
|
+
if (buffer.length - offset < 3) return void 0;
|
|
1265
|
+
const blockHeader = buffer.readUIntLE(offset, 3);
|
|
1266
|
+
offset += 3;
|
|
1267
|
+
const lastBlock = (blockHeader & 1) !== 0;
|
|
1268
|
+
const blockType = blockHeader >>> 1 & 3;
|
|
1269
|
+
const blockSize = blockHeader >>> 3;
|
|
1270
|
+
if (blockType === 3) return void 0;
|
|
1271
|
+
const payloadBytes = blockType === 1 ? 1 : blockSize;
|
|
1272
|
+
if (buffer.length - offset < payloadBytes) return void 0;
|
|
1273
|
+
offset += payloadBytes;
|
|
1274
|
+
if (lastBlock) break;
|
|
1275
|
+
}
|
|
1276
|
+
if (checksum) {
|
|
1277
|
+
if (buffer.length - offset < 4) return void 0;
|
|
1278
|
+
offset += 4;
|
|
1279
|
+
}
|
|
1280
|
+
return offset;
|
|
1281
|
+
}
|
|
1282
|
+
async function zlibZstd(name, data, options) {
|
|
1283
|
+
const transform = (await import("node:zlib"))[name];
|
|
1284
|
+
if (typeof transform !== "function") throw new SessionMoveError("session-move/zstd-unavailable", "当前 Node.js 运行时不支持 Zstd 会话迁移。");
|
|
1285
|
+
return new Promise((resolvePromise, rejectPromise) => {
|
|
1286
|
+
const done = (error, output) => {
|
|
1287
|
+
if (error === null) resolvePromise(output);
|
|
1288
|
+
else rejectPromise(error);
|
|
1289
|
+
};
|
|
1290
|
+
if (options === void 0) transform(data, done);
|
|
1291
|
+
else transform(data, options, done);
|
|
1292
|
+
});
|
|
1293
|
+
}
|
|
1294
|
+
async function zstdChecksumOptions() {
|
|
1295
|
+
const { constants } = await import("node:zlib");
|
|
1296
|
+
const flag = constants.ZSTD_c_checksumFlag;
|
|
1297
|
+
return typeof flag === "number" ? { params: { [flag]: 1 } } : void 0;
|
|
1298
|
+
}
|
|
1299
|
+
function isSessionGenerationFilename(name) {
|
|
1300
|
+
return /^session(?:\.v[1-9]\d*)?\.jsonl(?:\.zstd)?$/.test(name);
|
|
1301
|
+
}
|
|
1302
|
+
async function decodeArtifactFile(path) {
|
|
1303
|
+
let bytes;
|
|
1304
|
+
try {
|
|
1305
|
+
bytes = await readFile(path);
|
|
1306
|
+
} catch (error) {
|
|
1307
|
+
if (isMissingArtifact(error)) throw missingArtifactError(error);
|
|
1308
|
+
throw error;
|
|
1309
|
+
}
|
|
1310
|
+
if (!(path.endsWith(".zstd") || bytes.length >= 4 && bytes.readUInt32LE(0) === ZSTD_MAGIC)) {
|
|
1311
|
+
const text = bytes.toString("utf8");
|
|
1312
|
+
const newlineIndex = text.indexOf("\n");
|
|
1313
|
+
return {
|
|
1314
|
+
firstLine: newlineIndex < 0 ? text : text.slice(0, newlineIndex),
|
|
1315
|
+
body: newlineIndex < 0 ? "" : text.slice(newlineIndex + 1),
|
|
1316
|
+
zstd: false
|
|
1317
|
+
};
|
|
1318
|
+
}
|
|
1319
|
+
const frameEnd = firstZstdFrameEnd(bytes);
|
|
1320
|
+
if (frameEnd === void 0) throw new SessionMoveError("session-move/artifact-invalid", "会话工件头部无法解析。");
|
|
1321
|
+
return {
|
|
1322
|
+
firstLine: (await zlibZstd("zstdDecompress", bytes.subarray(0, frameEnd))).toString("utf8").replace(/\n$/, ""),
|
|
1323
|
+
body: "",
|
|
1324
|
+
zstd: true,
|
|
1325
|
+
trailingZstd: bytes.subarray(frameEnd)
|
|
1326
|
+
};
|
|
1327
|
+
}
|
|
1328
|
+
async function readSessionArtifact(persistence, sessionId, meta) {
|
|
1329
|
+
try {
|
|
1330
|
+
const path = await resolveArtifactPath(persistence, sessionId, meta);
|
|
1331
|
+
if (path === void 0) throw new SessionMoveError("session-move/path-invalid", "宿主无法定位会话工件。");
|
|
1332
|
+
if (typeof persistence.readRaw === "function") {
|
|
1333
|
+
const raw = await persistence.readRaw(sessionId);
|
|
1334
|
+
if (raw === void 0) throw missingArtifactError();
|
|
1335
|
+
const newlineIndex = raw.content.indexOf("\n");
|
|
1336
|
+
return {
|
|
1337
|
+
path,
|
|
1338
|
+
decoded: {
|
|
1339
|
+
firstLine: newlineIndex < 0 ? raw.content : raw.content.slice(0, newlineIndex),
|
|
1340
|
+
body: newlineIndex < 0 ? "" : raw.content.slice(newlineIndex + 1),
|
|
1341
|
+
zstd: path.endsWith(".zstd")
|
|
1342
|
+
}
|
|
1343
|
+
};
|
|
1344
|
+
}
|
|
1345
|
+
return {
|
|
1346
|
+
path,
|
|
1347
|
+
decoded: await decodeArtifactFile(path)
|
|
1348
|
+
};
|
|
1349
|
+
} catch (error) {
|
|
1350
|
+
if (error instanceof SessionMoveError) throw error;
|
|
1351
|
+
if (isMissingArtifact(error)) throw missingArtifactError(error);
|
|
1352
|
+
throw error;
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
async function encodeRewrittenArtifact(firstLine, decoded, encodeArtifact) {
|
|
1356
|
+
if (decoded.zstd && decoded.trailingZstd !== void 0) {
|
|
1357
|
+
const headerFrame = await zlibZstd("zstdCompress", Buffer.from(`${firstLine}\n`, "utf8"), await zstdChecksumOptions());
|
|
1358
|
+
return Buffer.concat([headerFrame, decoded.trailingZstd]);
|
|
1359
|
+
}
|
|
1360
|
+
return encodeArtifact(firstLine, decoded.body, decoded.zstd);
|
|
1361
|
+
}
|
|
1195
1362
|
function rawSessionIds(workspace) {
|
|
1196
1363
|
return Array.isArray(workspace.record?.sessionIds) ? workspace.record.sessionIds : workspace.sessionIds;
|
|
1197
1364
|
}
|
|
@@ -1220,17 +1387,10 @@ async function writeTemporaryFile(finalPath, data) {
|
|
|
1220
1387
|
}
|
|
1221
1388
|
async function encodeSessionArtifact(headerLine, body, zstd) {
|
|
1222
1389
|
if (!zstd) return Buffer.from(`${headerLine}\n${body}`, "utf8");
|
|
1223
|
-
const
|
|
1224
|
-
|
|
1225
|
-
const compress = (data) => new Promise((resolvePromise, rejectPromise) => {
|
|
1226
|
-
zstdCompress(data, (error, output) => {
|
|
1227
|
-
if (error === null) resolvePromise(output);
|
|
1228
|
-
else rejectPromise(error);
|
|
1229
|
-
});
|
|
1230
|
-
});
|
|
1231
|
-
const headerFrame = await compress(Buffer.from(`${headerLine}\n`, "utf8"));
|
|
1390
|
+
const options = await zstdChecksumOptions();
|
|
1391
|
+
const headerFrame = await zlibZstd("zstdCompress", Buffer.from(`${headerLine}\n`, "utf8"), options);
|
|
1232
1392
|
if (body === "") return headerFrame;
|
|
1233
|
-
const bodyFrame = await
|
|
1393
|
+
const bodyFrame = await zlibZstd("zstdCompress", Buffer.from(body, "utf8"), options);
|
|
1234
1394
|
return Buffer.concat([headerFrame, bodyFrame]);
|
|
1235
1395
|
}
|
|
1236
1396
|
var ArtifactDirectoryMove = class {
|
|
@@ -1240,6 +1400,7 @@ var ArtifactDirectoryMove = class {
|
|
|
1240
1400
|
backupCreated = false;
|
|
1241
1401
|
published = false;
|
|
1242
1402
|
temporaryPath;
|
|
1403
|
+
extraBackups = [];
|
|
1243
1404
|
oldDirectory;
|
|
1244
1405
|
newDirectory;
|
|
1245
1406
|
movedOldArtifact;
|
|
@@ -1254,7 +1415,7 @@ var ArtifactDirectoryMove = class {
|
|
|
1254
1415
|
this.movedOldArtifact = join(this.newDirectory, relativeArtifact);
|
|
1255
1416
|
this.backupArtifact = join(this.newDirectory, `${basename(oldArtifact)}.${randomBytes(6).toString("hex")}.dcu-backup`);
|
|
1256
1417
|
}
|
|
1257
|
-
async publish(bytes) {
|
|
1418
|
+
async publish(bytes, siblings) {
|
|
1258
1419
|
if (resolve(this.oldDirectory).toLowerCase() === resolve(this.newDirectory).toLowerCase()) throw new SessionMoveError("session-move/path-conflict", "源项目和目标项目使用了相同的会话目录。");
|
|
1259
1420
|
if (await pathExists(this.newDirectory)) throw new SessionMoveError("session-move/destination-occupied", "目标项目已经存在同名会话工件。");
|
|
1260
1421
|
try {
|
|
@@ -1267,6 +1428,7 @@ var ArtifactDirectoryMove = class {
|
|
|
1267
1428
|
await rename(this.temporaryPath, this.newArtifact);
|
|
1268
1429
|
this.temporaryPath = void 0;
|
|
1269
1430
|
this.published = true;
|
|
1431
|
+
if (siblings !== void 0) await this.rewriteSiblingGenerations(siblings);
|
|
1270
1432
|
} catch (error) {
|
|
1271
1433
|
try {
|
|
1272
1434
|
await this.rollback();
|
|
@@ -1277,6 +1439,39 @@ var ArtifactDirectoryMove = class {
|
|
|
1277
1439
|
throw new SessionMoveError("session-move/artifact-failed", "迁移会话工件失败,原会话已恢复。", { cause: error });
|
|
1278
1440
|
}
|
|
1279
1441
|
}
|
|
1442
|
+
async rewriteSiblingGenerations(siblings) {
|
|
1443
|
+
for (const name of await readdir(this.newDirectory)) {
|
|
1444
|
+
if (!isSessionGenerationFilename(name)) continue;
|
|
1445
|
+
const path = join(this.newDirectory, name);
|
|
1446
|
+
if (resolve(path) === resolve(this.newArtifact) || resolve(path) === resolve(this.backupArtifact)) continue;
|
|
1447
|
+
let decoded;
|
|
1448
|
+
try {
|
|
1449
|
+
decoded = await decodeArtifactFile(path);
|
|
1450
|
+
} catch {
|
|
1451
|
+
continue;
|
|
1452
|
+
}
|
|
1453
|
+
let header;
|
|
1454
|
+
try {
|
|
1455
|
+
header = JSON.parse(decoded.firstLine);
|
|
1456
|
+
} catch {
|
|
1457
|
+
continue;
|
|
1458
|
+
}
|
|
1459
|
+
if (header.id !== siblings.sessionId) continue;
|
|
1460
|
+
const backup = join(this.newDirectory, `${name}.${randomBytes(6).toString("hex")}.dcu-backup`);
|
|
1461
|
+
await rename(path, backup);
|
|
1462
|
+
this.extraBackups.push({
|
|
1463
|
+
path,
|
|
1464
|
+
backup
|
|
1465
|
+
});
|
|
1466
|
+
const rewritten = await encodeRewrittenArtifact(JSON.stringify({
|
|
1467
|
+
...header,
|
|
1468
|
+
cwd: siblings.targetCwd
|
|
1469
|
+
}), decoded, siblings.encodeArtifact);
|
|
1470
|
+
this.temporaryPath = await writeTemporaryFile(path, rewritten);
|
|
1471
|
+
await rename(this.temporaryPath, path);
|
|
1472
|
+
this.temporaryPath = void 0;
|
|
1473
|
+
}
|
|
1474
|
+
}
|
|
1280
1475
|
async rollback() {
|
|
1281
1476
|
const failures = [];
|
|
1282
1477
|
if (this.temporaryPath !== void 0) {
|
|
@@ -1287,6 +1482,18 @@ var ArtifactDirectoryMove = class {
|
|
|
1287
1482
|
}
|
|
1288
1483
|
this.temporaryPath = void 0;
|
|
1289
1484
|
}
|
|
1485
|
+
for (const extra of this.extraBackups.splice(0).reverse()) {
|
|
1486
|
+
try {
|
|
1487
|
+
await rm(extra.path, { force: true });
|
|
1488
|
+
} catch (error) {
|
|
1489
|
+
failures.push(error);
|
|
1490
|
+
}
|
|
1491
|
+
try {
|
|
1492
|
+
await rename(extra.backup, extra.path);
|
|
1493
|
+
} catch (error) {
|
|
1494
|
+
failures.push(error);
|
|
1495
|
+
}
|
|
1496
|
+
}
|
|
1290
1497
|
if (this.published) try {
|
|
1291
1498
|
await rm(this.newArtifact, { force: true });
|
|
1292
1499
|
this.published = false;
|
|
@@ -1309,10 +1516,10 @@ var ArtifactDirectoryMove = class {
|
|
|
1309
1516
|
if (failures.length > 0) throw new AggregateError(failures, "会话工件回滚失败");
|
|
1310
1517
|
}
|
|
1311
1518
|
async commit(logger) {
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1519
|
+
const leftovers = [...this.backupCreated ? [this.backupArtifact] : [], ...this.extraBackups.splice(0).map((extra) => extra.backup)];
|
|
1520
|
+
this.backupCreated = false;
|
|
1521
|
+
for (const backup of leftovers) try {
|
|
1522
|
+
await rm(backup, { force: true });
|
|
1316
1523
|
} catch (error) {
|
|
1317
1524
|
logger?.warn(`会话迁移成功,但旧工件备份清理失败:${String(error)}`);
|
|
1318
1525
|
}
|
|
@@ -1406,7 +1613,7 @@ async function moveAccounting(target, snapshots, sessionId) {
|
|
|
1406
1613
|
await target.attachSession(sessionId);
|
|
1407
1614
|
}
|
|
1408
1615
|
/**
|
|
1409
|
-
*
|
|
1616
|
+
* 把持久化会话完整迁移到目标项目。整个会话目录一起移动,当前代际和同目录旧代际的 cwd 一并改写;任何提交前失败都会恢复原工件和项目顺序。
|
|
1410
1617
|
*/
|
|
1411
1618
|
async function moveSessionToWorkspace(services, sessionId, targetWorkspaceId, options = {}) {
|
|
1412
1619
|
if (!validIdentifier(sessionId) || !validIdentifier(targetWorkspaceId)) throw new SessionMoveError("session-move/invalid-request", "会话或目标项目标识无效。");
|
|
@@ -1420,7 +1627,7 @@ async function moveSessionToWorkspace(services, sessionId, targetWorkspaceId, op
|
|
|
1420
1627
|
const targetSnapshot = snapshots.find((snapshot) => snapshot.workspace.id === target.id);
|
|
1421
1628
|
const sources = snapshots.filter((snapshot) => snapshot.contained && snapshot.workspace.id !== target.id);
|
|
1422
1629
|
if (sources.length > 1 || targetSnapshot?.contained === true && sources.length > 0) throw new SessionMoveError("session-move/accounting-invalid", "会话当前的项目归属不一致,无法安全移动。");
|
|
1423
|
-
const persistedHeader =
|
|
1630
|
+
const persistedHeader = await listPersistedHeader(services.sessionPersistence, sessionId);
|
|
1424
1631
|
if (persistedHeader === void 0) throw new SessionMoveError("session-move/session-not-found", "该会话没有可迁移的持久化记录。");
|
|
1425
1632
|
if (persistedHeader.origin === "subagent") throw new SessionMoveError("session-move/subagent-unsupported", "子代理会话不能移动到其他项目。");
|
|
1426
1633
|
const targetPath = await realpath(target.path);
|
|
@@ -1441,7 +1648,7 @@ async function moveSessionToWorkspace(services, sessionId, targetWorkspaceId, op
|
|
|
1441
1648
|
const originalEntry = services.sessions.store?.get(sessionId);
|
|
1442
1649
|
if (liveSession !== void 0 && (services.sessions.store === void 0 || originalEntry === void 0)) throw new SessionMoveError("session-move/service-unavailable", "宿主无法提供可恢复的会话入口,暂时不能移动活跃会话。");
|
|
1443
1650
|
if (currentPath === targetPath) {
|
|
1444
|
-
const stored = liveSession === void 0 ? await services.sessionPersistence
|
|
1651
|
+
const stored = liveSession === void 0 ? await loadStoredSession(services.sessionPersistence, sessionId) : void 0;
|
|
1445
1652
|
if (liveSession === void 0 && stored === void 0) throw new SessionMoveError("session-move/session-not-found", "读取会话持久化记录失败。");
|
|
1446
1653
|
let enteredPlaceholder;
|
|
1447
1654
|
try {
|
|
@@ -1476,15 +1683,12 @@ async function moveSessionToWorkspace(services, sessionId, targetWorkspaceId, op
|
|
|
1476
1683
|
} catch (error) {
|
|
1477
1684
|
throw new SessionMoveError("session-move/quiesce-failed", "会话仍在运行,暂时无法移动。", { cause: error });
|
|
1478
1685
|
}
|
|
1479
|
-
const
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
const newlineIndex = raw.content.indexOf("\n");
|
|
1483
|
-
const headerText = newlineIndex < 0 ? raw.content : raw.content.slice(0, newlineIndex);
|
|
1484
|
-
const body = newlineIndex < 0 ? "" : raw.content.slice(newlineIndex + 1);
|
|
1686
|
+
const originalStored = await loadStoredSession(services.sessionPersistence, sessionId);
|
|
1687
|
+
if (originalStored === void 0) throw new SessionMoveError("session-move/session-not-found", "读取会话持久化记录失败。");
|
|
1688
|
+
const artifact = await readSessionArtifact(services.sessionPersistence, sessionId, persistedHeader);
|
|
1485
1689
|
let header;
|
|
1486
1690
|
try {
|
|
1487
|
-
header = JSON.parse(
|
|
1691
|
+
header = JSON.parse(artifact.decoded.firstLine);
|
|
1488
1692
|
} catch (error) {
|
|
1489
1693
|
throw new SessionMoveError("session-move/artifact-invalid", "会话工件头部无法解析。", { cause: error });
|
|
1490
1694
|
}
|
|
@@ -1494,14 +1698,14 @@ async function moveSessionToWorkspace(services, sessionId, targetWorkspaceId, op
|
|
|
1494
1698
|
cwd: targetPath
|
|
1495
1699
|
};
|
|
1496
1700
|
const targetMeta = {
|
|
1497
|
-
...
|
|
1701
|
+
...originalStored.meta,
|
|
1498
1702
|
cwd: targetPath
|
|
1499
1703
|
};
|
|
1500
|
-
const
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
const bytes = await (
|
|
1504
|
-
const transaction = new ArtifactDirectoryMove(
|
|
1704
|
+
const newPath = locateArtifact(services.sessionPersistence, targetMeta)?.path;
|
|
1705
|
+
if (newPath === void 0) throw new SessionMoveError("session-move/path-invalid", "宿主无法定位会话工件。");
|
|
1706
|
+
const encoder = options.encodeArtifact ?? encodeSessionArtifact;
|
|
1707
|
+
const bytes = await encodeRewrittenArtifact(JSON.stringify(targetHeader), artifact.decoded, encoder);
|
|
1708
|
+
const transaction = new ArtifactDirectoryMove(artifact.path, newPath);
|
|
1505
1709
|
try {
|
|
1506
1710
|
await agent?.scope?.dispose?.();
|
|
1507
1711
|
services.agents.store?.delete(sessionId);
|
|
@@ -1516,8 +1720,17 @@ async function moveSessionToWorkspace(services, sessionId, targetWorkspaceId, op
|
|
|
1516
1720
|
}
|
|
1517
1721
|
let enteredPlaceholder;
|
|
1518
1722
|
try {
|
|
1519
|
-
await transaction.publish(bytes
|
|
1520
|
-
|
|
1723
|
+
await transaction.publish(bytes, {
|
|
1724
|
+
sessionId,
|
|
1725
|
+
targetCwd: targetPath,
|
|
1726
|
+
encodeArtifact: encoder
|
|
1727
|
+
});
|
|
1728
|
+
let movedStored;
|
|
1729
|
+
try {
|
|
1730
|
+
movedStored = await loadStoredSession(services.sessionPersistence, sessionId);
|
|
1731
|
+
} catch (error) {
|
|
1732
|
+
throw new SessionMoveError("session-move/validation-failed", "迁移后的会话工件校验失败。", { cause: error });
|
|
1733
|
+
}
|
|
1521
1734
|
if (movedStored === void 0 || movedStored.meta.cwd !== targetPath) throw new SessionMoveError("session-move/validation-failed", "迁移后的会话工件校验失败。");
|
|
1522
1735
|
enteredPlaceholder = await enterStoredSession(services, sessionId, movedStored);
|
|
1523
1736
|
try {
|
|
@@ -1556,30 +1769,607 @@ async function moveSessionToWorkspace(services, sessionId, targetWorkspaceId, op
|
|
|
1556
1769
|
}
|
|
1557
1770
|
}
|
|
1558
1771
|
//#endregion
|
|
1772
|
+
//#region src/client/locales.ts
|
|
1773
|
+
const zh = {
|
|
1774
|
+
"home.resizeInput": "拖动调整输入区宽度",
|
|
1775
|
+
"home.projectSearch": "搜索项目",
|
|
1776
|
+
"home.projectEmpty": "没有匹配的项目",
|
|
1777
|
+
"home.suggestions": "开始一项任务",
|
|
1778
|
+
"home.workspace": "请先选择工作区,再选择任务。",
|
|
1779
|
+
"home.draft": "已保留当前草稿和附件;清空输入后可填入任务建议。",
|
|
1780
|
+
"home.busy": "当前输入正在处理中,请稍后再试。",
|
|
1781
|
+
"home.explore": "探索并理解代码",
|
|
1782
|
+
"home.build": "构建新功能、应用或工具",
|
|
1783
|
+
"home.review": "审查代码并提出修改建议",
|
|
1784
|
+
"home.fix": "修复问题和失败",
|
|
1785
|
+
"home.explore.task1": "了解项目结构",
|
|
1786
|
+
"home.explore.prompt1": "请探索当前工作区的代码,解释项目结构、主要入口和模块之间的关系。",
|
|
1787
|
+
"home.explore.task2": "理解一项功能",
|
|
1788
|
+
"home.explore.prompt2": "我想了解一项功能的实现。先问我具体是哪项功能,再沿调用关系解释。",
|
|
1789
|
+
"home.build.task1": "实现一项新功能",
|
|
1790
|
+
"home.build.prompt1": "我想在当前项目中实现一项新功能。先和我确认需求与验收标准,再实施。",
|
|
1791
|
+
"home.build.task2": "构建一个小工具",
|
|
1792
|
+
"home.build.prompt2": "我想构建一个小工具。先问我目标、输入和输出,再结合当前项目提出方案。",
|
|
1793
|
+
"home.review.task1": "评审当前改动",
|
|
1794
|
+
"home.review.prompt1": "请评审当前工作区的未提交改动,优先指出真实缺陷、回归风险和缺少的测试;先不要修改。",
|
|
1795
|
+
"home.review.task2": "检查代码质量",
|
|
1796
|
+
"home.review.prompt2": "请检查当前项目的代码质量,给出有源码依据的改进建议,先评估不改动。",
|
|
1797
|
+
"home.fix.task1": "排查一个问题",
|
|
1798
|
+
"home.fix.prompt1": "我遇到了一个问题。先问我复现步骤、预期和实际表现,再定位根因并修复。",
|
|
1799
|
+
"home.fix.task2": "修复失败的测试",
|
|
1800
|
+
"home.fix.prompt2": "请运行当前项目适用的测试,定位失败根因并做最小修复,保留有效的回归覆盖。",
|
|
1801
|
+
"usage.title": "使用统计",
|
|
1802
|
+
"usage.loading": "正在加载费用面板…",
|
|
1803
|
+
"usage.failed": "费用面板暂时无法嵌入,可重试或打开原弹窗。",
|
|
1804
|
+
"usage.closed": "费用面板已关闭。",
|
|
1805
|
+
"usage.reopen": "重新打开费用面板",
|
|
1806
|
+
"usage.retry": "重试",
|
|
1807
|
+
"usage.original": "打开原弹窗",
|
|
1808
|
+
"settings.title": "设置",
|
|
1809
|
+
"settings.back": "返回应用",
|
|
1810
|
+
"settings.openDocument": "打开配置文件",
|
|
1811
|
+
"settings.advanced": "高级",
|
|
1812
|
+
"settings.documentTitle": "配置文件",
|
|
1813
|
+
"settings.documentDescription": "在外部编辑器中查看和编辑配置",
|
|
1814
|
+
"settings.documentOpen": "打开",
|
|
1815
|
+
"settings.documentOpening": "打开中…",
|
|
1816
|
+
"settings.openDocumentError": "无法打开配置文件",
|
|
1817
|
+
"settings.search": "搜索设置…",
|
|
1818
|
+
"settings.personal": "个人",
|
|
1819
|
+
"settings.integrations": "集成",
|
|
1820
|
+
"settings.records": "记录与应用",
|
|
1821
|
+
"settings.general": "常规",
|
|
1822
|
+
"settings.permissions": "权限",
|
|
1823
|
+
"settings.editor": "编辑器",
|
|
1824
|
+
"settings.noResults": "没有匹配的设置分类",
|
|
1825
|
+
"settings.filterHint": "仅筛选导航,选择分类后切换当前页面。",
|
|
1826
|
+
"settings.disconnected": "连接异常",
|
|
1827
|
+
"settings.reconnect": "立即重连",
|
|
1828
|
+
"settings.connecting": "连接中",
|
|
1829
|
+
"settings.recovered": "连接成功",
|
|
1830
|
+
"input.historyHint": "↑↓ 翻历史",
|
|
1831
|
+
"sidebar.label": "DSH 导航",
|
|
1832
|
+
"sidebar.expand": "展开侧边栏",
|
|
1833
|
+
"sidebar.collapse": "收缩侧边栏",
|
|
1834
|
+
"sidebar.newTask": "新建任务",
|
|
1835
|
+
"sidebar.search": "搜索会话",
|
|
1836
|
+
"sidebar.mainMenu": "主菜单",
|
|
1837
|
+
"sidebar.extensions": "扩展管理",
|
|
1838
|
+
"sidebar.experts": "专家",
|
|
1839
|
+
"sidebar.skills": "技能",
|
|
1840
|
+
"sidebar.plugins": "插件",
|
|
1841
|
+
"sidebar.marketplace": "插件市场",
|
|
1842
|
+
"sidebar.connectors": "连接器",
|
|
1843
|
+
"sidebar.schedule": "定时任务",
|
|
1844
|
+
"sidebar.assistant": "IM助理",
|
|
1845
|
+
"sidebar.imSettings": "IM助理",
|
|
1846
|
+
"sidebar.tasksTab": "任务",
|
|
1847
|
+
"sidebar.channelsTab": "频道",
|
|
1848
|
+
"sidebar.scheduleTab": "定时",
|
|
1849
|
+
"sidebar.runsTab": "执行记录",
|
|
1850
|
+
"sidebar.overviewTab": "任务总览",
|
|
1851
|
+
"channels.empty": "还没有频道会话。先在设置 → IM助理 里连接渠道。",
|
|
1852
|
+
"channels.loadError": "无法读取频道会话。",
|
|
1853
|
+
"channel.dingtalk": "钉钉",
|
|
1854
|
+
"channel.feishu": "飞书",
|
|
1855
|
+
"channel.lark": "Lark",
|
|
1856
|
+
"channel.weixin": "微信",
|
|
1857
|
+
"channel.wecom": "企业微信",
|
|
1858
|
+
"channel.qq": "QQ",
|
|
1859
|
+
"channel.telegram": "Telegram",
|
|
1860
|
+
"channel.unknown": "频道",
|
|
1861
|
+
"schedule.empty": "还没有定时任务运行记录。",
|
|
1862
|
+
"schedule.groupActions": "{name} 的任务操作",
|
|
1863
|
+
"schedule.taskSettings": "任务设置",
|
|
1864
|
+
"schedule.archiveGroup": "归档整组会话",
|
|
1865
|
+
"schedule.archiveGroupConfirm": "确认归档",
|
|
1866
|
+
"schedule.archiveGroupDescription": "将归档“{name}”下的 {count} 个会话。归档后可以在“设置 → 已归档”中恢复。",
|
|
1867
|
+
"schedule.archiveGroupPending": "正在归档整组会话…",
|
|
1868
|
+
"schedule.archiveGroupPartial": "已归档 {archived} 个会话,另有 {failed} 个失败。可以重试剩余会话。",
|
|
1869
|
+
"search.placeholder": "搜索会话、设置和操作",
|
|
1870
|
+
"search.empty": "没有匹配的结果。",
|
|
1871
|
+
"search.sessions": "会话",
|
|
1872
|
+
"search.settings": "设置",
|
|
1873
|
+
"search.actions": "快捷操作",
|
|
1874
|
+
"workspace.label": "工作区会话",
|
|
1875
|
+
"workspace.pinned": "置顶",
|
|
1876
|
+
"workspace.pinnedEmpty": "拖动项目到此处置顶",
|
|
1877
|
+
"workspace.projects": "项目",
|
|
1878
|
+
"workspace.ungrouped": "未分组",
|
|
1879
|
+
"workspace.createGroup": "新建分组",
|
|
1880
|
+
"workspace.createGroupDescription": "输入分组名称,项目不会自动移动。",
|
|
1881
|
+
"workspace.groupName": "分组名称",
|
|
1882
|
+
"workspace.moveToGroup": "移动到分组",
|
|
1883
|
+
"workspace.removeFromGroup": "移至未分组",
|
|
1884
|
+
"workspace.deleteGroup": "删除分组“{name}”",
|
|
1885
|
+
"workspace.deleteGroupAction": "删除分组",
|
|
1886
|
+
"workspace.deleteGroupDescription": "删除分组只会将其中项目移至未分组,不会删除项目或会话。",
|
|
1887
|
+
"workspace.groupActions": "分组“{name}”的操作",
|
|
1888
|
+
"workspace.renameGroup": "重命名分组",
|
|
1889
|
+
"workspace.recent": "最近",
|
|
1890
|
+
"workspace.recentEmpty": "无聊天",
|
|
1891
|
+
"workspace.noChat": "暂无聊天",
|
|
1892
|
+
"workspace.empty": "暂无项目",
|
|
1893
|
+
"workspace.newSession": "新建会话",
|
|
1894
|
+
"workspace.rename": "重命名项目",
|
|
1895
|
+
"workspace.pin": "置顶项目",
|
|
1896
|
+
"workspace.unpin": "取消置顶",
|
|
1897
|
+
"workspace.archive": "归档所有会话",
|
|
1898
|
+
"workspace.archiveTitle": "归档 {count} 个会话?",
|
|
1899
|
+
"workspace.archiveDescription": "这会归档“{name}”中的全部会话,项目和文件夹仍会保留。",
|
|
1900
|
+
"workspace.archiveConfirm": "全部归档",
|
|
1901
|
+
"workspace.archivePending": "正在归档会话…",
|
|
1902
|
+
"workspace.openPath": "在资源管理器中打开",
|
|
1903
|
+
"workspace.delete": "移除项目",
|
|
1904
|
+
"workspace.deleteDescription": "将把“{name}”从项目列表中移除。文件夹和会话记录会保留。",
|
|
1905
|
+
"workspace.deletePending": "正在移除项目…",
|
|
1906
|
+
"workspace.actions": "{name} 的项目操作",
|
|
1907
|
+
"workspace.taskCount": "{count} 个任务",
|
|
1908
|
+
"workspace.taskSummary": "{count} 个任务 · {unreadCount} 条未读",
|
|
1909
|
+
"workspace.edit": "重命名项目",
|
|
1910
|
+
"sessions.close": "关闭",
|
|
1911
|
+
"sessions.cancel": "取消",
|
|
1912
|
+
"sessions.save": "保存",
|
|
1913
|
+
"sessions.failed": "操作失败:{message}",
|
|
1914
|
+
"sessions.unknown": "找不到该会话。",
|
|
1915
|
+
"sessions.actions": "{name} 的会话操作",
|
|
1916
|
+
"sessions.markUnread": "标记为未读",
|
|
1917
|
+
"sessions.markRead": "标记为已读",
|
|
1918
|
+
"sessions.unread": "未读",
|
|
1919
|
+
"sessions.waitingAnswer": "等待回答",
|
|
1920
|
+
"sessions.waitingApproval": "等待审批",
|
|
1921
|
+
"sessions.planReview": "计划待审",
|
|
1922
|
+
"sessions.rename": "重命名会话",
|
|
1923
|
+
"sessions.renameDescription": "保持简短且易于识别",
|
|
1924
|
+
"sessions.renameInvalid": "会话名称无效,请修改后重试。",
|
|
1925
|
+
"sessions.renameFailed": "暂时无法重命名该会话,请稍后重试。",
|
|
1926
|
+
"sessions.archive": "归档会话",
|
|
1927
|
+
"sessions.archiveFailed": "暂时无法归档该会话,请稍后重试。",
|
|
1928
|
+
"sessions.delete": "删除会话",
|
|
1929
|
+
"sessions.deleteDescription": "将永久删除会话“{name}”及其全部记录,此操作不可恢复。",
|
|
1930
|
+
"sessions.deletePending": "正在删除会话…",
|
|
1931
|
+
"sessions.deleteUnavailable": "未安装或无法连接归档管理插件。",
|
|
1932
|
+
"sessions.deleteFailed": "暂时无法删除该会话,请稍后重试。",
|
|
1933
|
+
"sessions.fork": "在新会话中继续",
|
|
1934
|
+
"sessions.forkUnavailable": "当前会话暂时无法在新会话中继续,请等待本轮结束后重试。",
|
|
1935
|
+
"sessions.forkFailed": "暂时无法创建新会话,请稍后重试。",
|
|
1936
|
+
"sessions.openPath": "在资源管理器中打开",
|
|
1937
|
+
"sessions.moveWorkspace": "项目",
|
|
1938
|
+
"sessions.moveUnavailable": "会话移动服务暂不可用,请重启 DSH 后重试。",
|
|
1939
|
+
"sessions.moveUnauthorized": "登录状态已失效,请刷新页面并重新登录。",
|
|
1940
|
+
"sessions.moveForbidden": "当前访问地址不受信任,或请求来源已被拒绝。",
|
|
1941
|
+
"sessions.moveNotFound": "会话或目标项目已不存在,请刷新后重试。",
|
|
1942
|
+
"sessions.moveSubagent": "子代理会话不能移动到其他项目。",
|
|
1943
|
+
"sessions.moveBusy": "该会话正在移动,请稍后重试。",
|
|
1944
|
+
"sessions.moveRollbackFailed": "会话移动失败,自动恢复未完整完成,请查看 DSH 服务端日志。",
|
|
1945
|
+
"sessions.moveFailed": "暂时无法移动该会话,原会话已保留。",
|
|
1946
|
+
"sessions.moveConfirmTitle": "将会话移至“{project}”?",
|
|
1947
|
+
"sessions.moveConfirmDescription": "“{name}”将移动到此项目,工作目录也会随之更改。",
|
|
1948
|
+
"sessions.moveConfirmAction": "移动",
|
|
1949
|
+
"sessions.movePending": "正在移动会话…",
|
|
1950
|
+
"sessions.copyPath": "复制工作目录",
|
|
1951
|
+
"sessions.copyTitle": "复制会话标题",
|
|
1952
|
+
"sessions.copyId": "复制会话 ID",
|
|
1953
|
+
"connectors.title": "连接器",
|
|
1954
|
+
"connectors.description": "显示当前会话可用的 MCP 连接器及其工具,不显示地址、命令或凭证。",
|
|
1955
|
+
"connectors.openSession": "请先打开一个会话以读取连接器。",
|
|
1956
|
+
"connectors.loading": "正在读取连接器…",
|
|
1957
|
+
"connectors.failed": "连接器目录暂不可用,请稍后重试。",
|
|
1958
|
+
"connectors.toolCount": "{count} 个工具",
|
|
1959
|
+
"connectors.empty": "当前会话没有可用的 MCP 连接器。",
|
|
1960
|
+
"connectors.promptReady": "已带入新会话",
|
|
1961
|
+
"connectors.promptRequired": "Prompt 不能为空。",
|
|
1962
|
+
"connectors.workspacesLoading": "DSH 工作区数据尚未就绪,请稍后重试。",
|
|
1963
|
+
"connectors.workspaceRequired": "请先选择一个工作区,再使用示例 Prompt。",
|
|
1964
|
+
"connectors.workspaceUnavailable": "DSH 工作区服务尚未就绪,请稍后重试。",
|
|
1965
|
+
"connectors.conversationUnavailable": "DSH 对话服务尚未就绪,请稍后重试。",
|
|
1966
|
+
"connectors.sessionPending": "新会话尚未就绪,请稍后重试。",
|
|
1967
|
+
"about.nav": "Codex UI",
|
|
1968
|
+
"about.title": "Codex UI",
|
|
1969
|
+
"about.viewProject": "GitHub",
|
|
1970
|
+
"about.feedback": "问题反馈",
|
|
1971
|
+
"about.description": "为 DSH 提供 Codex 风格的侧栏、工作区会话管理、搜索与会话轮次导航。",
|
|
1972
|
+
"about.features": "基本功能",
|
|
1973
|
+
"about.feature.sidebar": "Codex 风格侧栏、全局会话搜索与设置入口",
|
|
1974
|
+
"about.feature.search": "通过顶部搜索快速定位会话、设置页面和新建任务等快捷操作",
|
|
1975
|
+
"about.feature.workspace": "项目文件夹可展开、折叠、拖拽排序和置顶,并保留原生工作区能力",
|
|
1976
|
+
"about.feature.sessions": "会话支持重命名、置顶、未读标记、归档、删除及右键快捷操作",
|
|
1977
|
+
"about.feature.conversation": "保留原生消息、工具调用、输入、权限和模型选择,仅调整容器视觉",
|
|
1978
|
+
"about.feature.navigator": "当前会话提供轮次缩略导航,可快速跳转至每一次用户提问",
|
|
1979
|
+
"about.dependencies": "配套管理插件",
|
|
1980
|
+
"about.dependenciesDescription": "每个配套插件都可单独安装和更新,也可一键安装缺失项并更新旧版本。可分别检查 Codex UI、专家、技能、归档、IM、定时任务、BTW、Simplify、PUA、代码审查、宠物和应用市场。",
|
|
1981
|
+
"about.loading": "正在读取依赖安装状态…",
|
|
1982
|
+
"about.statusFailed": "暂时无法读取依赖安装状态。",
|
|
1983
|
+
"about.refreshFailed": "依赖状态刷新失败,当前显示上次成功读取的结果。",
|
|
1984
|
+
"about.installed": "已安装",
|
|
1985
|
+
"about.missing": "未安装",
|
|
1986
|
+
"about.updateAvailable": "可更新",
|
|
1987
|
+
"about.install": "安装",
|
|
1988
|
+
"about.update": "更新",
|
|
1989
|
+
"about.installing": "安装中",
|
|
1990
|
+
"about.updateAll": "全部安装/更新",
|
|
1991
|
+
"about.updatingAll": "安装/更新中",
|
|
1992
|
+
"about.upToDate": "所有配套插件均已安装并为最新版本。",
|
|
1993
|
+
"about.installFailed": "依赖安装失败,请稍后重试。",
|
|
1994
|
+
"about.installUnchanged": "安装命令已结束,但插件没有进入当前 Profile。请重试。",
|
|
1995
|
+
"about.installExitDesktop": "请先完全退出 DSH Desktop,再重新打开后更新。",
|
|
1996
|
+
"about.installStoreMismatch": "插件目录与 pnpm 仓库不一致。请完全退出 DSH Desktop 后再更新。",
|
|
1997
|
+
"about.installExitWeb": "无法覆盖正在运行的插件文件。请停止当前 DSH Web 后,在终端更新插件,再重新启动 DSH Web。",
|
|
1998
|
+
"about.installStoreMismatchWeb": "插件目录与 pnpm 仓库不一致。请停止当前 DSH Web 后重试;仍失败时重新安装该 Profile 的依赖。",
|
|
1999
|
+
"about.installWebFailed": "插件更新失败。请查看 DSH Web 终端输出后重试。",
|
|
2000
|
+
"about.installBuildPolicy": "插件依赖的 pnpm 构建脚本策略尚未确认,请更新 Profile 配置后重试。",
|
|
2001
|
+
"about.installPnpmMissing": "当前环境找不到 pnpm,请安装 pnpm 并重启 DSH 后重试。",
|
|
2002
|
+
"about.installTimeout": "插件安装超时,请检查网络后重试。",
|
|
2003
|
+
"about.installServiceUnavailable": "DSH Desktop 依赖管理服务尚未就绪,请重启后重试。",
|
|
2004
|
+
"about.restartRequired": "正在热更新插件,窗口会自动刷新。",
|
|
2005
|
+
"about.restartManually": "安装完成。当前 Web 无法自动重启,请重启 DSH Web 后刷新页面。",
|
|
2006
|
+
"about.progressHint": "正在安装插件…",
|
|
2007
|
+
"about.packagesDone": "已完成 {0} 个包",
|
|
2008
|
+
"about.progress.resolving": "解析依赖",
|
|
2009
|
+
"about.progress.downloading": "下载中",
|
|
2010
|
+
"about.progress.linking": "写入文件",
|
|
2011
|
+
"about.progress.building": "构建中",
|
|
2012
|
+
"about.dependency.dsh": "DeepSeek Harness",
|
|
2013
|
+
"about.dependency.suite": "Codex 套件",
|
|
2014
|
+
"about.dependency.ui": "Codex UI",
|
|
2015
|
+
"about.dependency.experts": "专家管理",
|
|
2016
|
+
"about.dependency.skills": "技能管理",
|
|
2017
|
+
"about.dependency.archive": "归档会话",
|
|
2018
|
+
"about.dependency.im": "IM助理",
|
|
2019
|
+
"about.dependency.schedule": "定时任务",
|
|
2020
|
+
"about.dependency.pet": "Codex 宠物",
|
|
2021
|
+
"about.dependency.btw": "BTW 旁问",
|
|
2022
|
+
"about.dependency.simplify": "Simplify",
|
|
2023
|
+
"about.dependency.pua": "PUA",
|
|
2024
|
+
"about.dependency.review": "代码审查",
|
|
2025
|
+
"about.dependency.market": "应用市场",
|
|
2026
|
+
"about.totalDownloads": "npm 累计下载",
|
|
2027
|
+
"about.downloadsUnavailable": "暂不可用",
|
|
2028
|
+
"about.downloadsSource": "npm 官方统计:自包创建日至昨日的累计下载次数,点击查看 npm 包",
|
|
2029
|
+
"turns.label": "当前会话轮次导航",
|
|
2030
|
+
"turns.untitled": "未命名提问",
|
|
2031
|
+
"turns.jump": "跳转到第 {index} 轮:{summary}",
|
|
2032
|
+
"time.justNow": "刚刚",
|
|
2033
|
+
"time.minutes": "{count}分",
|
|
2034
|
+
"time.hours": "{count}小时",
|
|
2035
|
+
"time.days": "{count}天",
|
|
2036
|
+
"errors.unauthorized": "请先登录 DSH,再重试。",
|
|
2037
|
+
"errors.forbidden": "请求被安全检查拒绝,请从可信的 DSH 地址重试。",
|
|
2038
|
+
"errors.serviceUnavailable": "服务暂不可用,请稍后重试。",
|
|
2039
|
+
"workspace.preferencesLocalOnly": "项目偏好尚未同步,当前使用本地缓存。",
|
|
2040
|
+
"errors.generic": "操作未完成,请重试。",
|
|
2041
|
+
"errors.groupInvalid": "分组信息无效。",
|
|
2042
|
+
"errors.workspaceInvalid": "项目标识无效。",
|
|
2043
|
+
"errors.groupMissing": "目标分组不存在。",
|
|
2044
|
+
"errors.orderAnchorMissing": "排序位置已失效,请重试。",
|
|
2045
|
+
"sessionTitle.system": "为编程助手会话生成标题。\n只输出一行,恰好两段:类型|主题\n类型必须是其中一个:{types}\n主题必须是具体事项,不能重复类型,不要写类型名或空主题。不要写日期或表情,不要引号、前缀、解释或 Markdown。\n主题使用用户消息的语言。",
|
|
2046
|
+
"sessionTitle.userFrame": "根据这条用户消息生成会话标题。只返回“类型|主题”一行,不要第三段,不要重复主题。",
|
|
2047
|
+
"sessionTitle.type.feature": "功能",
|
|
2048
|
+
"sessionTitle.type.design": "设计",
|
|
2049
|
+
"sessionTitle.type.fix": "修复",
|
|
2050
|
+
"sessionTitle.type.optimize": "优化",
|
|
2051
|
+
"sessionTitle.type.release": "发布",
|
|
2052
|
+
"sessionTitle.type.explore": "探索",
|
|
2053
|
+
"sessionTitle.type.docs": "文档",
|
|
2054
|
+
"sessionTitle.type.research": "研究",
|
|
2055
|
+
"meta.locale": "zh-CN"
|
|
2056
|
+
};
|
|
2057
|
+
const en = {
|
|
2058
|
+
"home.resizeInput": "Drag to resize the input area",
|
|
2059
|
+
"home.projectSearch": "Search projects",
|
|
2060
|
+
"home.projectEmpty": "No matching projects",
|
|
2061
|
+
"home.suggestions": "Start a task",
|
|
2062
|
+
"home.workspace": "Choose a workspace first, then select a task.",
|
|
2063
|
+
"home.draft": "Your draft and attachments are preserved. Clear the input to use a suggestion.",
|
|
2064
|
+
"home.busy": "The input is busy. Please try again shortly.",
|
|
2065
|
+
"home.explore": "Explore and understand code",
|
|
2066
|
+
"home.build": "Build features, apps, or tools",
|
|
2067
|
+
"home.review": "Review code and suggest changes",
|
|
2068
|
+
"home.fix": "Fix bugs and failures",
|
|
2069
|
+
"home.explore.task1": "Explain the project structure",
|
|
2070
|
+
"home.explore.prompt1": "Explore this workspace and explain its structure, main entry points, and module relationships.",
|
|
2071
|
+
"home.explore.task2": "Understand a feature",
|
|
2072
|
+
"home.explore.prompt2": "Help me understand a feature. First ask which feature, then explain its implementation and call flow.",
|
|
2073
|
+
"home.build.task1": "Implement a feature",
|
|
2074
|
+
"home.build.prompt1": "Help me implement a feature in this project. First clarify requirements and acceptance criteria.",
|
|
2075
|
+
"home.build.task2": "Build a small tool",
|
|
2076
|
+
"home.build.prompt2": "Help me build a small tool. First ask about its goal, inputs, and outputs, then propose an approach.",
|
|
2077
|
+
"home.review.task1": "Review current changes",
|
|
2078
|
+
"home.review.prompt1": "Review uncommitted changes for bugs, regressions, and missing tests. Do not modify files yet.",
|
|
2079
|
+
"home.review.task2": "Inspect code quality",
|
|
2080
|
+
"home.review.prompt2": "Inspect code quality in this project and suggest improvements grounded in the source. Assess only; do not edit.",
|
|
2081
|
+
"home.fix.task1": "Investigate a bug",
|
|
2082
|
+
"home.fix.prompt1": "Help me fix a bug. First ask for reproduction steps, expected behavior, and actual behavior.",
|
|
2083
|
+
"home.fix.task2": "Fix failing tests",
|
|
2084
|
+
"home.fix.prompt2": "Run the applicable project tests, identify failure causes, and make minimal fixes with meaningful regression coverage.",
|
|
2085
|
+
"usage.title": "Usage statistics",
|
|
2086
|
+
"usage.loading": "Loading billing dashboard…",
|
|
2087
|
+
"usage.failed": "The embedded dashboard is unavailable. Retry or open the original dialog.",
|
|
2088
|
+
"usage.closed": "The billing dashboard is closed.",
|
|
2089
|
+
"usage.reopen": "Reopen billing dashboard",
|
|
2090
|
+
"usage.retry": "Retry",
|
|
2091
|
+
"usage.original": "Open original dialog",
|
|
2092
|
+
"settings.title": "Settings",
|
|
2093
|
+
"settings.back": "Back to app",
|
|
2094
|
+
"settings.openDocument": "Open configuration file",
|
|
2095
|
+
"settings.advanced": "Advanced",
|
|
2096
|
+
"settings.documentTitle": "Configuration file",
|
|
2097
|
+
"settings.documentDescription": "View and edit configuration in an external editor",
|
|
2098
|
+
"settings.documentOpen": "Open",
|
|
2099
|
+
"settings.documentOpening": "Opening…",
|
|
2100
|
+
"settings.openDocumentError": "Could not open configuration file",
|
|
2101
|
+
"settings.search": "Search settings…",
|
|
2102
|
+
"settings.personal": "Personal",
|
|
2103
|
+
"settings.integrations": "Integrations",
|
|
2104
|
+
"settings.records": "Records and app",
|
|
2105
|
+
"settings.general": "General",
|
|
2106
|
+
"settings.permissions": "Permissions",
|
|
2107
|
+
"settings.editor": "Editor",
|
|
2108
|
+
"settings.noResults": "No matching settings categories",
|
|
2109
|
+
"settings.filterHint": "Navigation is filtered. Select a category to switch pages.",
|
|
2110
|
+
"settings.disconnected": "Disconnected",
|
|
2111
|
+
"settings.reconnect": "Reconnect now",
|
|
2112
|
+
"settings.connecting": "Connecting",
|
|
2113
|
+
"settings.recovered": "Connected",
|
|
2114
|
+
"input.historyHint": "↑↓ History",
|
|
2115
|
+
"sidebar.label": "DSH navigation",
|
|
2116
|
+
"sidebar.expand": "Expand sidebar",
|
|
2117
|
+
"sidebar.collapse": "Collapse sidebar",
|
|
2118
|
+
"sidebar.newTask": "New task",
|
|
2119
|
+
"sidebar.search": "Search conversations",
|
|
2120
|
+
"sidebar.mainMenu": "Main menu",
|
|
2121
|
+
"sidebar.extensions": "Extensions",
|
|
2122
|
+
"sidebar.experts": "Experts",
|
|
2123
|
+
"sidebar.skills": "Skills",
|
|
2124
|
+
"sidebar.plugins": "Plugins",
|
|
2125
|
+
"sidebar.marketplace": "Plugin Market",
|
|
2126
|
+
"sidebar.connectors": "Connectors",
|
|
2127
|
+
"sidebar.schedule": "Scheduled tasks",
|
|
2128
|
+
"sidebar.assistant": "IM Assistant",
|
|
2129
|
+
"sidebar.imSettings": "IM Assistant",
|
|
2130
|
+
"sidebar.tasksTab": "Tasks",
|
|
2131
|
+
"sidebar.channelsTab": "Channels",
|
|
2132
|
+
"sidebar.scheduleTab": "Schedule",
|
|
2133
|
+
"sidebar.runsTab": "Run history",
|
|
2134
|
+
"sidebar.overviewTab": "Task overview",
|
|
2135
|
+
"channels.empty": "No channel conversations yet. Connect a channel in Settings → IM Assistant.",
|
|
2136
|
+
"channels.loadError": "Could not load channel conversations.",
|
|
2137
|
+
"channel.dingtalk": "DingTalk",
|
|
2138
|
+
"channel.feishu": "Feishu",
|
|
2139
|
+
"channel.lark": "Lark",
|
|
2140
|
+
"channel.weixin": "WeChat",
|
|
2141
|
+
"channel.wecom": "WeCom",
|
|
2142
|
+
"channel.qq": "QQ",
|
|
2143
|
+
"channel.telegram": "Telegram",
|
|
2144
|
+
"channel.unknown": "Channel",
|
|
2145
|
+
"schedule.empty": "No scheduled-task runs yet.",
|
|
2146
|
+
"schedule.groupActions": "Task actions for {name}",
|
|
2147
|
+
"schedule.taskSettings": "Task settings",
|
|
2148
|
+
"schedule.archiveGroup": "Archive all conversations",
|
|
2149
|
+
"schedule.archiveGroupConfirm": "Archive all",
|
|
2150
|
+
"schedule.archiveGroupDescription": "Archive all {count} conversations for “{name}”. You can restore them later in Settings → Archived.",
|
|
2151
|
+
"schedule.archiveGroupPending": "Archiving conversations…",
|
|
2152
|
+
"schedule.archiveGroupPartial": "Archived {archived} conversations; {failed} failed. You can retry the remaining conversations.",
|
|
2153
|
+
"search.placeholder": "Search conversations, settings, and actions",
|
|
2154
|
+
"search.empty": "No matching results.",
|
|
2155
|
+
"search.sessions": "Conversations",
|
|
2156
|
+
"search.settings": "Settings",
|
|
2157
|
+
"search.actions": "Quick actions",
|
|
2158
|
+
"workspace.label": "Workspace conversations",
|
|
2159
|
+
"workspace.projects": "Projects",
|
|
2160
|
+
"workspace.ungrouped": "Ungrouped",
|
|
2161
|
+
"workspace.createGroup": "Create group",
|
|
2162
|
+
"workspace.createGroupDescription": "Enter a group name. Projects will not move automatically.",
|
|
2163
|
+
"workspace.groupName": "Group name",
|
|
2164
|
+
"workspace.moveToGroup": "Move to group",
|
|
2165
|
+
"workspace.removeFromGroup": "Move to ungrouped",
|
|
2166
|
+
"workspace.deleteGroup": "Delete group {name}",
|
|
2167
|
+
"workspace.deleteGroupAction": "Delete group",
|
|
2168
|
+
"workspace.deleteGroupDescription": "Deleting this group moves its projects to Ungrouped. It does not delete projects or conversations.",
|
|
2169
|
+
"workspace.groupActions": "Actions for group {name}",
|
|
2170
|
+
"workspace.renameGroup": "Rename group",
|
|
2171
|
+
"workspace.recent": "Recents",
|
|
2172
|
+
"workspace.recentEmpty": "No chats",
|
|
2173
|
+
"workspace.noChat": "No chats",
|
|
2174
|
+
"workspace.empty": "No projects",
|
|
2175
|
+
"workspace.newSession": "New conversation",
|
|
2176
|
+
"workspace.rename": "Rename project",
|
|
2177
|
+
"workspace.pin": "Pin project",
|
|
2178
|
+
"workspace.unpin": "Unpin",
|
|
2179
|
+
"workspace.archive": "Archive all conversations",
|
|
2180
|
+
"workspace.archiveTitle": "Archive {count} conversations?",
|
|
2181
|
+
"workspace.archiveDescription": "This archives every conversation in “{name}”. The project and folder remain available.",
|
|
2182
|
+
"workspace.archiveConfirm": "Archive all",
|
|
2183
|
+
"workspace.archivePending": "Archiving conversations…",
|
|
2184
|
+
"workspace.pinned": "Pinned",
|
|
2185
|
+
"workspace.pinnedEmpty": "Drag a project here to pin it",
|
|
2186
|
+
"workspace.openPath": "Open in file explorer",
|
|
2187
|
+
"workspace.delete": "Remove project",
|
|
2188
|
+
"workspace.deleteDescription": "This removes “{name}” from the project list. The folder and conversation records remain.",
|
|
2189
|
+
"workspace.deletePending": "Removing project…",
|
|
2190
|
+
"workspace.actions": "Project actions for {name}",
|
|
2191
|
+
"workspace.taskCount": "{count} tasks",
|
|
2192
|
+
"workspace.taskSummary": "{count} tasks · {unreadCount} unread",
|
|
2193
|
+
"workspace.edit": "Rename project",
|
|
2194
|
+
"sessions.close": "Close",
|
|
2195
|
+
"sessions.cancel": "Cancel",
|
|
2196
|
+
"sessions.save": "Save",
|
|
2197
|
+
"sessions.failed": "Action failed: {message}",
|
|
2198
|
+
"sessions.unknown": "Conversation not found.",
|
|
2199
|
+
"sessions.actions": "Conversation actions for {name}",
|
|
2200
|
+
"sessions.markUnread": "Mark as unread",
|
|
2201
|
+
"sessions.markRead": "Mark as read",
|
|
2202
|
+
"sessions.unread": "Unread",
|
|
2203
|
+
"sessions.waitingAnswer": "Waiting for answer",
|
|
2204
|
+
"sessions.waitingApproval": "Waiting for approval",
|
|
2205
|
+
"sessions.planReview": "Plan awaiting review",
|
|
2206
|
+
"sessions.rename": "Rename conversation",
|
|
2207
|
+
"sessions.renameDescription": "Keep it short and easy to recognize.",
|
|
2208
|
+
"sessions.renameInvalid": "The conversation name is invalid. Change it and try again.",
|
|
2209
|
+
"sessions.renameFailed": "The conversation could not be renamed. Try again later.",
|
|
2210
|
+
"sessions.archive": "Archive conversation",
|
|
2211
|
+
"sessions.archiveFailed": "The conversation could not be archived. Try again later.",
|
|
2212
|
+
"sessions.delete": "Delete conversation",
|
|
2213
|
+
"sessions.deleteDescription": "This permanently deletes “{name}” and all of its records. This cannot be undone.",
|
|
2214
|
+
"sessions.deletePending": "Deleting conversation…",
|
|
2215
|
+
"sessions.deleteUnavailable": "The archive manager plugin is not installed or unavailable.",
|
|
2216
|
+
"sessions.deleteFailed": "The conversation could not be deleted. Try again later.",
|
|
2217
|
+
"sessions.fork": "Continue in new conversation",
|
|
2218
|
+
"sessions.forkUnavailable": "This conversation cannot be continued in a new conversation right now. Try again after the current turn finishes.",
|
|
2219
|
+
"sessions.forkFailed": "A new conversation could not be created. Try again later.",
|
|
2220
|
+
"sessions.openPath": "Open in file explorer",
|
|
2221
|
+
"sessions.moveWorkspace": "Project",
|
|
2222
|
+
"sessions.moveUnavailable": "Conversation moving is unavailable. Restart DSH and try again.",
|
|
2223
|
+
"sessions.moveUnauthorized": "Your login session has expired. Refresh the page and sign in again.",
|
|
2224
|
+
"sessions.moveForbidden": "The current address is not trusted, or the request origin was rejected.",
|
|
2225
|
+
"sessions.moveNotFound": "The conversation or target project no longer exists. Refresh and try again.",
|
|
2226
|
+
"sessions.moveSubagent": "Subagent conversations cannot be moved to another project.",
|
|
2227
|
+
"sessions.moveBusy": "This conversation is already being moved. Try again shortly.",
|
|
2228
|
+
"sessions.moveRollbackFailed": "The conversation could not be moved, and automatic recovery did not complete. Check the DSH server logs.",
|
|
2229
|
+
"sessions.moveFailed": "The conversation could not be moved. The original conversation was preserved.",
|
|
2230
|
+
"sessions.moveConfirmTitle": "Move conversation to “{project}”?",
|
|
2231
|
+
"sessions.moveConfirmDescription": "“{name}” will move to this project, and its working directory will change with it.",
|
|
2232
|
+
"sessions.moveConfirmAction": "Move",
|
|
2233
|
+
"sessions.movePending": "Moving conversation…",
|
|
2234
|
+
"sessions.copyPath": "Copy working directory",
|
|
2235
|
+
"sessions.copyTitle": "Copy conversation title",
|
|
2236
|
+
"sessions.copyId": "Copy conversation ID",
|
|
2237
|
+
"connectors.title": "Connectors",
|
|
2238
|
+
"connectors.description": "Shows MCP connectors and tools available to this session without exposing addresses, commands, or credentials.",
|
|
2239
|
+
"connectors.openSession": "Open a conversation to load connectors.",
|
|
2240
|
+
"connectors.loading": "Loading connectors…",
|
|
2241
|
+
"connectors.failed": "Connectors are unavailable. Try again shortly.",
|
|
2242
|
+
"connectors.toolCount": "{count} tools",
|
|
2243
|
+
"connectors.empty": "This conversation has no available MCP connectors.",
|
|
2244
|
+
"connectors.promptReady": "Added to a new conversation",
|
|
2245
|
+
"connectors.promptRequired": "The prompt cannot be empty.",
|
|
2246
|
+
"connectors.workspacesLoading": "DSH workspace data is still loading. Try again shortly.",
|
|
2247
|
+
"connectors.workspaceRequired": "Select a workspace before using an example prompt.",
|
|
2248
|
+
"connectors.workspaceUnavailable": "The DSH workspace service is not ready. Try again shortly.",
|
|
2249
|
+
"connectors.conversationUnavailable": "The DSH conversation service is not ready. Try again shortly.",
|
|
2250
|
+
"connectors.sessionPending": "The new conversation is not ready yet. Try again shortly.",
|
|
2251
|
+
"about.nav": "Codex UI",
|
|
2252
|
+
"about.title": "Codex UI",
|
|
2253
|
+
"about.viewProject": "GitHub",
|
|
2254
|
+
"about.feedback": "Issues",
|
|
2255
|
+
"about.description": "Adds a Codex-style sidebar, workspace conversation management, search, and turn navigation to DSH.",
|
|
2256
|
+
"about.features": "Core features",
|
|
2257
|
+
"about.feature.sidebar": "Codex-style sidebar, global conversation search, and settings entry points",
|
|
2258
|
+
"about.feature.search": "Find conversations, settings pages, and quick actions from the top search panel",
|
|
2259
|
+
"about.feature.workspace": "Expand, collapse, reorder, and pin workspace folders while retaining native workspace behavior",
|
|
2260
|
+
"about.feature.sessions": "Rename, pin, mark unread, archive, delete, or use context actions for conversations",
|
|
2261
|
+
"about.feature.conversation": "Keeps native messages, tool calls, composer, permissions, and model selection while refining the container visuals",
|
|
2262
|
+
"about.feature.navigator": "Navigate directly to each user prompt with the current conversation turn navigator",
|
|
2263
|
+
"about.dependencies": "Companion management plugins",
|
|
2264
|
+
"about.dependenciesDescription": "Install or update each companion separately, or install missing plugins and update old versions in one action. Check Codex UI, expert, skill, archive, IM, scheduled-task, BTW, Simplify, PUA, code review, pet, and marketplace plugins on their own.",
|
|
2265
|
+
"about.loading": "Loading dependency status…",
|
|
2266
|
+
"about.statusFailed": "Dependency status is temporarily unavailable.",
|
|
2267
|
+
"about.refreshFailed": "Dependency status refresh failed. Showing the last successful result.",
|
|
2268
|
+
"about.installed": "Installed",
|
|
2269
|
+
"about.missing": "Not installed",
|
|
2270
|
+
"about.updateAvailable": "Update available",
|
|
2271
|
+
"about.install": "Install",
|
|
2272
|
+
"about.update": "Update",
|
|
2273
|
+
"about.installing": "Installing",
|
|
2274
|
+
"about.updateAll": "Install/update all",
|
|
2275
|
+
"about.updatingAll": "Installing/updating",
|
|
2276
|
+
"about.upToDate": "All companion plugins are installed and up to date.",
|
|
2277
|
+
"about.installFailed": "Dependency installation failed. Try again later.",
|
|
2278
|
+
"about.installUnchanged": "The install command finished, but the plugin did not enter this profile. Try again.",
|
|
2279
|
+
"about.installExitDesktop": "Quit DSH Desktop completely, reopen it, and try the update again.",
|
|
2280
|
+
"about.installStoreMismatch": "The plugin directory and pnpm store do not match. Quit DSH Desktop completely and try again.",
|
|
2281
|
+
"about.installExitWeb": "Running plugin files cannot be replaced. Stop the current DSH Web host, update the plugin from a terminal, then restart DSH Web.",
|
|
2282
|
+
"about.installStoreMismatchWeb": "The plugin directory and pnpm store do not match. Stop the current DSH Web host and try again; if it persists, reinstall this Profile's dependencies.",
|
|
2283
|
+
"about.installWebFailed": "Plugin update failed. Check the DSH Web terminal output and try again.",
|
|
2284
|
+
"about.installBuildPolicy": "The pnpm build-script policy for this plugin is not configured. Update the Profile configuration and try again.",
|
|
2285
|
+
"about.installPnpmMissing": "pnpm is not available. Install pnpm, restart DSH, and try again.",
|
|
2286
|
+
"about.installTimeout": "Plugin installation timed out. Check the network and try again.",
|
|
2287
|
+
"about.installServiceUnavailable": "The DSH Desktop dependency service is not ready. Restart DSH Desktop and try again.",
|
|
2288
|
+
"about.restartRequired": "Updating plugins now. The window will refresh automatically.",
|
|
2289
|
+
"about.restartManually": "Installation complete. This Web host cannot restart itself; restart DSH Web, then refresh the page.",
|
|
2290
|
+
"about.progressHint": "Installing plugin…",
|
|
2291
|
+
"about.packagesDone": "{0} packages done",
|
|
2292
|
+
"about.progress.resolving": "Resolving",
|
|
2293
|
+
"about.progress.downloading": "Downloading",
|
|
2294
|
+
"about.progress.linking": "Writing files",
|
|
2295
|
+
"about.progress.building": "Building",
|
|
2296
|
+
"about.dependency.dsh": "DeepSeek Harness",
|
|
2297
|
+
"about.dependency.suite": "Codex suite",
|
|
2298
|
+
"about.dependency.ui": "Codex UI",
|
|
2299
|
+
"about.dependency.experts": "Expert management",
|
|
2300
|
+
"about.dependency.skills": "Skill management",
|
|
2301
|
+
"about.dependency.archive": "Archived conversations",
|
|
2302
|
+
"about.dependency.im": "IM Assistant",
|
|
2303
|
+
"about.dependency.schedule": "Scheduled tasks",
|
|
2304
|
+
"about.dependency.pet": "Codex Pet",
|
|
2305
|
+
"about.dependency.btw": "BTW",
|
|
2306
|
+
"about.dependency.simplify": "Simplify",
|
|
2307
|
+
"about.dependency.pua": "PUA",
|
|
2308
|
+
"about.dependency.review": "Code Review",
|
|
2309
|
+
"about.dependency.market": "App Market",
|
|
2310
|
+
"about.totalDownloads": "npm total downloads",
|
|
2311
|
+
"about.downloadsUnavailable": "Unavailable",
|
|
2312
|
+
"about.downloadsSource": "Cumulative npm downloads from package creation through yesterday. View package on npm.",
|
|
2313
|
+
"turns.label": "Conversation turn navigation",
|
|
2314
|
+
"turns.untitled": "Untitled prompt",
|
|
2315
|
+
"turns.jump": "Jump to turn {index}: {summary}",
|
|
2316
|
+
"time.justNow": "Just now",
|
|
2317
|
+
"time.minutes": "{count}m",
|
|
2318
|
+
"time.hours": "{count}h",
|
|
2319
|
+
"time.days": "{count}d",
|
|
2320
|
+
"errors.unauthorized": "Sign in to DSH, then try again.",
|
|
2321
|
+
"errors.forbidden": "The security check rejected this request. Try again from a trusted DSH address.",
|
|
2322
|
+
"errors.serviceUnavailable": "The service is unavailable. Try again later.",
|
|
2323
|
+
"workspace.preferencesLocalOnly": "Workspace preferences are not synced. Using the local cache.",
|
|
2324
|
+
"errors.generic": "The action could not be completed. Try again.",
|
|
2325
|
+
"errors.groupInvalid": "The group information is invalid.",
|
|
2326
|
+
"errors.workspaceInvalid": "The project identifier is invalid.",
|
|
2327
|
+
"errors.groupMissing": "The target group no longer exists.",
|
|
2328
|
+
"errors.orderAnchorMissing": "The target position is no longer available. Try again.",
|
|
2329
|
+
"sessionTitle.system": "Generate a title for a coding-assistant conversation.\nOutput exactly one line with two parts: Type|Theme\nType must be one of: {types}\nThe theme must be a concrete subject and must not repeat the type. Do not write dates, emoji, quotes, prefixes, explanations, or Markdown.\nWrite the theme in the language of the user message.",
|
|
2330
|
+
"sessionTitle.userFrame": "Generate a conversation title from this user message. Return only one Type|Theme line. Do not add a third part or repeat the theme.",
|
|
2331
|
+
"sessionTitle.type.feature": "Feature",
|
|
2332
|
+
"sessionTitle.type.design": "Design",
|
|
2333
|
+
"sessionTitle.type.fix": "Fix",
|
|
2334
|
+
"sessionTitle.type.optimize": "Optimize",
|
|
2335
|
+
"sessionTitle.type.release": "Release",
|
|
2336
|
+
"sessionTitle.type.explore": "Explore",
|
|
2337
|
+
"sessionTitle.type.docs": "Docs",
|
|
2338
|
+
"sessionTitle.type.research": "Research",
|
|
2339
|
+
"meta.locale": "en-US"
|
|
2340
|
+
};
|
|
2341
|
+
//#endregion
|
|
1559
2342
|
//#region src/session-title.ts
|
|
1560
|
-
const
|
|
1561
|
-
"
|
|
1562
|
-
"
|
|
1563
|
-
"
|
|
1564
|
-
"
|
|
1565
|
-
"
|
|
1566
|
-
"
|
|
1567
|
-
"
|
|
1568
|
-
"
|
|
2343
|
+
const SESSION_TITLE_KINDS = [
|
|
2344
|
+
"feature",
|
|
2345
|
+
"design",
|
|
2346
|
+
"fix",
|
|
2347
|
+
"optimize",
|
|
2348
|
+
"release",
|
|
2349
|
+
"explore",
|
|
2350
|
+
"docs",
|
|
2351
|
+
"research"
|
|
1569
2352
|
];
|
|
1570
2353
|
const SESSION_TITLE_EMOJI = {
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
2354
|
+
feature: "✨",
|
|
2355
|
+
design: "🎨",
|
|
2356
|
+
fix: "🐛",
|
|
2357
|
+
optimize: "⚡",
|
|
2358
|
+
release: "🚀",
|
|
2359
|
+
explore: "🔍",
|
|
2360
|
+
docs: "📝",
|
|
2361
|
+
research: "🔬"
|
|
2362
|
+
};
|
|
2363
|
+
const TYPE_KEYS = {
|
|
2364
|
+
feature: "sessionTitle.type.feature",
|
|
2365
|
+
design: "sessionTitle.type.design",
|
|
2366
|
+
fix: "sessionTitle.type.fix",
|
|
2367
|
+
optimize: "sessionTitle.type.optimize",
|
|
2368
|
+
release: "sessionTitle.type.release",
|
|
2369
|
+
explore: "sessionTitle.type.explore",
|
|
2370
|
+
docs: "sessionTitle.type.docs",
|
|
2371
|
+
research: "sessionTitle.type.research"
|
|
1579
2372
|
};
|
|
1580
|
-
const TITLE_TYPES = new Set(SESSION_TITLE_TYPES);
|
|
1581
|
-
const LEADING_EMOJI = /^(?:\p{Extended_Pictographic}(?:\uFE0F|\u200D\p{Extended_Pictographic})*)+\s*/u;
|
|
1582
|
-
const TITLE_SEPARATORS = /[||│]/g;
|
|
1583
2373
|
const GENERIC_THEME_SUFFIXES = [
|
|
1584
2374
|
"类型",
|
|
1585
2375
|
"分类",
|
|
@@ -1589,8 +2379,84 @@ const GENERIC_THEME_SUFFIXES = [
|
|
|
1589
2379
|
"会话",
|
|
1590
2380
|
"标题",
|
|
1591
2381
|
"主题",
|
|
1592
|
-
"内容"
|
|
2382
|
+
"内容",
|
|
2383
|
+
"type",
|
|
2384
|
+
"category",
|
|
2385
|
+
"related",
|
|
2386
|
+
"request",
|
|
2387
|
+
"issue",
|
|
2388
|
+
"session",
|
|
2389
|
+
"title",
|
|
2390
|
+
"topic",
|
|
2391
|
+
"content"
|
|
1593
2392
|
];
|
|
2393
|
+
const LEADING_EMOJI = /^(?:\p{Extended_Pictographic}(?:\uFE0F|\u200D\p{Extended_Pictographic})*)+\s*/u;
|
|
2394
|
+
const TITLE_SEPARATORS = /[||│]/g;
|
|
2395
|
+
function typeLabel(kind, locale) {
|
|
2396
|
+
const key = TYPE_KEYS[kind];
|
|
2397
|
+
return locale === "en" ? en[key] : zh[key];
|
|
2398
|
+
}
|
|
2399
|
+
function typeAliasMap() {
|
|
2400
|
+
const aliases = /* @__PURE__ */ new Map();
|
|
2401
|
+
for (const kind of SESSION_TITLE_KINDS) {
|
|
2402
|
+
aliases.set(kind, kind);
|
|
2403
|
+
aliases.set(zh[TYPE_KEYS[kind]], kind);
|
|
2404
|
+
aliases.set(en[TYPE_KEYS[kind]].toLowerCase(), kind);
|
|
2405
|
+
}
|
|
2406
|
+
return aliases;
|
|
2407
|
+
}
|
|
2408
|
+
const TYPE_ALIASES = typeAliasMap();
|
|
2409
|
+
function resolveKind(raw) {
|
|
2410
|
+
return TYPE_ALIASES.get(raw) ?? TYPE_ALIASES.get(raw.toLowerCase());
|
|
2411
|
+
}
|
|
2412
|
+
function sessionTitleTypeLabels(locale) {
|
|
2413
|
+
return SESSION_TITLE_KINDS.map((kind) => typeLabel(kind, locale)).join(locale === "en" ? ", " : "、");
|
|
2414
|
+
}
|
|
2415
|
+
function sessionTitlePrompt(locale) {
|
|
2416
|
+
const dict = locale === "en" ? en : zh;
|
|
2417
|
+
const types = sessionTitleTypeLabels(locale);
|
|
2418
|
+
return {
|
|
2419
|
+
system: dict["sessionTitle.system"].replaceAll("{types}", types),
|
|
2420
|
+
userFrame: dict["sessionTitle.userFrame"]
|
|
2421
|
+
};
|
|
2422
|
+
}
|
|
2423
|
+
function localeFromTag(tag) {
|
|
2424
|
+
const lower = tag.trim().toLowerCase();
|
|
2425
|
+
if (lower === "" || lower === "c" || lower === "posix") return void 0;
|
|
2426
|
+
if (lower === "en" || lower.startsWith("en-")) return "en";
|
|
2427
|
+
if (lower === "zh" || lower.startsWith("zh-")) return "zh";
|
|
2428
|
+
}
|
|
2429
|
+
function parseLocaleValue(value) {
|
|
2430
|
+
if (typeof value === "string") return localeFromTag(value);
|
|
2431
|
+
if (value === null || typeof value !== "object") return void 0;
|
|
2432
|
+
const row = value;
|
|
2433
|
+
if (typeof row.getSnapshot === "function") return parseLocaleValue(row.getSnapshot());
|
|
2434
|
+
for (const key of [
|
|
2435
|
+
"locale",
|
|
2436
|
+
"lang",
|
|
2437
|
+
"language",
|
|
2438
|
+
"current"
|
|
2439
|
+
]) {
|
|
2440
|
+
const parsed = parseLocaleValue(row[key]);
|
|
2441
|
+
if (parsed !== void 0) return parsed;
|
|
2442
|
+
}
|
|
2443
|
+
}
|
|
2444
|
+
function inferLocaleFromMessage(text) {
|
|
2445
|
+
if (text === void 0 || text.trim() === "") return void 0;
|
|
2446
|
+
if (/[㐀-鿿]/.test(text)) return "zh";
|
|
2447
|
+
if (/[A-Za-z]/.test(text)) return "en";
|
|
2448
|
+
}
|
|
2449
|
+
function resolveSessionTitleLocale(source, message) {
|
|
2450
|
+
return parseLocaleValue(source) ?? inferLocaleFromMessage(message) ?? "zh";
|
|
2451
|
+
}
|
|
2452
|
+
function inferLocaleFromTypeToken(token) {
|
|
2453
|
+
const lower = token.toLowerCase();
|
|
2454
|
+
for (const kind of SESSION_TITLE_KINDS) {
|
|
2455
|
+
if (en[TYPE_KEYS[kind]].toLowerCase() === lower) return "en";
|
|
2456
|
+
if (zh[TYPE_KEYS[kind]] === token) return "zh";
|
|
2457
|
+
}
|
|
2458
|
+
return "zh";
|
|
2459
|
+
}
|
|
1594
2460
|
function stripLeadingNoise(part) {
|
|
1595
2461
|
const trimmed = part.trim();
|
|
1596
2462
|
if (/^\d{4}$/.test(trimmed)) return "";
|
|
@@ -1600,22 +2466,44 @@ function parseTypeAndTheme(raw) {
|
|
|
1600
2466
|
const parts = raw.trim().replace(/^["'`]+|["'`]+$/g, "").trim().replace(TITLE_SEPARATORS, "|").split("|").map(stripLeadingNoise).filter((part) => part !== "");
|
|
1601
2467
|
const type = parts[0];
|
|
1602
2468
|
const theme = parts[1];
|
|
1603
|
-
if (type === void 0 || theme === void 0
|
|
2469
|
+
if (type === void 0 || theme === void 0) return void 0;
|
|
2470
|
+
const kind = resolveKind(type);
|
|
2471
|
+
if (kind === void 0) return void 0;
|
|
1604
2472
|
return {
|
|
1605
|
-
type,
|
|
2473
|
+
type: kind,
|
|
1606
2474
|
theme
|
|
1607
2475
|
};
|
|
1608
2476
|
}
|
|
1609
|
-
function
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
2477
|
+
function compactToken(value) {
|
|
2478
|
+
return value.replace(/[\s·.\-_/]/g, "");
|
|
2479
|
+
}
|
|
2480
|
+
function isRedundantAgainst(type, theme) {
|
|
2481
|
+
const normalized = compactToken(theme);
|
|
2482
|
+
const typeNorm = compactToken(type);
|
|
2483
|
+
if (normalized === "" || typeNorm === "") return false;
|
|
2484
|
+
if (normalized.toLowerCase() === typeNorm.toLowerCase()) return true;
|
|
2485
|
+
if (normalized.toLowerCase() === `新${typeNorm}`.toLowerCase()) return true;
|
|
2486
|
+
if (normalized.toLowerCase() === `new${typeNorm}`.toLowerCase()) return true;
|
|
2487
|
+
if (typeNorm !== "" && normalized.length >= typeNorm.length * 2 && normalized.length % typeNorm.length === 0 && normalized.toLowerCase() === typeNorm.repeat(normalized.length / typeNorm.length).toLowerCase()) return true;
|
|
2488
|
+
return GENERIC_THEME_SUFFIXES.some((suffix) => {
|
|
2489
|
+
const compactSuffix = compactToken(suffix);
|
|
2490
|
+
return normalized.toLowerCase() === compactSuffix.toLowerCase() || normalized.toLowerCase() === `${typeNorm}${compactSuffix}`.toLowerCase();
|
|
2491
|
+
});
|
|
1614
2492
|
}
|
|
1615
|
-
function
|
|
2493
|
+
function isRedundantTheme(type, theme) {
|
|
2494
|
+
const kind = resolveKind(type);
|
|
2495
|
+
return (kind === void 0 ? [type] : [
|
|
2496
|
+
typeLabel(kind, "zh"),
|
|
2497
|
+
typeLabel(kind, "en"),
|
|
2498
|
+
kind
|
|
2499
|
+
]).some((label) => isRedundantAgainst(label, theme));
|
|
2500
|
+
}
|
|
2501
|
+
function assembleSessionTitle(type, theme, locale) {
|
|
1616
2502
|
const parsed = parseTypeAndTheme(`${type}|${theme}`);
|
|
1617
2503
|
if (parsed === void 0) return void 0;
|
|
1618
|
-
const
|
|
2504
|
+
const displayLocale = locale ?? inferLocaleFromTypeToken(type);
|
|
2505
|
+
const label = typeLabel(parsed.type, displayLocale);
|
|
2506
|
+
const title = isRedundantTheme(parsed.type, parsed.theme) ? `${SESSION_TITLE_EMOJI[parsed.type]} ${label}` : `${SESSION_TITLE_EMOJI[parsed.type]} ${label}|${parsed.theme}`;
|
|
1619
2507
|
if (Buffer.byteLength(title, "utf8") > 80) return void 0;
|
|
1620
2508
|
return title;
|
|
1621
2509
|
}
|
|
@@ -1629,13 +2517,6 @@ const SESSION_TITLE_PROVIDER_ID = "michengai-codex-ui-session-title";
|
|
|
1629
2517
|
const MAX_INPUT_BYTES = 4096;
|
|
1630
2518
|
const MAX_OUTPUT_TOKENS = 64;
|
|
1631
2519
|
const TIMEOUT_MS = 6e4;
|
|
1632
|
-
const SYSTEM_PROMPT = [
|
|
1633
|
-
"为编程助手会话生成标题。",
|
|
1634
|
-
"只输出一行,恰好两段:类型|主题",
|
|
1635
|
-
"类型必须是其中一个:功能、设计、修复、优化、发布、探索、文档、研究",
|
|
1636
|
-
"主题必须是具体事项,不能重复类型,不要写“功能”“功能类型”这类空主题。不要写日期或表情,不要引号、前缀、解释或 Markdown。",
|
|
1637
|
-
"使用用户消息的语言。"
|
|
1638
|
-
].join("\n");
|
|
1639
2520
|
function asTitleService(value) {
|
|
1640
2521
|
if (value === null || typeof value !== "object") return void 0;
|
|
1641
2522
|
return typeof value.register === "function" ? value : void 0;
|
|
@@ -1694,7 +2575,10 @@ function collectText(chunks) {
|
|
|
1694
2575
|
return [...parts.values()].map((segment) => segment.text).join("").trim().split(/\r?\n/, 1)[0] ?? "";
|
|
1695
2576
|
})();
|
|
1696
2577
|
}
|
|
1697
|
-
|
|
2578
|
+
function hostLocale(host, message) {
|
|
2579
|
+
return resolveSessionTitleLocale(host.locale ?? host.get("locale"), message);
|
|
2580
|
+
}
|
|
2581
|
+
async function generateCodexSessionTitle(llm, request, locale = "zh") {
|
|
1698
2582
|
request.signal?.throwIfAborted();
|
|
1699
2583
|
const session = request.session;
|
|
1700
2584
|
if (session === void 0 || shouldSkipAutoTitle(session)) throw new Error("codex-ui session title skipped");
|
|
@@ -1703,7 +2587,8 @@ async function generateCodexSessionTitle(llm, request) {
|
|
|
1703
2587
|
if (first === void 0 || text === "") throw new Error("codex-ui session title missing message");
|
|
1704
2588
|
const route = request.route;
|
|
1705
2589
|
if (route === void 0 || route.provider === "" || route.model === "") throw new Error("codex-ui session title missing route");
|
|
1706
|
-
const
|
|
2590
|
+
const prompt = sessionTitlePrompt(locale);
|
|
2591
|
+
const framed = `${prompt.userFrame}\n${JSON.stringify([{
|
|
1707
2592
|
seq: first.seq,
|
|
1708
2593
|
text
|
|
1709
2594
|
}])}`;
|
|
@@ -1724,7 +2609,7 @@ async function generateCodexSessionTitle(llm, request) {
|
|
|
1724
2609
|
plugin: "michengai-codex-ui"
|
|
1725
2610
|
}
|
|
1726
2611
|
}],
|
|
1727
|
-
system:
|
|
2612
|
+
system: prompt.system,
|
|
1728
2613
|
maxTokens: MAX_OUTPUT_TOKENS,
|
|
1729
2614
|
sessionId: session.id,
|
|
1730
2615
|
purpose: "session-title",
|
|
@@ -1733,7 +2618,7 @@ async function generateCodexSessionTitle(llm, request) {
|
|
|
1733
2618
|
signal.throwIfAborted();
|
|
1734
2619
|
const parsed = parseTypeAndTheme(raw);
|
|
1735
2620
|
if (parsed === void 0) throw new Error("codex-ui session title invalid model output");
|
|
1736
|
-
const title = assembleSessionTitle(parsed.type, parsed.theme);
|
|
2621
|
+
const title = assembleSessionTitle(parsed.type, parsed.theme, locale);
|
|
1737
2622
|
if (title === void 0) throw new Error("codex-ui session title rejected");
|
|
1738
2623
|
return {
|
|
1739
2624
|
title,
|
|
@@ -1749,7 +2634,7 @@ function registerSessionTitleProvider(ctx) {
|
|
|
1749
2634
|
const dispose = sessionTitle.register({
|
|
1750
2635
|
id: SESSION_TITLE_PROVIDER_ID,
|
|
1751
2636
|
automatic: "first-prompt",
|
|
1752
|
-
generate: (request) => generateCodexSessionTitle(llm, request)
|
|
2637
|
+
generate: (request) => generateCodexSessionTitle(llm, request, hostLocale(ctx, request.messages?.[0]?.text))
|
|
1753
2638
|
});
|
|
1754
2639
|
return typeof dispose === "function" ? dispose : void 0;
|
|
1755
2640
|
} catch (error) {
|