@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/server/index.cjs
CHANGED
|
@@ -1149,6 +1149,9 @@ var PROVIDER_LABEL = {
|
|
|
1149
1149
|
claude: "Claude Code",
|
|
1150
1150
|
codex: "Codex"
|
|
1151
1151
|
};
|
|
1152
|
+
function chatKey(provider, providerSessionId, sessionId) {
|
|
1153
|
+
return providerSessionId ? `${provider}:${providerSessionId}` : `session:${sessionId ?? ""}`;
|
|
1154
|
+
}
|
|
1152
1155
|
var TERMINAL_RUN_STATUSES = ["completed", "stopped", "failed"];
|
|
1153
1156
|
function isTerminalRunStatus(status) {
|
|
1154
1157
|
return TERMINAL_RUN_STATUSES.includes(status);
|
|
@@ -1463,6 +1466,78 @@ var import_promises4 = __toESM(require("fs/promises"), 1);
|
|
|
1463
1466
|
var import_node_os3 = __toESM(require("os"), 1);
|
|
1464
1467
|
var import_node_path5 = __toESM(require("path"), 1);
|
|
1465
1468
|
|
|
1469
|
+
// src/sessions/titles.ts
|
|
1470
|
+
var TITLE_MAX = 300;
|
|
1471
|
+
var BRIDGE_PLACEHOLDER = /^AI Matrx · [a-z-]+$/;
|
|
1472
|
+
var TITLE_PRECEDENCE = [
|
|
1473
|
+
/** The person's rename inside Matrx (Coding Sessions' preference). */
|
|
1474
|
+
"override",
|
|
1475
|
+
/** What the coding app's own sidebar shows: Claude's record `title` (renames included); Codex's thread name; Cursor's name. */
|
|
1476
|
+
"app",
|
|
1477
|
+
/** The title the app last showed, remembered by the claude-accounts engine after the record went away. */
|
|
1478
|
+
"app-remembered",
|
|
1479
|
+
/** Claude Code transcript entries (the last one of each kind that is not the bridge placeholder). */
|
|
1480
|
+
"custom-title",
|
|
1481
|
+
"ai-title",
|
|
1482
|
+
"agent-name",
|
|
1483
|
+
"summary",
|
|
1484
|
+
/** The person's first typed message (Codex: the thread's title / first user message). */
|
|
1485
|
+
"first-prompt",
|
|
1486
|
+
"last-prompt"
|
|
1487
|
+
];
|
|
1488
|
+
var STAMPABLE = /* @__PURE__ */ new Set(["custom-title", "ai-title", "agent-name"]);
|
|
1489
|
+
function isBridgePlaceholder(value) {
|
|
1490
|
+
return typeof value === "string" && BRIDGE_PLACEHOLDER.test(value.trim());
|
|
1491
|
+
}
|
|
1492
|
+
function cleanTitle(value) {
|
|
1493
|
+
if (typeof value !== "string") return null;
|
|
1494
|
+
const line = value.split(/\r?\n/).map((l) => l.trim()).find((l) => l.length > 0);
|
|
1495
|
+
if (!line) return null;
|
|
1496
|
+
return line.length > TITLE_MAX ? `${line.slice(0, TITLE_MAX - 1)}\u2026` : line;
|
|
1497
|
+
}
|
|
1498
|
+
function resolveSessionTitle(sources) {
|
|
1499
|
+
for (const source of TITLE_PRECEDENCE) {
|
|
1500
|
+
const raw = sources[source];
|
|
1501
|
+
if (STAMPABLE.has(source) && isBridgePlaceholder(raw)) continue;
|
|
1502
|
+
const title = cleanTitle(raw);
|
|
1503
|
+
if (title) return { title, source };
|
|
1504
|
+
}
|
|
1505
|
+
return null;
|
|
1506
|
+
}
|
|
1507
|
+
function claudeTranscriptTitleSources(head, tail = []) {
|
|
1508
|
+
const out = {};
|
|
1509
|
+
const take = (source, value) => {
|
|
1510
|
+
if (typeof value !== "string" || !value.trim()) return;
|
|
1511
|
+
if (STAMPABLE.has(source) && isBridgePlaceholder(value)) return;
|
|
1512
|
+
out[source] = value;
|
|
1513
|
+
};
|
|
1514
|
+
const normalizer = createClaudeNormalizer();
|
|
1515
|
+
let firstPrompt = null;
|
|
1516
|
+
const scan = (entries, findPrompt) => {
|
|
1517
|
+
for (const entry of entries) {
|
|
1518
|
+
if (typeof entry !== "object" || entry === null) continue;
|
|
1519
|
+
const e = entry;
|
|
1520
|
+
if (e.type === "custom-title") take("custom-title", e.customTitle);
|
|
1521
|
+
else if (e.type === "ai-title") take("ai-title", e.aiTitle);
|
|
1522
|
+
else if (e.type === "agent-name") take("agent-name", e.agentName);
|
|
1523
|
+
else if (e.type === "summary") take("summary", e.summary);
|
|
1524
|
+
else if (e.type === "last-prompt") take("last-prompt", e.lastPrompt);
|
|
1525
|
+
else if (findPrompt && firstPrompt === null) {
|
|
1526
|
+
for (const item of normalizer.push(entry)) {
|
|
1527
|
+
if (item.kind === "user" && item.text.trim()) {
|
|
1528
|
+
firstPrompt = item.text;
|
|
1529
|
+
break;
|
|
1530
|
+
}
|
|
1531
|
+
}
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
};
|
|
1535
|
+
scan(head, true);
|
|
1536
|
+
scan(tail, false);
|
|
1537
|
+
if (firstPrompt !== null) out["first-prompt"] = firstPrompt;
|
|
1538
|
+
return out;
|
|
1539
|
+
}
|
|
1540
|
+
|
|
1466
1541
|
// src/engine/activity.ts
|
|
1467
1542
|
var import_node_child_process2 = require("child_process");
|
|
1468
1543
|
var RECENT_WRITE_MS = 2 * 60 * 1e3;
|
|
@@ -1528,11 +1603,17 @@ function describeActivity(input) {
|
|
|
1528
1603
|
}
|
|
1529
1604
|
|
|
1530
1605
|
// src/engine/files.ts
|
|
1606
|
+
function codexHome(dir) {
|
|
1607
|
+
return {
|
|
1608
|
+
sessions: import_node_path5.default.join(dir, "sessions"),
|
|
1609
|
+
archived: import_node_path5.default.join(dir, "archived_sessions"),
|
|
1610
|
+
index: import_node_path5.default.join(dir, "session_index.jsonl")
|
|
1611
|
+
};
|
|
1612
|
+
}
|
|
1531
1613
|
function defaultProviderHomes(home = import_node_os3.default.homedir()) {
|
|
1532
1614
|
return {
|
|
1533
1615
|
claudeProjects: import_node_path5.default.join(home, ".claude", "projects"),
|
|
1534
|
-
|
|
1535
|
-
codexIndex: import_node_path5.default.join(home, ".codex", "session_index.jsonl")
|
|
1616
|
+
codex: [codexHome(import_node_path5.default.join(home, ".codex"))]
|
|
1536
1617
|
};
|
|
1537
1618
|
}
|
|
1538
1619
|
var UUIDISH = /([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\.jsonl$/i;
|
|
@@ -1566,7 +1647,7 @@ async function claudeCandidates(root) {
|
|
|
1566
1647
|
}
|
|
1567
1648
|
return out;
|
|
1568
1649
|
}
|
|
1569
|
-
async function
|
|
1650
|
+
async function codexTree(root) {
|
|
1570
1651
|
const out = [];
|
|
1571
1652
|
for (const year of await safeReaddir(root)) {
|
|
1572
1653
|
if (!year.isDirectory()) continue;
|
|
@@ -1574,17 +1655,23 @@ async function codexCandidates(root) {
|
|
|
1574
1655
|
if (!month.isDirectory()) continue;
|
|
1575
1656
|
for (const day of await safeReaddir(import_node_path5.default.join(root, year.name, month.name))) {
|
|
1576
1657
|
if (!day.isDirectory()) continue;
|
|
1577
|
-
|
|
1578
|
-
const entries = await safeReaddir(dir);
|
|
1579
|
-
const stats = await Promise.all(
|
|
1580
|
-
entries.filter((e) => e.isFile() && e.name.startsWith("rollout-") && e.name.endsWith(".jsonl")).map((e) => statCandidate("codex", import_node_path5.default.join(dir, e.name)))
|
|
1581
|
-
);
|
|
1582
|
-
for (const s of stats) if (s) out.push(s);
|
|
1658
|
+
out.push(...await codexFlat(import_node_path5.default.join(root, year.name, month.name, day.name), false));
|
|
1583
1659
|
}
|
|
1584
1660
|
}
|
|
1585
1661
|
}
|
|
1586
1662
|
return out;
|
|
1587
1663
|
}
|
|
1664
|
+
async function codexFlat(dir, archived) {
|
|
1665
|
+
const entries = await safeReaddir(dir);
|
|
1666
|
+
const stats = await Promise.all(
|
|
1667
|
+
entries.filter((e) => e.isFile() && e.name.startsWith("rollout-") && e.name.endsWith(".jsonl")).map((e) => statCandidate("codex", import_node_path5.default.join(dir, e.name)))
|
|
1668
|
+
);
|
|
1669
|
+
return stats.filter((s) => s !== null).map((s) => archived ? { ...s, archived: true } : s);
|
|
1670
|
+
}
|
|
1671
|
+
async function codexCandidates(homes) {
|
|
1672
|
+
const lists = await Promise.all(homes.flatMap((h) => [codexTree(h.sessions), codexFlat(h.archived, true)]));
|
|
1673
|
+
return lists.flat();
|
|
1674
|
+
}
|
|
1588
1675
|
async function readSlice(file, start, length) {
|
|
1589
1676
|
const handle = await import_promises4.default.open(file, "r");
|
|
1590
1677
|
try {
|
|
@@ -1606,56 +1693,38 @@ function completeLines(text, droppedFirst) {
|
|
|
1606
1693
|
}
|
|
1607
1694
|
var HEAD_BYTES = 256 * 1024;
|
|
1608
1695
|
var TAIL_BYTES = 128 * 1024;
|
|
1609
|
-
function oneLine(text, max = 140) {
|
|
1610
|
-
const flat = text.replace(/\s+/g, " ").trim();
|
|
1611
|
-
return flat.length > max ? `${flat.slice(0, max - 1)}\u2026` : flat;
|
|
1612
|
-
}
|
|
1613
1696
|
async function describeClaude(candidate) {
|
|
1614
1697
|
const head = parseJsonl(completeLines(await readSlice(candidate.file, 0, HEAD_BYTES), false));
|
|
1615
1698
|
const tailStart = Math.max(0, candidate.size - TAIL_BYTES);
|
|
1616
1699
|
const tail = tailStart > 0 ? parseJsonl(completeLines(await readSlice(candidate.file, tailStart, TAIL_BYTES), true)) : head;
|
|
1617
1700
|
let cwd = null;
|
|
1618
|
-
let firstPrompt = null;
|
|
1619
1701
|
let entrypoint = null;
|
|
1620
|
-
const normalizer = createClaudeNormalizer();
|
|
1621
1702
|
for (const entry of head) {
|
|
1622
1703
|
if (typeof entry !== "object" || entry === null) continue;
|
|
1623
1704
|
const e = entry;
|
|
1624
1705
|
if (!cwd && typeof e.cwd === "string") cwd = e.cwd;
|
|
1625
1706
|
if (!entrypoint && typeof e.entrypoint === "string") entrypoint = e.entrypoint;
|
|
1626
|
-
if (
|
|
1627
|
-
for (const item of normalizer.push(entry)) {
|
|
1628
|
-
if (item.kind === "user") {
|
|
1629
|
-
firstPrompt = item.text;
|
|
1630
|
-
break;
|
|
1631
|
-
}
|
|
1632
|
-
}
|
|
1633
|
-
}
|
|
1634
|
-
if (cwd && firstPrompt !== null) break;
|
|
1707
|
+
if (cwd && entrypoint) break;
|
|
1635
1708
|
}
|
|
1636
|
-
const
|
|
1637
|
-
|
|
1638
|
-
if (typeof entry !== "object" || entry === null) continue;
|
|
1639
|
-
const e = entry;
|
|
1640
|
-
if (e.type === "custom-title" && typeof e.customTitle === "string") titles.custom = e.customTitle;
|
|
1641
|
-
if (e.type === "ai-title" && typeof e.aiTitle === "string") titles.ai = e.aiTitle;
|
|
1642
|
-
if (e.type === "agent-name" && typeof e.agentName === "string") titles.agent = e.agentName;
|
|
1643
|
-
if (e.type === "summary" && typeof e.summary === "string") titles.summary = e.summary;
|
|
1644
|
-
if (e.type === "last-prompt" && typeof e.lastPrompt === "string") titles.last = e.lastPrompt;
|
|
1645
|
-
}
|
|
1646
|
-
const title = titles.custom ?? titles.ai ?? titles.agent ?? titles.summary ?? firstPrompt ?? titles.last;
|
|
1647
|
-
if (!title) return null;
|
|
1709
|
+
const resolved = resolveSessionTitle(claudeTranscriptTitleSources(head, tailStart > 0 ? tail : []));
|
|
1710
|
+
if (!resolved) return null;
|
|
1648
1711
|
return {
|
|
1649
1712
|
provider: "claude",
|
|
1650
1713
|
providerSessionId: candidate.id,
|
|
1651
1714
|
cwd,
|
|
1652
|
-
title:
|
|
1715
|
+
title: resolved.title,
|
|
1716
|
+
titleSource: resolved.source,
|
|
1653
1717
|
updatedAt: new Date(candidate.mtimeMs).toISOString(),
|
|
1654
1718
|
source: entrypoint,
|
|
1655
1719
|
activeElsewhere: null
|
|
1656
1720
|
};
|
|
1657
1721
|
}
|
|
1658
|
-
async function readCodexIndex(
|
|
1722
|
+
async function readCodexIndex(files) {
|
|
1723
|
+
const names = /* @__PURE__ */ new Map();
|
|
1724
|
+
for (const file of files) for (const [id, name] of await readCodexIndexFile(file)) if (!names.has(id)) names.set(id, name);
|
|
1725
|
+
return names;
|
|
1726
|
+
}
|
|
1727
|
+
async function readCodexIndexFile(file) {
|
|
1659
1728
|
const names = /* @__PURE__ */ new Map();
|
|
1660
1729
|
let text;
|
|
1661
1730
|
try {
|
|
@@ -1701,13 +1770,15 @@ async function describeCodex(candidate, names) {
|
|
|
1701
1770
|
}
|
|
1702
1771
|
}
|
|
1703
1772
|
if (isSubAgent) return null;
|
|
1704
|
-
const
|
|
1705
|
-
if (!
|
|
1773
|
+
const resolved = resolveSessionTitle({ app: names.get(candidate.id), "first-prompt": firstPrompt });
|
|
1774
|
+
if (!resolved) return null;
|
|
1706
1775
|
return {
|
|
1707
1776
|
provider: "codex",
|
|
1708
1777
|
providerSessionId: candidate.id,
|
|
1709
1778
|
cwd,
|
|
1710
|
-
title:
|
|
1779
|
+
title: resolved.title,
|
|
1780
|
+
titleSource: resolved.source,
|
|
1781
|
+
...candidate.archived ? { archived: true } : {},
|
|
1711
1782
|
updatedAt: new Date(candidate.mtimeMs).toISOString(),
|
|
1712
1783
|
source,
|
|
1713
1784
|
activeElsewhere: null
|
|
@@ -1752,8 +1823,8 @@ async function discoverLocalSessions(options = {}) {
|
|
|
1752
1823
|
const homes = options.homes ?? defaultProviderHomes();
|
|
1753
1824
|
const [claude, codex, names] = await Promise.all([
|
|
1754
1825
|
claudeCandidates(homes.claudeProjects),
|
|
1755
|
-
codexCandidates(homes.
|
|
1756
|
-
readCodexIndex(homes.
|
|
1826
|
+
codexCandidates(homes.codex),
|
|
1827
|
+
readCodexIndex(homes.codex.map((h) => h.index))
|
|
1757
1828
|
]);
|
|
1758
1829
|
const candidates = [...claude, ...codex].sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
1759
1830
|
const processes = await listProcesses();
|
|
@@ -1773,7 +1844,7 @@ async function discoverLocalSessions(options = {}) {
|
|
|
1773
1844
|
}
|
|
1774
1845
|
async function findSessionFile(provider, providerSessionId, homes = defaultProviderHomes()) {
|
|
1775
1846
|
if (!/^[0-9a-f-]{36}$/i.test(providerSessionId)) return null;
|
|
1776
|
-
const candidates = provider === "claude" ? await claudeCandidates(homes.claudeProjects) : await codexCandidates(homes.
|
|
1847
|
+
const candidates = provider === "claude" ? await claudeCandidates(homes.claudeProjects) : await codexCandidates(homes.codex);
|
|
1777
1848
|
const hits = candidates.filter((c) => c.id === providerSessionId).sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
1778
1849
|
return hits[0]?.file ?? null;
|
|
1779
1850
|
}
|
|
@@ -1825,7 +1896,7 @@ async function readSessionFile(provider, file, endOffset, maxBytes = HISTORY_BUD
|
|
|
1825
1896
|
async function listSessionFiles(homes = defaultProviderHomes()) {
|
|
1826
1897
|
const [claude, codex] = await Promise.all([
|
|
1827
1898
|
claudeCandidates(homes.claudeProjects),
|
|
1828
|
-
codexCandidates(homes.
|
|
1899
|
+
codexCandidates(homes.codex)
|
|
1829
1900
|
]);
|
|
1830
1901
|
return [...claude, ...codex].sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
1831
1902
|
}
|
|
@@ -1868,7 +1939,7 @@ var PreferenceStore = class {
|
|
|
1868
1939
|
if (!key || key.length > 200) throw new Error("That chat can't be found.");
|
|
1869
1940
|
const next = { ...this.data[key] };
|
|
1870
1941
|
if ("title" in patch) {
|
|
1871
|
-
const title = typeof patch.title === "string" ? patch.title.replace(/\s+/g, " ").trim().slice(0,
|
|
1942
|
+
const title = typeof patch.title === "string" ? patch.title.replace(/\s+/g, " ").trim().slice(0, TITLE_MAX) : "";
|
|
1872
1943
|
if (title) next.title = title;
|
|
1873
1944
|
else delete next.title;
|
|
1874
1945
|
}
|
|
@@ -1886,6 +1957,18 @@ var PreferenceStore = class {
|
|
|
1886
1957
|
this.persist();
|
|
1887
1958
|
return this.all();
|
|
1888
1959
|
}
|
|
1960
|
+
/**
|
|
1961
|
+
* Move a preference to the chat's lasting key (`session:<id>` → `<provider>:<id>` once the
|
|
1962
|
+
* provider's id is known), so a rename made early is the one rename everywhere. A preference
|
|
1963
|
+
* already under the lasting key wins field by field.
|
|
1964
|
+
*/
|
|
1965
|
+
rekey(from, to) {
|
|
1966
|
+
if (from === to || !this.data[from]) return false;
|
|
1967
|
+
this.data[to] = { ...this.data[from], ...this.data[to] };
|
|
1968
|
+
delete this.data[from];
|
|
1969
|
+
this.persist();
|
|
1970
|
+
return true;
|
|
1971
|
+
}
|
|
1889
1972
|
persist() {
|
|
1890
1973
|
const snapshot = JSON.stringify(this.data, null, 1);
|
|
1891
1974
|
this.writing = this.writing.then(async () => {
|
|
@@ -2013,7 +2096,7 @@ async function searchLocalChats(query, options = {}) {
|
|
|
2013
2096
|
const raw = JSON.stringify(needle).slice(1, -1);
|
|
2014
2097
|
const limit = Math.max(1, Math.min(options.limit ?? 30, 100));
|
|
2015
2098
|
const deadline = Date.now() + (options.budgetMs ?? 3500);
|
|
2016
|
-
const names = await readCodexIndex(homes.
|
|
2099
|
+
const names = await readCodexIndex(homes.codex.map((h) => h.index));
|
|
2017
2100
|
const hits = [];
|
|
2018
2101
|
let i = cursor;
|
|
2019
2102
|
for (; i < files.length && hits.length < limit && Date.now() < deadline; i += 1) {
|
|
@@ -2048,6 +2131,50 @@ async function searchLocalChats(query, options = {}) {
|
|
|
2048
2131
|
};
|
|
2049
2132
|
}
|
|
2050
2133
|
|
|
2134
|
+
// src/sessions/hints.ts
|
|
2135
|
+
function titleHintKey(provider, sessionId) {
|
|
2136
|
+
return `${provider}:${sessionId}`;
|
|
2137
|
+
}
|
|
2138
|
+
|
|
2139
|
+
// src/engine/title-hints.ts
|
|
2140
|
+
function overlayHint(chat, hints, expected) {
|
|
2141
|
+
if (!expected) return chat;
|
|
2142
|
+
if (hints === null) return { ...chat, titlePending: true };
|
|
2143
|
+
const hint = hints.get(titleHintKey(chat.provider, chat.providerSessionId));
|
|
2144
|
+
if (!hint) return chat;
|
|
2145
|
+
return {
|
|
2146
|
+
...chat,
|
|
2147
|
+
title: hint.title,
|
|
2148
|
+
titleSource: hint.source,
|
|
2149
|
+
...hint.archived || chat.archived ? { archived: true } : {},
|
|
2150
|
+
...hint.scheduled ? { scheduled: true } : {}
|
|
2151
|
+
};
|
|
2152
|
+
}
|
|
2153
|
+
function unavailableFromHints(hints, covered) {
|
|
2154
|
+
if (!hints) return [];
|
|
2155
|
+
const out = [];
|
|
2156
|
+
for (const [key, hint] of hints) {
|
|
2157
|
+
if (!hint.unavailable || covered.has(key)) continue;
|
|
2158
|
+
const at = key.indexOf(":");
|
|
2159
|
+
const provider = key.slice(0, at);
|
|
2160
|
+
if (provider !== "claude" && provider !== "codex") continue;
|
|
2161
|
+
out.push({
|
|
2162
|
+
provider,
|
|
2163
|
+
providerSessionId: key.slice(at + 1),
|
|
2164
|
+
cwd: null,
|
|
2165
|
+
title: hint.title,
|
|
2166
|
+
titleSource: hint.source,
|
|
2167
|
+
updatedAt: new Date(hint.lastActiveMs ?? 0).toISOString(),
|
|
2168
|
+
source: null,
|
|
2169
|
+
activeElsewhere: null,
|
|
2170
|
+
unavailable: hint.unavailable,
|
|
2171
|
+
...hint.archived ? { archived: true } : {},
|
|
2172
|
+
...hint.scheduled ? { scheduled: true } : {}
|
|
2173
|
+
});
|
|
2174
|
+
}
|
|
2175
|
+
return out.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
|
|
2176
|
+
}
|
|
2177
|
+
|
|
2051
2178
|
// src/engine/store.ts
|
|
2052
2179
|
var import_node_crypto4 = require("crypto");
|
|
2053
2180
|
var import_promises6 = __toESM(require("fs/promises"), 1);
|
|
@@ -2398,6 +2525,13 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
2398
2525
|
}
|
|
2399
2526
|
}
|
|
2400
2527
|
}
|
|
2528
|
+
const titleHints = options.titleHints;
|
|
2529
|
+
const hintsNow = () => titleHints?.current() ?? null;
|
|
2530
|
+
const unsubscribeHints = titleHints?.subscribe(() => emit({ type: "titles" })) ?? null;
|
|
2531
|
+
const withHintTitle = (session) => {
|
|
2532
|
+
const hint = session.providerSessionId ? hintsNow()?.get(titleHintKey(session.provider, session.providerSessionId)) : void 0;
|
|
2533
|
+
return hint ? { ...session, title: hint.title } : session;
|
|
2534
|
+
};
|
|
2401
2535
|
function saveSession(session) {
|
|
2402
2536
|
index.putSession(session);
|
|
2403
2537
|
emit({ type: "session", session });
|
|
@@ -2554,7 +2688,12 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
2554
2688
|
if (sid && sid !== knownProviderSession) {
|
|
2555
2689
|
knownProviderSession = sid;
|
|
2556
2690
|
const current = index.session(session.id);
|
|
2557
|
-
if (current)
|
|
2691
|
+
if (current) {
|
|
2692
|
+
saveSession({ ...current, providerSessionId: sid });
|
|
2693
|
+
if (preferences.rekey(chatKey(current.provider, null, current.id), chatKey(current.provider, sid))) {
|
|
2694
|
+
emit({ type: "preferences", preferences: preferences.all() });
|
|
2695
|
+
}
|
|
2696
|
+
}
|
|
2558
2697
|
}
|
|
2559
2698
|
for (const item of items) emitItem(liveRun, item);
|
|
2560
2699
|
}
|
|
@@ -2862,7 +3001,9 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
2862
3001
|
);
|
|
2863
3002
|
},
|
|
2864
3003
|
async discoverSessions(opts) {
|
|
2865
|
-
const
|
|
3004
|
+
const read = await discoverLocalSessions({ limit: opts?.limit, homes, excludePids: ourPids() });
|
|
3005
|
+
const hints = hintsNow();
|
|
3006
|
+
const found = read.map((chat) => overlayHint(chat, hints, titleHints !== void 0));
|
|
2866
3007
|
if (backups) {
|
|
2867
3008
|
for (const chat of found) {
|
|
2868
3009
|
const key = `${chat.provider}:${chat.providerSessionId}`;
|
|
@@ -2873,7 +3014,12 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
2873
3014
|
}
|
|
2874
3015
|
}
|
|
2875
3016
|
}
|
|
2876
|
-
|
|
3017
|
+
const limit = opts?.limit ?? 60;
|
|
3018
|
+
const oldest = found.length >= limit ? found[found.length - 1]?.updatedAt ?? "" : "";
|
|
3019
|
+
const gone = unavailableFromHints(hints, new Set(found.map((c) => titleHintKey(c.provider, c.providerSessionId)))).filter(
|
|
3020
|
+
(c) => c.updatedAt >= oldest
|
|
3021
|
+
);
|
|
3022
|
+
return gone.length ? [...found, ...gone].sort((a, b) => b.updatedAt.localeCompare(a.updatedAt)).slice(0, limit) : found;
|
|
2877
3023
|
},
|
|
2878
3024
|
async recentFolders() {
|
|
2879
3025
|
const seen = /* @__PURE__ */ new Map();
|
|
@@ -2894,7 +3040,7 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
2894
3040
|
);
|
|
2895
3041
|
},
|
|
2896
3042
|
async listSessions() {
|
|
2897
|
-
return index.sessions();
|
|
3043
|
+
return index.sessions().map(withHintTitle);
|
|
2898
3044
|
},
|
|
2899
3045
|
async listRuns(sessionId) {
|
|
2900
3046
|
const session = index.session(sessionId);
|
|
@@ -3069,7 +3215,9 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
3069
3215
|
return all;
|
|
3070
3216
|
},
|
|
3071
3217
|
async searchChats(query, opts) {
|
|
3072
|
-
|
|
3218
|
+
const result = await searchLocalChats(String(query ?? ""), { homes, cursor: opts?.cursor, limit: opts?.limit });
|
|
3219
|
+
const hints = hintsNow();
|
|
3220
|
+
return { ...result, hits: result.hits.map((hit) => ({ ...hit, title: hints?.get(titleHintKey(hit.provider, hit.providerSessionId))?.title ?? hit.title })) };
|
|
3073
3221
|
},
|
|
3074
3222
|
async openFolder(raw, target) {
|
|
3075
3223
|
const folder = await assertFolder(String(raw ?? ""));
|
|
@@ -3099,6 +3247,7 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
3099
3247
|
return agents.handleMcp(secret, body);
|
|
3100
3248
|
},
|
|
3101
3249
|
async dispose() {
|
|
3250
|
+
unsubscribeHints?.();
|
|
3102
3251
|
for (const liveRun of live.values()) {
|
|
3103
3252
|
liveRun.stopRequested = true;
|
|
3104
3253
|
if (liveRun.pgid !== null) killGroup(liveRun);
|