@ai-matrx/coding-sessions 0.1.166 → 0.3.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 +35 -0
- package/dist/engine/index.cjs +273 -59
- 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 +269 -58
- package/dist/engine/index.js.map +1 -1
- package/dist/{index-CDeDKQHe.d.cts → index-Bbe11HoB.d.cts} +108 -3
- package/dist/{index-CDeDKQHe.d.ts → index-Bbe11HoB.d.ts} +108 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +30 -1
- package/dist/index.d.ts +30 -1
- package/dist/index.js.map +1 -1
- package/dist/react/index.cjs +47 -6
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +37 -0
- package/dist/react/index.d.ts +37 -0
- package/dist/react/index.js +47 -6
- package/dist/react/index.js.map +1 -1
- package/dist/server/index.cjs +241 -57
- 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 +241 -57
- package/dist/server/index.js.map +1 -1
- package/dist/sessions/index.cjs +360 -0
- package/dist/sessions/index.cjs.map +1 -0
- package/dist/sessions/index.d.cts +71 -0
- package/dist/sessions/index.d.ts +71 -0
- package/dist/sessions/index.js +337 -0
- package/dist/sessions/index.js.map +1 -0
- package/dist/titles-DjxUb0ji.d.cts +48 -0
- package/dist/titles-DjxUb0ji.d.ts +48 -0
- package/dist/ui/assets/index-BqzTnRFe.css +2 -0
- package/dist/ui/assets/{index-7eK8XQtL.js → index-U98AY8r-.js} +17 -17
- package/dist/ui/index.html +2 -2
- package/package.json +13 -3
- package/dist/ui/assets/index-BqpVx_My.css +0 -2
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-Bbe11HoB.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-Bbe11HoB.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,18 @@ 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
|
-
|
|
1600
|
+
claudeVault: path5.join(home, ".claude", "claude-app-vault"),
|
|
1601
|
+
codex: [codexHome(path5.join(home, ".codex"))]
|
|
1520
1602
|
};
|
|
1521
1603
|
}
|
|
1522
1604
|
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 +1632,7 @@ async function claudeCandidates(root) {
|
|
|
1550
1632
|
}
|
|
1551
1633
|
return out;
|
|
1552
1634
|
}
|
|
1553
|
-
async function
|
|
1635
|
+
async function codexTree(root) {
|
|
1554
1636
|
const out = [];
|
|
1555
1637
|
for (const year of await safeReaddir(root)) {
|
|
1556
1638
|
if (!year.isDirectory()) continue;
|
|
@@ -1558,17 +1640,23 @@ async function codexCandidates(root) {
|
|
|
1558
1640
|
if (!month.isDirectory()) continue;
|
|
1559
1641
|
for (const day of await safeReaddir(path5.join(root, year.name, month.name))) {
|
|
1560
1642
|
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);
|
|
1643
|
+
out.push(...await codexFlat(path5.join(root, year.name, month.name, day.name), false));
|
|
1567
1644
|
}
|
|
1568
1645
|
}
|
|
1569
1646
|
}
|
|
1570
1647
|
return out;
|
|
1571
1648
|
}
|
|
1649
|
+
async function codexFlat(dir, archived) {
|
|
1650
|
+
const entries = await safeReaddir(dir);
|
|
1651
|
+
const stats = await Promise.all(
|
|
1652
|
+
entries.filter((e) => e.isFile() && e.name.startsWith("rollout-") && e.name.endsWith(".jsonl")).map((e) => statCandidate("codex", path5.join(dir, e.name)))
|
|
1653
|
+
);
|
|
1654
|
+
return stats.filter((s) => s !== null).map((s) => archived ? { ...s, archived: true } : s);
|
|
1655
|
+
}
|
|
1656
|
+
async function codexCandidates(homes) {
|
|
1657
|
+
const lists = await Promise.all(homes.flatMap((h) => [codexTree(h.sessions), codexFlat(h.archived, true)]));
|
|
1658
|
+
return lists.flat();
|
|
1659
|
+
}
|
|
1572
1660
|
async function readSlice(file, start, length) {
|
|
1573
1661
|
const handle = await fs4.open(file, "r");
|
|
1574
1662
|
try {
|
|
@@ -1590,56 +1678,38 @@ function completeLines(text, droppedFirst) {
|
|
|
1590
1678
|
}
|
|
1591
1679
|
var HEAD_BYTES = 256 * 1024;
|
|
1592
1680
|
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
1681
|
async function describeClaude(candidate) {
|
|
1598
1682
|
const head = parseJsonl(completeLines(await readSlice(candidate.file, 0, HEAD_BYTES), false));
|
|
1599
1683
|
const tailStart = Math.max(0, candidate.size - TAIL_BYTES);
|
|
1600
1684
|
const tail = tailStart > 0 ? parseJsonl(completeLines(await readSlice(candidate.file, tailStart, TAIL_BYTES), true)) : head;
|
|
1601
1685
|
let cwd = null;
|
|
1602
|
-
let firstPrompt = null;
|
|
1603
1686
|
let entrypoint = null;
|
|
1604
|
-
const normalizer = createClaudeNormalizer();
|
|
1605
1687
|
for (const entry of head) {
|
|
1606
1688
|
if (typeof entry !== "object" || entry === null) continue;
|
|
1607
1689
|
const e = entry;
|
|
1608
1690
|
if (!cwd && typeof e.cwd === "string") cwd = e.cwd;
|
|
1609
1691
|
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;
|
|
1692
|
+
if (cwd && entrypoint) break;
|
|
1619
1693
|
}
|
|
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;
|
|
1694
|
+
const resolved = resolveSessionTitle(claudeTranscriptTitleSources(head, tailStart > 0 ? tail : []));
|
|
1695
|
+
if (!resolved) return null;
|
|
1632
1696
|
return {
|
|
1633
1697
|
provider: "claude",
|
|
1634
1698
|
providerSessionId: candidate.id,
|
|
1635
1699
|
cwd,
|
|
1636
|
-
title:
|
|
1700
|
+
title: resolved.title,
|
|
1701
|
+
titleSource: resolved.source,
|
|
1637
1702
|
updatedAt: new Date(candidate.mtimeMs).toISOString(),
|
|
1638
1703
|
source: entrypoint,
|
|
1639
1704
|
activeElsewhere: null
|
|
1640
1705
|
};
|
|
1641
1706
|
}
|
|
1642
|
-
async function readCodexIndex(
|
|
1707
|
+
async function readCodexIndex(files) {
|
|
1708
|
+
const names = /* @__PURE__ */ new Map();
|
|
1709
|
+
for (const file of files) for (const [id, name] of await readCodexIndexFile(file)) if (!names.has(id)) names.set(id, name);
|
|
1710
|
+
return names;
|
|
1711
|
+
}
|
|
1712
|
+
async function readCodexIndexFile(file) {
|
|
1643
1713
|
const names = /* @__PURE__ */ new Map();
|
|
1644
1714
|
let text;
|
|
1645
1715
|
try {
|
|
@@ -1685,18 +1755,26 @@ async function describeCodex(candidate, names) {
|
|
|
1685
1755
|
}
|
|
1686
1756
|
}
|
|
1687
1757
|
if (isSubAgent) return null;
|
|
1688
|
-
const
|
|
1689
|
-
if (!
|
|
1758
|
+
const resolved = resolveSessionTitle({ app: names.get(candidate.id), "first-prompt": firstPrompt });
|
|
1759
|
+
if (!resolved) return null;
|
|
1690
1760
|
return {
|
|
1691
1761
|
provider: "codex",
|
|
1692
1762
|
providerSessionId: candidate.id,
|
|
1693
1763
|
cwd,
|
|
1694
|
-
title:
|
|
1764
|
+
title: resolved.title,
|
|
1765
|
+
titleSource: resolved.source,
|
|
1766
|
+
...candidate.archived ? { archived: true } : {},
|
|
1695
1767
|
updatedAt: new Date(candidate.mtimeMs).toISOString(),
|
|
1696
1768
|
source,
|
|
1697
1769
|
activeElsewhere: null
|
|
1698
1770
|
};
|
|
1699
1771
|
}
|
|
1772
|
+
async function codexNames(homes, appTitles) {
|
|
1773
|
+
if (!appTitles) return readCodexIndex(homes.codex.map((h) => h.index));
|
|
1774
|
+
const out = /* @__PURE__ */ new Map();
|
|
1775
|
+
for (const [key, title] of appTitles) if (key.startsWith("codex:")) out.set(key.slice(6), title);
|
|
1776
|
+
return out;
|
|
1777
|
+
}
|
|
1700
1778
|
async function readTail(file, size) {
|
|
1701
1779
|
const start = Math.max(0, size - TAIL_BYTES);
|
|
1702
1780
|
return parseJsonl(completeLines(await readSlice(file, start, Math.min(size, TAIL_BYTES)), start > 0));
|
|
@@ -1736,8 +1814,8 @@ async function discoverLocalSessions(options = {}) {
|
|
|
1736
1814
|
const homes = options.homes ?? defaultProviderHomes();
|
|
1737
1815
|
const [claude, codex, names] = await Promise.all([
|
|
1738
1816
|
claudeCandidates(homes.claudeProjects),
|
|
1739
|
-
codexCandidates(homes.
|
|
1740
|
-
|
|
1817
|
+
codexCandidates(homes.codex),
|
|
1818
|
+
codexNames(homes, options.appTitles)
|
|
1741
1819
|
]);
|
|
1742
1820
|
const candidates = [...claude, ...codex].sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
1743
1821
|
const processes = await listProcesses();
|
|
@@ -1757,9 +1835,14 @@ async function discoverLocalSessions(options = {}) {
|
|
|
1757
1835
|
}
|
|
1758
1836
|
async function findSessionFile(provider, providerSessionId, homes = defaultProviderHomes()) {
|
|
1759
1837
|
if (!/^[0-9a-f-]{36}$/i.test(providerSessionId)) return null;
|
|
1760
|
-
const candidates = provider === "claude" ? await claudeCandidates(homes.claudeProjects) : await codexCandidates(homes.
|
|
1838
|
+
const candidates = provider === "claude" ? await claudeCandidates(homes.claudeProjects) : await codexCandidates(homes.codex);
|
|
1761
1839
|
const hits = candidates.filter((c) => c.id === providerSessionId).sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
1762
|
-
return hits[0]
|
|
1840
|
+
if (hits[0]) return hits[0].file;
|
|
1841
|
+
if (provider === "claude" && homes.claudeVault) {
|
|
1842
|
+
const copy = path5.join(homes.claudeVault, providerSessionId, "transcript.jsonl");
|
|
1843
|
+
if ((await fs4.stat(copy).catch(() => null))?.isFile()) return copy;
|
|
1844
|
+
}
|
|
1845
|
+
return null;
|
|
1763
1846
|
}
|
|
1764
1847
|
var HISTORY_BUDGET = 24 * 1024 * 1024;
|
|
1765
1848
|
var MAX_HISTORY_BUDGET = 256 * 1024 * 1024;
|
|
@@ -1809,12 +1892,12 @@ async function readSessionFile(provider, file, endOffset, maxBytes = HISTORY_BUD
|
|
|
1809
1892
|
async function listSessionFiles(homes = defaultProviderHomes()) {
|
|
1810
1893
|
const [claude, codex] = await Promise.all([
|
|
1811
1894
|
claudeCandidates(homes.claudeProjects),
|
|
1812
|
-
codexCandidates(homes.
|
|
1895
|
+
codexCandidates(homes.codex)
|
|
1813
1896
|
]);
|
|
1814
1897
|
return [...claude, ...codex].sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
1815
1898
|
}
|
|
1816
|
-
async function describeSessionFile(candidate,
|
|
1817
|
-
return (candidate.provider === "claude" ? describeClaude(candidate) : describeCodex(candidate,
|
|
1899
|
+
async function describeSessionFile(candidate, codexNames2) {
|
|
1900
|
+
return (candidate.provider === "claude" ? describeClaude(candidate) : describeCodex(candidate, codexNames2)).catch(
|
|
1818
1901
|
() => null
|
|
1819
1902
|
);
|
|
1820
1903
|
}
|
|
@@ -1852,7 +1935,7 @@ var PreferenceStore = class {
|
|
|
1852
1935
|
if (!key || key.length > 200) throw new Error("That chat can't be found.");
|
|
1853
1936
|
const next = { ...this.data[key] };
|
|
1854
1937
|
if ("title" in patch) {
|
|
1855
|
-
const title = typeof patch.title === "string" ? patch.title.replace(/\s+/g, " ").trim().slice(0,
|
|
1938
|
+
const title = typeof patch.title === "string" ? patch.title.replace(/\s+/g, " ").trim().slice(0, TITLE_MAX) : "";
|
|
1856
1939
|
if (title) next.title = title;
|
|
1857
1940
|
else delete next.title;
|
|
1858
1941
|
}
|
|
@@ -1870,6 +1953,18 @@ var PreferenceStore = class {
|
|
|
1870
1953
|
this.persist();
|
|
1871
1954
|
return this.all();
|
|
1872
1955
|
}
|
|
1956
|
+
/**
|
|
1957
|
+
* Move a preference to the chat's lasting key (`session:<id>` → `<provider>:<id>` once the
|
|
1958
|
+
* provider's id is known), so a rename made early is the one rename everywhere. A preference
|
|
1959
|
+
* already under the lasting key wins field by field.
|
|
1960
|
+
*/
|
|
1961
|
+
rekey(from, to) {
|
|
1962
|
+
if (from === to || !this.data[from]) return false;
|
|
1963
|
+
this.data[to] = { ...this.data[from], ...this.data[to] };
|
|
1964
|
+
delete this.data[from];
|
|
1965
|
+
this.persist();
|
|
1966
|
+
return true;
|
|
1967
|
+
}
|
|
1873
1968
|
persist() {
|
|
1874
1969
|
const snapshot = JSON.stringify(this.data, null, 1);
|
|
1875
1970
|
this.writing = this.writing.then(async () => {
|
|
@@ -1997,12 +2092,12 @@ async function searchLocalChats(query, options = {}) {
|
|
|
1997
2092
|
const raw = JSON.stringify(needle).slice(1, -1);
|
|
1998
2093
|
const limit = Math.max(1, Math.min(options.limit ?? 30, 100));
|
|
1999
2094
|
const deadline = Date.now() + (options.budgetMs ?? 3500);
|
|
2000
|
-
const names = await
|
|
2095
|
+
const names = await codexNames(homes, options.appTitles);
|
|
2001
2096
|
const hits = [];
|
|
2002
2097
|
let i = cursor;
|
|
2003
2098
|
for (; i < files.length && hits.length < limit && Date.now() < deadline; i += 1) {
|
|
2004
2099
|
const candidate = files[i];
|
|
2005
|
-
const named = candidate.provider === "codex" ? names.get(candidate.id) : void 0;
|
|
2100
|
+
const named = options.appTitles?.get(`${candidate.provider}:${candidate.id}`) ?? (candidate.provider === "codex" ? names.get(candidate.id) : void 0);
|
|
2006
2101
|
const nameHit = named !== void 0 && named.toLowerCase().includes(needle);
|
|
2007
2102
|
const scanned = nameHit ? null : await scanFile(candidate.provider, candidate.file, needle, raw, deadline);
|
|
2008
2103
|
if (scanned === TIMED_OUT) {
|
|
@@ -2032,6 +2127,81 @@ async function searchLocalChats(query, options = {}) {
|
|
|
2032
2127
|
};
|
|
2033
2128
|
}
|
|
2034
2129
|
|
|
2130
|
+
// src/sessions/hints.ts
|
|
2131
|
+
function titleHintKey(provider, sessionId) {
|
|
2132
|
+
return `${provider}:${sessionId}`;
|
|
2133
|
+
}
|
|
2134
|
+
function createTitleHintsStore() {
|
|
2135
|
+
let hints = null;
|
|
2136
|
+
let problem = null;
|
|
2137
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
2138
|
+
const tell = () => {
|
|
2139
|
+
for (const l of listeners) l();
|
|
2140
|
+
};
|
|
2141
|
+
return {
|
|
2142
|
+
current: () => hints,
|
|
2143
|
+
problem: () => problem,
|
|
2144
|
+
subscribe(listener) {
|
|
2145
|
+
listeners.add(listener);
|
|
2146
|
+
return () => listeners.delete(listener);
|
|
2147
|
+
},
|
|
2148
|
+
set(next) {
|
|
2149
|
+
hints = next;
|
|
2150
|
+
problem = null;
|
|
2151
|
+
tell();
|
|
2152
|
+
},
|
|
2153
|
+
setProblem(next) {
|
|
2154
|
+
if (next === problem) return;
|
|
2155
|
+
problem = next;
|
|
2156
|
+
tell();
|
|
2157
|
+
}
|
|
2158
|
+
};
|
|
2159
|
+
}
|
|
2160
|
+
function appTitlesOf(hints) {
|
|
2161
|
+
const out = /* @__PURE__ */ new Map();
|
|
2162
|
+
for (const [key, hint] of hints) out.set(key, hint.title);
|
|
2163
|
+
return out;
|
|
2164
|
+
}
|
|
2165
|
+
|
|
2166
|
+
// src/engine/title-hints.ts
|
|
2167
|
+
function overlayHint(chat, hints, expected) {
|
|
2168
|
+
if (!expected) return chat;
|
|
2169
|
+
if (hints === null) return { ...chat, titlePending: true };
|
|
2170
|
+
const hint = hints.get(titleHintKey(chat.provider, chat.providerSessionId));
|
|
2171
|
+
if (!hint) return chat;
|
|
2172
|
+
return {
|
|
2173
|
+
...chat,
|
|
2174
|
+
title: hint.title,
|
|
2175
|
+
titleSource: hint.source,
|
|
2176
|
+
...hint.archived || chat.archived ? { archived: true } : {},
|
|
2177
|
+
...hint.scheduled ? { scheduled: true } : {}
|
|
2178
|
+
};
|
|
2179
|
+
}
|
|
2180
|
+
function unavailableFromHints(hints, covered) {
|
|
2181
|
+
if (!hints) return [];
|
|
2182
|
+
const out = [];
|
|
2183
|
+
for (const [key, hint] of hints) {
|
|
2184
|
+
if (!hint.unavailable || covered.has(key)) continue;
|
|
2185
|
+
const at = key.indexOf(":");
|
|
2186
|
+
const provider = key.slice(0, at);
|
|
2187
|
+
if (provider !== "claude" && provider !== "codex") continue;
|
|
2188
|
+
out.push({
|
|
2189
|
+
provider,
|
|
2190
|
+
providerSessionId: key.slice(at + 1),
|
|
2191
|
+
cwd: null,
|
|
2192
|
+
title: hint.title,
|
|
2193
|
+
titleSource: hint.source,
|
|
2194
|
+
updatedAt: new Date(hint.lastActiveMs ?? 0).toISOString(),
|
|
2195
|
+
source: null,
|
|
2196
|
+
activeElsewhere: null,
|
|
2197
|
+
unavailable: hint.unavailable,
|
|
2198
|
+
...hint.archived ? { archived: true } : {},
|
|
2199
|
+
...hint.scheduled ? { scheduled: true } : {}
|
|
2200
|
+
});
|
|
2201
|
+
}
|
|
2202
|
+
return out.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
|
|
2203
|
+
}
|
|
2204
|
+
|
|
2035
2205
|
// src/engine/store.ts
|
|
2036
2206
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
2037
2207
|
import fs6 from "fs/promises";
|
|
@@ -2302,7 +2472,7 @@ function describeAuthStatus(provider, probe) {
|
|
|
2302
2472
|
async function createCodingSessionsEngine(options = {}) {
|
|
2303
2473
|
const dataDir = options.dataDir ?? path9.join(os5.homedir(), ".matrx", "coding-sessions");
|
|
2304
2474
|
const stateDir = path9.join(dataDir, "paperclip-state");
|
|
2305
|
-
|
|
2475
|
+
let homes = options.homes ?? defaultProviderHomes();
|
|
2306
2476
|
const index = new RunIndex(dataDir);
|
|
2307
2477
|
await index.load();
|
|
2308
2478
|
const preferences = new PreferenceStore(dataDir);
|
|
@@ -2382,6 +2552,18 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
2382
2552
|
}
|
|
2383
2553
|
}
|
|
2384
2554
|
}
|
|
2555
|
+
const titleHints = options.titleHints;
|
|
2556
|
+
const hintsNow = () => titleHints?.current() ?? null;
|
|
2557
|
+
const appTitles = () => {
|
|
2558
|
+
if (!titleHints) return void 0;
|
|
2559
|
+
const hints = hintsNow();
|
|
2560
|
+
return hints ? appTitlesOf(hints) : /* @__PURE__ */ new Map();
|
|
2561
|
+
};
|
|
2562
|
+
const unsubscribeHints = titleHints?.subscribe(() => emit({ type: "titles" })) ?? null;
|
|
2563
|
+
const withHintTitle = (session) => {
|
|
2564
|
+
const hint = session.providerSessionId ? hintsNow()?.get(titleHintKey(session.provider, session.providerSessionId)) : void 0;
|
|
2565
|
+
return hint ? { ...session, title: hint.title } : session;
|
|
2566
|
+
};
|
|
2385
2567
|
function saveSession(session) {
|
|
2386
2568
|
index.putSession(session);
|
|
2387
2569
|
emit({ type: "session", session });
|
|
@@ -2538,7 +2720,12 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
2538
2720
|
if (sid && sid !== knownProviderSession) {
|
|
2539
2721
|
knownProviderSession = sid;
|
|
2540
2722
|
const current = index.session(session.id);
|
|
2541
|
-
if (current)
|
|
2723
|
+
if (current) {
|
|
2724
|
+
saveSession({ ...current, providerSessionId: sid });
|
|
2725
|
+
if (preferences.rekey(chatKey(current.provider, null, current.id), chatKey(current.provider, sid))) {
|
|
2726
|
+
emit({ type: "preferences", preferences: preferences.all() });
|
|
2727
|
+
}
|
|
2728
|
+
}
|
|
2542
2729
|
}
|
|
2543
2730
|
for (const item of items) emitItem(liveRun, item);
|
|
2544
2731
|
}
|
|
@@ -2846,7 +3033,9 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
2846
3033
|
);
|
|
2847
3034
|
},
|
|
2848
3035
|
async discoverSessions(opts) {
|
|
2849
|
-
const
|
|
3036
|
+
const read = await discoverLocalSessions({ limit: opts?.limit, homes, excludePids: ourPids(), appTitles: appTitles() });
|
|
3037
|
+
const hints = hintsNow();
|
|
3038
|
+
const found = read.map((chat) => overlayHint(chat, hints, titleHints !== void 0));
|
|
2850
3039
|
if (backups) {
|
|
2851
3040
|
for (const chat of found) {
|
|
2852
3041
|
const key = `${chat.provider}:${chat.providerSessionId}`;
|
|
@@ -2857,7 +3046,12 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
2857
3046
|
}
|
|
2858
3047
|
}
|
|
2859
3048
|
}
|
|
2860
|
-
|
|
3049
|
+
const limit = opts?.limit ?? 60;
|
|
3050
|
+
const oldest = found.length >= limit ? found[found.length - 1]?.updatedAt ?? "" : "";
|
|
3051
|
+
const gone = unavailableFromHints(hints, new Set(found.map((c) => titleHintKey(c.provider, c.providerSessionId)))).filter(
|
|
3052
|
+
(c) => c.updatedAt >= oldest
|
|
3053
|
+
);
|
|
3054
|
+
return gone.length ? [...found, ...gone].sort((a, b) => b.updatedAt.localeCompare(a.updatedAt)).slice(0, limit) : found;
|
|
2861
3055
|
},
|
|
2862
3056
|
async recentFolders() {
|
|
2863
3057
|
const seen = /* @__PURE__ */ new Map();
|
|
@@ -2877,8 +3071,19 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
2877
3071
|
}))
|
|
2878
3072
|
);
|
|
2879
3073
|
},
|
|
3074
|
+
async titleStatus() {
|
|
3075
|
+
if (!titleHints) return { owner: false, pending: false, problem: null };
|
|
3076
|
+
const problem = titleHints.problem?.() ?? null;
|
|
3077
|
+
return { owner: true, pending: hintsNow() === null && problem === null, problem };
|
|
3078
|
+
},
|
|
3079
|
+
async retryTitles() {
|
|
3080
|
+
titleHints?.retry?.();
|
|
3081
|
+
},
|
|
3082
|
+
setHomes(next) {
|
|
3083
|
+
homes = next;
|
|
3084
|
+
},
|
|
2880
3085
|
async listSessions() {
|
|
2881
|
-
return index.sessions();
|
|
3086
|
+
return index.sessions().map(withHintTitle);
|
|
2882
3087
|
},
|
|
2883
3088
|
async listRuns(sessionId) {
|
|
2884
3089
|
const session = index.session(sessionId);
|
|
@@ -3053,7 +3258,9 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
3053
3258
|
return all;
|
|
3054
3259
|
},
|
|
3055
3260
|
async searchChats(query, opts) {
|
|
3056
|
-
|
|
3261
|
+
const result = await searchLocalChats(String(query ?? ""), { homes, cursor: opts?.cursor, limit: opts?.limit, appTitles: appTitles() });
|
|
3262
|
+
const hints = hintsNow();
|
|
3263
|
+
return { ...result, hits: result.hits.map((hit) => ({ ...hit, title: hints?.get(titleHintKey(hit.provider, hit.providerSessionId))?.title ?? hit.title })) };
|
|
3057
3264
|
},
|
|
3058
3265
|
async openFolder(raw, target) {
|
|
3059
3266
|
const folder = await assertFolder(String(raw ?? ""));
|
|
@@ -3083,6 +3290,7 @@ async function createCodingSessionsEngine(options = {}) {
|
|
|
3083
3290
|
return agents.handleMcp(secret, body);
|
|
3084
3291
|
},
|
|
3085
3292
|
async dispose() {
|
|
3293
|
+
unsubscribeHints?.();
|
|
3086
3294
|
for (const liveRun of live.values()) {
|
|
3087
3295
|
liveRun.stopRequested = true;
|
|
3088
3296
|
if (liveRun.pgid !== null) killGroup(liveRun);
|
|
@@ -3138,9 +3346,11 @@ export {
|
|
|
3138
3346
|
buildCleanEnv,
|
|
3139
3347
|
claudeDefaults,
|
|
3140
3348
|
codexDefaults,
|
|
3349
|
+
codexHome,
|
|
3141
3350
|
createAiMatrxRawBackupSink,
|
|
3142
3351
|
createCodingSessionsEngine,
|
|
3143
3352
|
createRawBackupScheduler,
|
|
3353
|
+
createTitleHintsStore,
|
|
3144
3354
|
defaultProviderHomes,
|
|
3145
3355
|
describeActivity,
|
|
3146
3356
|
describeProviderStatus,
|
|
@@ -3153,6 +3363,7 @@ export {
|
|
|
3153
3363
|
readSessionFile,
|
|
3154
3364
|
searchLocalChats,
|
|
3155
3365
|
sessionActivity,
|
|
3156
|
-
snippetAround
|
|
3366
|
+
snippetAround,
|
|
3367
|
+
titleHintKey
|
|
3157
3368
|
};
|
|
3158
3369
|
//# sourceMappingURL=index.js.map
|