aicodeman 1.16.2 → 1.16.4

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 (158) hide show
  1. package/README.md +11 -10
  2. package/dist/claude-credentials.d.ts +50 -0
  3. package/dist/claude-credentials.d.ts.map +1 -0
  4. package/dist/claude-credentials.js +102 -0
  5. package/dist/claude-credentials.js.map +1 -0
  6. package/dist/config/voice.d.ts +43 -0
  7. package/dist/config/voice.d.ts.map +1 -0
  8. package/dist/config/voice.js +48 -0
  9. package/dist/config/voice.js.map +1 -0
  10. package/dist/search-service.d.ts +9 -1
  11. package/dist/search-service.d.ts.map +1 -1
  12. package/dist/search-service.js +13 -3
  13. package/dist/search-service.js.map +1 -1
  14. package/dist/services/unified-session-service.d.ts +9 -0
  15. package/dist/services/unified-session-service.d.ts.map +1 -1
  16. package/dist/services/unified-session-service.js +4 -1
  17. package/dist/services/unified-session-service.js.map +1 -1
  18. package/dist/types/search.d.ts +17 -3
  19. package/dist/types/search.d.ts.map +1 -1
  20. package/dist/web/ports/config-port.d.ts +2 -0
  21. package/dist/web/ports/config-port.d.ts.map +1 -1
  22. package/dist/web/public/admin-ui.js +35 -20
  23. package/dist/web/public/admin-ui.js.br +0 -0
  24. package/dist/web/public/admin-ui.js.gz +0 -0
  25. package/dist/web/public/api-client.c9b1cddc.js.gz +0 -0
  26. package/dist/web/public/{app.4d006245.js → app.f9c6aadd.js} +18 -18
  27. package/dist/web/public/app.f9c6aadd.js.br +0 -0
  28. package/dist/web/public/app.f9c6aadd.js.gz +0 -0
  29. package/dist/web/public/approvals-ui.js.gz +0 -0
  30. package/dist/web/public/{constants.7d0f4047.js → constants.53dc0fa1.js} +43 -0
  31. package/dist/web/public/constants.53dc0fa1.js.br +0 -0
  32. package/dist/web/public/constants.53dc0fa1.js.gz +0 -0
  33. package/dist/web/public/cron-ui.js.gz +0 -0
  34. package/dist/web/public/entrance-animations.js.gz +0 -0
  35. package/dist/web/public/home-sessions.js +132 -10
  36. package/dist/web/public/home-sessions.js.br +0 -0
  37. package/dist/web/public/home-sessions.js.gz +0 -0
  38. package/dist/web/public/i18n.89097ff5.js +1 -0
  39. package/dist/web/public/i18n.89097ff5.js.br +0 -0
  40. package/dist/web/public/i18n.89097ff5.js.gz +0 -0
  41. package/dist/web/public/image-input.ee16ad88.js.gz +0 -0
  42. package/dist/web/public/index.html +1335 -1000
  43. package/dist/web/public/index.html.br +0 -0
  44. package/dist/web/public/index.html.gz +0 -0
  45. package/dist/web/public/input-cjk.63794d0b.js.gz +0 -0
  46. package/dist/web/public/{keyboard-accessory.1f35b482.js → keyboard-accessory.0f524637.js} +207 -5
  47. package/dist/web/public/keyboard-accessory.0f524637.js.br +0 -0
  48. package/dist/web/public/keyboard-accessory.0f524637.js.gz +0 -0
  49. package/dist/web/public/mobile-handlers.32fdd57f.js.gz +0 -0
  50. package/dist/web/public/mobile-overview.js.gz +0 -0
  51. package/dist/web/public/mobile.4f875962.css +1 -0
  52. package/dist/web/public/mobile.4f875962.css.br +0 -0
  53. package/dist/web/public/mobile.4f875962.css.gz +0 -0
  54. package/dist/web/public/notification-manager.5d229063.js.gz +0 -0
  55. package/dist/web/public/orchestrator-panel.js.gz +0 -0
  56. package/dist/web/public/{panels-ui.3df9cd95.js → panels-ui.54b0d004.js} +1 -1
  57. package/dist/web/public/panels-ui.54b0d004.js.br +0 -0
  58. package/dist/web/public/panels-ui.54b0d004.js.gz +0 -0
  59. package/dist/web/public/ralph-panel.6de2d0f8.js.gz +0 -0
  60. package/dist/web/public/ralph-wizard.13a1831e.js.gz +0 -0
  61. package/dist/web/public/readmymind-ui.js +98 -18
  62. package/dist/web/public/readmymind-ui.js.br +0 -0
  63. package/dist/web/public/readmymind-ui.js.gz +0 -0
  64. package/dist/web/public/respawn-ui.ff0dae4c.js.gz +0 -0
  65. package/dist/web/public/sanitize-html.bc7078d6.js.gz +0 -0
  66. package/dist/web/public/{session-ui.f4fed187.js → session-ui.46a0993c.js} +5 -5
  67. package/dist/web/public/session-ui.46a0993c.js.br +0 -0
  68. package/dist/web/public/session-ui.46a0993c.js.gz +0 -0
  69. package/dist/web/public/settings-ui.63be4178.js +67 -0
  70. package/dist/web/public/settings-ui.63be4178.js.br +0 -0
  71. package/dist/web/public/settings-ui.63be4178.js.gz +0 -0
  72. package/dist/web/public/styles.9e04378d.css +1 -0
  73. package/dist/web/public/styles.9e04378d.css.br +0 -0
  74. package/dist/web/public/styles.9e04378d.css.gz +0 -0
  75. package/dist/web/public/subagent-windows.4cc8b005.js.gz +0 -0
  76. package/dist/web/public/sw.js.gz +0 -0
  77. package/dist/web/public/terminal-ui.ae776405.js +4 -0
  78. package/dist/web/public/terminal-ui.ae776405.js.br +0 -0
  79. package/dist/web/public/terminal-ui.ae776405.js.gz +0 -0
  80. package/dist/web/public/ultracode-panel.js.gz +0 -0
  81. package/dist/web/public/ultracode-windows.js.gz +0 -0
  82. package/dist/web/public/upload.html.gz +0 -0
  83. package/dist/web/public/vendor/dompurify.min.js.gz +0 -0
  84. package/dist/web/public/vendor/marked.min.js.gz +0 -0
  85. package/dist/web/public/vendor/xterm-addon-fit.min.js.gz +0 -0
  86. package/dist/web/public/vendor/xterm-addon-serialize.min.js.gz +0 -0
  87. package/dist/web/public/vendor/xterm-addon-unicode11.min.js.gz +0 -0
  88. package/dist/web/public/vendor/xterm-addon-webgl.min.js.gz +0 -0
  89. package/dist/web/public/vendor/xterm-predictive-echo.6cec0fba.js.gz +0 -0
  90. package/dist/web/public/vendor/xterm-zerolag-input.137ad9f0.js.gz +0 -0
  91. package/dist/web/public/vendor/xterm.css.gz +0 -0
  92. package/dist/web/public/vendor/xterm.min.js.gz +0 -0
  93. package/dist/web/public/{voice-input.e5461859.js → voice-input.4ec670b0.js} +421 -13
  94. package/dist/web/public/voice-input.4ec670b0.js.br +0 -0
  95. package/dist/web/public/voice-input.4ec670b0.js.gz +0 -0
  96. package/dist/web/public/voice-pcm-worklet.js +57 -0
  97. package/dist/web/public/voice-pcm-worklet.js.br +0 -0
  98. package/dist/web/public/voice-pcm-worklet.js.gz +0 -0
  99. package/dist/web/public/webview-tabs.js.gz +0 -0
  100. package/dist/web/routes/index.d.ts +1 -0
  101. package/dist/web/routes/index.d.ts.map +1 -1
  102. package/dist/web/routes/index.js +1 -0
  103. package/dist/web/routes/index.js.map +1 -1
  104. package/dist/web/routes/search-routes.d.ts +3 -1
  105. package/dist/web/routes/search-routes.d.ts.map +1 -1
  106. package/dist/web/routes/search-routes.js +34 -1
  107. package/dist/web/routes/search-routes.js.map +1 -1
  108. package/dist/web/routes/session-routes.d.ts.map +1 -1
  109. package/dist/web/routes/session-routes.js +151 -9
  110. package/dist/web/routes/session-routes.js.map +1 -1
  111. package/dist/web/routes/voice-routes.d.ts +45 -0
  112. package/dist/web/routes/voice-routes.d.ts.map +1 -0
  113. package/dist/web/routes/voice-routes.js +172 -0
  114. package/dist/web/routes/voice-routes.js.map +1 -0
  115. package/dist/web/schemas.d.ts +2 -0
  116. package/dist/web/schemas.d.ts.map +1 -1
  117. package/dist/web/schemas.js +12 -0
  118. package/dist/web/schemas.js.map +1 -1
  119. package/dist/web/server.d.ts +1 -0
  120. package/dist/web/server.d.ts.map +1 -1
  121. package/dist/web/server.js +11 -1
  122. package/dist/web/server.js.map +1 -1
  123. package/dist/web/session-history-index.d.ts +108 -0
  124. package/dist/web/session-history-index.d.ts.map +1 -0
  125. package/dist/web/session-history-index.js +118 -0
  126. package/dist/web/session-history-index.js.map +1 -0
  127. package/dist/web/voice-stream.d.ts +96 -0
  128. package/dist/web/voice-stream.d.ts.map +1 -0
  129. package/dist/web/voice-stream.js +268 -0
  130. package/dist/web/voice-stream.js.map +1 -0
  131. package/package.json +1 -1
  132. package/dist/web/public/app.4d006245.js.br +0 -0
  133. package/dist/web/public/app.4d006245.js.gz +0 -0
  134. package/dist/web/public/constants.7d0f4047.js.br +0 -0
  135. package/dist/web/public/constants.7d0f4047.js.gz +0 -0
  136. package/dist/web/public/i18n.ef50083f.js +0 -1
  137. package/dist/web/public/i18n.ef50083f.js.br +0 -0
  138. package/dist/web/public/i18n.ef50083f.js.gz +0 -0
  139. package/dist/web/public/keyboard-accessory.1f35b482.js.br +0 -0
  140. package/dist/web/public/keyboard-accessory.1f35b482.js.gz +0 -0
  141. package/dist/web/public/mobile.5d8c896c.css +0 -1
  142. package/dist/web/public/mobile.5d8c896c.css.br +0 -0
  143. package/dist/web/public/mobile.5d8c896c.css.gz +0 -0
  144. package/dist/web/public/panels-ui.3df9cd95.js.br +0 -0
  145. package/dist/web/public/panels-ui.3df9cd95.js.gz +0 -0
  146. package/dist/web/public/session-ui.f4fed187.js.br +0 -0
  147. package/dist/web/public/session-ui.f4fed187.js.gz +0 -0
  148. package/dist/web/public/settings-ui.1c15649c.js +0 -67
  149. package/dist/web/public/settings-ui.1c15649c.js.br +0 -0
  150. package/dist/web/public/settings-ui.1c15649c.js.gz +0 -0
  151. package/dist/web/public/styles.0024c22e.css +0 -1
  152. package/dist/web/public/styles.0024c22e.css.br +0 -0
  153. package/dist/web/public/styles.0024c22e.css.gz +0 -0
  154. package/dist/web/public/terminal-ui.e7c5d233.js +0 -4
  155. package/dist/web/public/terminal-ui.e7c5d233.js.br +0 -0
  156. package/dist/web/public/terminal-ui.e7c5d233.js.gz +0 -0
  157. package/dist/web/public/voice-input.e5461859.js.br +0 -0
  158. package/dist/web/public/voice-input.e5461859.js.gz +0 -0
@@ -3,7 +3,9 @@
3
3
  *
4
4
  * Registers `GET /api/search?q=&types=&limit=` — a bounded, in-memory search
5
5
  * across three v1 sources, returned in the standard ApiResponse envelope:
6
- * 1. sessions/cases — name, working directory, session id
6
+ * 1. sessions/cases, name, working directory, session id, for LIVE sessions
7
+ * plus the past-session snapshot in `session-history-index.ts` (issue #261:
8
+ * the live map alone made every closed session unfindable by folder name)
7
9
  * 2. run-summary events — event title/details (from the live run-summary trackers)
8
10
  * 3. file paths — per-session attachment history (workspace-relative paths only)
9
11
  *
@@ -24,6 +26,7 @@
24
26
  import { canAccessOwned, getAuthUser, parseBody } from '../route-helpers.js';
25
27
  import { SearchQuerySchema } from '../schemas.js';
26
28
  import { searchSources, } from '../../search-service.js';
29
+ import { ensureHistorySessionIndexFresh, getHistorySessionIndex } from '../session-history-index.js';
27
30
  /**
28
31
  * Per-source harvest caps. These bound how much in-memory data we hand to the
29
32
  * pure core BEFORE it applies its own result caps — they keep the harvest itself
@@ -34,17 +37,24 @@ const MAX_EVENTS_PER_SESSION = 500;
34
37
  /**
35
38
  * Harvest the three source arrays from the live in-memory stores. Reads only
36
39
  * bounded, already-loaded data — no disk I/O, no terminal buffers.
40
+ *
41
+ * Past sessions come from the `session-history-index` snapshot, which is built
42
+ * outside the request path for exactly that reason. Live rows are harvested
43
+ * first and win the dedupe, so a session that is both live and in the snapshot
44
+ * keeps its live jump-to (switch to the tab) instead of a resume.
37
45
  */
38
46
  function harvestSources(ctx, canSee) {
39
47
  const sessions = [];
40
48
  const events = [];
41
49
  const files = [];
50
+ const seenSessionIds = new Set();
42
51
  for (const raw of ctx.sessions.values()) {
43
52
  const s = raw;
44
53
  if (canSee && !canSee(s.owner))
45
54
  continue; // multi-user ownership scope
46
55
  const sessionName = s.name ?? '';
47
56
  const timestamp = s.lastActivityAt ?? s.createdAt ?? 0;
57
+ seenSessionIds.add(s.id);
48
58
  sessions.push({
49
59
  sessionId: s.id,
50
60
  sessionName,
@@ -65,6 +75,25 @@ function harvestSources(ctx, canSee) {
65
75
  });
66
76
  }
67
77
  }
78
+ // Past sessions: the out-of-band snapshot of the unified list. Unscoped on
79
+ // disk, so every row goes through the same ownership check as a live one,
80
+ // host-wide transcript rows carry no owner and are therefore admin-only in
81
+ // multi-user mode, matching GET /api/sessions/unified.
82
+ for (const item of getHistorySessionIndex().items) {
83
+ if (seenSessionIds.has(item.sessionId))
84
+ continue;
85
+ if (canSee && !canSee(item.owner))
86
+ continue;
87
+ seenSessionIds.add(item.sessionId);
88
+ sessions.push({
89
+ sessionId: item.sessionId,
90
+ sessionName: item.name,
91
+ workingDir: item.workingDir,
92
+ timestamp: item.timestamp,
93
+ history: true,
94
+ claudeSessionId: item.claudeSessionId,
95
+ });
96
+ }
68
97
  // Events: from the live run-summary trackers, keyed by session id.
69
98
  for (const [sessionId, tracker] of ctx.runSummaryTrackers) {
70
99
  const session = ctx.sessions.get(sessionId);
@@ -99,6 +128,10 @@ export function registerSearchRoutes(app, ctx) {
99
128
  .map((t) => t.trim())
100
129
  .filter(Boolean))
101
130
  : null;
131
+ // Fire-and-forget: a stale past-session snapshot is rebuilt in the
132
+ // background. This query still answers from whatever is already in memory,
133
+ // which is what keeps the request path free of disk I/O.
134
+ ensureHistorySessionIndexFresh();
102
135
  const sources = harvestSources(ctx, canSee);
103
136
  // Apply the optional source-type filter before searching so excluded
104
137
  // sources never contribute to (or consume budget in) the result set.
@@ -1 +1 @@
1
- {"version":3,"file":"search-routes.js","sourceRoot":"","sources":["../../../src/web/routes/search-routes.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"search-routes.js","sourceRoot":"","sources":["../../../src/web/routes/search-routes.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"session-routes.d.ts","sourceRoot":"","sources":["../../../src/web/routes/session-routes.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,eAAe,EAAqB,MAAM,SAAS,CAAC;AA2F7D,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,UAAU,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AA+DjG;;;;;;;;;;;;;GAaG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CA0C1D;AAED;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAmCvE;AAWD,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,GAAE,MAAmB,GAAG,OAAO,CAiBhF;AAGD,wBAAgB,sBAAsB,IAAI,IAAI,CAE7C;AA+ND,2FAA2F;AAC3F,wBAAgB,uBAAuB,IAAI,IAAI,CAI9C;AAED,+EAA+E;AAC/E,wBAAgB,sBAAsB,IAAI,MAAM,CAE/C;AA6DD,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,eAAe,EACpB,GAAG,EAAE,WAAW,GAAG,SAAS,GAAG,UAAU,GAAG,SAAS,GAAG,QAAQ,GAC/D,IAAI,CAguGN"}
1
+ {"version":3,"file":"session-routes.d.ts","sourceRoot":"","sources":["../../../src/web/routes/session-routes.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,eAAe,EAAqB,MAAM,SAAS,CAAC;AAiG7D,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,UAAU,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AA+DjG;;;;;;;;;;;;;GAaG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CA0C1D;AAED;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAmCvE;AAWD,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,GAAE,MAAmB,GAAG,OAAO,CAiBhF;AAGD,wBAAgB,sBAAsB,IAAI,IAAI,CAE7C;AA+ND,2FAA2F;AAC3F,wBAAgB,uBAAuB,IAAI,IAAI,CAI9C;AAED,+EAA+E;AAC/E,wBAAgB,sBAAsB,IAAI,MAAM,CAE/C;AA6DD,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,eAAe,EACpB,GAAG,EAAE,WAAW,GAAG,SAAS,GAAG,UAAU,GAAG,SAAS,GAAG,QAAQ,GAC/D,IAAI,CAq4GN"}
@@ -26,6 +26,7 @@ import { imageWatcher } from '../../image-watcher.js';
26
26
  import { convertHeicToJpeg } from '../heic-jpeg-converter.js';
27
27
  import { getLifecycleLog } from '../../session-lifecycle-log.js';
28
28
  import { mergeUnifiedSessions, filterAndPaginate, } from '../../services/unified-session-service.js';
29
+ import { buildHistorySessionIndexItems, setHistoryIndexRefresher, setHistorySessionIndex, } from '../session-history-index.js';
29
30
  import { RunSummaryTracker } from '../../run-summary.js';
30
31
  import { MAX_INPUT_LENGTH, MAX_SESSION_NAME_LENGTH } from '../../config/terminal-limits.js';
31
32
  import { MAX_PASTE_IMAGE_BYTES } from '../../config/buffer-limits.js';
@@ -2731,6 +2732,82 @@ export function registerSessionRoutes(app, ctx) {
2731
2732
  }
2732
2733
  return sawNonCli;
2733
2734
  }
2735
+ /** `<repo>/.claude/worktrees/<name>` — the layout Claude Code's own worktree feature creates. */
2736
+ const CLAUDE_WORKTREE_PATH = /^(.*)\/\.claude\/worktrees\/([^/]+)\/?$/;
2737
+ /**
2738
+ * Recover cwd / branch / worktree from a transcript chunk.
2739
+ *
2740
+ * Claude Code stamps `"cwd"` and `"gitBranch"` on every user/assistant record,
2741
+ * and writes a dedicated `worktree-state` record when the session was started
2742
+ * through its own worktree feature. This reads buffers `scanProjectDir` has
2743
+ * ALREADY loaded, so it costs no extra file I/O.
2744
+ *
2745
+ * Why this matters beyond a label: `decodeProjectKey()` reconstructs a path by
2746
+ * stat-walking the filesystem and falls back to `$HOME` when nothing resolves.
2747
+ * A deleted worktree is the normal end of a worktree's life, so every past
2748
+ * worktree session used to collapse onto `$HOME` (#265). The transcript value
2749
+ * is the literal cwd — non-lossy, and it survives the directory being removed.
2750
+ *
2751
+ * cwd is taken from the FIRST record that carries it (a session's cwd does not
2752
+ * move); gitBranch from the LAST (a branch genuinely changes mid-session, and
2753
+ * the newest value in the scanned chunk is the closest to current).
2754
+ */
2755
+ function extractTranscriptGitInfo(text) {
2756
+ const info = {};
2757
+ let start = 0;
2758
+ while (start < text.length) {
2759
+ const end = text.indexOf('\n', start);
2760
+ const line = end === -1 ? text.slice(start) : text.slice(start, end);
2761
+ start = end === -1 ? text.length : end + 1;
2762
+ // Highest-confidence source: Claude's own worktree record. Names the
2763
+ // worktree explicitly, so it beats anything inferred from the path.
2764
+ if (line.includes('"worktree-state"')) {
2765
+ try {
2766
+ const rec = JSON.parse(line);
2767
+ const ws = rec.worktreeSession;
2768
+ if (ws) {
2769
+ if (typeof ws.worktreeName === 'string')
2770
+ info.worktreeName ||= ws.worktreeName;
2771
+ if (typeof ws.originalCwd === 'string')
2772
+ info.worktreeRepo ||= ws.originalCwd;
2773
+ if (typeof ws.worktreePath === 'string')
2774
+ info.cwd ||= ws.worktreePath;
2775
+ }
2776
+ }
2777
+ catch {
2778
+ // Malformed/truncated line — skip
2779
+ }
2780
+ continue;
2781
+ }
2782
+ if (!line.includes('"cwd"') && !line.includes('"gitBranch"'))
2783
+ continue;
2784
+ if (!line.includes('"type":"user"') && !line.includes('"type":"assistant"'))
2785
+ continue;
2786
+ try {
2787
+ const rec = JSON.parse(line);
2788
+ if (!info.cwd && typeof rec.cwd === 'string' && rec.cwd)
2789
+ info.cwd = rec.cwd;
2790
+ // Last one wins — closest to the session's current branch.
2791
+ if (typeof rec.gitBranch === 'string' && rec.gitBranch)
2792
+ info.gitBranch = rec.gitBranch;
2793
+ }
2794
+ catch {
2795
+ // Malformed/truncated line — skip
2796
+ }
2797
+ }
2798
+ // No explicit worktree record: infer from Claude's own worktree path layout.
2799
+ // A worktree created by hand (`git worktree add` anywhere) has no recoverable
2800
+ // NAME here — it still gets a branch, and the badge degrades to branch-only
2801
+ // rather than guessing.
2802
+ if (!info.worktreeName && info.cwd) {
2803
+ const m = CLAUDE_WORKTREE_PATH.exec(info.cwd);
2804
+ if (m) {
2805
+ info.worktreeName = m[2];
2806
+ info.worktreeRepo ||= m[1];
2807
+ }
2808
+ }
2809
+ return info;
2810
+ }
2734
2811
  /**
2735
2812
  * Extract the text of the LAST user message from a JSONL transcript chunk
2736
2813
  * (COD-145). Mirrors `extractFirstUserPrompt` exactly — same user-message
@@ -3081,14 +3158,33 @@ export function registerSessionRoutes(app, ctx) {
3081
3158
  const entrypoint = headEntrypoint === 'cli' || tailEntrypoint === 'cli' ? 'cli' : (headEntrypoint ?? tailEntrypoint);
3082
3159
  if (entrypoint && isAutomatedEntrypoint(entrypoint))
3083
3160
  continue;
3161
+ // Git/worktree facts from the buffers already read above — no extra I/O.
3162
+ // head first (cwd is stamped near the top; median offset ~1KB), tail as the
3163
+ // fallback for transcripts whose head read failed or came up empty.
3164
+ const headGit = head ? extractTranscriptGitInfo(head) : {};
3165
+ const tailGit = tail ? extractTranscriptGitInfo(tail) : {};
3166
+ const git = {
3167
+ cwd: headGit.cwd ?? tailGit.cwd,
3168
+ // Last-wins within a chunk; across chunks the tail is the newer one.
3169
+ gitBranch: tailGit.gitBranch ?? headGit.gitBranch,
3170
+ worktreeName: headGit.worktreeName ?? tailGit.worktreeName,
3171
+ worktreeRepo: headGit.worktreeRepo ?? tailGit.worktreeRepo,
3172
+ };
3084
3173
  out.push({
3085
3174
  sessionId,
3086
- workingDir,
3175
+ // The transcript's literal cwd beats decodeProjectKey's stat-walked guess,
3176
+ // which silently collapses to $HOME once the directory is gone (#265).
3177
+ // Absent cwd falls back to the old behaviour rather than inventing a path.
3178
+ workingDir: git.cwd ?? workingDir,
3179
+ workingDirExact: git.cwd !== undefined,
3087
3180
  projectKey: projDir,
3088
3181
  sizeBytes: fileStat.size,
3089
3182
  lastModified: fileStat.mtime.toISOString(),
3090
3183
  firstPrompt,
3091
3184
  lastPrompt,
3185
+ gitBranch: git.gitBranch,
3186
+ worktreeName: git.worktreeName,
3187
+ worktreeRepo: git.worktreeRepo,
3092
3188
  });
3093
3189
  }
3094
3190
  return out;
@@ -3145,14 +3241,14 @@ export function registerSessionRoutes(app, ctx) {
3145
3241
  results.sort((a, b) => new Date(b.lastModified).getTime() - new Date(a.lastModified).getTime());
3146
3242
  return { sessions: results.slice(0, 50) };
3147
3243
  });
3148
- // Unified, read-only session list: merges live + persisted + lifecycle +
3149
- // transcript history + mux stats into one de-duplicated, searchable list
3150
- // (COD-121). Pure merge/filter logic lives in unified-session-service.ts.
3151
- app.get('/api/sessions/unified', async (req) => {
3152
- const query = req.query;
3153
- if (ctx.testMode) {
3154
- return { sessions: [], total: 0 };
3155
- }
3244
+ /**
3245
+ * Gather the four read-only views the unified list is merged from, plus mux
3246
+ * stats. This is the expensive half (the lifecycle log and a scan of every
3247
+ * Claude transcript), factored out of the route handler because the
3248
+ * past-session search index rebuilds itself from the very same inputs, off
3249
+ * the request path, see session-history-index.ts.
3250
+ */
3251
+ async function gatherUnifiedInputs() {
3156
3252
  // Live (in-memory) sessions.
3157
3253
  const live = [...ctx.sessions.values()].map((s) => {
3158
3254
  const st = s.toState();
@@ -3220,6 +3316,9 @@ export function registerSessionRoutes(app, ctx) {
3220
3316
  firstPrompt: h.firstPrompt,
3221
3317
  lastPrompt: h.lastPrompt,
3222
3318
  projectKey: h.projectKey,
3319
+ gitBranch: h.gitBranch,
3320
+ worktreeName: h.worktreeName,
3321
+ worktreeRepo: h.worktreeRepo,
3223
3322
  });
3224
3323
  }
3225
3324
  }
@@ -3245,14 +3344,51 @@ export function registerSessionRoutes(app, ctx) {
3245
3344
  catch {
3246
3345
  // Mux stats are optional.
3247
3346
  }
3347
+ return { live, persisted, lifecycle, history, mux };
3348
+ }
3349
+ /**
3350
+ * Publish a merged unified list as the past-session search index (issue #261).
3351
+ * The snapshot is stored UNSCOPED with a per-row owner, so it must only ever be
3352
+ * built from an unscoped merge, `harvestSources()` in search-routes re-applies
3353
+ * the ownership check on read.
3354
+ */
3355
+ function publishHistorySessionIndex(merged) {
3356
+ const ownerById = new Map();
3357
+ const stored = ctx.store.getState().sessions;
3358
+ for (const p of Object.values(stored))
3359
+ ownerById.set(p.id, p.owner);
3360
+ // Live wins: a session's owner on disk can lag the running one.
3361
+ for (const s of ctx.sessions.values())
3362
+ ownerById.set(s.id, s.owner);
3363
+ const liveIds = new Set(ctx.sessions.keys());
3364
+ setHistorySessionIndex(buildHistorySessionIndexItems(merged, ownerById, liveIds));
3365
+ }
3366
+ // Rebuild hook for the search route: it kicks this (fire-and-forget) when the
3367
+ // snapshot goes stale, so a search never pays for the scan itself.
3368
+ setHistoryIndexRefresher(async () => {
3369
+ if (ctx.testMode)
3370
+ return;
3371
+ publishHistorySessionIndex(mergeUnifiedSessions(await gatherUnifiedInputs()));
3372
+ });
3373
+ // Unified, read-only session list: merges live + persisted + lifecycle +
3374
+ // transcript history + mux stats into one de-duplicated, searchable list
3375
+ // (COD-121). Pure merge/filter logic lives in unified-session-service.ts.
3376
+ app.get('/api/sessions/unified', async (req) => {
3377
+ const query = req.query;
3378
+ if (ctx.testMode) {
3379
+ return { sessions: [], total: 0 };
3380
+ }
3381
+ const { live, persisted, lifecycle, history, mux } = await gatherUnifiedInputs();
3248
3382
  // Multi-user: a non-admin only sees their own sessions; host-wide transcript
3249
3383
  // history (not tied to an owned session) is admin-only.
3250
3384
  let sLive = live;
3251
3385
  let sPersisted = persisted;
3252
3386
  let sLifecycle = lifecycle;
3253
3387
  let sHistory = history;
3388
+ let scoped = false;
3254
3389
  const uUser = getAuthUser(req);
3255
3390
  if (isMultiUserMode() && uUser.role !== 'admin') {
3391
+ scoped = true;
3256
3392
  const ownedLive = new Set([...ctx.sessions.values()].filter((s) => canAccessOwned(uUser, s.owner)).map((s) => s.id));
3257
3393
  const stored = ctx.store.getState().sessions;
3258
3394
  const ownedPersisted = new Set(Object.values(stored)
@@ -3271,6 +3407,12 @@ export function registerSessionRoutes(app, ctx) {
3271
3407
  history: sHistory,
3272
3408
  mux,
3273
3409
  });
3410
+ // Refresh the search index off the back of this request, the home screen
3411
+ // fetches this endpoint whenever it opens, which is the same screen the
3412
+ // search box lives on, so the snapshot is warm before anyone types. A scoped
3413
+ // merge is a per-user subset and would corrupt the shared snapshot, so that
3414
+ // path re-merges unscoped instead (multi-user is opt-in and rarely hit).
3415
+ publishHistorySessionIndex(scoped ? mergeUnifiedSessions({ live, persisted, lifecycle, history, mux }) : merged);
3274
3416
  const offset = query.offset !== undefined ? parseInt(query.offset, 10) : undefined;
3275
3417
  const limit = query.limit !== undefined ? parseInt(query.limit, 10) : undefined;
3276
3418
  return filterAndPaginate(merged, {