@ai-matrx/coding-sessions 0.1.165 → 0.2.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.
- package/CHANGELOG.md +26 -0
- package/dist/engine/index.cjs +224 -54
- package/dist/engine/index.cjs.map +1 -1
- package/dist/engine/index.d.cts +1 -1
- package/dist/engine/index.d.ts +1 -1
- package/dist/engine/index.js +220 -53
- package/dist/engine/index.js.map +1 -1
- package/dist/{index-CDeDKQHe.d.cts → index-29t7n472.d.cts} +81 -3
- package/dist/{index-CDeDKQHe.d.ts → index-29t7n472.d.ts} +81 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +20 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js.map +1 -1
- package/dist/react/index.cjs +15 -3
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +23 -0
- package/dist/react/index.d.ts +23 -0
- package/dist/react/index.js +15 -3
- package/dist/react/index.js.map +1 -1
- package/dist/server/index.cjs +201 -52
- package/dist/server/index.cjs.map +1 -1
- package/dist/server/index.d.cts +2 -2
- package/dist/server/index.d.ts +2 -2
- package/dist/server/index.js +201 -52
- package/dist/server/index.js.map +1 -1
- package/dist/sessions/index.cjs +335 -0
- package/dist/sessions/index.cjs.map +1 -0
- package/dist/sessions/index.d.cts +63 -0
- package/dist/sessions/index.d.ts +63 -0
- package/dist/sessions/index.js +312 -0
- package/dist/sessions/index.js.map +1 -0
- package/dist/titles-eCnA5sY7.d.cts +41 -0
- package/dist/titles-eCnA5sY7.d.ts +41 -0
- package/dist/ui/assets/{index-5Iix3spw.js → index-SuRr0-D2.js} +2 -2
- package/dist/ui/index.html +1 -1
- package/package.json +13 -3
package/dist/engine/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { e as CodingSessionsAgentsOptions, c as CodingSessionsEngine, d as CodingSessionsEngineOptions, D as DEFAULT_SERVER_URL,
|
|
1
|
+
export { f as CodexHome, e as CodingSessionsAgentsOptions, c as CodingSessionsEngine, d as CodingSessionsEngineOptions, D as DEFAULT_SERVER_URL, g as DEFAULT_SUPABASE_URL, E as EFFORT_LEVELS, P as PreferenceStore, h as ProviderHomes, R as RAW_TRANSCRIPT_MAX_BYTES, i as RawBackupPolicy, j as RawBackupSink, k as RunStatus, T as TitleHint, l as TitleHints, m as buildCleanEnv, n as claudeDefaults, o as codexDefaults, p as codexHome, q as createAiMatrxRawBackupSink, r as createCodingSessionsEngine, s as createRawBackupScheduler, t as createTitleHintsStore, u as defaultProviderHomes, v as describeActivity, w as describeProviderStatus, x as discoverLocalSessions, y as endsWithOpenToolCall, z as findDrivingProcess, A as findSessionFile, B as isAllowedEnvName, F as listProcesses, G as readSessionFile, H as searchLocalChats, I as sessionActivity, J as snippetAround, K as titleHintKey } from '../index-29t7n472.cjs';
|
|
2
2
|
import '@ai-matrx/agents/portable/mcp';
|
|
3
3
|
import '@ai-matrx/agents/portable';
|
|
4
4
|
import '@ai-matrx/data';
|
package/dist/engine/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { e as CodingSessionsAgentsOptions, c as CodingSessionsEngine, d as CodingSessionsEngineOptions, D as DEFAULT_SERVER_URL,
|
|
1
|
+
export { f as CodexHome, e as CodingSessionsAgentsOptions, c as CodingSessionsEngine, d as CodingSessionsEngineOptions, D as DEFAULT_SERVER_URL, g as DEFAULT_SUPABASE_URL, E as EFFORT_LEVELS, P as PreferenceStore, h as ProviderHomes, R as RAW_TRANSCRIPT_MAX_BYTES, i as RawBackupPolicy, j as RawBackupSink, k as RunStatus, T as TitleHint, l as TitleHints, m as buildCleanEnv, n as claudeDefaults, o as codexDefaults, p as codexHome, q as createAiMatrxRawBackupSink, r as createCodingSessionsEngine, s as createRawBackupScheduler, t as createTitleHintsStore, u as defaultProviderHomes, v as describeActivity, w as describeProviderStatus, x as discoverLocalSessions, y as endsWithOpenToolCall, z as findDrivingProcess, A as findSessionFile, B as isAllowedEnvName, F as listProcesses, G as readSessionFile, H as searchLocalChats, I as sessionActivity, J as snippetAround, K as titleHintKey } from '../index-29t7n472.js';
|
|
2
2
|
import '@ai-matrx/agents/portable/mcp';
|
|
3
3
|
import '@ai-matrx/agents/portable';
|
|
4
4
|
import '@ai-matrx/data';
|
package/dist/engine/index.js
CHANGED
|
@@ -1133,6 +1133,9 @@ var PROVIDER_LABEL = {
|
|
|
1133
1133
|
claude: "Claude Code",
|
|
1134
1134
|
codex: "Codex"
|
|
1135
1135
|
};
|
|
1136
|
+
function chatKey(provider, providerSessionId, sessionId) {
|
|
1137
|
+
return providerSessionId ? `${provider}:${providerSessionId}` : `session:${sessionId ?? ""}`;
|
|
1138
|
+
}
|
|
1136
1139
|
var TERMINAL_RUN_STATUSES = ["completed", "stopped", "failed"];
|
|
1137
1140
|
function isTerminalRunStatus(status) {
|
|
1138
1141
|
return TERMINAL_RUN_STATUSES.includes(status);
|
|
@@ -1447,6 +1450,78 @@ import fs4 from "fs/promises";
|
|
|
1447
1450
|
import os3 from "os";
|
|
1448
1451
|
import path5 from "path";
|
|
1449
1452
|
|
|
1453
|
+
// src/sessions/titles.ts
|
|
1454
|
+
var TITLE_MAX = 300;
|
|
1455
|
+
var BRIDGE_PLACEHOLDER = /^AI Matrx · [a-z-]+$/;
|
|
1456
|
+
var TITLE_PRECEDENCE = [
|
|
1457
|
+
/** The person's rename inside Matrx (Coding Sessions' preference). */
|
|
1458
|
+
"override",
|
|
1459
|
+
/** What the coding app's own sidebar shows: Claude's record `title` (renames included); Codex's thread name; Cursor's name. */
|
|
1460
|
+
"app",
|
|
1461
|
+
/** The title the app last showed, remembered by the claude-accounts engine after the record went away. */
|
|
1462
|
+
"app-remembered",
|
|
1463
|
+
/** Claude Code transcript entries (the last one of each kind that is not the bridge placeholder). */
|
|
1464
|
+
"custom-title",
|
|
1465
|
+
"ai-title",
|
|
1466
|
+
"agent-name",
|
|
1467
|
+
"summary",
|
|
1468
|
+
/** The person's first typed message (Codex: the thread's title / first user message). */
|
|
1469
|
+
"first-prompt",
|
|
1470
|
+
"last-prompt"
|
|
1471
|
+
];
|
|
1472
|
+
var STAMPABLE = /* @__PURE__ */ new Set(["custom-title", "ai-title", "agent-name"]);
|
|
1473
|
+
function isBridgePlaceholder(value) {
|
|
1474
|
+
return typeof value === "string" && BRIDGE_PLACEHOLDER.test(value.trim());
|
|
1475
|
+
}
|
|
1476
|
+
function cleanTitle(value) {
|
|
1477
|
+
if (typeof value !== "string") return null;
|
|
1478
|
+
const line = value.split(/\r?\n/).map((l) => l.trim()).find((l) => l.length > 0);
|
|
1479
|
+
if (!line) return null;
|
|
1480
|
+
return line.length > TITLE_MAX ? `${line.slice(0, TITLE_MAX - 1)}\u2026` : line;
|
|
1481
|
+
}
|
|
1482
|
+
function resolveSessionTitle(sources) {
|
|
1483
|
+
for (const source of TITLE_PRECEDENCE) {
|
|
1484
|
+
const raw = sources[source];
|
|
1485
|
+
if (STAMPABLE.has(source) && isBridgePlaceholder(raw)) continue;
|
|
1486
|
+
const title = cleanTitle(raw);
|
|
1487
|
+
if (title) return { title, source };
|
|
1488
|
+
}
|
|
1489
|
+
return null;
|
|
1490
|
+
}
|
|
1491
|
+
function claudeTranscriptTitleSources(head, tail = []) {
|
|
1492
|
+
const out = {};
|
|
1493
|
+
const take = (source, value) => {
|
|
1494
|
+
if (typeof value !== "string" || !value.trim()) return;
|
|
1495
|
+
if (STAMPABLE.has(source) && isBridgePlaceholder(value)) return;
|
|
1496
|
+
out[source] = value;
|
|
1497
|
+
};
|
|
1498
|
+
const normalizer = createClaudeNormalizer();
|
|
1499
|
+
let firstPrompt = null;
|
|
1500
|
+
const scan = (entries, findPrompt) => {
|
|
1501
|
+
for (const entry of entries) {
|
|
1502
|
+
if (typeof entry !== "object" || entry === null) continue;
|
|
1503
|
+
const e = entry;
|
|
1504
|
+
if (e.type === "custom-title") take("custom-title", e.customTitle);
|
|
1505
|
+
else if (e.type === "ai-title") take("ai-title", e.aiTitle);
|
|
1506
|
+
else if (e.type === "agent-name") take("agent-name", e.agentName);
|
|
1507
|
+
else if (e.type === "summary") take("summary", e.summary);
|
|
1508
|
+
else if (e.type === "last-prompt") take("last-prompt", e.lastPrompt);
|
|
1509
|
+
else if (findPrompt && firstPrompt === null) {
|
|
1510
|
+
for (const item of normalizer.push(entry)) {
|
|
1511
|
+
if (item.kind === "user" && item.text.trim()) {
|
|
1512
|
+
firstPrompt = item.text;
|
|
1513
|
+
break;
|
|
1514
|
+
}
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
};
|
|
1519
|
+
scan(head, true);
|
|
1520
|
+
scan(tail, false);
|
|
1521
|
+
if (firstPrompt !== null) out["first-prompt"] = firstPrompt;
|
|
1522
|
+
return out;
|
|
1523
|
+
}
|
|
1524
|
+
|
|
1450
1525
|
// src/engine/activity.ts
|
|
1451
1526
|
import { execFile as execFile2 } from "child_process";
|
|
1452
1527
|
var RECENT_WRITE_MS = 2 * 60 * 1e3;
|
|
@@ -1512,11 +1587,17 @@ function describeActivity(input) {
|
|
|
1512
1587
|
}
|
|
1513
1588
|
|
|
1514
1589
|
// src/engine/files.ts
|
|
1590
|
+
function codexHome(dir) {
|
|
1591
|
+
return {
|
|
1592
|
+
sessions: path5.join(dir, "sessions"),
|
|
1593
|
+
archived: path5.join(dir, "archived_sessions"),
|
|
1594
|
+
index: path5.join(dir, "session_index.jsonl")
|
|
1595
|
+
};
|
|
1596
|
+
}
|
|
1515
1597
|
function defaultProviderHomes(home = os3.homedir()) {
|
|
1516
1598
|
return {
|
|
1517
1599
|
claudeProjects: path5.join(home, ".claude", "projects"),
|
|
1518
|
-
|
|
1519
|
-
codexIndex: path5.join(home, ".codex", "session_index.jsonl")
|
|
1600
|
+
codex: [codexHome(path5.join(home, ".codex"))]
|
|
1520
1601
|
};
|
|
1521
1602
|
}
|
|
1522
1603
|
var UUIDISH = /([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\.jsonl$/i;
|
|
@@ -1550,7 +1631,7 @@ async function claudeCandidates(root) {
|
|
|
1550
1631
|
}
|
|
1551
1632
|
return out;
|
|
1552
1633
|
}
|
|
1553
|
-
async function
|
|
1634
|
+
async function codexTree(root) {
|
|
1554
1635
|
const out = [];
|
|
1555
1636
|
for (const year of await safeReaddir(root)) {
|
|
1556
1637
|
if (!year.isDirectory()) continue;
|
|
@@ -1558,17 +1639,23 @@ async function codexCandidates(root) {
|
|
|
1558
1639
|
if (!month.isDirectory()) continue;
|
|
1559
1640
|
for (const day of await safeReaddir(path5.join(root, year.name, month.name))) {
|
|
1560
1641
|
if (!day.isDirectory()) continue;
|
|
1561
|
-
|
|
1562
|
-
const entries = await safeReaddir(dir);
|
|
1563
|
-
const stats = await Promise.all(
|
|
1564
|
-
entries.filter((e) => e.isFile() && e.name.startsWith("rollout-") && e.name.endsWith(".jsonl")).map((e) => statCandidate("codex", path5.join(dir, e.name)))
|
|
1565
|
-
);
|
|
1566
|
-
for (const s of stats) if (s) out.push(s);
|
|
1642
|
+
out.push(...await codexFlat(path5.join(root, year.name, month.name, day.name), false));
|
|
1567
1643
|
}
|
|
1568
1644
|
}
|
|
1569
1645
|
}
|
|
1570
1646
|
return out;
|
|
1571
1647
|
}
|
|
1648
|
+
async function codexFlat(dir, archived) {
|
|
1649
|
+
const entries = await safeReaddir(dir);
|
|
1650
|
+
const stats = await Promise.all(
|
|
1651
|
+
entries.filter((e) => e.isFile() && e.name.startsWith("rollout-") && e.name.endsWith(".jsonl")).map((e) => statCandidate("codex", path5.join(dir, e.name)))
|
|
1652
|
+
);
|
|
1653
|
+
return stats.filter((s) => s !== null).map((s) => archived ? { ...s, archived: true } : s);
|
|
1654
|
+
}
|
|
1655
|
+
async function codexCandidates(homes) {
|
|
1656
|
+
const lists = await Promise.all(homes.flatMap((h) => [codexTree(h.sessions), codexFlat(h.archived, true)]));
|
|
1657
|
+
return lists.flat();
|
|
1658
|
+
}
|
|
1572
1659
|
async function readSlice(file, start, length) {
|
|
1573
1660
|
const handle = await fs4.open(file, "r");
|
|
1574
1661
|
try {
|
|
@@ -1590,56 +1677,38 @@ function completeLines(text, droppedFirst) {
|
|
|
1590
1677
|
}
|
|
1591
1678
|
var HEAD_BYTES = 256 * 1024;
|
|
1592
1679
|
var TAIL_BYTES = 128 * 1024;
|
|
1593
|
-
function oneLine(text, max = 140) {
|
|
1594
|
-
const flat = text.replace(/\s+/g, " ").trim();
|
|
1595
|
-
return flat.length > max ? `${flat.slice(0, max - 1)}\u2026` : flat;
|
|
1596
|
-
}
|
|
1597
1680
|
async function describeClaude(candidate) {
|
|
1598
1681
|
const head = parseJsonl(completeLines(await readSlice(candidate.file, 0, HEAD_BYTES), false));
|
|
1599
1682
|
const tailStart = Math.max(0, candidate.size - TAIL_BYTES);
|
|
1600
1683
|
const tail = tailStart > 0 ? parseJsonl(completeLines(await readSlice(candidate.file, tailStart, TAIL_BYTES), true)) : head;
|
|
1601
1684
|
let cwd = null;
|
|
1602
|
-
let firstPrompt = null;
|
|
1603
1685
|
let entrypoint = null;
|
|
1604
|
-
const normalizer = createClaudeNormalizer();
|
|
1605
1686
|
for (const entry of head) {
|
|
1606
1687
|
if (typeof entry !== "object" || entry === null) continue;
|
|
1607
1688
|
const e = entry;
|
|
1608
1689
|
if (!cwd && typeof e.cwd === "string") cwd = e.cwd;
|
|
1609
1690
|
if (!entrypoint && typeof e.entrypoint === "string") entrypoint = e.entrypoint;
|
|
1610
|
-
if (
|
|
1611
|
-
for (const item of normalizer.push(entry)) {
|
|
1612
|
-
if (item.kind === "user") {
|
|
1613
|
-
firstPrompt = item.text;
|
|
1614
|
-
break;
|
|
1615
|
-
}
|
|
1616
|
-
}
|
|
1617
|
-
}
|
|
1618
|
-
if (cwd && firstPrompt !== null) break;
|
|
1691
|
+
if (cwd && entrypoint) break;
|
|
1619
1692
|
}
|
|
1620
|
-
const
|
|
1621
|
-
|
|
1622
|
-
if (typeof entry !== "object" || entry === null) continue;
|
|
1623
|
-
const e = entry;
|
|
1624
|
-
if (e.type === "custom-title" && typeof e.customTitle === "string") titles.custom = e.customTitle;
|
|
1625
|
-
if (e.type === "ai-title" && typeof e.aiTitle === "string") titles.ai = e.aiTitle;
|
|
1626
|
-
if (e.type === "agent-name" && typeof e.agentName === "string") titles.agent = e.agentName;
|
|
1627
|
-
if (e.type === "summary" && typeof e.summary === "string") titles.summary = e.summary;
|
|
1628
|
-
if (e.type === "last-prompt" && typeof e.lastPrompt === "string") titles.last = e.lastPrompt;
|
|
1629
|
-
}
|
|
1630
|
-
const title = titles.custom ?? titles.ai ?? titles.agent ?? titles.summary ?? firstPrompt ?? titles.last;
|
|
1631
|
-
if (!title) return null;
|
|
1693
|
+
const resolved = resolveSessionTitle(claudeTranscriptTitleSources(head, tailStart > 0 ? tail : []));
|
|
1694
|
+
if (!resolved) return null;
|
|
1632
1695
|
return {
|
|
1633
1696
|
provider: "claude",
|
|
1634
1697
|
providerSessionId: candidate.id,
|
|
1635
1698
|
cwd,
|
|
1636
|
-
title:
|
|
1699
|
+
title: resolved.title,
|
|
1700
|
+
titleSource: resolved.source,
|
|
1637
1701
|
updatedAt: new Date(candidate.mtimeMs).toISOString(),
|
|
1638
1702
|
source: entrypoint,
|
|
1639
1703
|
activeElsewhere: null
|
|
1640
1704
|
};
|
|
1641
1705
|
}
|
|
1642
|
-
async function readCodexIndex(
|
|
1706
|
+
async function readCodexIndex(files) {
|
|
1707
|
+
const names = /* @__PURE__ */ new Map();
|
|
1708
|
+
for (const file of files) for (const [id, name] of await readCodexIndexFile(file)) if (!names.has(id)) names.set(id, name);
|
|
1709
|
+
return names;
|
|
1710
|
+
}
|
|
1711
|
+
async function readCodexIndexFile(file) {
|
|
1643
1712
|
const names = /* @__PURE__ */ new Map();
|
|
1644
1713
|
let text;
|
|
1645
1714
|
try {
|
|
@@ -1685,13 +1754,15 @@ async function describeCodex(candidate, names) {
|
|
|
1685
1754
|
}
|
|
1686
1755
|
}
|
|
1687
1756
|
if (isSubAgent) return null;
|
|
1688
|
-
const
|
|
1689
|
-
if (!
|
|
1757
|
+
const resolved = resolveSessionTitle({ app: names.get(candidate.id), "first-prompt": firstPrompt });
|
|
1758
|
+
if (!resolved) return null;
|
|
1690
1759
|
return {
|
|
1691
1760
|
provider: "codex",
|
|
1692
1761
|
providerSessionId: candidate.id,
|
|
1693
1762
|
cwd,
|
|
1694
|
-
title:
|
|
1763
|
+
title: resolved.title,
|
|
1764
|
+
titleSource: resolved.source,
|
|
1765
|
+
...candidate.archived ? { archived: true } : {},
|
|
1695
1766
|
updatedAt: new Date(candidate.mtimeMs).toISOString(),
|
|
1696
1767
|
source,
|
|
1697
1768
|
activeElsewhere: null
|
|
@@ -1736,8 +1807,8 @@ async function discoverLocalSessions(options = {}) {
|
|
|
1736
1807
|
const homes = options.homes ?? defaultProviderHomes();
|
|
1737
1808
|
const [claude, codex, names] = await Promise.all([
|
|
1738
1809
|
claudeCandidates(homes.claudeProjects),
|
|
1739
|
-
codexCandidates(homes.
|
|
1740
|
-
readCodexIndex(homes.
|
|
1810
|
+
codexCandidates(homes.codex),
|
|
1811
|
+
readCodexIndex(homes.codex.map((h) => h.index))
|
|
1741
1812
|
]);
|
|
1742
1813
|
const candidates = [...claude, ...codex].sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
1743
1814
|
const processes = await listProcesses();
|
|
@@ -1757,7 +1828,7 @@ async function discoverLocalSessions(options = {}) {
|
|
|
1757
1828
|
}
|
|
1758
1829
|
async function findSessionFile(provider, providerSessionId, homes = defaultProviderHomes()) {
|
|
1759
1830
|
if (!/^[0-9a-f-]{36}$/i.test(providerSessionId)) return null;
|
|
1760
|
-
const candidates = provider === "claude" ? await claudeCandidates(homes.claudeProjects) : await codexCandidates(homes.
|
|
1831
|
+
const candidates = provider === "claude" ? await claudeCandidates(homes.claudeProjects) : await codexCandidates(homes.codex);
|
|
1761
1832
|
const hits = candidates.filter((c) => c.id === providerSessionId).sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
1762
1833
|
return hits[0]?.file ?? null;
|
|
1763
1834
|
}
|
|
@@ -1809,7 +1880,7 @@ async function readSessionFile(provider, file, endOffset, maxBytes = HISTORY_BUD
|
|
|
1809
1880
|
async function listSessionFiles(homes = defaultProviderHomes()) {
|
|
1810
1881
|
const [claude, codex] = await Promise.all([
|
|
1811
1882
|
claudeCandidates(homes.claudeProjects),
|
|
1812
|
-
codexCandidates(homes.
|
|
1883
|
+
codexCandidates(homes.codex)
|
|
1813
1884
|
]);
|
|
1814
1885
|
return [...claude, ...codex].sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
1815
1886
|
}
|
|
@@ -1852,7 +1923,7 @@ var PreferenceStore = class {
|
|
|
1852
1923
|
if (!key || key.length > 200) throw new Error("That chat can't be found.");
|
|
1853
1924
|
const next = { ...this.data[key] };
|
|
1854
1925
|
if ("title" in patch) {
|
|
1855
|
-
const title = typeof patch.title === "string" ? patch.title.replace(/\s+/g, " ").trim().slice(0,
|
|
1926
|
+
const title = typeof patch.title === "string" ? patch.title.replace(/\s+/g, " ").trim().slice(0, TITLE_MAX) : "";
|
|
1856
1927
|
if (title) next.title = title;
|
|
1857
1928
|
else delete next.title;
|
|
1858
1929
|
}
|
|
@@ -1870,6 +1941,18 @@ var PreferenceStore = class {
|
|
|
1870
1941
|
this.persist();
|
|
1871
1942
|
return this.all();
|
|
1872
1943
|
}
|
|
1944
|
+
/**
|
|
1945
|
+
* Move a preference to the chat's lasting key (`session:<id>` → `<provider>:<id>` once the
|
|
1946
|
+
* provider's id is known), so a rename made early is the one rename everywhere. A preference
|
|
1947
|
+
* already under the lasting key wins field by field.
|
|
1948
|
+
*/
|
|
1949
|
+
rekey(from, to) {
|
|
1950
|
+
if (from === to || !this.data[from]) return false;
|
|
1951
|
+
this.data[to] = { ...this.data[from], ...this.data[to] };
|
|
1952
|
+
delete this.data[from];
|
|
1953
|
+
this.persist();
|
|
1954
|
+
return true;
|
|
1955
|
+
}
|
|
1873
1956
|
persist() {
|
|
1874
1957
|
const snapshot = JSON.stringify(this.data, null, 1);
|
|
1875
1958
|
this.writing = this.writing.then(async () => {
|
|
@@ -1997,7 +2080,7 @@ async function searchLocalChats(query, options = {}) {
|
|
|
1997
2080
|
const raw = JSON.stringify(needle).slice(1, -1);
|
|
1998
2081
|
const limit = Math.max(1, Math.min(options.limit ?? 30, 100));
|
|
1999
2082
|
const deadline = Date.now() + (options.budgetMs ?? 3500);
|
|
2000
|
-
const names = await readCodexIndex(homes.
|
|
2083
|
+
const names = await readCodexIndex(homes.codex.map((h) => h.index));
|
|
2001
2084
|
const hits = [];
|
|
2002
2085
|
let i = cursor;
|
|
2003
2086
|
for (; i < files.length && hits.length < limit && Date.now() < deadline; i += 1) {
|
|
@@ -2032,6 +2115,65 @@ async function searchLocalChats(query, options = {}) {
|
|
|
2032
2115
|
};
|
|
2033
2116
|
}
|
|
2034
2117
|
|
|
2118
|
+
// src/sessions/hints.ts
|
|
2119
|
+
function titleHintKey(provider, sessionId) {
|
|
2120
|
+
return `${provider}:${sessionId}`;
|
|
2121
|
+
}
|
|
2122
|
+
function createTitleHintsStore() {
|
|
2123
|
+
let hints = null;
|
|
2124
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
2125
|
+
return {
|
|
2126
|
+
current: () => hints,
|
|
2127
|
+
subscribe(listener) {
|
|
2128
|
+
listeners.add(listener);
|
|
2129
|
+
return () => listeners.delete(listener);
|
|
2130
|
+
},
|
|
2131
|
+
set(next) {
|
|
2132
|
+
hints = next;
|
|
2133
|
+
for (const l of listeners) l();
|
|
2134
|
+
}
|
|
2135
|
+
};
|
|
2136
|
+
}
|
|
2137
|
+
|
|
2138
|
+
// src/engine/title-hints.ts
|
|
2139
|
+
function overlayHint(chat, hints, expected) {
|
|
2140
|
+
if (!expected) return chat;
|
|
2141
|
+
if (hints === null) return { ...chat, titlePending: true };
|
|
2142
|
+
const hint = hints.get(titleHintKey(chat.provider, chat.providerSessionId));
|
|
2143
|
+
if (!hint) return chat;
|
|
2144
|
+
return {
|
|
2145
|
+
...chat,
|
|
2146
|
+
title: hint.title,
|
|
2147
|
+
titleSource: hint.source,
|
|
2148
|
+
...hint.archived || chat.archived ? { archived: true } : {},
|
|
2149
|
+
...hint.scheduled ? { scheduled: true } : {}
|
|
2150
|
+
};
|
|
2151
|
+
}
|
|
2152
|
+
function unavailableFromHints(hints, covered) {
|
|
2153
|
+
if (!hints) return [];
|
|
2154
|
+
const out = [];
|
|
2155
|
+
for (const [key, hint] of hints) {
|
|
2156
|
+
if (!hint.unavailable || covered.has(key)) continue;
|
|
2157
|
+
const at = key.indexOf(":");
|
|
2158
|
+
const provider = key.slice(0, at);
|
|
2159
|
+
if (provider !== "claude" && provider !== "codex") continue;
|
|
2160
|
+
out.push({
|
|
2161
|
+
provider,
|
|
2162
|
+
providerSessionId: key.slice(at + 1),
|
|
2163
|
+
cwd: null,
|
|
2164
|
+
title: hint.title,
|
|
2165
|
+
titleSource: hint.source,
|
|
2166
|
+
updatedAt: new Date(hint.lastActiveMs ?? 0).toISOString(),
|
|
2167
|
+
source: null,
|
|
2168
|
+
activeElsewhere: null,
|
|
2169
|
+
unavailable: hint.unavailable,
|
|
2170
|
+
...hint.archived ? { archived: true } : {},
|
|
2171
|
+
...hint.scheduled ? { scheduled: true } : {}
|
|
2172
|
+
});
|
|
2173
|
+
}
|
|
2174
|
+
return out.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
|
|
2175
|
+
}
|
|
2176
|
+
|
|
2035
2177
|
// src/engine/store.ts
|
|
2036
2178
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
2037
2179
|
import fs6 from "fs/promises";
|
|
@@ -2382,6 +2524,13 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
2382
2524
|
}
|
|
2383
2525
|
}
|
|
2384
2526
|
}
|
|
2527
|
+
const titleHints = options.titleHints;
|
|
2528
|
+
const hintsNow = () => titleHints?.current() ?? null;
|
|
2529
|
+
const unsubscribeHints = titleHints?.subscribe(() => emit({ type: "titles" })) ?? null;
|
|
2530
|
+
const withHintTitle = (session) => {
|
|
2531
|
+
const hint = session.providerSessionId ? hintsNow()?.get(titleHintKey(session.provider, session.providerSessionId)) : void 0;
|
|
2532
|
+
return hint ? { ...session, title: hint.title } : session;
|
|
2533
|
+
};
|
|
2385
2534
|
function saveSession(session) {
|
|
2386
2535
|
index.putSession(session);
|
|
2387
2536
|
emit({ type: "session", session });
|
|
@@ -2538,7 +2687,12 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
2538
2687
|
if (sid && sid !== knownProviderSession) {
|
|
2539
2688
|
knownProviderSession = sid;
|
|
2540
2689
|
const current = index.session(session.id);
|
|
2541
|
-
if (current)
|
|
2690
|
+
if (current) {
|
|
2691
|
+
saveSession({ ...current, providerSessionId: sid });
|
|
2692
|
+
if (preferences.rekey(chatKey(current.provider, null, current.id), chatKey(current.provider, sid))) {
|
|
2693
|
+
emit({ type: "preferences", preferences: preferences.all() });
|
|
2694
|
+
}
|
|
2695
|
+
}
|
|
2542
2696
|
}
|
|
2543
2697
|
for (const item of items) emitItem(liveRun, item);
|
|
2544
2698
|
}
|
|
@@ -2846,7 +3000,9 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
2846
3000
|
);
|
|
2847
3001
|
},
|
|
2848
3002
|
async discoverSessions(opts) {
|
|
2849
|
-
const
|
|
3003
|
+
const read = await discoverLocalSessions({ limit: opts?.limit, homes, excludePids: ourPids() });
|
|
3004
|
+
const hints = hintsNow();
|
|
3005
|
+
const found = read.map((chat) => overlayHint(chat, hints, titleHints !== void 0));
|
|
2850
3006
|
if (backups) {
|
|
2851
3007
|
for (const chat of found) {
|
|
2852
3008
|
const key = `${chat.provider}:${chat.providerSessionId}`;
|
|
@@ -2857,7 +3013,12 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
2857
3013
|
}
|
|
2858
3014
|
}
|
|
2859
3015
|
}
|
|
2860
|
-
|
|
3016
|
+
const limit = opts?.limit ?? 60;
|
|
3017
|
+
const oldest = found.length >= limit ? found[found.length - 1]?.updatedAt ?? "" : "";
|
|
3018
|
+
const gone = unavailableFromHints(hints, new Set(found.map((c) => titleHintKey(c.provider, c.providerSessionId)))).filter(
|
|
3019
|
+
(c) => c.updatedAt >= oldest
|
|
3020
|
+
);
|
|
3021
|
+
return gone.length ? [...found, ...gone].sort((a, b) => b.updatedAt.localeCompare(a.updatedAt)).slice(0, limit) : found;
|
|
2861
3022
|
},
|
|
2862
3023
|
async recentFolders() {
|
|
2863
3024
|
const seen = /* @__PURE__ */ new Map();
|
|
@@ -2878,7 +3039,7 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
2878
3039
|
);
|
|
2879
3040
|
},
|
|
2880
3041
|
async listSessions() {
|
|
2881
|
-
return index.sessions();
|
|
3042
|
+
return index.sessions().map(withHintTitle);
|
|
2882
3043
|
},
|
|
2883
3044
|
async listRuns(sessionId) {
|
|
2884
3045
|
const session = index.session(sessionId);
|
|
@@ -3053,7 +3214,9 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
3053
3214
|
return all;
|
|
3054
3215
|
},
|
|
3055
3216
|
async searchChats(query, opts) {
|
|
3056
|
-
|
|
3217
|
+
const result = await searchLocalChats(String(query ?? ""), { homes, cursor: opts?.cursor, limit: opts?.limit });
|
|
3218
|
+
const hints = hintsNow();
|
|
3219
|
+
return { ...result, hits: result.hits.map((hit) => ({ ...hit, title: hints?.get(titleHintKey(hit.provider, hit.providerSessionId))?.title ?? hit.title })) };
|
|
3057
3220
|
},
|
|
3058
3221
|
async openFolder(raw, target) {
|
|
3059
3222
|
const folder = await assertFolder(String(raw ?? ""));
|
|
@@ -3083,6 +3246,7 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
3083
3246
|
return agents.handleMcp(secret, body);
|
|
3084
3247
|
},
|
|
3085
3248
|
async dispose() {
|
|
3249
|
+
unsubscribeHints?.();
|
|
3086
3250
|
for (const liveRun of live.values()) {
|
|
3087
3251
|
liveRun.stopRequested = true;
|
|
3088
3252
|
if (liveRun.pgid !== null) killGroup(liveRun);
|
|
@@ -3138,9 +3302,11 @@ export {
|
|
|
3138
3302
|
buildCleanEnv,
|
|
3139
3303
|
claudeDefaults,
|
|
3140
3304
|
codexDefaults,
|
|
3305
|
+
codexHome,
|
|
3141
3306
|
createAiMatrxRawBackupSink,
|
|
3142
3307
|
createCodingSessionsEngine,
|
|
3143
3308
|
createRawBackupScheduler,
|
|
3309
|
+
createTitleHintsStore,
|
|
3144
3310
|
defaultProviderHomes,
|
|
3145
3311
|
describeActivity,
|
|
3146
3312
|
describeProviderStatus,
|
|
@@ -3153,6 +3319,7 @@ export {
|
|
|
3153
3319
|
readSessionFile,
|
|
3154
3320
|
searchLocalChats,
|
|
3155
3321
|
sessionActivity,
|
|
3156
|
-
snippetAround
|
|
3322
|
+
snippetAround,
|
|
3323
|
+
titleHintKey
|
|
3157
3324
|
};
|
|
3158
3325
|
//# sourceMappingURL=index.js.map
|