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.
- package/README.md +11 -10
- package/dist/claude-credentials.d.ts +50 -0
- package/dist/claude-credentials.d.ts.map +1 -0
- package/dist/claude-credentials.js +102 -0
- package/dist/claude-credentials.js.map +1 -0
- package/dist/config/voice.d.ts +43 -0
- package/dist/config/voice.d.ts.map +1 -0
- package/dist/config/voice.js +48 -0
- package/dist/config/voice.js.map +1 -0
- package/dist/search-service.d.ts +9 -1
- package/dist/search-service.d.ts.map +1 -1
- package/dist/search-service.js +13 -3
- package/dist/search-service.js.map +1 -1
- package/dist/services/unified-session-service.d.ts +9 -0
- package/dist/services/unified-session-service.d.ts.map +1 -1
- package/dist/services/unified-session-service.js +4 -1
- package/dist/services/unified-session-service.js.map +1 -1
- package/dist/types/search.d.ts +17 -3
- package/dist/types/search.d.ts.map +1 -1
- package/dist/web/ports/config-port.d.ts +2 -0
- package/dist/web/ports/config-port.d.ts.map +1 -1
- package/dist/web/public/admin-ui.js +35 -20
- package/dist/web/public/admin-ui.js.br +0 -0
- package/dist/web/public/admin-ui.js.gz +0 -0
- package/dist/web/public/api-client.c9b1cddc.js.gz +0 -0
- package/dist/web/public/{app.4d006245.js → app.f9c6aadd.js} +18 -18
- package/dist/web/public/app.f9c6aadd.js.br +0 -0
- package/dist/web/public/app.f9c6aadd.js.gz +0 -0
- package/dist/web/public/approvals-ui.js.gz +0 -0
- package/dist/web/public/{constants.7d0f4047.js → constants.53dc0fa1.js} +43 -0
- package/dist/web/public/constants.53dc0fa1.js.br +0 -0
- package/dist/web/public/constants.53dc0fa1.js.gz +0 -0
- package/dist/web/public/cron-ui.js.gz +0 -0
- package/dist/web/public/entrance-animations.js.gz +0 -0
- package/dist/web/public/home-sessions.js +132 -10
- package/dist/web/public/home-sessions.js.br +0 -0
- package/dist/web/public/home-sessions.js.gz +0 -0
- package/dist/web/public/i18n.89097ff5.js +1 -0
- package/dist/web/public/i18n.89097ff5.js.br +0 -0
- package/dist/web/public/i18n.89097ff5.js.gz +0 -0
- package/dist/web/public/image-input.ee16ad88.js.gz +0 -0
- package/dist/web/public/index.html +1335 -1000
- package/dist/web/public/index.html.br +0 -0
- package/dist/web/public/index.html.gz +0 -0
- package/dist/web/public/input-cjk.63794d0b.js.gz +0 -0
- package/dist/web/public/{keyboard-accessory.1f35b482.js → keyboard-accessory.0f524637.js} +207 -5
- package/dist/web/public/keyboard-accessory.0f524637.js.br +0 -0
- package/dist/web/public/keyboard-accessory.0f524637.js.gz +0 -0
- package/dist/web/public/mobile-handlers.32fdd57f.js.gz +0 -0
- package/dist/web/public/mobile-overview.js.gz +0 -0
- package/dist/web/public/mobile.4f875962.css +1 -0
- package/dist/web/public/mobile.4f875962.css.br +0 -0
- package/dist/web/public/mobile.4f875962.css.gz +0 -0
- package/dist/web/public/notification-manager.5d229063.js.gz +0 -0
- package/dist/web/public/orchestrator-panel.js.gz +0 -0
- package/dist/web/public/{panels-ui.3df9cd95.js → panels-ui.54b0d004.js} +1 -1
- package/dist/web/public/panels-ui.54b0d004.js.br +0 -0
- package/dist/web/public/panels-ui.54b0d004.js.gz +0 -0
- package/dist/web/public/ralph-panel.6de2d0f8.js.gz +0 -0
- package/dist/web/public/ralph-wizard.13a1831e.js.gz +0 -0
- package/dist/web/public/readmymind-ui.js +98 -18
- package/dist/web/public/readmymind-ui.js.br +0 -0
- package/dist/web/public/readmymind-ui.js.gz +0 -0
- package/dist/web/public/respawn-ui.ff0dae4c.js.gz +0 -0
- package/dist/web/public/sanitize-html.bc7078d6.js.gz +0 -0
- package/dist/web/public/{session-ui.f4fed187.js → session-ui.46a0993c.js} +5 -5
- package/dist/web/public/session-ui.46a0993c.js.br +0 -0
- package/dist/web/public/session-ui.46a0993c.js.gz +0 -0
- package/dist/web/public/settings-ui.63be4178.js +67 -0
- package/dist/web/public/settings-ui.63be4178.js.br +0 -0
- package/dist/web/public/settings-ui.63be4178.js.gz +0 -0
- package/dist/web/public/styles.9e04378d.css +1 -0
- package/dist/web/public/styles.9e04378d.css.br +0 -0
- package/dist/web/public/styles.9e04378d.css.gz +0 -0
- package/dist/web/public/subagent-windows.4cc8b005.js.gz +0 -0
- package/dist/web/public/sw.js.gz +0 -0
- package/dist/web/public/terminal-ui.ae776405.js +4 -0
- package/dist/web/public/terminal-ui.ae776405.js.br +0 -0
- package/dist/web/public/terminal-ui.ae776405.js.gz +0 -0
- package/dist/web/public/ultracode-panel.js.gz +0 -0
- package/dist/web/public/ultracode-windows.js.gz +0 -0
- package/dist/web/public/upload.html.gz +0 -0
- package/dist/web/public/vendor/dompurify.min.js.gz +0 -0
- package/dist/web/public/vendor/marked.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-fit.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-serialize.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-unicode11.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-webgl.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-predictive-echo.6cec0fba.js.gz +0 -0
- package/dist/web/public/vendor/xterm-zerolag-input.137ad9f0.js.gz +0 -0
- package/dist/web/public/vendor/xterm.css.gz +0 -0
- package/dist/web/public/vendor/xterm.min.js.gz +0 -0
- package/dist/web/public/{voice-input.e5461859.js → voice-input.4ec670b0.js} +421 -13
- package/dist/web/public/voice-input.4ec670b0.js.br +0 -0
- package/dist/web/public/voice-input.4ec670b0.js.gz +0 -0
- package/dist/web/public/voice-pcm-worklet.js +57 -0
- package/dist/web/public/voice-pcm-worklet.js.br +0 -0
- package/dist/web/public/voice-pcm-worklet.js.gz +0 -0
- package/dist/web/public/webview-tabs.js.gz +0 -0
- package/dist/web/routes/index.d.ts +1 -0
- package/dist/web/routes/index.d.ts.map +1 -1
- package/dist/web/routes/index.js +1 -0
- package/dist/web/routes/index.js.map +1 -1
- package/dist/web/routes/search-routes.d.ts +3 -1
- package/dist/web/routes/search-routes.d.ts.map +1 -1
- package/dist/web/routes/search-routes.js +34 -1
- package/dist/web/routes/search-routes.js.map +1 -1
- package/dist/web/routes/session-routes.d.ts.map +1 -1
- package/dist/web/routes/session-routes.js +151 -9
- package/dist/web/routes/session-routes.js.map +1 -1
- package/dist/web/routes/voice-routes.d.ts +45 -0
- package/dist/web/routes/voice-routes.d.ts.map +1 -0
- package/dist/web/routes/voice-routes.js +172 -0
- package/dist/web/routes/voice-routes.js.map +1 -0
- package/dist/web/schemas.d.ts +2 -0
- package/dist/web/schemas.d.ts.map +1 -1
- package/dist/web/schemas.js +12 -0
- package/dist/web/schemas.js.map +1 -1
- package/dist/web/server.d.ts +1 -0
- package/dist/web/server.d.ts.map +1 -1
- package/dist/web/server.js +11 -1
- package/dist/web/server.js.map +1 -1
- package/dist/web/session-history-index.d.ts +108 -0
- package/dist/web/session-history-index.d.ts.map +1 -0
- package/dist/web/session-history-index.js +118 -0
- package/dist/web/session-history-index.js.map +1 -0
- package/dist/web/voice-stream.d.ts +96 -0
- package/dist/web/voice-stream.d.ts.map +1 -0
- package/dist/web/voice-stream.js +268 -0
- package/dist/web/voice-stream.js.map +1 -0
- package/package.json +1 -1
- package/dist/web/public/app.4d006245.js.br +0 -0
- package/dist/web/public/app.4d006245.js.gz +0 -0
- package/dist/web/public/constants.7d0f4047.js.br +0 -0
- package/dist/web/public/constants.7d0f4047.js.gz +0 -0
- package/dist/web/public/i18n.ef50083f.js +0 -1
- package/dist/web/public/i18n.ef50083f.js.br +0 -0
- package/dist/web/public/i18n.ef50083f.js.gz +0 -0
- package/dist/web/public/keyboard-accessory.1f35b482.js.br +0 -0
- package/dist/web/public/keyboard-accessory.1f35b482.js.gz +0 -0
- package/dist/web/public/mobile.5d8c896c.css +0 -1
- package/dist/web/public/mobile.5d8c896c.css.br +0 -0
- package/dist/web/public/mobile.5d8c896c.css.gz +0 -0
- package/dist/web/public/panels-ui.3df9cd95.js.br +0 -0
- package/dist/web/public/panels-ui.3df9cd95.js.gz +0 -0
- package/dist/web/public/session-ui.f4fed187.js.br +0 -0
- package/dist/web/public/session-ui.f4fed187.js.gz +0 -0
- package/dist/web/public/settings-ui.1c15649c.js +0 -67
- package/dist/web/public/settings-ui.1c15649c.js.br +0 -0
- package/dist/web/public/settings-ui.1c15649c.js.gz +0 -0
- package/dist/web/public/styles.0024c22e.css +0 -1
- package/dist/web/public/styles.0024c22e.css.br +0 -0
- package/dist/web/public/styles.0024c22e.css.gz +0 -0
- package/dist/web/public/terminal-ui.e7c5d233.js +0 -4
- package/dist/web/public/terminal-ui.e7c5d233.js.br +0 -0
- package/dist/web/public/terminal-ui.e7c5d233.js.gz +0 -0
- package/dist/web/public/voice-input.e5461859.js.br +0 -0
- 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
|
|
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":"AAAA
|
|
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;
|
|
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
|
-
|
|
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
|
-
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
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, {
|