offsite 0.0.65 → 0.0.66
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/apps/client/dist/assets/{XIcon-xUbaoMG0.js → XIcon-DJv99Y50.js} +1 -1
- package/apps/client/dist/assets/{context-menu-Bx40w107.js → context-menu-D44HzRLd.js} +26 -26
- package/apps/client/dist/assets/{diff-panel-BOgBzrDX.js → diff-panel-BBvzgxNm.js} +2 -2
- package/apps/client/dist/assets/{dist-BgeQ7Ie_.js → dist-B51OED4H.js} +1 -1
- package/apps/client/dist/assets/{editor-dialog-D50LOSVh.js → editor-dialog-C1nmCjos.js} +28 -28
- package/apps/client/dist/assets/{image-viewer-BtUbYUvJ.js → image-viewer-WSm9jLKC.js} +1 -1
- package/apps/client/dist/assets/index-BsG3KfVy.css +2 -0
- package/apps/client/dist/assets/index-pPV26qEN.js +21 -0
- package/apps/client/dist/assets/{preview-dialog-sl5f3G7U.js → preview-dialog-DDqLe_ku.js} +1 -1
- package/apps/client/dist/assets/{video-viewer-drNms_Cg.js → video-viewer-Dqv4SNRY.js} +1 -1
- package/apps/client/dist/index.html +5 -5
- package/apps/client/dist/sw.js +1 -1
- package/apps/server/dist/main.mjs +273 -149
- package/apps/server/dist/main.mjs.map +1 -1
- package/package.json +1 -1
- package/apps/client/dist/assets/index-7NxPuVPI.js +0 -21
- package/apps/client/dist/assets/index-C3dESZgy.css +0 -2
|
@@ -763,7 +763,7 @@ function toItems$1(turnId, item, parent, time) {
|
|
|
763
763
|
case "userMessage": return [{
|
|
764
764
|
...base,
|
|
765
765
|
kind: "user",
|
|
766
|
-
text: item.content.flatMap(userText$
|
|
766
|
+
text: item.content.flatMap(userText$2).join("\n").trim(),
|
|
767
767
|
images: item.content.flatMap(userImages),
|
|
768
768
|
format: "text"
|
|
769
769
|
}];
|
|
@@ -861,7 +861,8 @@ function toItems$1(turnId, item, parent, time) {
|
|
|
861
861
|
}];
|
|
862
862
|
case "contextCompaction": return [{
|
|
863
863
|
...base,
|
|
864
|
-
kind: "compaction"
|
|
864
|
+
kind: "compaction",
|
|
865
|
+
summary: null
|
|
865
866
|
}];
|
|
866
867
|
case "hookPrompt":
|
|
867
868
|
case "functionCallOutput":
|
|
@@ -872,7 +873,7 @@ function toItems$1(turnId, item, parent, time) {
|
|
|
872
873
|
case "exitedReviewMode": return [];
|
|
873
874
|
}
|
|
874
875
|
}
|
|
875
|
-
function userText$
|
|
876
|
+
function userText$2(input) {
|
|
876
877
|
switch (input.type) {
|
|
877
878
|
case "text": return [input.text.replaceAll(/\[Image: source: [^\]]+\]/g, "")];
|
|
878
879
|
case "skill":
|
|
@@ -1948,7 +1949,8 @@ function messageItems(message, parent) {
|
|
|
1948
1949
|
case "running": return [];
|
|
1949
1950
|
case "completed": return [{
|
|
1950
1951
|
...base,
|
|
1951
|
-
kind: "compaction"
|
|
1952
|
+
kind: "compaction",
|
|
1953
|
+
summary: null
|
|
1952
1954
|
}];
|
|
1953
1955
|
case "failed": return [{
|
|
1954
1956
|
...errorItem(message.id, message.error.message),
|
|
@@ -4820,7 +4822,8 @@ function toItems(messages) {
|
|
|
4820
4822
|
items.push({
|
|
4821
4823
|
...base,
|
|
4822
4824
|
id: message.uuid,
|
|
4823
|
-
kind: "compaction"
|
|
4825
|
+
kind: "compaction",
|
|
4826
|
+
summary: message.summary
|
|
4824
4827
|
});
|
|
4825
4828
|
continue;
|
|
4826
4829
|
}
|
|
@@ -4927,7 +4930,7 @@ function toItems(messages) {
|
|
|
4927
4930
|
});
|
|
4928
4931
|
continue;
|
|
4929
4932
|
}
|
|
4930
|
-
texts.push(userText(block.text));
|
|
4933
|
+
texts.push(userText$1(block.text));
|
|
4931
4934
|
}
|
|
4932
4935
|
if (texts.length === 0 && images.length === 0) continue;
|
|
4933
4936
|
items.push({
|
|
@@ -5178,7 +5181,7 @@ function applyResult(item, result) {
|
|
|
5178
5181
|
default: return item;
|
|
5179
5182
|
}
|
|
5180
5183
|
}
|
|
5181
|
-
function userText(value) {
|
|
5184
|
+
function userText$1(value) {
|
|
5182
5185
|
const command = /<command-name>(.*?)<\/command-name>/s.exec(value);
|
|
5183
5186
|
if (command !== null) {
|
|
5184
5187
|
const args = /<command-args>(.*?)<\/command-args>/s.exec(value);
|
|
@@ -5286,12 +5289,19 @@ function parseLine(line) {
|
|
|
5286
5289
|
function mainMessages(disk, entries) {
|
|
5287
5290
|
const times = messageTimes(entries);
|
|
5288
5291
|
const outputs = toolOutputs(entries);
|
|
5292
|
+
const summaries = new Set(entries.flatMap((entry) => entry.isCompactSummary === true && typeof entry.uuid === "string" ? [entry.uuid] : []));
|
|
5289
5293
|
return disk.flatMap((item) => {
|
|
5290
5294
|
if (item.type === "system" || item.parent_tool_use_id !== null) return [];
|
|
5291
5295
|
const message = toMessage(item.type, {
|
|
5292
5296
|
...item,
|
|
5293
5297
|
tool_use_result: outputs.get(item.uuid)
|
|
5294
5298
|
}, times.get(item.uuid) ?? null);
|
|
5299
|
+
if (summaries.has(item.uuid)) return [{
|
|
5300
|
+
type: "compaction",
|
|
5301
|
+
uuid: item.uuid,
|
|
5302
|
+
time: message.time,
|
|
5303
|
+
summary: message.content.flatMap((block) => block.type === "text" ? [block.text] : []).join("\n").replace(/<system-reminder>[\s\S]*?<\/system-reminder>\s*/g, "")
|
|
5304
|
+
}];
|
|
5295
5305
|
return [{
|
|
5296
5306
|
...message,
|
|
5297
5307
|
content: message.content.map((block) => block.type === "image" ? {
|
|
@@ -5349,11 +5359,7 @@ async function earlier(sessionId, before) {
|
|
|
5349
5359
|
const index = session.pages.findIndex((page) => page.before === before);
|
|
5350
5360
|
if (index < 0) throw new Error(`no messages before entry ${before}`);
|
|
5351
5361
|
return {
|
|
5352
|
-
items: toItems(
|
|
5353
|
-
type: "compaction",
|
|
5354
|
-
uuid: `compacted:${before}`,
|
|
5355
|
-
time: null
|
|
5356
|
-
}]),
|
|
5362
|
+
items: toItems(mainMessages(session.pages[index].disk, session.entries)),
|
|
5357
5363
|
before: session.pages[index + 1]?.before ?? null
|
|
5358
5364
|
};
|
|
5359
5365
|
}
|
|
@@ -5637,7 +5643,8 @@ var Run = class {
|
|
|
5637
5643
|
this.add({
|
|
5638
5644
|
type: "compaction",
|
|
5639
5645
|
uuid: message.uuid,
|
|
5640
|
-
time: Date.now()
|
|
5646
|
+
time: Date.now(),
|
|
5647
|
+
summary: null
|
|
5641
5648
|
});
|
|
5642
5649
|
return;
|
|
5643
5650
|
}
|
|
@@ -5851,141 +5858,6 @@ async function isLocalHost(hostname) {
|
|
|
5851
5858
|
if (isIP(hostname) !== 0) return isPrivate(hostname);
|
|
5852
5859
|
return (await dns.promises.lookup(hostname, { all: true })).some((item) => isPrivate(item.address));
|
|
5853
5860
|
}
|
|
5854
|
-
// src/services/message-search.ts
|
|
5855
|
-
const READ_LIMIT = 30;
|
|
5856
|
-
const CODEX_READ_LIMIT = 10;
|
|
5857
|
-
const RG_TIME_LIMIT = 5e3;
|
|
5858
|
-
const generatedText = /^(?:This session is being continued from a previous conversation|<command-|<local-command-|<bash-|<task-notification|<cross-session-message|Another Claude session sent a message:|\[Request interrupted by user)/;
|
|
5859
|
-
const run$2 = promisify(execFile);
|
|
5860
|
-
async function searchMessages(query) {
|
|
5861
|
-
if (!hasSearchableWord(query)) return {
|
|
5862
|
-
matches: [],
|
|
5863
|
-
failed: []
|
|
5864
|
-
};
|
|
5865
|
-
const paths = new Map(projects().map((project) => [project.path, project.id]));
|
|
5866
|
-
const folders = new Map(projects().map((project) => [project.id, project.path]));
|
|
5867
|
-
const results = await Promise.all([
|
|
5868
|
-
"claude",
|
|
5869
|
-
"codex",
|
|
5870
|
-
"opencode"
|
|
5871
|
-
].filter((provider) => readConfig().enabledProviders[provider]).map(async (provider) => {
|
|
5872
|
-
try {
|
|
5873
|
-
return {
|
|
5874
|
-
provider,
|
|
5875
|
-
matches: await timeLimit(search(provider, query, paths, folders), `${providerName(provider)} did not search in time`),
|
|
5876
|
-
failed: false
|
|
5877
|
-
};
|
|
5878
|
-
} catch (error) {
|
|
5879
|
-
console.warn(`could not search ${providerName(provider)} messages: ${error}`);
|
|
5880
|
-
return {
|
|
5881
|
-
provider,
|
|
5882
|
-
matches: [],
|
|
5883
|
-
failed: true
|
|
5884
|
-
};
|
|
5885
|
-
}
|
|
5886
|
-
}));
|
|
5887
|
-
return {
|
|
5888
|
-
matches: results.flatMap((result) => result.matches),
|
|
5889
|
-
failed: results.filter((result) => result.failed).map((result) => result.provider)
|
|
5890
|
-
};
|
|
5891
|
-
}
|
|
5892
|
-
async function search(provider, query, paths, folders) {
|
|
5893
|
-
const found = await find(provider, paths);
|
|
5894
|
-
if (found === null) return [];
|
|
5895
|
-
const sessions = newestFirst(found);
|
|
5896
|
-
switch (provider) {
|
|
5897
|
-
case "claude": return searchClaude(query, sessions, folders);
|
|
5898
|
-
case "codex": return searchCodex(query, sessions);
|
|
5899
|
-
case "opencode": return readAndMatch(query, sessions.slice(0, READ_LIMIT), openCodeMessages);
|
|
5900
|
-
}
|
|
5901
|
-
}
|
|
5902
|
-
function newestFirst(sessions) {
|
|
5903
|
-
return [...sessions].sort((a, b) => b.updatedAt - a.updatedAt);
|
|
5904
|
-
}
|
|
5905
|
-
async function searchClaude(query, sessions, folders) {
|
|
5906
|
-
const candidates = await claudeCandidates(query);
|
|
5907
|
-
const matching = candidates === null ? sessions : sessions.filter((session) => candidates.has(session.id));
|
|
5908
|
-
const projectOf = new Map(sessions.map((session) => [session.id, session.projectId]));
|
|
5909
|
-
return readAndMatch(query, matching.slice(0, READ_LIMIT), (sessionId) => claudeMessages(sessionId, folders.get(projectOf.get(sessionId) ?? "")));
|
|
5910
|
-
}
|
|
5911
|
-
async function claudeMessages(sessionId, dir) {
|
|
5912
|
-
return (await getSessionMessages(sessionId, { dir })).flatMap((item) => {
|
|
5913
|
-
if (item.type === "system" || item.parent_tool_use_id !== null) return [];
|
|
5914
|
-
const text = toMessage(item.type, item, null).content.flatMap((block) => block.type === "text" ? [block.text] : []).join("\n");
|
|
5915
|
-
return generatedText.test(text) ? [] : [{
|
|
5916
|
-
author: item.type,
|
|
5917
|
-
text
|
|
5918
|
-
}];
|
|
5919
|
-
});
|
|
5920
|
-
}
|
|
5921
|
-
async function claudeCandidates(query) {
|
|
5922
|
-
const phrase = exactPhrase(query);
|
|
5923
|
-
const words = phrase !== null ? [JSON.stringify(phrase).slice(1, -1)] : searchWords(query).filter((word) => word.length >= 3 && !/["\\]/.test(word));
|
|
5924
|
-
if (words.length === 0) return null;
|
|
5925
|
-
try {
|
|
5926
|
-
const lists = await Promise.all(words.map((word) => sessionsWith(word)));
|
|
5927
|
-
return new Set(lists[0].filter((id) => lists.every((list) => list.includes(id))));
|
|
5928
|
-
} catch (error) {
|
|
5929
|
-
console.warn(`could not narrow Claude sessions with rg: ${error}`);
|
|
5930
|
-
return null;
|
|
5931
|
-
}
|
|
5932
|
-
}
|
|
5933
|
-
async function sessionsWith(word) {
|
|
5934
|
-
return (await run$2("rg", [
|
|
5935
|
-
"--files-with-matches",
|
|
5936
|
-
"--fixed-strings",
|
|
5937
|
-
"--ignore-case",
|
|
5938
|
-
"--no-ignore",
|
|
5939
|
-
"--hidden",
|
|
5940
|
-
"--max-depth",
|
|
5941
|
-
"2",
|
|
5942
|
-
"--glob",
|
|
5943
|
-
"*.jsonl",
|
|
5944
|
-
"--",
|
|
5945
|
-
word,
|
|
5946
|
-
path.join(claudeBaseDir(), "projects")
|
|
5947
|
-
], {
|
|
5948
|
-
maxBuffer: 16777216,
|
|
5949
|
-
timeout: RG_TIME_LIMIT
|
|
5950
|
-
}).catch((error) => {
|
|
5951
|
-
if (error.code === 1) return { stdout: "" };
|
|
5952
|
-
throw error;
|
|
5953
|
-
})).stdout.split("\n").filter(Boolean).map((file) => path.basename(file, ".jsonl"));
|
|
5954
|
-
}
|
|
5955
|
-
async function searchCodex(query, sessions) {
|
|
5956
|
-
const longest = exactPhrase(query) ?? [...searchWords(query)].sort((a, b) => b.length - a.length)[0];
|
|
5957
|
-
const known = new Map(sessions.map((session) => [session.id, session]));
|
|
5958
|
-
const found = (await codexSearch(longest)).filter((result) => known.has(result.thread.id)).sort((a, b) => b.thread.updatedAt - a.thread.updatedAt);
|
|
5959
|
-
const verified = await readAndMatch(query, found.slice(0, CODEX_READ_LIMIT).map((result) => ({ id: result.thread.id })), codexMessages);
|
|
5960
|
-
const unverified = found.slice(CODEX_READ_LIMIT).flatMap((result) => {
|
|
5961
|
-
const match = matchMessage(query, result.snippet);
|
|
5962
|
-
return match === null ? [] : [{
|
|
5963
|
-
sessionId: result.thread.id,
|
|
5964
|
-
author: "assistant",
|
|
5965
|
-
quality: match.quality,
|
|
5966
|
-
snippet: messageSnippet(result.snippet, match.offset)
|
|
5967
|
-
}];
|
|
5968
|
-
});
|
|
5969
|
-
return [...verified, ...unverified];
|
|
5970
|
-
}
|
|
5971
|
-
async function readAndMatch(query, sessions, read) {
|
|
5972
|
-
return (await Promise.all(sessions.map(async (session) => bestMatch(query, session.id, await read(session.id))))).filter((match) => match !== null);
|
|
5973
|
-
}
|
|
5974
|
-
function bestMatch(query, sessionId, messages) {
|
|
5975
|
-
let best = null;
|
|
5976
|
-
for (const message of messages) {
|
|
5977
|
-
const match = matchMessage(query, message.text);
|
|
5978
|
-
if (match === null) continue;
|
|
5979
|
-
const candidate = {
|
|
5980
|
-
sessionId,
|
|
5981
|
-
author: message.author,
|
|
5982
|
-
quality: match.quality,
|
|
5983
|
-
snippet: messageSnippet(message.text, match.offset)
|
|
5984
|
-
};
|
|
5985
|
-
if (best === null || compareMessages(candidate, best) < 0) best = candidate;
|
|
5986
|
-
}
|
|
5987
|
-
return best;
|
|
5988
|
-
}
|
|
5989
5861
|
// src/services/runs.ts
|
|
5990
5862
|
const locks = /* @__PURE__ */ new Map();
|
|
5991
5863
|
function stopAll() {
|
|
@@ -6113,7 +5985,7 @@ async function runFor(sessionId, cwd, settings) {
|
|
|
6113
5985
|
sessionId,
|
|
6114
5986
|
cwd,
|
|
6115
5987
|
resume: true,
|
|
6116
|
-
resumeAt: null,
|
|
5988
|
+
resumeAt: run?.provider === "claude" && run.messages.length === 0 ? run.resumeAt : null,
|
|
6117
5989
|
settings
|
|
6118
5990
|
});
|
|
6119
5991
|
case "codex": return CodexRun.resume(sessionId, cwd, settings);
|
|
@@ -6138,6 +6010,257 @@ async function changedElsewhere(run, cwd) {
|
|
|
6138
6010
|
const last = (await getSessionMessages(run.sessionId, { dir: cwd })).at(-1)?.uuid;
|
|
6139
6011
|
return last !== void 0 && !run.messages.some((item) => item.uuid === last);
|
|
6140
6012
|
}
|
|
6013
|
+
// src/services/export.ts
|
|
6014
|
+
async function exportSession(sessionId, projectId) {
|
|
6015
|
+
const [session, event] = await Promise.all([findSession(sessionId), sessionHistory(sessionId, projectPath(projectId))]);
|
|
6016
|
+
if (session === null) throw new Error(`session ${sessionId} not found`);
|
|
6017
|
+
if (event.type !== "history") throw new Error(`unexpected ${event.type} event for ${sessionId}`);
|
|
6018
|
+
let items = event.items;
|
|
6019
|
+
let before = event.before;
|
|
6020
|
+
while (before !== null) {
|
|
6021
|
+
const page = await earlier(sessionId, before);
|
|
6022
|
+
items = [...page.items, ...items];
|
|
6023
|
+
before = page.before;
|
|
6024
|
+
}
|
|
6025
|
+
return {
|
|
6026
|
+
messages: markdown(session, items, 0),
|
|
6027
|
+
steps: markdown(session, items, 1),
|
|
6028
|
+
details: markdown(session, items, 2),
|
|
6029
|
+
full: markdown(session, items, 3)
|
|
6030
|
+
};
|
|
6031
|
+
}
|
|
6032
|
+
function markdown(session, items, level) {
|
|
6033
|
+
const sections = [];
|
|
6034
|
+
let assistant = [];
|
|
6035
|
+
for (const item of items) {
|
|
6036
|
+
if (item.parent !== null) continue;
|
|
6037
|
+
if (item.kind === "user") {
|
|
6038
|
+
if (assistant.length > 0) {
|
|
6039
|
+
sections.push(`## Assistant\n\n${assistant.join("\n\n")}`);
|
|
6040
|
+
assistant = [];
|
|
6041
|
+
}
|
|
6042
|
+
sections.push(`## User\n\n${userText(item)}`);
|
|
6043
|
+
continue;
|
|
6044
|
+
}
|
|
6045
|
+
const text = itemText(item, level);
|
|
6046
|
+
if (text !== null) assistant.push(text);
|
|
6047
|
+
}
|
|
6048
|
+
if (assistant.length > 0) sections.push(`## Assistant\n\n${assistant.join("\n\n")}`);
|
|
6049
|
+
return `${[[
|
|
6050
|
+
`# ${session.title}`,
|
|
6051
|
+
"",
|
|
6052
|
+
`**Session ID:** ${session.id}`,
|
|
6053
|
+
`**Provider:** ${providerName(session.provider)}`,
|
|
6054
|
+
...session.branch === null ? [] : [`**Branch:** ${session.branch}`],
|
|
6055
|
+
`**Updated:** ${new Date(session.updatedAt).toLocaleString()}`
|
|
6056
|
+
].join("\n"), ...sections].join("\n\n---\n\n")}\n`;
|
|
6057
|
+
}
|
|
6058
|
+
function userText(item) {
|
|
6059
|
+
const images = item.images.length === 0 ? "" : `\n\n_[${item.images.length} image${item.images.length === 1 ? "" : "s"} attached]_`;
|
|
6060
|
+
switch (item.format) {
|
|
6061
|
+
case "text": return `${item.text}${images}`;
|
|
6062
|
+
case "command":
|
|
6063
|
+
case "output": return `${fenced("", item.text)}${images}`;
|
|
6064
|
+
}
|
|
6065
|
+
}
|
|
6066
|
+
function itemText(item, level) {
|
|
6067
|
+
switch (item.kind) {
|
|
6068
|
+
case "assistant": return item.text;
|
|
6069
|
+
case "plan": return `**Plan:**\n\n${item.plan}`;
|
|
6070
|
+
case "question": return item.questions.map((question) => `**Question:** ${question.question}\n\n**Answer:** ${item.answers?.[question.question] ?? "_No answer_"}`).join("\n\n");
|
|
6071
|
+
case "error": return `**Error:** ${item.text}`;
|
|
6072
|
+
case "reasoning": return level < 2 ? null : `_Thinking:_\n\n${item.text}`;
|
|
6073
|
+
case "command":
|
|
6074
|
+
if (level < 1) return null;
|
|
6075
|
+
if (level < 2) return `**Shell:** ${item.description === "" ? `\`${item.command.split("\n")[0]}\`` : item.description}${statusText(item.status)}`;
|
|
6076
|
+
return `**Shell**${item.description === "" ? "" : `: ${item.description}`}${statusText(item.status)}\n\n${fenced("sh", `$ ${item.command}${level < 3 || item.output === "" ? "" : `\n${item.output}`}`)}`;
|
|
6077
|
+
case "file_change": {
|
|
6078
|
+
if (level < 1) return null;
|
|
6079
|
+
const title = `**Edit:** \`${item.path}\`${statusText(item.status)}`;
|
|
6080
|
+
if (level < 2) return title;
|
|
6081
|
+
return `${title}\n\n${fenced("diff", item.diffs.map((diff) => [...lines(diff.before).map((line) => `-${line}`), ...lines(diff.after).map((line) => `+${line}`)].join("\n")).join("\n"))}${level < 3 || item.output === "" ? "" : `\n\n${fenced("", item.output)}`}`;
|
|
6082
|
+
}
|
|
6083
|
+
case "web_search": {
|
|
6084
|
+
if (level < 1) return null;
|
|
6085
|
+
const title = `**Web search:** ${item.query ?? item.url ?? ""}${statusText(item.status)}`;
|
|
6086
|
+
if (level < 2) return title;
|
|
6087
|
+
return [title, ...item.results.map((result) => `- [${result.title}](${result.url})`)].join("\n");
|
|
6088
|
+
}
|
|
6089
|
+
case "skill": {
|
|
6090
|
+
if (level < 1) return null;
|
|
6091
|
+
const title = `**Skill:** ${item.skill}${statusText(item.status)}`;
|
|
6092
|
+
return level < 3 || item.output === "" ? title : `${title}\n\n${fenced("", item.output)}`;
|
|
6093
|
+
}
|
|
6094
|
+
case "tool": {
|
|
6095
|
+
if (level < 1) return null;
|
|
6096
|
+
const title = `**Tool:** ${item.name}${statusText(item.status)}`;
|
|
6097
|
+
if (level < 2) return title;
|
|
6098
|
+
return `${title}\n\n**Input:**\n\n${fenced("json", JSON.stringify(item.input, null, 2))}${level < 3 || item.output === "" ? "" : `\n\n**Output:**\n\n${fenced("", item.output)}`}`;
|
|
6099
|
+
}
|
|
6100
|
+
case "subagent": return level < 1 ? null : `**Subagent (${item.agent}):** ${item.description}${statusText(item.status)}`;
|
|
6101
|
+
case "agent_message": return level < 1 ? null : `**Message${item.target === null ? "" : ` to ${item.target}`}:** ${item.summary}${statusText(item.status)}`;
|
|
6102
|
+
case "notice": return level < 1 ? null : `_${item.text}_`;
|
|
6103
|
+
case "compaction":
|
|
6104
|
+
if (level < 1) return null;
|
|
6105
|
+
return level < 2 || item.summary === null ? "_Conversation compacted_" : `_Conversation compacted_\n\n${item.summary}`;
|
|
6106
|
+
case "todo_list": return level < 2 ? null : [
|
|
6107
|
+
"**Todos:**",
|
|
6108
|
+
"",
|
|
6109
|
+
...item.steps.map((step) => `- [${step.status === "completed" ? "x" : " "}] ${step.text}`)
|
|
6110
|
+
].join("\n");
|
|
6111
|
+
}
|
|
6112
|
+
}
|
|
6113
|
+
function statusText(status) {
|
|
6114
|
+
switch (status) {
|
|
6115
|
+
case "completed": return "";
|
|
6116
|
+
case "running": return " _(running)_";
|
|
6117
|
+
case "failed": return " _(failed)_";
|
|
6118
|
+
case "stopped": return " _(stopped)_";
|
|
6119
|
+
}
|
|
6120
|
+
}
|
|
6121
|
+
function lines(text) {
|
|
6122
|
+
return text === "" ? [] : text.split("\n");
|
|
6123
|
+
}
|
|
6124
|
+
function fenced(language, text) {
|
|
6125
|
+
const longest = Math.max(0, ...(text.match(/`+/g) ?? []).map((run) => run.length));
|
|
6126
|
+
const fence = "`".repeat(Math.max(3, longest + 1));
|
|
6127
|
+
return `${fence}${language}\n${text}\n${fence}`;
|
|
6128
|
+
}
|
|
6129
|
+
// src/services/message-search.ts
|
|
6130
|
+
const READ_LIMIT = 30;
|
|
6131
|
+
const CODEX_READ_LIMIT = 10;
|
|
6132
|
+
const RG_TIME_LIMIT = 5e3;
|
|
6133
|
+
const generatedText = /^(?:This session is being continued from a previous conversation|<command-|<local-command-|<bash-|<task-notification|<cross-session-message|Another Claude session sent a message:|\[Request interrupted by user)/;
|
|
6134
|
+
const run$2 = promisify(execFile);
|
|
6135
|
+
async function searchMessages(query) {
|
|
6136
|
+
if (!hasSearchableWord(query)) return {
|
|
6137
|
+
matches: [],
|
|
6138
|
+
failed: []
|
|
6139
|
+
};
|
|
6140
|
+
const paths = new Map(projects().map((project) => [project.path, project.id]));
|
|
6141
|
+
const folders = new Map(projects().map((project) => [project.id, project.path]));
|
|
6142
|
+
const results = await Promise.all([
|
|
6143
|
+
"claude",
|
|
6144
|
+
"codex",
|
|
6145
|
+
"opencode"
|
|
6146
|
+
].filter((provider) => readConfig().enabledProviders[provider]).map(async (provider) => {
|
|
6147
|
+
try {
|
|
6148
|
+
return {
|
|
6149
|
+
provider,
|
|
6150
|
+
matches: await timeLimit(search(provider, query, paths, folders), `${providerName(provider)} did not search in time`),
|
|
6151
|
+
failed: false
|
|
6152
|
+
};
|
|
6153
|
+
} catch (error) {
|
|
6154
|
+
console.warn(`could not search ${providerName(provider)} messages: ${error}`);
|
|
6155
|
+
return {
|
|
6156
|
+
provider,
|
|
6157
|
+
matches: [],
|
|
6158
|
+
failed: true
|
|
6159
|
+
};
|
|
6160
|
+
}
|
|
6161
|
+
}));
|
|
6162
|
+
return {
|
|
6163
|
+
matches: results.flatMap((result) => result.matches),
|
|
6164
|
+
failed: results.filter((result) => result.failed).map((result) => result.provider)
|
|
6165
|
+
};
|
|
6166
|
+
}
|
|
6167
|
+
async function search(provider, query, paths, folders) {
|
|
6168
|
+
const found = await find(provider, paths);
|
|
6169
|
+
if (found === null) return [];
|
|
6170
|
+
const sessions = newestFirst(found);
|
|
6171
|
+
switch (provider) {
|
|
6172
|
+
case "claude": return searchClaude(query, sessions, folders);
|
|
6173
|
+
case "codex": return searchCodex(query, sessions);
|
|
6174
|
+
case "opencode": return readAndMatch(query, sessions.slice(0, READ_LIMIT), openCodeMessages);
|
|
6175
|
+
}
|
|
6176
|
+
}
|
|
6177
|
+
function newestFirst(sessions) {
|
|
6178
|
+
return [...sessions].sort((a, b) => b.updatedAt - a.updatedAt);
|
|
6179
|
+
}
|
|
6180
|
+
async function searchClaude(query, sessions, folders) {
|
|
6181
|
+
const candidates = await claudeCandidates(query);
|
|
6182
|
+
const matching = candidates === null ? sessions : sessions.filter((session) => candidates.has(session.id));
|
|
6183
|
+
const projectOf = new Map(sessions.map((session) => [session.id, session.projectId]));
|
|
6184
|
+
return readAndMatch(query, matching.slice(0, READ_LIMIT), (sessionId) => claudeMessages(sessionId, folders.get(projectOf.get(sessionId) ?? "")));
|
|
6185
|
+
}
|
|
6186
|
+
async function claudeMessages(sessionId, dir) {
|
|
6187
|
+
return (await getSessionMessages(sessionId, { dir })).flatMap((item) => {
|
|
6188
|
+
if (item.type === "system" || item.parent_tool_use_id !== null) return [];
|
|
6189
|
+
const text = toMessage(item.type, item, null).content.flatMap((block) => block.type === "text" ? [block.text] : []).join("\n");
|
|
6190
|
+
return generatedText.test(text) ? [] : [{
|
|
6191
|
+
author: item.type,
|
|
6192
|
+
text
|
|
6193
|
+
}];
|
|
6194
|
+
});
|
|
6195
|
+
}
|
|
6196
|
+
async function claudeCandidates(query) {
|
|
6197
|
+
const phrase = exactPhrase(query);
|
|
6198
|
+
const words = phrase !== null ? [JSON.stringify(phrase).slice(1, -1)] : searchWords(query).filter((word) => word.length >= 3 && !/["\\]/.test(word));
|
|
6199
|
+
if (words.length === 0) return null;
|
|
6200
|
+
try {
|
|
6201
|
+
const lists = await Promise.all(words.map((word) => sessionsWith(word)));
|
|
6202
|
+
return new Set(lists[0].filter((id) => lists.every((list) => list.includes(id))));
|
|
6203
|
+
} catch (error) {
|
|
6204
|
+
console.warn(`could not narrow Claude sessions with rg: ${error}`);
|
|
6205
|
+
return null;
|
|
6206
|
+
}
|
|
6207
|
+
}
|
|
6208
|
+
async function sessionsWith(word) {
|
|
6209
|
+
return (await run$2("rg", [
|
|
6210
|
+
"--files-with-matches",
|
|
6211
|
+
"--fixed-strings",
|
|
6212
|
+
"--ignore-case",
|
|
6213
|
+
"--no-ignore",
|
|
6214
|
+
"--hidden",
|
|
6215
|
+
"--max-depth",
|
|
6216
|
+
"2",
|
|
6217
|
+
"--glob",
|
|
6218
|
+
"*.jsonl",
|
|
6219
|
+
"--",
|
|
6220
|
+
word,
|
|
6221
|
+
path.join(claudeBaseDir(), "projects")
|
|
6222
|
+
], {
|
|
6223
|
+
maxBuffer: 16777216,
|
|
6224
|
+
timeout: RG_TIME_LIMIT
|
|
6225
|
+
}).catch((error) => {
|
|
6226
|
+
if (error.code === 1) return { stdout: "" };
|
|
6227
|
+
throw error;
|
|
6228
|
+
})).stdout.split("\n").filter(Boolean).map((file) => path.basename(file, ".jsonl"));
|
|
6229
|
+
}
|
|
6230
|
+
async function searchCodex(query, sessions) {
|
|
6231
|
+
const longest = exactPhrase(query) ?? [...searchWords(query)].sort((a, b) => b.length - a.length)[0];
|
|
6232
|
+
const known = new Map(sessions.map((session) => [session.id, session]));
|
|
6233
|
+
const found = (await codexSearch(longest)).filter((result) => known.has(result.thread.id)).sort((a, b) => b.thread.updatedAt - a.thread.updatedAt);
|
|
6234
|
+
const verified = await readAndMatch(query, found.slice(0, CODEX_READ_LIMIT).map((result) => ({ id: result.thread.id })), codexMessages);
|
|
6235
|
+
const unverified = found.slice(CODEX_READ_LIMIT).flatMap((result) => {
|
|
6236
|
+
const match = matchMessage(query, result.snippet);
|
|
6237
|
+
return match === null ? [] : [{
|
|
6238
|
+
sessionId: result.thread.id,
|
|
6239
|
+
author: "assistant",
|
|
6240
|
+
quality: match.quality,
|
|
6241
|
+
snippet: messageSnippet(result.snippet, match.offset)
|
|
6242
|
+
}];
|
|
6243
|
+
});
|
|
6244
|
+
return [...verified, ...unverified];
|
|
6245
|
+
}
|
|
6246
|
+
async function readAndMatch(query, sessions, read) {
|
|
6247
|
+
return (await Promise.all(sessions.map(async (session) => bestMatch(query, session.id, await read(session.id))))).filter((match) => match !== null);
|
|
6248
|
+
}
|
|
6249
|
+
function bestMatch(query, sessionId, messages) {
|
|
6250
|
+
let best = null;
|
|
6251
|
+
for (const message of messages) {
|
|
6252
|
+
const match = matchMessage(query, message.text);
|
|
6253
|
+
if (match === null) continue;
|
|
6254
|
+
const candidate = {
|
|
6255
|
+
sessionId,
|
|
6256
|
+
author: message.author,
|
|
6257
|
+
quality: match.quality,
|
|
6258
|
+
snippet: messageSnippet(message.text, match.offset)
|
|
6259
|
+
};
|
|
6260
|
+
if (best === null || compareMessages(candidate, best) < 0) best = candidate;
|
|
6261
|
+
}
|
|
6262
|
+
return best;
|
|
6263
|
+
}
|
|
6141
6264
|
// src/services/session-title.ts
|
|
6142
6265
|
const CONTEXT_LIMIT = 8e3;
|
|
6143
6266
|
const MESSAGE_LIMIT = 2e3;
|
|
@@ -7533,6 +7656,7 @@ const handlers = {
|
|
|
7533
7656
|
"sessions.searchable": () => searchableSessions(),
|
|
7534
7657
|
"sessions.searchMessages": (params) => searchMessages(params.query),
|
|
7535
7658
|
"sessions.earlier": (params) => earlier(params.sessionId, params.before),
|
|
7659
|
+
"sessions.export": (params) => exportSession(params.sessionId, params.projectId),
|
|
7536
7660
|
"sessions.image": (params) => image(params.sessionId, params.itemId, params.index),
|
|
7537
7661
|
"sessions.agent": (params) => {
|
|
7538
7662
|
switch (providerOf(params.sessionId)) {
|