cc-viewer 1.6.348 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. package/README.md +13 -1
  2. package/cli.js +116 -37
  3. package/dist/assets/App-DTkacfRW.js +2 -0
  4. package/dist/assets/{MdxEditorPanel-ualoRFOL.js → MdxEditorPanel-C5Q6uUyH.js} +1 -1
  5. package/dist/assets/Mobile-MoBp9c8k.js +1 -0
  6. package/dist/assets/{_baseUniq-CG81mgN_.js → _baseUniq-D9_E5ZDp.js} +1 -1
  7. package/dist/assets/{arc-C161J4f2.js → arc-D4iFrlhb.js} +1 -1
  8. package/dist/assets/{architectureDiagram-Q4EWVU46-BmbDMdn9.js → architectureDiagram-Q4EWVU46-f1neo7hg.js} +1 -1
  9. package/dist/assets/{blockDiagram-DXYQGD6D-DXpMvdxd.js → blockDiagram-DXYQGD6D-DWi9e9tG.js} +1 -1
  10. package/dist/assets/{c4Diagram-AHTNJAMY-8Mypg0IU.js → c4Diagram-AHTNJAMY-DAdj49A2.js} +1 -1
  11. package/dist/assets/{channel-B2khdmvc.js → channel-B3VthT0m.js} +1 -1
  12. package/dist/assets/{chunk-4BX2VUAB-I61r1g6z.js → chunk-4BX2VUAB-BrPcAHV3.js} +1 -1
  13. package/dist/assets/{chunk-4TB4RGXK-VFPlRE5E.js → chunk-4TB4RGXK-Bvopc1V9.js} +1 -1
  14. package/dist/assets/{chunk-55IACEB6-DvCamg_6.js → chunk-55IACEB6-Juw-7Z3i.js} +1 -1
  15. package/dist/assets/{chunk-EDXVE4YY-Dj4Q8RwR.js → chunk-EDXVE4YY-DVYP9tpB.js} +1 -1
  16. package/dist/assets/{chunk-FMBD7UC4-_bgQuuJh.js → chunk-FMBD7UC4-VQ7VOXl3.js} +1 -1
  17. package/dist/assets/{chunk-OYMX7WX6-3to38LyN.js → chunk-OYMX7WX6-F8Rj4ZUZ.js} +1 -1
  18. package/dist/assets/{chunk-QZHKN3VN-Zl26wKKz.js → chunk-QZHKN3VN-Dn7K3yi9.js} +1 -1
  19. package/dist/assets/{chunk-YZCP3GAM-RLYPrlh0.js → chunk-YZCP3GAM-UjO-KTsn.js} +1 -1
  20. package/dist/assets/classDiagram-6PBFFD2Q-DmSozxoO.js +1 -0
  21. package/dist/assets/classDiagram-v2-HSJHXN6E-DmSozxoO.js +1 -0
  22. package/dist/assets/clone-BQtvy9Ae.js +1 -0
  23. package/dist/assets/{cose-bilkent-S5V4N54A-CsYOjJ0V.js → cose-bilkent-S5V4N54A-BDhNj8rq.js} +1 -1
  24. package/dist/assets/{dagre-KV5264BT-C3YXXaFU.js → dagre-KV5264BT-BzdcDuyt.js} +1 -1
  25. package/dist/assets/{diagram-5BDNPKRD-DUCOg1ld.js → diagram-5BDNPKRD-DP_SX65N.js} +1 -1
  26. package/dist/assets/{diagram-G4DWMVQ6-BtlLl_zX.js → diagram-G4DWMVQ6-B-pBFw8Q.js} +1 -1
  27. package/dist/assets/{diagram-MMDJMWI5-1Qp9xfOk.js → diagram-MMDJMWI5-6XSfN1eN.js} +1 -1
  28. package/dist/assets/{diagram-TYMM5635-Cj7VMp3L.js → diagram-TYMM5635-DJvGxvpE.js} +1 -1
  29. package/dist/assets/{erDiagram-SMLLAGMA-CVZVn541.js → erDiagram-SMLLAGMA-CpqdpGYj.js} +1 -1
  30. package/dist/assets/{flowDiagram-DWJPFMVM-CiU9Fkkv.js → flowDiagram-DWJPFMVM-C9m7wjwI.js} +1 -1
  31. package/dist/assets/{ganttDiagram-T4ZO3ILL-aRaG97ID.js → ganttDiagram-T4ZO3ILL-CU0XOfY3.js} +1 -1
  32. package/dist/assets/{gitGraphDiagram-UUTBAWPF-Cr2LJlvX.js → gitGraphDiagram-UUTBAWPF-ipyGULnP.js} +1 -1
  33. package/dist/assets/{graph-B59lDyMn.js → graph-BR2KmES9.js} +1 -1
  34. package/dist/assets/{index-C1ptnCpq.js → index--wgRW_OB.js} +1 -1
  35. package/dist/assets/index-Ac8vnV3v.js +2 -0
  36. package/dist/assets/{index-CRAWA8PC.js → index-C-be29ey.js} +1 -1
  37. package/dist/assets/{index-CSM1Kw8l.js → index-C16fiVhv.js} +1 -1
  38. package/dist/assets/{index-Ddk-kg22.js → index-D5YruOKD.js} +1 -1
  39. package/dist/assets/{index-BnhNIjbj.js → index-D5tSeMU7.js} +1 -1
  40. package/dist/assets/{index-vFEQq-NB.js → index-DuZQ6FqR.js} +1 -1
  41. package/dist/assets/{index-CO2hZ1s4.js → index-OWzSX2T8.js} +1 -1
  42. package/dist/assets/{infoDiagram-42DDH7IO-CnO22bw8.js → infoDiagram-42DDH7IO-BQWQjfFX.js} +1 -1
  43. package/dist/assets/{ishikawaDiagram-UXIWVN3A-ByNzpScH.js → ishikawaDiagram-UXIWVN3A-Df-KH_Hz.js} +1 -1
  44. package/dist/assets/{journeyDiagram-VCZTEJTY-yaVaCWPq.js → journeyDiagram-VCZTEJTY-CMbB_4G9.js} +1 -1
  45. package/dist/assets/{jszip.min-BdVFP3OW.js → jszip.min-B6k2t9Ch.js} +1 -1
  46. package/dist/assets/{kanban-definition-6JOO6SKY-RIPR_RQ3.js → kanban-definition-6JOO6SKY-CImdlKN3.js} +1 -1
  47. package/dist/assets/{layout-0rzel0v_.js → layout-DQl4aw09.js} +1 -1
  48. package/dist/assets/{linear-sktN0tB6.js → linear-CLD3aPlK.js} +1 -1
  49. package/dist/assets/mermaid.core-CN35dvKJ.js +7 -0
  50. package/dist/assets/{min-rspGOkz_.js → min-_D86henJ.js} +1 -1
  51. package/dist/assets/{mindmap-definition-QFDTVHPH-BsVzZzTz.js → mindmap-definition-QFDTVHPH-2JVcu1nk.js} +1 -1
  52. package/dist/assets/{pieDiagram-DEJITSTG-CsoWsw4f.js → pieDiagram-DEJITSTG-Dik-m5u7.js} +1 -1
  53. package/dist/assets/{quadrantDiagram-34T5L4WZ-CTMKuCAI.js → quadrantDiagram-34T5L4WZ-Bm5l40Jq.js} +1 -1
  54. package/dist/assets/{requirementDiagram-MS252O5E-C67256te.js → requirementDiagram-MS252O5E-Ck9w5rTj.js} +1 -1
  55. package/dist/assets/{sankeyDiagram-XADWPNL6-X8Y7KnW0.js → sankeyDiagram-XADWPNL6-BbGVCFEf.js} +1 -1
  56. package/dist/assets/seqResourceLoaders-Cd_TAXTb.js +2 -0
  57. package/dist/assets/{seqResourceLoaders-B5Zy_CC_.css → seqResourceLoaders-CxuD1CYl.css} +1 -1
  58. package/dist/assets/{sequenceDiagram-FGHM5R23-Cils0TAp.js → sequenceDiagram-FGHM5R23-vjvEdksk.js} +1 -1
  59. package/dist/assets/{stateDiagram-FHFEXIEX-DFnohvpG.js → stateDiagram-FHFEXIEX-DTqMoiB2.js} +1 -1
  60. package/dist/assets/{stateDiagram-v2-QKLJ7IA2-BTX_4o81.js → stateDiagram-v2-QKLJ7IA2-CNbAwnhu.js} +1 -1
  61. package/dist/assets/{timeline-definition-GMOUNBTQ-DrIYFrzZ.js → timeline-definition-GMOUNBTQ-Cw8O-52d.js} +1 -1
  62. package/dist/assets/{vendor-antd-D5HmSDSg.js → vendor-antd-DI7JL-mE.js} +1 -1
  63. package/dist/assets/{vendor-codemirror-C5yuj7ze.js → vendor-codemirror-B9c49dtM.js} +1 -1
  64. package/dist/assets/{vendor-mdxeditor-BcEooTvb.js → vendor-mdxeditor-B6cpBtIE.js} +2 -2
  65. package/dist/assets/{vendor-qrcode-d9LCn0-9.js → vendor-qrcode-Dn90-s-u.js} +1 -1
  66. package/dist/assets/{vendor-virtuoso-K9m4MN6a.js → vendor-virtuoso-CpdSKD_6.js} +1 -1
  67. package/dist/assets/{vennDiagram-DHZGUBPP-lRqBYTr9.js → vennDiagram-DHZGUBPP-DCuqymb7.js} +1 -1
  68. package/dist/assets/{wardley-RL74JXVD-e8YfxpOe.js → wardley-RL74JXVD-BJ20wJUU.js} +1 -1
  69. package/dist/assets/{wardleyDiagram-NUSXRM2D-Bxrg774h.js → wardleyDiagram-NUSXRM2D-Vz6US3qt.js} +1 -1
  70. package/dist/assets/{xychartDiagram-5P7HB3ND-DWhZqhq8.js → xychartDiagram-5P7HB3ND-CzkLKrmi.js} +1 -1
  71. package/dist/index.html +4 -4
  72. package/findcc.js +4 -3
  73. package/package.json +2 -1
  74. package/server/i18n.js +21 -141
  75. package/server/interceptor.js +211 -403
  76. package/server/lib/async-write-queue.js +12 -1
  77. package/server/lib/delta-reconstructor.js +3 -3
  78. package/server/lib/error-report.js +45 -0
  79. package/server/lib/im-log-watcher.js +68 -8
  80. package/server/lib/interceptor-core.js +19 -174
  81. package/server/lib/log-management.js +214 -247
  82. package/server/lib/log-stream.js +59 -108
  83. package/server/lib/log-watcher.js +34 -272
  84. package/server/lib/log-zip.js +148 -0
  85. package/server/lib/session-boundary.js +266 -0
  86. package/server/lib/session-pin-store.js +10 -19
  87. package/server/lib/stats-worker.js +138 -267
  88. package/server/lib/user-prompt-extract.js +250 -0
  89. package/server/lib/v2/adapter.js +1059 -0
  90. package/server/lib/v2/blob-store.js +44 -0
  91. package/server/lib/v2/conversation-store.js +217 -0
  92. package/server/lib/v2/convert-manager.js +79 -0
  93. package/server/lib/v2/convert-worker.js +42 -0
  94. package/server/lib/v2/convert.js +409 -0
  95. package/server/lib/v2/identity.js +204 -0
  96. package/server/lib/v2/journal.js +62 -0
  97. package/server/lib/v2/layout.js +167 -0
  98. package/server/lib/v2/live-feed.js +451 -0
  99. package/server/lib/v2/migrate-prompt.js +54 -0
  100. package/server/lib/v2/replay.js +191 -0
  101. package/server/lib/v2/session-select.js +222 -0
  102. package/server/lib/v2/singleflight.js +104 -0
  103. package/server/lib/v2/v2-writer.js +513 -0
  104. package/server/lib/v2/verify.js +233 -0
  105. package/server/routes/events.js +30 -130
  106. package/server/routes/files-fs.js +4 -2
  107. package/server/routes/im.js +11 -2
  108. package/server/routes/logs.js +194 -152
  109. package/server/routes/preferences.js +0 -3
  110. package/server/routes/project-meta.js +2 -3
  111. package/server/routes/session-pin.js +7 -8
  112. package/server/routes/workspaces.js +44 -9
  113. package/server/server.js +47 -65
  114. package/server/workspace-registry.js +24 -4
  115. package/dist/assets/App-VTo4NVdo.js +0 -2
  116. package/dist/assets/Mobile-CcmYvMm_.js +0 -1
  117. package/dist/assets/classDiagram-6PBFFD2Q-BXnyUgsj.js +0 -1
  118. package/dist/assets/classDiagram-v2-HSJHXN6E-BXnyUgsj.js +0 -1
  119. package/dist/assets/clone-md-Mc1-T.js +0 -1
  120. package/dist/assets/index-DCo650PG.js +0 -2
  121. package/dist/assets/mermaid.core-C2Parj1q.js +0 -7
  122. package/dist/assets/seqResourceLoaders-BVozq68i.js +0 -2
  123. package/server/lib/instance-registry.js +0 -57
  124. package/server/lib/jsonl-archive.js +0 -180
  125. package/server/lib/teammate-detect.js +0 -32
  126. package/server/lib/zip-safety.js +0 -68
package/server/server.js CHANGED
@@ -74,18 +74,19 @@ function execWithStdin(cmd, args, input, options) {
74
74
  child.stdin.end();
75
75
  });
76
76
  }
77
- import { LOG_FILE, _initPromise, _resumeState, _projectName, _logDir, streamingState, resetStreamingState, PROFILE_PATH, setLivePort, getImLiveText, resetImLiveText } from './interceptor.js';
78
- import { recordInstance, listInstances } from './lib/instance-registry.js';
77
+ import { _initPromise, _projectName, _logDir, _v2Writer, streamingState, resetStreamingState, PROFILE_PATH, setLivePort, getImLiveText, resetImLiveText } from './interceptor.js';
78
+ import { V2LiveFeed } from './lib/v2/live-feed.js';
79
+ import { sanitizePathComponent } from './lib/v2/layout.js';
80
+ import { maybeResumeConvert } from './lib/v2/convert-manager.js';
79
81
  import { LOG_DIR, setLogDir, getClaudeConfigDir, isBrowserOpenSuppressed } from '../findcc.js';
80
82
  import { t, getLang, setLang } from './i18n.js';
81
83
  import { loadAuthConfig, loadAuthState, saveAuthConfig, clearProjectOverride, generatePassword, decideAuth, parseCookies, renderLoginPage, localeFromAcceptLanguage } from './lib/auth.js';
82
84
  import { checkAndUpdate } from './lib/updater.js';
83
85
  import { loadPlugins, runWaterfallHook, runParallelHook } from './lib/plugin-loader.js';
84
86
  import { CONTEXT_WINDOW_FILE, readModelContextSize } from './lib/context-watcher.js';
85
- import { watchLogFile, startWatching, unwatchAll, sendEventToClients, sendToClients } from './lib/log-watcher.js';
87
+ import { sendEventToClients, sendToClients } from './lib/log-watcher.js';
86
88
  import { createImLogWatcher } from './lib/im-log-watcher.js';
87
89
  import { unwatchAllWorkflows } from './lib/workflow-watcher.js';
88
- import { cleanupExtractCache } from './lib/jsonl-archive.js';
89
90
  import { backupConfigs } from './lib/config-backup.js';
90
91
  import { normalizeBasePath, validateBasePath, stripBasePath } from './lib/base-path.js';
91
92
  import { createHardenedCleanup } from './lib/term-signals.js';
@@ -111,12 +112,6 @@ try {
111
112
  const isCliMode = process.env.CCV_CLI_MODE === '1';
112
113
  const isSdkMode = process.env.CCV_SDK_MODE === '1';
113
114
  const isWorkspaceMode = process.env.CCV_WORKSPACE_MODE === '1';
114
- // Instance id from `ccv --pid <name>` (sanitized in cli.js). null = default mode (no
115
- // per-instance isolation): the session-pin file falls back to the project-shared key.
116
- const INSTANCE_ID = process.env.CCV_INSTANCE_ID || null;
117
- // Remember this id under the project so the CLI banner can list past ids next run (memory
118
- // aid only). Fire-and-forget; no-ops when there's no active project (e.g. workspace mode).
119
- if (INSTANCE_ID && _logDir) recordInstance(_logDir, INSTANCE_ID).catch(() => {});
120
115
  const _defaultProxyProfiles = { active: 'max', profiles: [{ id: 'max', name: 'Default' }] };
121
116
  const _maskApiKey = (k) => k && typeof k === 'string' && k.length > 4 ? '****' + k.slice(-4) : k ? '****' : '';
122
117
  const _maskProfiles = (data) => {
@@ -305,9 +300,12 @@ let _launchCallback = null;
305
300
  export function setLaunchCallback(fn) { _launchCallback = fn; }
306
301
  export function setWorkspaceLaunched(v) { _workspaceLaunched = v; }
307
302
  export function initPostLaunch() {
308
- watchLogFile(_logWatcherOpts(LOG_FILE));
303
+ startLogWatch();
309
304
  if (!statsWorker) startStatsWorker();
310
305
  startStreamingStatusTimer();
306
+ // wire-v2 S8: a migration the previous process left unfinished resumes here
307
+ // (resident-until-done, user decision; file-level checkpoints make it cheap).
308
+ try { if (_projectName) maybeResumeConvert(LOG_DIR, _projectName); } catch (err) { console.error('[CC Viewer] wire-v2 convert resume failed:', err.message); }
311
309
  }
312
310
 
313
311
  // Global POST body size limit (10MB) to prevent OOM from malicious/buggy clients
@@ -418,16 +416,34 @@ const MIME_TYPES = {
418
416
  '.woff2': 'font/woff2',
419
417
  };
420
418
 
421
- // Helper to build log-watcher options object
422
- function _logWatcherOpts(logFile) {
423
- return {
424
- logFile: logFile || LOG_FILE,
425
- clients,
426
- getClaudePid,
427
- runParallelHook,
428
- notifyStatsWorker,
429
- getLogFile: () => LOG_FILE,
430
- };
419
+ // S6b: v2 live feed the live channel when the v2 writer is active. Lazy
420
+ // singleton so worker/test paths that never watch anything pay nothing.
421
+ let _v2LiveFeed = null;
422
+ function _ensureV2LiveFeed() {
423
+ if (!_v2LiveFeed) {
424
+ _v2LiveFeed = new V2LiveFeed({
425
+ clients,
426
+ getClaudePid,
427
+ runParallelHook,
428
+ // The stats worker's v2 units are session dirs (stats-worker.js v9).
429
+ notifyStatsWorker,
430
+ });
431
+ }
432
+ return _v2LiveFeed;
433
+ }
434
+
435
+ /** Start the live channel for the current project (v2 live feed). No-op until
436
+ * a project is bound (workspace mode defers to the launch route). */
437
+ function startLogWatch() {
438
+ if (!_projectName) return;
439
+ const feed = _ensureV2LiveFeed();
440
+ feed.start(join(LOG_DIR, sanitizePathComponent(_projectName)));
441
+ _v2Writer.setOnActivity((dir) => feed.tick(dir));
442
+ }
443
+
444
+ function stopLogWatch() {
445
+ if (_v2LiveFeed) _v2LiveFeed.stop();
446
+ _v2Writer.setOnActivity(null);
431
447
  }
432
448
 
433
449
  function getLocalIp() {
@@ -516,7 +532,8 @@ const deps = {
516
532
  persistAskEntry: _persistAskEntry,
517
533
  persistAskDelete: _persistAskDelete,
518
534
  notifyParentPending: _notifyParentPending,
519
- logWatcherOpts: _logWatcherOpts,
535
+ startLogWatch,
536
+ stopLogWatch,
520
537
  scheduleTurnEndBroadcast: _scheduleTurnEndBroadcast,
521
538
  ensureImWatch: _ensureImWatch,
522
539
  maskProfiles: _maskProfiles,
@@ -578,7 +595,6 @@ const deps = {
578
595
  isCliMode,
579
596
  isSdkMode,
580
597
  isWorkspaceMode,
581
- instanceId: INSTANCE_ID,
582
598
  defaultProxyProfiles: _defaultProxyProfiles,
583
599
  };
584
600
 
@@ -855,9 +871,6 @@ export async function startViewer() {
855
871
  // 加载插件(需要在创建服务器之前,以便通过 hook 获取 HTTPS 证书)
856
872
  await loadPlugins();
857
873
 
858
- // 清理过期解压缓存(fire-and-forget;任何错误吞掉)
859
- setImmediate(() => { try { cleanupExtractCache(); } catch { /* ignore */ } });
860
-
861
874
  // 启动期配置备份:preferences/profile/workspaces → LOG_DIR 外的 cc-viewer-config-backups/
862
875
  // (滚动留 10 份)。2026-06-06 事故:配置随 LOG_DIR 整树丢失后无处可恢复。fire-and-forget。
863
876
  setImmediate(() => { try { backupConfigs(); } catch { /* ignore */ } });
@@ -1025,9 +1038,11 @@ export async function startViewer() {
1025
1038
  // 工作区模式下延迟到选择工作区后再启动监听
1026
1039
  if (!isWorkspaceMode) {
1027
1040
  readModelContextSize(); // Cache model→size mapping at startup
1028
- startWatching(_logWatcherOpts(LOG_FILE));
1041
+ startLogWatch();
1029
1042
  startStatsWorker();
1030
1043
  startStreamingStatusTimer();
1044
+ // wire-v2 S8: resume an unfinished migration (see initPostLaunch)
1045
+ try { if (_projectName) maybeResumeConvert(LOG_DIR, _projectName); } catch (err) { console.error('[CC Viewer] wire-v2 convert resume failed:', err.message); }
1031
1046
  }
1032
1047
  // CLI 模式下启动 WebSocket 服务 (必须 await,否则插件 hook 拿不到 upgrade listeners)
1033
1048
  if (isCliMode) {
@@ -2004,15 +2019,6 @@ export function getAuthConfig() {
2004
2019
  return authConfig;
2005
2020
  }
2006
2021
 
2007
- // Instance id (`--pid`) + the project's previously-used ids — printed by cli.js in the
2008
- // CLI-mode startup banner so the user can recall which id to reuse with `-c` next time.
2009
- export function getInstanceId() {
2010
- return INSTANCE_ID;
2011
- }
2012
- export function getKnownInstances() {
2013
- try { return listInstances(_logDir); } catch { return []; }
2014
- }
2015
-
2016
2022
  // In-process broadcast helper for the `turn_end` SSE event. Two callers:
2017
2023
  // 1. /api/turn-end-notify POST handler (PTY/CLI mode via Stop hook bridge)
2018
2024
  // 2. cli.js runSdkMode → sdkManager.initSdkSession({ onTurnEnd }) (SDK mode —
@@ -2238,28 +2244,12 @@ async function _doStop() {
2238
2244
  new Promise(r => setTimeout(r, 3000)),
2239
2245
  ]);
2240
2246
  } catch { }
2241
- // 如果用户未做选择,将临时文件转为正式文件
2242
- if (_resumeState && _resumeState.tempFile) {
2243
- try {
2244
- const { tempFile } = _resumeState;
2245
- if (existsSync(tempFile)) {
2246
- // 只有非空 temp 文件才 rename 为正式文件,空文件直接删除
2247
- const sz = statSync(tempFile).size;
2248
- if (sz > 0) {
2249
- const newPath = tempFile.replace('_temp.jsonl', '.jsonl');
2250
- renameSync(tempFile, newPath);
2251
- } else {
2252
- unlinkSync(tempFile);
2253
- }
2254
- }
2255
- } catch { }
2256
- }
2257
- unwatchAll();
2247
+ stopLogWatch();
2258
2248
  unwatchAllWorkflows();
2259
2249
  unwatchFile(CONTEXT_WINDOW_FILE);
2260
2250
  clients.forEach(client => client.end());
2261
2251
  // Truncate in place (not `clients = []`) so the array reference stays stable across
2262
- // stop/start cycles — deps.clients and _logWatcherOpts() hold this same reference.
2252
+ // stop/start cycles — deps.clients holds this same reference.
2263
2253
  clients.length = 0;
2264
2254
  if (server) {
2265
2255
  // 销毁所有活跃连接,防止 keep-alive 阻止进程退出
@@ -2399,17 +2389,9 @@ if (!isWorkspaceMode) {
2399
2389
  });
2400
2390
  }
2401
2391
 
2402
- // 进程退出时,将未决的临时文件转为正式文件
2403
- function handleExit() {
2404
- if (_resumeState && _resumeState.tempFile) {
2405
- try {
2406
- if (existsSync(_resumeState.tempFile)) {
2407
- const newPath = _resumeState.tempFile.replace('_temp.jsonl', '.jsonl');
2408
- renameSync(_resumeState.tempFile, newPath);
2409
- }
2410
- } catch { }
2411
- }
2412
- }
2392
+ // 进程退出钩子(v1 时代负责临时文件转正;v2 无暂存文件,保留空挂点防
2393
+ // 未来清理动作无处可挂)
2394
+ function handleExit() {}
2413
2395
  // 防御性单次注册:ESM cache 已保证模块顶层只执行一次,但若未来重构把这段
2414
2396
  // 移入函数(cleanupViewer 等)或用 dev hot-reload,缺守卫会导致 handler 堆积
2415
2397
  // → 进程退出时多次调用 stopViewer。globalThis flag 兼容所有 import 路径。
@@ -3,6 +3,7 @@ import { readFileSync, writeFileSync, existsSync, mkdirSync, unlinkSync } from '
3
3
  import { readdir, stat } from 'node:fs/promises';
4
4
  import { renameSyncWithRetry } from './lib/file-api.js';
5
5
  import { withFileLockAsync } from './lib/async-file-lock.js';
6
+ import { dirSizeSync } from './lib/v2/layout.js';
6
7
  import { join, basename, resolve } from 'node:path';
7
8
  import { randomBytes } from 'node:crypto';
8
9
  import { LOG_DIR } from '../findcc.js';
@@ -92,19 +93,38 @@ export async function removeWorkspace(id) {
92
93
  export async function getWorkspaces() {
93
94
  const list = loadWorkspaces();
94
95
  const enriched = await Promise.all(list.map(async (w) => {
95
- let logCount = 0;
96
+ // wire-v2 (1.7.0): logs live in per-session dirs under sessions/. The v1
97
+ // *.jsonl glob is kept as unmigratedV1Count — the combined logCount must
98
+ // stay non-zero for a declined-migration workspace, because the launcher's
99
+ // `logCount > 0 → auto -c` heuristic hangs off it (WorkspaceList.jsx).
100
+ let sessionCount = 0;
101
+ let unmigratedV1Count = 0;
96
102
  let totalSize = 0;
97
103
  const logDir = join(LOG_DIR, w.projectName);
98
104
  try {
99
105
  const files = await readdir(logDir);
100
106
  for (const f of files) {
101
- if (f.endsWith('.jsonl')) {
102
- logCount++;
107
+ if (f.endsWith('.jsonl') && !f.endsWith('_temp.jsonl')) {
108
+ unmigratedV1Count++;
103
109
  try { totalSize += (await stat(join(logDir, f))).size; } catch { }
104
110
  }
105
111
  }
106
112
  } catch { }
107
- return { ...w, logCount, totalSize };
113
+ try {
114
+ const sids = await readdir(join(logDir, 'sessions'), { withFileTypes: true });
115
+ for (const e of sids) {
116
+ if (!e.isDirectory()) continue;
117
+ const sessionDir = join(logDir, 'sessions', e.name);
118
+ try {
119
+ await stat(join(sessionDir, 'journal.jsonl'));
120
+ // Folder size, not journal size — conv/blob/response files carry
121
+ // most of a session's bytes (journal alone undercounts ~12x).
122
+ totalSize += dirSizeSync(sessionDir);
123
+ sessionCount++;
124
+ } catch { /* dir without a journal is not a session yet */ }
125
+ }
126
+ } catch { }
127
+ return { ...w, logCount: sessionCount + unmigratedV1Count, sessionCount, unmigratedV1Count, totalSize };
108
128
  }));
109
129
  return enriched.sort((a, b) => new Date(b.lastUsed) - new Date(a.lastUsed));
110
130
  }