offsite 0.0.64 → 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-CTnyaJjt.js → XIcon-DJv99Y50.js} +1 -1
- package/apps/client/dist/assets/{context-menu-PUpo3KSB.js → context-menu-D44HzRLd.js} +26 -26
- package/apps/client/dist/assets/{diff-panel-D21KovGL.js → diff-panel-BBvzgxNm.js} +2 -2
- package/apps/client/dist/assets/{dist-Dru-KKjx.js → dist-B51OED4H.js} +1 -1
- package/apps/client/dist/assets/{editor-dialog-D340lWxq.js → editor-dialog-C1nmCjos.js} +28 -28
- package/apps/client/dist/assets/{image-viewer-BnE64jz6.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-CheTA7-7.js → preview-dialog-DDqLe_ku.js} +1 -1
- package/apps/client/dist/assets/{video-viewer-9KuQg3l7.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 +275 -150
- package/apps/server/dist/main.mjs.map +1 -1
- package/package.json +1 -1
- package/apps/client/dist/assets/index-B0oIygQ9.css +0 -2
- package/apps/client/dist/assets/index-B4cIMymV.js +0 -21
|
@@ -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":
|
|
@@ -1048,7 +1049,8 @@ function configInfo() {
|
|
|
1048
1049
|
defaults: defaults$1,
|
|
1049
1050
|
platform: platform(),
|
|
1050
1051
|
defaultShell: defaultShell(),
|
|
1051
|
-
defaultClaudeBaseDir: path.join(os.homedir(), ".claude")
|
|
1052
|
+
defaultClaudeBaseDir: path.join(os.homedir(), ".claude"),
|
|
1053
|
+
homeDir: os.homedir()
|
|
1052
1054
|
};
|
|
1053
1055
|
}
|
|
1054
1056
|
function readConfig() {
|
|
@@ -1947,7 +1949,8 @@ function messageItems(message, parent) {
|
|
|
1947
1949
|
case "running": return [];
|
|
1948
1950
|
case "completed": return [{
|
|
1949
1951
|
...base,
|
|
1950
|
-
kind: "compaction"
|
|
1952
|
+
kind: "compaction",
|
|
1953
|
+
summary: null
|
|
1951
1954
|
}];
|
|
1952
1955
|
case "failed": return [{
|
|
1953
1956
|
...errorItem(message.id, message.error.message),
|
|
@@ -4819,7 +4822,8 @@ function toItems(messages) {
|
|
|
4819
4822
|
items.push({
|
|
4820
4823
|
...base,
|
|
4821
4824
|
id: message.uuid,
|
|
4822
|
-
kind: "compaction"
|
|
4825
|
+
kind: "compaction",
|
|
4826
|
+
summary: message.summary
|
|
4823
4827
|
});
|
|
4824
4828
|
continue;
|
|
4825
4829
|
}
|
|
@@ -4926,7 +4930,7 @@ function toItems(messages) {
|
|
|
4926
4930
|
});
|
|
4927
4931
|
continue;
|
|
4928
4932
|
}
|
|
4929
|
-
texts.push(userText(block.text));
|
|
4933
|
+
texts.push(userText$1(block.text));
|
|
4930
4934
|
}
|
|
4931
4935
|
if (texts.length === 0 && images.length === 0) continue;
|
|
4932
4936
|
items.push({
|
|
@@ -5177,7 +5181,7 @@ function applyResult(item, result) {
|
|
|
5177
5181
|
default: return item;
|
|
5178
5182
|
}
|
|
5179
5183
|
}
|
|
5180
|
-
function userText(value) {
|
|
5184
|
+
function userText$1(value) {
|
|
5181
5185
|
const command = /<command-name>(.*?)<\/command-name>/s.exec(value);
|
|
5182
5186
|
if (command !== null) {
|
|
5183
5187
|
const args = /<command-args>(.*?)<\/command-args>/s.exec(value);
|
|
@@ -5285,12 +5289,19 @@ function parseLine(line) {
|
|
|
5285
5289
|
function mainMessages(disk, entries) {
|
|
5286
5290
|
const times = messageTimes(entries);
|
|
5287
5291
|
const outputs = toolOutputs(entries);
|
|
5292
|
+
const summaries = new Set(entries.flatMap((entry) => entry.isCompactSummary === true && typeof entry.uuid === "string" ? [entry.uuid] : []));
|
|
5288
5293
|
return disk.flatMap((item) => {
|
|
5289
5294
|
if (item.type === "system" || item.parent_tool_use_id !== null) return [];
|
|
5290
5295
|
const message = toMessage(item.type, {
|
|
5291
5296
|
...item,
|
|
5292
5297
|
tool_use_result: outputs.get(item.uuid)
|
|
5293
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
|
+
}];
|
|
5294
5305
|
return [{
|
|
5295
5306
|
...message,
|
|
5296
5307
|
content: message.content.map((block) => block.type === "image" ? {
|
|
@@ -5348,11 +5359,7 @@ async function earlier(sessionId, before) {
|
|
|
5348
5359
|
const index = session.pages.findIndex((page) => page.before === before);
|
|
5349
5360
|
if (index < 0) throw new Error(`no messages before entry ${before}`);
|
|
5350
5361
|
return {
|
|
5351
|
-
items: toItems(
|
|
5352
|
-
type: "compaction",
|
|
5353
|
-
uuid: `compacted:${before}`,
|
|
5354
|
-
time: null
|
|
5355
|
-
}]),
|
|
5362
|
+
items: toItems(mainMessages(session.pages[index].disk, session.entries)),
|
|
5356
5363
|
before: session.pages[index + 1]?.before ?? null
|
|
5357
5364
|
};
|
|
5358
5365
|
}
|
|
@@ -5636,7 +5643,8 @@ var Run = class {
|
|
|
5636
5643
|
this.add({
|
|
5637
5644
|
type: "compaction",
|
|
5638
5645
|
uuid: message.uuid,
|
|
5639
|
-
time: Date.now()
|
|
5646
|
+
time: Date.now(),
|
|
5647
|
+
summary: null
|
|
5640
5648
|
});
|
|
5641
5649
|
return;
|
|
5642
5650
|
}
|
|
@@ -5850,141 +5858,6 @@ async function isLocalHost(hostname) {
|
|
|
5850
5858
|
if (isIP(hostname) !== 0) return isPrivate(hostname);
|
|
5851
5859
|
return (await dns.promises.lookup(hostname, { all: true })).some((item) => isPrivate(item.address));
|
|
5852
5860
|
}
|
|
5853
|
-
// src/services/message-search.ts
|
|
5854
|
-
const READ_LIMIT = 30;
|
|
5855
|
-
const CODEX_READ_LIMIT = 10;
|
|
5856
|
-
const RG_TIME_LIMIT = 5e3;
|
|
5857
|
-
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)/;
|
|
5858
|
-
const run$2 = promisify(execFile);
|
|
5859
|
-
async function searchMessages(query) {
|
|
5860
|
-
if (!hasSearchableWord(query)) return {
|
|
5861
|
-
matches: [],
|
|
5862
|
-
failed: []
|
|
5863
|
-
};
|
|
5864
|
-
const paths = new Map(projects().map((project) => [project.path, project.id]));
|
|
5865
|
-
const folders = new Map(projects().map((project) => [project.id, project.path]));
|
|
5866
|
-
const results = await Promise.all([
|
|
5867
|
-
"claude",
|
|
5868
|
-
"codex",
|
|
5869
|
-
"opencode"
|
|
5870
|
-
].filter((provider) => readConfig().enabledProviders[provider]).map(async (provider) => {
|
|
5871
|
-
try {
|
|
5872
|
-
return {
|
|
5873
|
-
provider,
|
|
5874
|
-
matches: await timeLimit(search(provider, query, paths, folders), `${providerName(provider)} did not search in time`),
|
|
5875
|
-
failed: false
|
|
5876
|
-
};
|
|
5877
|
-
} catch (error) {
|
|
5878
|
-
console.warn(`could not search ${providerName(provider)} messages: ${error}`);
|
|
5879
|
-
return {
|
|
5880
|
-
provider,
|
|
5881
|
-
matches: [],
|
|
5882
|
-
failed: true
|
|
5883
|
-
};
|
|
5884
|
-
}
|
|
5885
|
-
}));
|
|
5886
|
-
return {
|
|
5887
|
-
matches: results.flatMap((result) => result.matches),
|
|
5888
|
-
failed: results.filter((result) => result.failed).map((result) => result.provider)
|
|
5889
|
-
};
|
|
5890
|
-
}
|
|
5891
|
-
async function search(provider, query, paths, folders) {
|
|
5892
|
-
const found = await find(provider, paths);
|
|
5893
|
-
if (found === null) return [];
|
|
5894
|
-
const sessions = newestFirst(found);
|
|
5895
|
-
switch (provider) {
|
|
5896
|
-
case "claude": return searchClaude(query, sessions, folders);
|
|
5897
|
-
case "codex": return searchCodex(query, sessions);
|
|
5898
|
-
case "opencode": return readAndMatch(query, sessions.slice(0, READ_LIMIT), openCodeMessages);
|
|
5899
|
-
}
|
|
5900
|
-
}
|
|
5901
|
-
function newestFirst(sessions) {
|
|
5902
|
-
return [...sessions].sort((a, b) => b.updatedAt - a.updatedAt);
|
|
5903
|
-
}
|
|
5904
|
-
async function searchClaude(query, sessions, folders) {
|
|
5905
|
-
const candidates = await claudeCandidates(query);
|
|
5906
|
-
const matching = candidates === null ? sessions : sessions.filter((session) => candidates.has(session.id));
|
|
5907
|
-
const projectOf = new Map(sessions.map((session) => [session.id, session.projectId]));
|
|
5908
|
-
return readAndMatch(query, matching.slice(0, READ_LIMIT), (sessionId) => claudeMessages(sessionId, folders.get(projectOf.get(sessionId) ?? "")));
|
|
5909
|
-
}
|
|
5910
|
-
async function claudeMessages(sessionId, dir) {
|
|
5911
|
-
return (await getSessionMessages(sessionId, { dir })).flatMap((item) => {
|
|
5912
|
-
if (item.type === "system" || item.parent_tool_use_id !== null) return [];
|
|
5913
|
-
const text = toMessage(item.type, item, null).content.flatMap((block) => block.type === "text" ? [block.text] : []).join("\n");
|
|
5914
|
-
return generatedText.test(text) ? [] : [{
|
|
5915
|
-
author: item.type,
|
|
5916
|
-
text
|
|
5917
|
-
}];
|
|
5918
|
-
});
|
|
5919
|
-
}
|
|
5920
|
-
async function claudeCandidates(query) {
|
|
5921
|
-
const phrase = exactPhrase(query);
|
|
5922
|
-
const words = phrase !== null ? [JSON.stringify(phrase).slice(1, -1)] : searchWords(query).filter((word) => word.length >= 3 && !/["\\]/.test(word));
|
|
5923
|
-
if (words.length === 0) return null;
|
|
5924
|
-
try {
|
|
5925
|
-
const lists = await Promise.all(words.map((word) => sessionsWith(word)));
|
|
5926
|
-
return new Set(lists[0].filter((id) => lists.every((list) => list.includes(id))));
|
|
5927
|
-
} catch (error) {
|
|
5928
|
-
console.warn(`could not narrow Claude sessions with rg: ${error}`);
|
|
5929
|
-
return null;
|
|
5930
|
-
}
|
|
5931
|
-
}
|
|
5932
|
-
async function sessionsWith(word) {
|
|
5933
|
-
return (await run$2("rg", [
|
|
5934
|
-
"--files-with-matches",
|
|
5935
|
-
"--fixed-strings",
|
|
5936
|
-
"--ignore-case",
|
|
5937
|
-
"--no-ignore",
|
|
5938
|
-
"--hidden",
|
|
5939
|
-
"--max-depth",
|
|
5940
|
-
"2",
|
|
5941
|
-
"--glob",
|
|
5942
|
-
"*.jsonl",
|
|
5943
|
-
"--",
|
|
5944
|
-
word,
|
|
5945
|
-
path.join(claudeBaseDir(), "projects")
|
|
5946
|
-
], {
|
|
5947
|
-
maxBuffer: 16777216,
|
|
5948
|
-
timeout: RG_TIME_LIMIT
|
|
5949
|
-
}).catch((error) => {
|
|
5950
|
-
if (error.code === 1) return { stdout: "" };
|
|
5951
|
-
throw error;
|
|
5952
|
-
})).stdout.split("\n").filter(Boolean).map((file) => path.basename(file, ".jsonl"));
|
|
5953
|
-
}
|
|
5954
|
-
async function searchCodex(query, sessions) {
|
|
5955
|
-
const longest = exactPhrase(query) ?? [...searchWords(query)].sort((a, b) => b.length - a.length)[0];
|
|
5956
|
-
const known = new Map(sessions.map((session) => [session.id, session]));
|
|
5957
|
-
const found = (await codexSearch(longest)).filter((result) => known.has(result.thread.id)).sort((a, b) => b.thread.updatedAt - a.thread.updatedAt);
|
|
5958
|
-
const verified = await readAndMatch(query, found.slice(0, CODEX_READ_LIMIT).map((result) => ({ id: result.thread.id })), codexMessages);
|
|
5959
|
-
const unverified = found.slice(CODEX_READ_LIMIT).flatMap((result) => {
|
|
5960
|
-
const match = matchMessage(query, result.snippet);
|
|
5961
|
-
return match === null ? [] : [{
|
|
5962
|
-
sessionId: result.thread.id,
|
|
5963
|
-
author: "assistant",
|
|
5964
|
-
quality: match.quality,
|
|
5965
|
-
snippet: messageSnippet(result.snippet, match.offset)
|
|
5966
|
-
}];
|
|
5967
|
-
});
|
|
5968
|
-
return [...verified, ...unverified];
|
|
5969
|
-
}
|
|
5970
|
-
async function readAndMatch(query, sessions, read) {
|
|
5971
|
-
return (await Promise.all(sessions.map(async (session) => bestMatch(query, session.id, await read(session.id))))).filter((match) => match !== null);
|
|
5972
|
-
}
|
|
5973
|
-
function bestMatch(query, sessionId, messages) {
|
|
5974
|
-
let best = null;
|
|
5975
|
-
for (const message of messages) {
|
|
5976
|
-
const match = matchMessage(query, message.text);
|
|
5977
|
-
if (match === null) continue;
|
|
5978
|
-
const candidate = {
|
|
5979
|
-
sessionId,
|
|
5980
|
-
author: message.author,
|
|
5981
|
-
quality: match.quality,
|
|
5982
|
-
snippet: messageSnippet(message.text, match.offset)
|
|
5983
|
-
};
|
|
5984
|
-
if (best === null || compareMessages(candidate, best) < 0) best = candidate;
|
|
5985
|
-
}
|
|
5986
|
-
return best;
|
|
5987
|
-
}
|
|
5988
5861
|
// src/services/runs.ts
|
|
5989
5862
|
const locks = /* @__PURE__ */ new Map();
|
|
5990
5863
|
function stopAll() {
|
|
@@ -6112,7 +5985,7 @@ async function runFor(sessionId, cwd, settings) {
|
|
|
6112
5985
|
sessionId,
|
|
6113
5986
|
cwd,
|
|
6114
5987
|
resume: true,
|
|
6115
|
-
resumeAt: null,
|
|
5988
|
+
resumeAt: run?.provider === "claude" && run.messages.length === 0 ? run.resumeAt : null,
|
|
6116
5989
|
settings
|
|
6117
5990
|
});
|
|
6118
5991
|
case "codex": return CodexRun.resume(sessionId, cwd, settings);
|
|
@@ -6137,6 +6010,257 @@ async function changedElsewhere(run, cwd) {
|
|
|
6137
6010
|
const last = (await getSessionMessages(run.sessionId, { dir: cwd })).at(-1)?.uuid;
|
|
6138
6011
|
return last !== void 0 && !run.messages.some((item) => item.uuid === last);
|
|
6139
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
|
+
}
|
|
6140
6264
|
// src/services/session-title.ts
|
|
6141
6265
|
const CONTEXT_LIMIT = 8e3;
|
|
6142
6266
|
const MESSAGE_LIMIT = 2e3;
|
|
@@ -7532,6 +7656,7 @@ const handlers = {
|
|
|
7532
7656
|
"sessions.searchable": () => searchableSessions(),
|
|
7533
7657
|
"sessions.searchMessages": (params) => searchMessages(params.query),
|
|
7534
7658
|
"sessions.earlier": (params) => earlier(params.sessionId, params.before),
|
|
7659
|
+
"sessions.export": (params) => exportSession(params.sessionId, params.projectId),
|
|
7535
7660
|
"sessions.image": (params) => image(params.sessionId, params.itemId, params.index),
|
|
7536
7661
|
"sessions.agent": (params) => {
|
|
7537
7662
|
switch (providerOf(params.sessionId)) {
|