@michengai/dsh-codex-ui 1.1.7 → 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/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 { zstdCompress } = await import("node:zlib");
1224
- if (typeof zstdCompress !== "function") throw new SessionMoveError("session-move/zstd-unavailable", "当前 Node.js 运行时不支持 Zstd 会话迁移。");
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 compress(Buffer.from(body, "utf8"));
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
- if (!this.backupCreated) return;
1313
- try {
1314
- await rm(this.backupArtifact, { force: true });
1315
- this.backupCreated = false;
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 = (await services.sessionPersistence.list()).find((header) => header.id === sessionId);
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.loadStored(sessionId) : void 0;
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 raw = await services.sessionPersistence.readRaw(sessionId);
1480
- const originalStored = await services.sessionPersistence.loadStored(sessionId);
1481
- if (raw === void 0 || originalStored === void 0) throw new SessionMoveError("session-move/session-not-found", "读取会话持久化记录失败。");
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(headerText);
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
- ...raw.meta,
1701
+ ...originalStored.meta,
1498
1702
  cwd: targetPath
1499
1703
  };
1500
- const oldLocation = services.sessionPersistence.locate(raw.meta);
1501
- const newLocation = services.sessionPersistence.locate(targetMeta);
1502
- if (oldLocation === void 0 || newLocation === void 0) throw new SessionMoveError("session-move/path-invalid", "宿主无法定位会话工件。");
1503
- const bytes = await (options.encodeArtifact ?? encodeSessionArtifact)(JSON.stringify(targetHeader), body, newLocation.path.endsWith(".zstd"));
1504
- const transaction = new ArtifactDirectoryMove(oldLocation.path, newLocation.path);
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
- const movedStored = await services.sessionPersistence.loadStored(sessionId);
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 {
@@ -1555,6 +1768,881 @@ async function moveSessionToWorkspace(services, sessionId, targetWorkspaceId, op
1555
1768
  movingSessions.delete(sessionId);
1556
1769
  }
1557
1770
  }
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
2342
+ //#region src/session-title.ts
2343
+ const SESSION_TITLE_KINDS = [
2344
+ "feature",
2345
+ "design",
2346
+ "fix",
2347
+ "optimize",
2348
+ "release",
2349
+ "explore",
2350
+ "docs",
2351
+ "research"
2352
+ ];
2353
+ const SESSION_TITLE_EMOJI = {
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"
2372
+ };
2373
+ const GENERIC_THEME_SUFFIXES = [
2374
+ "类型",
2375
+ "分类",
2376
+ "相关",
2377
+ "需求",
2378
+ "问题",
2379
+ "会话",
2380
+ "标题",
2381
+ "主题",
2382
+ "内容",
2383
+ "type",
2384
+ "category",
2385
+ "related",
2386
+ "request",
2387
+ "issue",
2388
+ "session",
2389
+ "title",
2390
+ "topic",
2391
+ "content"
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
+ }
2460
+ function stripLeadingNoise(part) {
2461
+ const trimmed = part.trim();
2462
+ if (/^\d{4}$/.test(trimmed)) return "";
2463
+ return trimmed.replace(/^\d{4}\s*/, "").replace(LEADING_EMOJI, "").trim();
2464
+ }
2465
+ function parseTypeAndTheme(raw) {
2466
+ const parts = raw.trim().replace(/^["'`]+|["'`]+$/g, "").trim().replace(TITLE_SEPARATORS, "|").split("|").map(stripLeadingNoise).filter((part) => part !== "");
2467
+ const type = parts[0];
2468
+ const theme = parts[1];
2469
+ if (type === void 0 || theme === void 0) return void 0;
2470
+ const kind = resolveKind(type);
2471
+ if (kind === void 0) return void 0;
2472
+ return {
2473
+ type: kind,
2474
+ theme
2475
+ };
2476
+ }
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
+ });
2492
+ }
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) {
2502
+ const parsed = parseTypeAndTheme(`${type}|${theme}`);
2503
+ if (parsed === void 0) return void 0;
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}`;
2507
+ if (Buffer.byteLength(title, "utf8") > 80) return void 0;
2508
+ return title;
2509
+ }
2510
+ function shouldSkipAutoTitle(session) {
2511
+ const origin = session.origin ?? session.header?.origin;
2512
+ return session.id.startsWith("dsh-automation-session-") || session.id.startsWith("im:") || origin === "subagent" || session.header?.parentSession !== void 0 && session.header.parentSession !== "";
2513
+ }
2514
+ //#endregion
2515
+ //#region src/session-title-provider.ts
2516
+ const SESSION_TITLE_PROVIDER_ID = "michengai-codex-ui-session-title";
2517
+ const MAX_INPUT_BYTES = 4096;
2518
+ const MAX_OUTPUT_TOKENS = 64;
2519
+ const TIMEOUT_MS = 6e4;
2520
+ function asTitleService(value) {
2521
+ if (value === null || typeof value !== "object") return void 0;
2522
+ return typeof value.register === "function" ? value : void 0;
2523
+ }
2524
+ function asLlm(value) {
2525
+ if (value === null || typeof value !== "object") return void 0;
2526
+ return typeof value.stream === "function" ? value : void 0;
2527
+ }
2528
+ function abortableSignal(signal) {
2529
+ const timeout = AbortSignal.timeout(TIMEOUT_MS);
2530
+ return signal === void 0 ? timeout : AbortSignal.any([signal, timeout]);
2531
+ }
2532
+ /**
2533
+ * Assemble the streamed text the way the host's `BlockAssembler` does: deltas
2534
+ * accumulate per block index and a closing `block-end` replaces them, so the
2535
+ * adapters that send both channels cannot duplicate the title text.
2536
+ */
2537
+ function collectText(chunks) {
2538
+ return (async () => {
2539
+ const parts = /* @__PURE__ */ new Map();
2540
+ let finish = "stop";
2541
+ const part = (index) => {
2542
+ let existing = parts.get(index);
2543
+ if (existing === void 0) {
2544
+ existing = {
2545
+ text: "",
2546
+ closed: false
2547
+ };
2548
+ parts.set(index, existing);
2549
+ }
2550
+ return existing;
2551
+ };
2552
+ for await (const chunk of chunks) {
2553
+ if (chunk === null || typeof chunk !== "object") continue;
2554
+ const row = chunk;
2555
+ if (row.type === "tool-call-delta" || row.block?.type === "tool-call") throw new Error("codex-ui session title output must be text");
2556
+ if (row.type === "finish") {
2557
+ finish = typeof row.reason?.kind === "string" ? row.reason.kind : "unknown";
2558
+ continue;
2559
+ }
2560
+ if (row.type !== "text-delta" && row.type !== "block-end") continue;
2561
+ if (typeof row.index !== "number" || !Number.isSafeInteger(row.index)) continue;
2562
+ if (row.type === "text-delta") {
2563
+ if (typeof row.text !== "string") continue;
2564
+ const target = part(row.index);
2565
+ if (!target.closed) target.text += row.text;
2566
+ continue;
2567
+ }
2568
+ if (row.block?.type !== "text" || typeof row.block.text !== "string") continue;
2569
+ const target = part(row.index);
2570
+ if (target.closed) continue;
2571
+ target.text = row.block.text;
2572
+ target.closed = true;
2573
+ }
2574
+ if (finish !== "stop") throw new Error(`codex-ui session title output finished with "${finish}"`);
2575
+ return [...parts.values()].map((segment) => segment.text).join("").trim().split(/\r?\n/, 1)[0] ?? "";
2576
+ })();
2577
+ }
2578
+ function hostLocale(host, message) {
2579
+ return resolveSessionTitleLocale(host.locale ?? host.get("locale"), message);
2580
+ }
2581
+ async function generateCodexSessionTitle(llm, request, locale = "zh") {
2582
+ request.signal?.throwIfAborted();
2583
+ const session = request.session;
2584
+ if (session === void 0 || shouldSkipAutoTitle(session)) throw new Error("codex-ui session title skipped");
2585
+ const first = request.messages?.[0];
2586
+ const text = first?.text?.trim() ?? "";
2587
+ if (first === void 0 || text === "") throw new Error("codex-ui session title missing message");
2588
+ const route = request.route;
2589
+ if (route === void 0 || route.provider === "" || route.model === "") throw new Error("codex-ui session title missing route");
2590
+ const prompt = sessionTitlePrompt(locale);
2591
+ const framed = `${prompt.userFrame}\n${JSON.stringify([{
2592
+ seq: first.seq,
2593
+ text
2594
+ }])}`;
2595
+ if (Buffer.byteLength(framed, "utf8") > MAX_INPUT_BYTES) throw new Error("codex-ui session title input too large");
2596
+ const signal = abortableSignal(request.signal);
2597
+ const raw = await collectText(llm.stream({
2598
+ provider: route.provider,
2599
+ model: route.model,
2600
+ messages: [{
2601
+ id: crypto.randomUUID(),
2602
+ role: "user",
2603
+ content: [{
2604
+ type: "text",
2605
+ text: framed
2606
+ }],
2607
+ source: {
2608
+ kind: "plugin",
2609
+ plugin: "michengai-codex-ui"
2610
+ }
2611
+ }],
2612
+ system: prompt.system,
2613
+ maxTokens: MAX_OUTPUT_TOKENS,
2614
+ sessionId: session.id,
2615
+ purpose: "session-title",
2616
+ signal
2617
+ }));
2618
+ signal.throwIfAborted();
2619
+ const parsed = parseTypeAndTheme(raw);
2620
+ if (parsed === void 0) throw new Error("codex-ui session title invalid model output");
2621
+ const title = assembleSessionTitle(parsed.type, parsed.theme, locale);
2622
+ if (title === void 0) throw new Error("codex-ui session title rejected");
2623
+ return {
2624
+ title,
2625
+ messageSeqs: typeof first.seq === "number" ? [first.seq] : [],
2626
+ model: route
2627
+ };
2628
+ }
2629
+ function registerSessionTitleProvider(ctx) {
2630
+ const sessionTitle = asTitleService(ctx.get("sessionTitle"));
2631
+ const llm = asLlm(ctx.get("llm"));
2632
+ if (sessionTitle === void 0 || llm === void 0) return void 0;
2633
+ try {
2634
+ const dispose = sessionTitle.register({
2635
+ id: SESSION_TITLE_PROVIDER_ID,
2636
+ automatic: "first-prompt",
2637
+ generate: (request) => generateCodexSessionTitle(llm, request, hostLocale(ctx, request.messages?.[0]?.text))
2638
+ });
2639
+ return typeof dispose === "function" ? dispose : void 0;
2640
+ } catch (error) {
2641
+ const reason = error instanceof Error ? error.message : typeof error;
2642
+ ctx.logger?.warn(`Codex UI 未能注册会话标题提供方:${reason}`);
2643
+ return;
2644
+ }
2645
+ }
1558
2646
  /** 校验结构和归属;存储兼容模式保留旧 locale 下合法的大小写变体名称。 */
1559
2647
  function parseGroups(value, preserveCaseVariants) {
1560
2648
  if (!Array.isArray(value) || value.length > 100) return void 0;
@@ -1773,6 +2861,7 @@ function sessionMoveAuthenticationError(status, error) {
1773
2861
  /** 提供不泄露地址、命令和凭证的连接器目录。 */
1774
2862
  function apply(ctx) {
1775
2863
  apply$1(ctx);
2864
+ ctx.effect(() => registerSessionTitleProvider(ctx) ?? (() => {}), "michengai-codex-ui: session title");
1776
2865
  const host = hostServices(ctx);
1777
2866
  ctx.effect(() => {
1778
2867
  const disposers = USAGE_FRAME_ROUTES.map((path) => host.webServer.register({