@cryptiklemur/lattice 5.10.0 → 5.11.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/dist/client/assets/{angular-html-BDIcxkJq.js → angular-html-BoFzmWT8.js} +1 -1
- package/dist/client/assets/{angular-ts-Bt22ouNH.js → angular-ts-DZnI8rKE.js} +1 -1
- package/dist/client/assets/{apl-p8qkxzEK.js → apl-DstVmncE.js} +1 -1
- package/dist/client/assets/{astro-CIaMc49M.js → astro-DTPCjzEx.js} +1 -1
- package/dist/client/assets/{blade-BR56EAMD.js → blade-6q42Ss3F.js} +1 -1
- package/dist/client/assets/{c-Dli0HzAh.js → c-BQDGJ-nQ.js} +1 -1
- package/dist/client/assets/{cobol-Cad15ECy.js → cobol-Dlh0WvsZ.js} +1 -1
- package/dist/client/assets/{coffee-DpyATEbF.js → coffee-DdQv129j.js} +1 -1
- package/dist/client/assets/{cpp-KN8_NFsf.js → cpp-DhbQJIv4.js} +1 -1
- package/dist/client/assets/{crystal-CuyGv0kh.js → crystal-C22kERUB.js} +1 -1
- package/dist/client/assets/{css-Cm3q4bxn.js → css-n31O5kHj.js} +1 -1
- package/dist/client/assets/{dist-BjxsMc4u.js → dist-D8okl7lw.js} +2 -2
- package/dist/client/assets/{edge-B6S7CSbx.js → edge-Cgwx-o_7.js} +1 -1
- package/dist/client/assets/{elixir-CNUy9H8T.js → elixir-DAGM2WKD.js} +1 -1
- package/dist/client/assets/{elm-CNfcWmb9.js → elm-BLw_7oO9.js} +1 -1
- package/dist/client/assets/{erb-DWebzDaI.js → erb-DCaNhYa7.js} +1 -1
- package/dist/client/assets/{git-rebase-B_Pt2ZBK.js → git-rebase-CNNhb8-g.js} +1 -1
- package/dist/client/assets/{glimmer-js-CVwoOd72.js → glimmer-js-BnZd88Wi.js} +1 -1
- package/dist/client/assets/{glimmer-ts-CjtFSxjz.js → glimmer-ts-DvFNbZu-.js} +1 -1
- package/dist/client/assets/{glsl-CP4rggAA.js → glsl-Dnrk_Jnx.js} +1 -1
- package/dist/client/assets/{graphql-Dbm6sAtp.js → graphql-DlWTPvCG.js} +1 -1
- package/dist/client/assets/{hack-Bj9y3SGf.js → hack-DQg1Ek33.js} +1 -1
- package/dist/client/assets/{haml-DRGrdf3f.js → haml-DSk45qIE.js} +1 -1
- package/dist/client/assets/{handlebars-CFKjcBMg.js → handlebars-DuLvATB2.js} +1 -1
- package/dist/client/assets/{html-Vcd4eHHg.js → html-D4DiUnLg.js} +1 -1
- package/dist/client/assets/{html-derivative-BF0YbD4L.js → html-derivative-CS5MZ6d9.js} +1 -1
- package/dist/client/assets/{http-CGVTa2NT.js → http-CkDncfer.js} +1 -1
- package/dist/client/assets/{hurl-B0GrsGqd.js → hurl-DU39oO3U.js} +1 -1
- package/dist/client/assets/{index-CX1tudsF.js → index-CHPfE1Zl.js} +129 -129
- package/dist/client/assets/index-DHUKmLLC.css +2 -0
- package/dist/client/assets/{java-BJHQqHsm.js → java-lntACKEu.js} +1 -1
- package/dist/client/assets/{javascript-CmuMsKrc.js → javascript-CxkFc6nV.js} +1 -1
- package/dist/client/assets/{jinja-JxCLeq1j.js → jinja-DolO2zO7.js} +1 -1
- package/dist/client/assets/{jison-BdgAUhei.js → jison-Cok5FPev.js} +1 -1
- package/dist/client/assets/{json-DtPissHL.js → json-BebuQPrq.js} +1 -1
- package/dist/client/assets/{jsx-DUAxxDkP.js → jsx-iLBaUyXr.js} +1 -1
- package/dist/client/assets/{julia-DxDlbL6e.js → julia-C5Dsc7cH.js} +1 -1
- package/dist/client/assets/{just-CVmAAx2R.js → just-DJYqq_9R.js} +1 -1
- package/dist/client/assets/{latex-uwxggTWA.js → latex-BTTYiKj1.js} +1 -1
- package/dist/client/assets/{liquid-xsETAJJy.js → liquid-DpAKCrOB.js} +1 -1
- package/dist/client/assets/{lua-B2Hh8PgD.js → lua-BZ6b1hko.js} +1 -1
- package/dist/client/assets/{marko-yDeGxD87.js → marko-D8VK6iGt.js} +1 -1
- package/dist/client/assets/{mdc-QMp4ieYR.js → mdc-Paa3XzwY.js} +1 -1
- package/dist/client/assets/{nginx-7gmRmcqz.js → nginx-C5k9mWtJ.js} +1 -1
- package/dist/client/assets/{nim-CA8SNY_7.js → nim-Dst6YSnE.js} +1 -1
- package/dist/client/assets/{perl-lx5nW4VC.js → perl-XhiCjgBp.js} +1 -1
- package/dist/client/assets/{php-DgHiW953.js → php-BcsPLnLU.js} +1 -1
- package/dist/client/assets/{pug-CbbB1vwb.js → pug-GLH9-eAJ.js} +1 -1
- package/dist/client/assets/{qml-COrzwCIh.js → qml-Cj_lJioE.js} +1 -1
- package/dist/client/assets/{r-Dv7pZJDH.js → r-B70aGYK5.js} +1 -1
- package/dist/client/assets/{razor-D2m8EDP5.js → razor-R3gub_zy.js} +1 -1
- package/dist/client/assets/{regexp-BXLT-jPc.js → regexp-itC0dIUJ.js} +1 -1
- package/dist/client/assets/{rst-_S6rrUYh.js → rst-DdyoV8E2.js} +1 -1
- package/dist/client/assets/{ruby-C3XO7tYY.js → ruby-BYBZsv66.js} +1 -1
- package/dist/client/assets/{sas-DP2k4iuN.js → sas-fqfqXqj1.js} +1 -1
- package/dist/client/assets/{scss-lhLFMXGn.js → scss-B-ELv6mu.js} +1 -1
- package/dist/client/assets/{shellscript-BYlBPHen.js → shellscript-BgB8TNw6.js} +1 -1
- package/dist/client/assets/{shellsession-CbVyQKWZ.js → shellsession-BLK2Dgkm.js} +1 -1
- package/dist/client/assets/{soy-Be8a0lHq.js → soy-C7_RmNrp.js} +1 -1
- package/dist/client/assets/{sql-2KxvU9YS.js → sql-AUgbUJq4.js} +1 -1
- package/dist/client/assets/{stata-BxlWftTS.js → stata-CIVqSIOr.js} +1 -1
- package/dist/client/assets/{surrealql-CJ-q86nR.js → surrealql-BzRQzc5S.js} +1 -1
- package/dist/client/assets/{svelte-Q1ml0OiY.js → svelte-BCIwEwtb.js} +1 -1
- package/dist/client/assets/{templ-BbfPZhtu.js → templ-C1hbwe4u.js} +1 -1
- package/dist/client/assets/{tex-Dcth4Gi6.js → tex-CI4tIsaP.js} +1 -1
- package/dist/client/assets/{ts-tags-BKhSOXI3.js → ts-tags-SUeikhEp.js} +1 -1
- package/dist/client/assets/{tsx-CS6iQ0XH.js → tsx-xkp7aIZs.js} +1 -1
- package/dist/client/assets/{twig-BHp31ZxS.js → twig-CGgBSAyc.js} +1 -1
- package/dist/client/assets/{typescript-16YJBTaO.js → typescript-O2YMTl_s.js} +1 -1
- package/dist/client/assets/{vue-CMKwTi4r.js → vue-DsNRxos1.js} +1 -1
- package/dist/client/assets/{vue-html-Dr8VUA2G.js → vue-html-CuY3t7bs.js} +1 -1
- package/dist/client/assets/{vue-vine-DZUqDerl.js → vue-vine-C6kSCKwY.js} +1 -1
- package/dist/client/assets/{xml-CBbBKKDC.js → xml-DafwzOLY.js} +1 -1
- package/dist/client/assets/{xsl-DWEX6PKX.js → xsl-1SGGZibr.js} +1 -1
- package/dist/client/assets/{yaml-DvKvvh3X.js → yaml-DSVhzmhr.js} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/client/sw.js +1 -1
- package/dist/server/analytics/engine.js +241 -241
- package/dist/server/assets.js +4 -4
- package/dist/server/auth/passphrase.js +13 -13
- package/dist/server/config.js +7 -7
- package/dist/server/daemon.js +93 -93
- package/dist/server/features/brainstorm.js +42 -42
- package/dist/server/features/ralph-loop.js +33 -33
- package/dist/server/features/scheduler.js +53 -53
- package/dist/server/features/specs.js +54 -54
- package/dist/server/features/sticky-notes.js +17 -17
- package/dist/server/features/superpowers.js +24 -24
- package/dist/server/handlers/analytics.js +1 -1
- package/dist/server/handlers/attachment.js +32 -32
- package/dist/server/handlers/bookmarks.js +4 -4
- package/dist/server/handlers/brainstorm.js +4 -4
- package/dist/server/handlers/chat.js +54 -54
- package/dist/server/handlers/editor.js +13 -13
- package/dist/server/handlers/fs.js +51 -51
- package/dist/server/handlers/hooks.js +20 -20
- package/dist/server/handlers/loop.js +6 -6
- package/dist/server/handlers/memory.js +44 -44
- package/dist/server/handlers/mesh.js +60 -60
- package/dist/server/handlers/notes.js +7 -7
- package/dist/server/handlers/plugins.js +174 -174
- package/dist/server/handlers/project-settings.js +26 -26
- package/dist/server/handlers/scheduler.js +6 -6
- package/dist/server/handlers/session.js +24 -24
- package/dist/server/handlers/settings.js +21 -21
- package/dist/server/handlers/skills.js +91 -91
- package/dist/server/handlers/specs.js +51 -28
- package/dist/server/handlers/terminal.js +13 -13
- package/dist/server/handlers/themes.js +21 -21
- package/dist/server/handlers/update.js +17 -17
- package/dist/server/hooks/event_forward.sh +34 -0
- package/dist/server/hooks/post_tool_use.sh +26 -0
- package/dist/server/hooks/statusline.sh +26 -0
- package/dist/server/identity.js +6 -6
- package/dist/server/index.js +111 -111
- package/dist/server/logger.js +1 -1
- package/dist/server/mesh/connector.js +78 -78
- package/dist/server/mesh/crypto.js +20 -20
- package/dist/server/mesh/discovery.js +14 -14
- package/dist/server/mesh/pairing.js +30 -30
- package/dist/server/mesh/peers.js +10 -10
- package/dist/server/mesh/proxy.js +14 -14
- package/dist/server/mesh/session-sync.js +23 -23
- package/dist/server/project/bookmarks.js +11 -11
- package/dist/server/project/context-breakdown.js +70 -70
- package/dist/server/project/file-browser.js +17 -17
- package/dist/server/project/project-files.js +68 -68
- package/dist/server/project/registry.js +10 -10
- package/dist/server/project/sdk-bridge.js +157 -157
- package/dist/server/project/session.js +201 -199
- package/dist/server/project/terminal.js +15 -15
- package/dist/server/project/warmup.js +37 -37
- package/dist/server/push.js +11 -11
- package/dist/server/runtime.js +1 -1
- package/dist/server/tls.js +15 -15
- package/dist/server/tui.js +15 -15
- package/dist/server/update-checker.js +21 -21
- package/dist/server/ws/broadcast.js +18 -18
- package/dist/server/ws/router.js +17 -17
- package/dist/shared/constants.js +8 -8
- package/package.json +2 -2
- package/dist/client/assets/index-DlfI20Gn.css +0 -2
|
@@ -7,8 +7,8 @@ import { homedir } from "node:os";
|
|
|
7
7
|
import { loadConfig, getLatticeHome } from "../config.js";
|
|
8
8
|
import { log } from "../logger.js";
|
|
9
9
|
function getProjectPath(projectSlug) {
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
const config = loadConfig();
|
|
11
|
+
const project = config.projects.find(function (p) { return p.slug === projectSlug; });
|
|
12
12
|
return project ? project.path : null;
|
|
13
13
|
}
|
|
14
14
|
export function projectPathToHash(projectPath) {
|
|
@@ -23,10 +23,10 @@ function mapSDKSession(info, projectSlug) {
|
|
|
23
23
|
updatedAt: info.lastModified,
|
|
24
24
|
};
|
|
25
25
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
const LITELLM_PRICING_URL = "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json";
|
|
27
|
+
const pricingCache = {};
|
|
28
|
+
let pricingLoaded = false;
|
|
29
|
+
const FALLBACK_PRICING = {
|
|
30
30
|
"claude-opus-4-6": { input: 15, output: 75 },
|
|
31
31
|
"claude-sonnet-4-6": { input: 3, output: 15 },
|
|
32
32
|
"claude-haiku-4-5": { input: 0.80, output: 4 },
|
|
@@ -38,15 +38,15 @@ export function loadPricing() {
|
|
|
38
38
|
fetch(LITELLM_PRICING_URL).then(function (res) {
|
|
39
39
|
return res.json();
|
|
40
40
|
}).then(function (data) {
|
|
41
|
-
for (
|
|
41
|
+
for (const key in data) {
|
|
42
42
|
if (!key.includes("claude"))
|
|
43
43
|
continue;
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
44
|
+
const entry = data[key];
|
|
45
|
+
const inputCost = entry.input_cost_per_token;
|
|
46
|
+
const outputCost = entry.output_cost_per_token;
|
|
47
47
|
if (inputCost == null || outputCost == null)
|
|
48
48
|
continue;
|
|
49
|
-
|
|
49
|
+
const modelId = key.replace("anthropic/", "").replace("claude-", "claude-");
|
|
50
50
|
pricingCache[modelId] = {
|
|
51
51
|
input: inputCost * 1000000,
|
|
52
52
|
output: outputCost * 1000000,
|
|
@@ -63,12 +63,12 @@ loadPricing();
|
|
|
63
63
|
export function getPricing(model) {
|
|
64
64
|
if (pricingCache[model])
|
|
65
65
|
return pricingCache[model];
|
|
66
|
-
for (
|
|
66
|
+
for (const key in pricingCache) {
|
|
67
67
|
if (key.includes(model) || model.includes(key))
|
|
68
68
|
return pricingCache[key];
|
|
69
69
|
}
|
|
70
|
-
|
|
71
|
-
for (
|
|
70
|
+
const shortModel = model.replace("claude-", "").split("-")[0];
|
|
71
|
+
for (const key2 in pricingCache) {
|
|
72
72
|
if (key2.includes(shortModel))
|
|
73
73
|
return pricingCache[key2];
|
|
74
74
|
}
|
|
@@ -78,22 +78,22 @@ export function getPricing(model) {
|
|
|
78
78
|
return FALLBACK_PRICING["claude-sonnet-4-6"];
|
|
79
79
|
}
|
|
80
80
|
export function estimateCost(model, inputTokens, outputTokens, cacheRead, cacheCreation) {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
81
|
+
const pricing = getPricing(model);
|
|
82
|
+
const normalInput = inputTokens - cacheRead - cacheCreation;
|
|
83
|
+
const inputCost = (normalInput * pricing.input) / 1000000;
|
|
84
|
+
const cacheCost = pricing.cacheRead != null
|
|
85
85
|
? (cacheRead * pricing.cacheRead) / 1000000
|
|
86
86
|
: (cacheRead * pricing.input * 0.1) / 1000000;
|
|
87
|
-
|
|
87
|
+
const cacheCreateCost = pricing.cacheCreation != null
|
|
88
88
|
? (cacheCreation * pricing.cacheCreation) / 1000000
|
|
89
89
|
: (cacheCreation * pricing.input * 1.25) / 1000000;
|
|
90
|
-
|
|
90
|
+
const outputCost = (outputTokens * pricing.output) / 1000000;
|
|
91
91
|
return Math.max(0, inputCost + cacheCost + cacheCreateCost + outputCost);
|
|
92
92
|
}
|
|
93
93
|
function parseTimestamp(msg) {
|
|
94
|
-
|
|
94
|
+
const raw = msg.timestamp;
|
|
95
95
|
if (raw) {
|
|
96
|
-
|
|
96
|
+
const parsed = new Date(raw).getTime();
|
|
97
97
|
if (!isNaN(parsed))
|
|
98
98
|
return parsed;
|
|
99
99
|
}
|
|
@@ -116,12 +116,12 @@ function extractUserText(content) {
|
|
|
116
116
|
return stripXmlTags(content);
|
|
117
117
|
}
|
|
118
118
|
if (Array.isArray(content)) {
|
|
119
|
-
for (
|
|
120
|
-
|
|
119
|
+
for (let i = 0; i < content.length; i++) {
|
|
120
|
+
const block = content[i];
|
|
121
121
|
if (block.type === "text" && typeof block.text === "string") {
|
|
122
122
|
if (isSystemContent(block.text))
|
|
123
123
|
continue;
|
|
124
|
-
|
|
124
|
+
const cleaned = stripXmlTags(block.text);
|
|
125
125
|
if (cleaned)
|
|
126
126
|
return cleaned;
|
|
127
127
|
}
|
|
@@ -129,22 +129,22 @@ function extractUserText(content) {
|
|
|
129
129
|
}
|
|
130
130
|
return "";
|
|
131
131
|
}
|
|
132
|
-
|
|
132
|
+
const HIDDEN_TOOLS = new Set([
|
|
133
133
|
"TaskUpdate", "TaskCreate", "TaskGet", "TaskList", "TaskOutput", "TaskStop",
|
|
134
134
|
"TodoWrite", "TodoRead",
|
|
135
135
|
"EnterPlanMode", "ExitPlanMode",
|
|
136
136
|
"ToolSearch",
|
|
137
137
|
]);
|
|
138
138
|
function convertSessionMessages(messages) {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
for (
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
139
|
+
const result = [];
|
|
140
|
+
const hiddenToolIds = new Set();
|
|
141
|
+
for (let i = 0; i < messages.length; i++) {
|
|
142
|
+
const msg = messages[i];
|
|
143
|
+
const ts = parseTimestamp(msg);
|
|
144
|
+
const apiMsg = msg.message;
|
|
145
145
|
if (msg.type === "user") {
|
|
146
146
|
if (msg.isCompactSummary === true) {
|
|
147
|
-
|
|
147
|
+
const summaryText = typeof apiMsg.content === "string" ? apiMsg.content : "";
|
|
148
148
|
if (summaryText) {
|
|
149
149
|
result.push({
|
|
150
150
|
type: "compact_summary",
|
|
@@ -156,21 +156,21 @@ function convertSessionMessages(messages) {
|
|
|
156
156
|
continue;
|
|
157
157
|
}
|
|
158
158
|
if (Array.isArray(apiMsg.content)) {
|
|
159
|
-
|
|
160
|
-
for (
|
|
161
|
-
|
|
159
|
+
let hadToolResult = false;
|
|
160
|
+
for (let j = 0; j < apiMsg.content.length; j++) {
|
|
161
|
+
const block = apiMsg.content[j];
|
|
162
162
|
if (block.type === "tool_result" && block.tool_use_id) {
|
|
163
163
|
if (hiddenToolIds.has(block.tool_use_id))
|
|
164
164
|
continue;
|
|
165
165
|
hadToolResult = true;
|
|
166
|
-
|
|
166
|
+
let resultContent = "";
|
|
167
167
|
if (typeof block.content === "string") {
|
|
168
168
|
resultContent = block.content;
|
|
169
169
|
}
|
|
170
170
|
else if (Array.isArray(block.content)) {
|
|
171
|
-
|
|
172
|
-
for (
|
|
173
|
-
|
|
171
|
+
const texts = [];
|
|
172
|
+
for (let ri = 0; ri < block.content.length; ri++) {
|
|
173
|
+
const rb = block.content[ri];
|
|
174
174
|
if (rb.type === "text" && rb.text)
|
|
175
175
|
texts.push(rb.text);
|
|
176
176
|
}
|
|
@@ -189,7 +189,7 @@ function convertSessionMessages(messages) {
|
|
|
189
189
|
else if (block.type === "text" && block.text) {
|
|
190
190
|
if (isSystemContent(block.text))
|
|
191
191
|
continue;
|
|
192
|
-
|
|
192
|
+
const cleaned = stripXmlTags(block.text);
|
|
193
193
|
if (cleaned) {
|
|
194
194
|
result.push({
|
|
195
195
|
type: "user",
|
|
@@ -202,7 +202,7 @@ function convertSessionMessages(messages) {
|
|
|
202
202
|
}
|
|
203
203
|
}
|
|
204
204
|
else {
|
|
205
|
-
|
|
205
|
+
const text = extractUserText(apiMsg.content);
|
|
206
206
|
if (text) {
|
|
207
207
|
result.push({
|
|
208
208
|
type: "user",
|
|
@@ -214,12 +214,12 @@ function convertSessionMessages(messages) {
|
|
|
214
214
|
}
|
|
215
215
|
}
|
|
216
216
|
else if (msg.type === "assistant") {
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
217
|
+
const msgUsage = apiMsg.usage;
|
|
218
|
+
const msgModel = apiMsg.model || "";
|
|
219
|
+
let lastAssistantIdx = -1;
|
|
220
220
|
if (Array.isArray(apiMsg.content)) {
|
|
221
|
-
for (
|
|
222
|
-
|
|
221
|
+
for (let k = 0; k < apiMsg.content.length; k++) {
|
|
222
|
+
const aBlock = apiMsg.content[k];
|
|
223
223
|
if (aBlock.type === "text" && aBlock.text) {
|
|
224
224
|
lastAssistantIdx = result.length;
|
|
225
225
|
result.push({
|
|
@@ -245,10 +245,10 @@ function convertSessionMessages(messages) {
|
|
|
245
245
|
}
|
|
246
246
|
}
|
|
247
247
|
if (msgUsage && lastAssistantIdx >= 0) {
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
248
|
+
const inTok = msgUsage.input_tokens || 0;
|
|
249
|
+
const outTok = msgUsage.output_tokens || 0;
|
|
250
|
+
const cacheRead = msgUsage.cache_read_input_tokens || 0;
|
|
251
|
+
const cacheCreate = msgUsage.cache_creation_input_tokens || 0;
|
|
252
252
|
result[lastAssistantIdx].inputTokens = inTok;
|
|
253
253
|
result[lastAssistantIdx].outputTokens = outTok;
|
|
254
254
|
result[lastAssistantIdx].model = msgModel;
|
|
@@ -256,7 +256,7 @@ function convertSessionMessages(messages) {
|
|
|
256
256
|
}
|
|
257
257
|
}
|
|
258
258
|
else {
|
|
259
|
-
|
|
259
|
+
const aText = typeof apiMsg.content === "string" ? apiMsg.content : "";
|
|
260
260
|
if (aText) {
|
|
261
261
|
lastAssistantIdx = result.length;
|
|
262
262
|
result.push({
|
|
@@ -266,10 +266,10 @@ function convertSessionMessages(messages) {
|
|
|
266
266
|
timestamp: ts,
|
|
267
267
|
});
|
|
268
268
|
if (msgUsage) {
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
269
|
+
const inTok2 = msgUsage.input_tokens || 0;
|
|
270
|
+
const outTok2 = msgUsage.output_tokens || 0;
|
|
271
|
+
const cacheRead2 = msgUsage.cache_read_input_tokens || 0;
|
|
272
|
+
const cacheCreate2 = msgUsage.cache_creation_input_tokens || 0;
|
|
273
273
|
result[lastAssistantIdx].inputTokens = inTok2;
|
|
274
274
|
result[lastAssistantIdx].outputTokens = outTok2;
|
|
275
275
|
result[lastAssistantIdx].model = msgModel;
|
|
@@ -281,7 +281,7 @@ function convertSessionMessages(messages) {
|
|
|
281
281
|
}
|
|
282
282
|
return result;
|
|
283
283
|
}
|
|
284
|
-
|
|
284
|
+
const MODEL_CONTEXT_WINDOWS = {
|
|
285
285
|
"claude-opus-4-6": 1048576,
|
|
286
286
|
"claude-sonnet-4-6": 1048576,
|
|
287
287
|
"claude-sonnet-4-5-20250514": 1048576,
|
|
@@ -301,31 +301,31 @@ export function guessContextWindow(model) {
|
|
|
301
301
|
return 200000;
|
|
302
302
|
}
|
|
303
303
|
export async function getSessionUsage(projectSlug, sessionId) {
|
|
304
|
-
|
|
304
|
+
const projectPath = getProjectPath(projectSlug);
|
|
305
305
|
if (!projectPath)
|
|
306
306
|
return null;
|
|
307
|
-
|
|
308
|
-
|
|
307
|
+
const hash = projectPathToHash(projectPath);
|
|
308
|
+
const sessionFile = join(homedir(), ".claude", "projects", hash, sessionId + ".jsonl");
|
|
309
309
|
if (!existsSync(sessionFile))
|
|
310
310
|
return null;
|
|
311
311
|
try {
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
312
|
+
const fhUsage = await fsPromises.open(sessionFile, "r");
|
|
313
|
+
const statUsage = await fhUsage.stat();
|
|
314
|
+
const tailSize = Math.min(statUsage.size, 512 * 1024);
|
|
315
|
+
const bufUsage = Buffer.alloc(tailSize);
|
|
316
316
|
await fhUsage.read(bufUsage, 0, tailSize, statUsage.size - tailSize);
|
|
317
317
|
await fhUsage.close();
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
for (
|
|
321
|
-
|
|
318
|
+
const text = bufUsage.toString("utf-8");
|
|
319
|
+
const lines = text.split("\n");
|
|
320
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
321
|
+
const line = lines[i].trim();
|
|
322
322
|
if (!line)
|
|
323
323
|
continue;
|
|
324
324
|
try {
|
|
325
|
-
|
|
325
|
+
const parsed = JSON.parse(line);
|
|
326
326
|
if (parsed.type === "assistant" && parsed.message && parsed.message.usage) {
|
|
327
|
-
|
|
328
|
-
|
|
327
|
+
const usage = parsed.message.usage;
|
|
328
|
+
const model = parsed.message.model || "";
|
|
329
329
|
return {
|
|
330
330
|
inputTokens: usage.input_tokens || 0,
|
|
331
331
|
outputTokens: usage.output_tokens || 0,
|
|
@@ -344,32 +344,32 @@ export async function getSessionUsage(projectSlug, sessionId) {
|
|
|
344
344
|
}
|
|
345
345
|
}
|
|
346
346
|
export async function getSessionPreview(projectSlug, sessionId) {
|
|
347
|
-
|
|
347
|
+
const projectPath = getProjectPath(projectSlug);
|
|
348
348
|
if (!projectPath)
|
|
349
349
|
return null;
|
|
350
|
-
|
|
351
|
-
|
|
350
|
+
const hash = projectPathToHash(projectPath);
|
|
351
|
+
const sessionFile = join(homedir(), ".claude", "projects", hash, sessionId + ".jsonl");
|
|
352
352
|
if (!existsSync(sessionFile))
|
|
353
353
|
return null;
|
|
354
354
|
try {
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
for (
|
|
364
|
-
|
|
355
|
+
const content = readFileSync(sessionFile, "utf-8");
|
|
356
|
+
const lines = content.trim().split("\n");
|
|
357
|
+
let totalCost = 0;
|
|
358
|
+
let messageCount = 0;
|
|
359
|
+
let model = "";
|
|
360
|
+
let lastMessage = "";
|
|
361
|
+
let firstTimestamp = 0;
|
|
362
|
+
let lastTimestamp = 0;
|
|
363
|
+
for (let i = 0; i < lines.length; i++) {
|
|
364
|
+
const line = lines[i].trim();
|
|
365
365
|
if (!line)
|
|
366
366
|
continue;
|
|
367
367
|
try {
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
368
|
+
const parsed = JSON.parse(line);
|
|
369
|
+
let ts = 0;
|
|
370
|
+
const rawTs = parsed.timestamp;
|
|
371
371
|
if (rawTs) {
|
|
372
|
-
|
|
372
|
+
const parsedTs = new Date(rawTs).getTime();
|
|
373
373
|
if (!isNaN(parsedTs))
|
|
374
374
|
ts = parsedTs;
|
|
375
375
|
}
|
|
@@ -379,26 +379,26 @@ export async function getSessionPreview(projectSlug, sessionId) {
|
|
|
379
379
|
lastTimestamp = ts;
|
|
380
380
|
if (parsed.type === "user") {
|
|
381
381
|
messageCount++;
|
|
382
|
-
|
|
382
|
+
const userText = extractUserText(parsed.message?.content);
|
|
383
383
|
if (userText)
|
|
384
384
|
lastMessage = userText;
|
|
385
385
|
}
|
|
386
386
|
else if (parsed.type === "assistant" && parsed.message) {
|
|
387
387
|
messageCount++;
|
|
388
|
-
|
|
389
|
-
|
|
388
|
+
const usage = parsed.message.usage;
|
|
389
|
+
const msgModel = parsed.message.model || "";
|
|
390
390
|
if (msgModel)
|
|
391
391
|
model = msgModel;
|
|
392
392
|
if (usage) {
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
393
|
+
const inTok = usage.input_tokens || 0;
|
|
394
|
+
const outTok = usage.output_tokens || 0;
|
|
395
|
+
const cacheRead = usage.cache_read_input_tokens || 0;
|
|
396
|
+
const cacheCreate = usage.cache_creation_input_tokens || 0;
|
|
397
397
|
totalCost += estimateCost(msgModel || model, inTok, outTok, cacheRead, cacheCreate);
|
|
398
398
|
}
|
|
399
|
-
|
|
399
|
+
const aContent = parsed.message.content;
|
|
400
400
|
if (Array.isArray(aContent)) {
|
|
401
|
-
for (
|
|
401
|
+
for (let j = aContent.length - 1; j >= 0; j--) {
|
|
402
402
|
if (aContent[j].type === "text" && aContent[j].text) {
|
|
403
403
|
lastMessage = aContent[j].text;
|
|
404
404
|
break;
|
|
@@ -412,7 +412,7 @@ export async function getSessionPreview(projectSlug, sessionId) {
|
|
|
412
412
|
}
|
|
413
413
|
catch { }
|
|
414
414
|
}
|
|
415
|
-
|
|
415
|
+
const maxSnippet = 120;
|
|
416
416
|
if (lastMessage.length > maxSnippet) {
|
|
417
417
|
lastMessage = lastMessage.slice(0, maxSnippet) + "...";
|
|
418
418
|
}
|
|
@@ -429,15 +429,15 @@ export async function getSessionPreview(projectSlug, sessionId) {
|
|
|
429
429
|
return null;
|
|
430
430
|
}
|
|
431
431
|
}
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
432
|
+
const sessionListCache = new Map();
|
|
433
|
+
const SESSION_CACHE_TTL = 60000;
|
|
434
|
+
const RECONCILE_INTERVAL = 5 * 60 * 1000;
|
|
435
|
+
const lastReconcile = new Map();
|
|
436
436
|
function getIndexPath() {
|
|
437
437
|
return join(getLatticeHome(), "session-index.json");
|
|
438
438
|
}
|
|
439
439
|
function loadSessionIndex() {
|
|
440
|
-
|
|
440
|
+
const indexPath = getIndexPath();
|
|
441
441
|
if (!existsSync(indexPath))
|
|
442
442
|
return {};
|
|
443
443
|
try {
|
|
@@ -456,10 +456,10 @@ function saveSessionIndex(index) {
|
|
|
456
456
|
}
|
|
457
457
|
}
|
|
458
458
|
export function updateSessionInIndex(projectSlug, session) {
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
for (
|
|
459
|
+
const index = loadSessionIndex();
|
|
460
|
+
const sessions = index[projectSlug] || [];
|
|
461
|
+
let existing = -1;
|
|
462
|
+
for (let i = 0; i < sessions.length; i++) {
|
|
463
463
|
if (sessions[i].id === session.id) {
|
|
464
464
|
existing = i;
|
|
465
465
|
break;
|
|
@@ -477,22 +477,22 @@ export function updateSessionInIndex(projectSlug, session) {
|
|
|
477
477
|
sessionListCache.set(projectSlug, { sessions, time: Date.now() });
|
|
478
478
|
}
|
|
479
479
|
export function removeSessionFromIndex(projectSlug, sessionId) {
|
|
480
|
-
|
|
481
|
-
|
|
480
|
+
const index = loadSessionIndex();
|
|
481
|
+
const sessions = index[projectSlug] || [];
|
|
482
482
|
index[projectSlug] = sessions.filter(function (s) { return s.id !== sessionId; });
|
|
483
483
|
saveSessionIndex(index);
|
|
484
484
|
sessionListCache.set(projectSlug, { sessions: index[projectSlug], time: Date.now() });
|
|
485
485
|
}
|
|
486
486
|
async function reconcileWithSDK(projectSlug) {
|
|
487
|
-
|
|
487
|
+
const projectPath = getProjectPath(projectSlug);
|
|
488
488
|
if (!projectPath)
|
|
489
489
|
return [];
|
|
490
|
-
|
|
491
|
-
|
|
490
|
+
const sdkT0 = Date.now();
|
|
491
|
+
const sdkSessions = await sdkListSessions({ dir: projectPath });
|
|
492
492
|
log.session("sdkListSessions for %s: %dms (%d sessions)", projectSlug, Date.now() - sdkT0, sdkSessions.length);
|
|
493
|
-
|
|
493
|
+
const summaries = sdkSessions.map(function (s) { return mapSDKSession(s, projectSlug); });
|
|
494
494
|
summaries.sort(function (a, b) { return b.updatedAt - a.updatedAt; });
|
|
495
|
-
|
|
495
|
+
const index = loadSessionIndex();
|
|
496
496
|
index[projectSlug] = summaries;
|
|
497
497
|
saveSessionIndex(index);
|
|
498
498
|
sessionListCache.set(projectSlug, { sessions: summaries, time: Date.now() });
|
|
@@ -500,25 +500,25 @@ async function reconcileWithSDK(projectSlug) {
|
|
|
500
500
|
return summaries;
|
|
501
501
|
}
|
|
502
502
|
function needsReconcile(projectSlug) {
|
|
503
|
-
|
|
503
|
+
const last = lastReconcile.get(projectSlug);
|
|
504
504
|
if (!last)
|
|
505
505
|
return true;
|
|
506
506
|
return Date.now() - last > RECONCILE_INTERVAL;
|
|
507
507
|
}
|
|
508
508
|
export async function listSessions(projectSlug, options) {
|
|
509
|
-
|
|
509
|
+
const projectPath = getProjectPath(projectSlug);
|
|
510
510
|
if (!projectPath) {
|
|
511
511
|
return { sessions: [], totalCount: 0 };
|
|
512
512
|
}
|
|
513
|
-
|
|
513
|
+
const cached = sessionListCache.get(projectSlug);
|
|
514
514
|
if (cached && !options?.noCache && Date.now() - cached.time < SESSION_CACHE_TTL) {
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
515
|
+
const offset = options?.offset ?? 0;
|
|
516
|
+
const limit = options?.limit ?? 0;
|
|
517
|
+
const sliced = limit > 0 ? cached.sessions.slice(offset, offset + limit) : cached.sessions;
|
|
518
518
|
return { sessions: sliced, totalCount: cached.sessions.length };
|
|
519
519
|
}
|
|
520
|
-
|
|
521
|
-
|
|
520
|
+
const index = loadSessionIndex();
|
|
521
|
+
const indexed = index[projectSlug];
|
|
522
522
|
if (indexed && indexed.length > 0) {
|
|
523
523
|
sessionListCache.set(projectSlug, { sessions: indexed, time: Date.now() });
|
|
524
524
|
if (needsReconcile(projectSlug)) {
|
|
@@ -526,16 +526,16 @@ export async function listSessions(projectSlug, options) {
|
|
|
526
526
|
log.session("Background reconcile failed: %O", err);
|
|
527
527
|
});
|
|
528
528
|
}
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
529
|
+
const offset3 = options?.offset ?? 0;
|
|
530
|
+
const limit3 = options?.limit ?? 0;
|
|
531
|
+
const sliced3 = limit3 > 0 ? indexed.slice(offset3, offset3 + limit3) : indexed;
|
|
532
532
|
return { sessions: sliced3, totalCount: indexed.length };
|
|
533
533
|
}
|
|
534
534
|
try {
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
535
|
+
const summaries = await reconcileWithSDK(projectSlug);
|
|
536
|
+
const offset2 = options?.offset ?? 0;
|
|
537
|
+
const limit2 = options?.limit ?? 0;
|
|
538
|
+
const sliced2 = limit2 > 0 ? summaries.slice(offset2, offset2 + limit2) : summaries;
|
|
539
539
|
return { sessions: sliced2, totalCount: summaries.length };
|
|
540
540
|
}
|
|
541
541
|
catch (err) {
|
|
@@ -548,61 +548,75 @@ export function invalidateSessionCache(projectSlug) {
|
|
|
548
548
|
}
|
|
549
549
|
export function invalidateHistoryCache(sessionId) {
|
|
550
550
|
historyCache.delete(sessionId);
|
|
551
|
-
|
|
551
|
+
const idx = historyCacheOrder.indexOf(sessionId);
|
|
552
552
|
if (idx >= 0)
|
|
553
553
|
historyCacheOrder.splice(idx, 1);
|
|
554
554
|
}
|
|
555
555
|
export async function getSessionTitle(projectSlug, sessionId) {
|
|
556
|
-
|
|
557
|
-
|
|
556
|
+
const filePath = getSessionFilePath(projectSlug, sessionId);
|
|
557
|
+
if (!filePath) {
|
|
558
|
+
const index = loadSessionIndex();
|
|
559
|
+
const sessions = index[projectSlug] || [];
|
|
560
|
+
const entry = sessions.find(function (s) { return s.id === sessionId; });
|
|
561
|
+
if (entry && entry.title && entry.title !== "Untitled")
|
|
562
|
+
return entry.title;
|
|
563
|
+
return "Untitled";
|
|
564
|
+
}
|
|
565
|
+
const projectPath = getProjectPath(projectSlug);
|
|
566
|
+
const options = projectPath ? { dir: projectPath } : undefined;
|
|
558
567
|
try {
|
|
559
|
-
|
|
568
|
+
const info = await getSessionInfo(sessionId, options);
|
|
560
569
|
if (info) {
|
|
561
570
|
return info.customTitle || info.summary || info.firstPrompt || "Untitled";
|
|
562
571
|
}
|
|
563
572
|
}
|
|
564
573
|
catch { }
|
|
574
|
+
const index = loadSessionIndex();
|
|
575
|
+
const sessions = index[projectSlug] || [];
|
|
576
|
+
const entry = sessions.find(function (s) { return s.id === sessionId; });
|
|
577
|
+
if (entry && entry.title && entry.title !== "Untitled")
|
|
578
|
+
return entry.title;
|
|
565
579
|
return "Untitled";
|
|
566
580
|
}
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
581
|
+
const historyCache = new Map();
|
|
582
|
+
const historyCacheOrder = [];
|
|
583
|
+
const MAX_HISTORY_CACHE = 50;
|
|
570
584
|
function touchCache(sessionId) {
|
|
571
|
-
|
|
585
|
+
const idx = historyCacheOrder.indexOf(sessionId);
|
|
572
586
|
if (idx >= 0)
|
|
573
587
|
historyCacheOrder.splice(idx, 1);
|
|
574
588
|
historyCacheOrder.push(sessionId);
|
|
575
589
|
}
|
|
576
590
|
function evictOldest() {
|
|
577
591
|
while (historyCacheOrder.length > MAX_HISTORY_CACHE) {
|
|
578
|
-
|
|
592
|
+
const oldest = historyCacheOrder.shift();
|
|
579
593
|
if (oldest)
|
|
580
594
|
historyCache.delete(oldest);
|
|
581
595
|
}
|
|
582
596
|
}
|
|
583
597
|
export function appendToHistoryCache(sessionId, message) {
|
|
584
|
-
|
|
598
|
+
const cached = historyCache.get(sessionId);
|
|
585
599
|
if (!cached)
|
|
586
600
|
return;
|
|
587
601
|
cached.messages.push(message);
|
|
588
602
|
touchCache(sessionId);
|
|
589
603
|
}
|
|
590
|
-
|
|
591
|
-
|
|
604
|
+
const INITIAL_MESSAGE_COUNT = 300;
|
|
605
|
+
const TAIL_READ_BYTES = 512 * 1024;
|
|
592
606
|
function getSessionFilePath(projectSlug, sessionId) {
|
|
593
|
-
|
|
607
|
+
const projectPath = getProjectPath(projectSlug);
|
|
594
608
|
if (!projectPath)
|
|
595
609
|
return null;
|
|
596
|
-
|
|
597
|
-
|
|
610
|
+
const hash = projectPathToHash(projectPath);
|
|
611
|
+
const filePath = join(homedir(), ".claude", "projects", hash, sessionId + ".jsonl");
|
|
598
612
|
return existsSync(filePath) ? filePath : null;
|
|
599
613
|
}
|
|
600
614
|
export async function getSessionFileSizeBytes(projectSlug, sessionId) {
|
|
601
|
-
|
|
615
|
+
const filePath = getSessionFilePath(projectSlug, sessionId);
|
|
602
616
|
if (!filePath)
|
|
603
617
|
return null;
|
|
604
618
|
try {
|
|
605
|
-
|
|
619
|
+
const fileStat = await fsPromises.stat(filePath);
|
|
606
620
|
return fileStat.size;
|
|
607
621
|
}
|
|
608
622
|
catch {
|
|
@@ -610,20 +624,20 @@ export async function getSessionFileSizeBytes(projectSlug, sessionId) {
|
|
|
610
624
|
}
|
|
611
625
|
}
|
|
612
626
|
async function readTailLines(filePath, maxBytes) {
|
|
613
|
-
|
|
627
|
+
const fh = await fsPromises.open(filePath, "r");
|
|
614
628
|
try {
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
629
|
+
const stat = await fh.stat();
|
|
630
|
+
const readStart = Math.max(0, stat.size - maxBytes);
|
|
631
|
+
const length = stat.size - readStart;
|
|
632
|
+
const buf = Buffer.alloc(length);
|
|
619
633
|
await fh.read(buf, 0, length, readStart);
|
|
620
|
-
|
|
634
|
+
let text = buf.toString("utf-8");
|
|
621
635
|
if (readStart > 0) {
|
|
622
|
-
|
|
636
|
+
const firstNewline = text.indexOf("\n");
|
|
623
637
|
if (firstNewline >= 0)
|
|
624
638
|
text = text.slice(firstNewline + 1);
|
|
625
639
|
}
|
|
626
|
-
|
|
640
|
+
const lines = text.split("\n").filter(function (l) { return l.length > 0; });
|
|
627
641
|
return { lines, isPartial: readStart > 0, fileSize: stat.size };
|
|
628
642
|
}
|
|
629
643
|
finally {
|
|
@@ -631,10 +645,10 @@ async function readTailLines(filePath, maxBytes) {
|
|
|
631
645
|
}
|
|
632
646
|
}
|
|
633
647
|
function parseJsonlLines(lines) {
|
|
634
|
-
|
|
635
|
-
for (
|
|
648
|
+
const results = [];
|
|
649
|
+
for (let i = 0; i < lines.length; i++) {
|
|
636
650
|
try {
|
|
637
|
-
|
|
651
|
+
const parsed = JSON.parse(lines[i]);
|
|
638
652
|
if (parsed.type === "user" || parsed.type === "assistant" || parsed.type === "system") {
|
|
639
653
|
results.push(parsed);
|
|
640
654
|
}
|
|
@@ -645,47 +659,35 @@ function parseJsonlLines(lines) {
|
|
|
645
659
|
}
|
|
646
660
|
export async function loadSessionHistory(projectSlug, sessionId) {
|
|
647
661
|
try {
|
|
648
|
-
|
|
649
|
-
|
|
662
|
+
const t0 = Date.now();
|
|
663
|
+
const cached = historyCache.get(sessionId);
|
|
650
664
|
if (cached) {
|
|
651
665
|
touchCache(sessionId);
|
|
652
|
-
|
|
666
|
+
const tail = cached.messages.length > INITIAL_MESSAGE_COUNT
|
|
653
667
|
? cached.messages.slice(cached.messages.length - INITIAL_MESSAGE_COUNT)
|
|
654
668
|
: cached.messages;
|
|
655
669
|
log.session("loadSessionHistory %s: %dms (cached, %d total)", sessionId.slice(0, 8), Date.now() - t0, cached.messages.length);
|
|
656
670
|
return { messages: tail, totalMessages: cached.messages.length, hasMore: cached.messages.length > INITIAL_MESSAGE_COUNT };
|
|
657
671
|
}
|
|
658
|
-
|
|
672
|
+
const filePath = getSessionFilePath(projectSlug, sessionId);
|
|
659
673
|
if (filePath) {
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
674
|
+
const tailData = await readTailLines(filePath, TAIL_READ_BYTES);
|
|
675
|
+
const tailRaw = parseJsonlLines(tailData.lines);
|
|
676
|
+
const tailMessages = convertSessionMessages(tailRaw);
|
|
677
|
+
const hasMore = tailData.isPartial;
|
|
664
678
|
log.session("loadSessionHistory %s: %dms (tail read, %d msgs, partial=%s)", sessionId.slice(0, 8), Date.now() - t0, tailMessages.length, hasMore);
|
|
665
679
|
if (!hasMore && tailMessages.length > 0) {
|
|
666
680
|
historyCache.set(sessionId, { messages: tailMessages, title: null });
|
|
667
681
|
touchCache(sessionId);
|
|
668
682
|
evictOldest();
|
|
669
683
|
}
|
|
670
|
-
|
|
684
|
+
const initialSlice = tailMessages.length > INITIAL_MESSAGE_COUNT
|
|
671
685
|
? tailMessages.slice(tailMessages.length - INITIAL_MESSAGE_COUNT)
|
|
672
686
|
: tailMessages;
|
|
673
687
|
return { messages: initialSlice, totalMessages: tailMessages.length, hasMore: hasMore };
|
|
674
688
|
}
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
var rawMessages = await getSessionMessages(sessionId, options);
|
|
678
|
-
var allMessages = convertSessionMessages(rawMessages);
|
|
679
|
-
if (allMessages.length > 0) {
|
|
680
|
-
historyCache.set(sessionId, { messages: allMessages, title: null });
|
|
681
|
-
touchCache(sessionId);
|
|
682
|
-
evictOldest();
|
|
683
|
-
}
|
|
684
|
-
log.session("loadSessionHistory %s: %dms (full SDK, %d msgs)", sessionId.slice(0, 8), Date.now() - t0, allMessages.length);
|
|
685
|
-
var tailSlice = allMessages.length > INITIAL_MESSAGE_COUNT
|
|
686
|
-
? allMessages.slice(allMessages.length - INITIAL_MESSAGE_COUNT)
|
|
687
|
-
: allMessages;
|
|
688
|
-
return { messages: tailSlice, totalMessages: allMessages.length, hasMore: allMessages.length > INITIAL_MESSAGE_COUNT };
|
|
689
|
+
log.session("loadSessionHistory %s: %dms (no session file found)", sessionId.slice(0, 8), Date.now() - t0);
|
|
690
|
+
return { messages: [], totalMessages: 0, hasMore: false };
|
|
689
691
|
}
|
|
690
692
|
catch (err) {
|
|
691
693
|
log.session("Failed to load session history: %O", err);
|
|
@@ -693,13 +695,13 @@ export async function loadSessionHistory(projectSlug, sessionId) {
|
|
|
693
695
|
}
|
|
694
696
|
}
|
|
695
697
|
export async function getSessionHistoryPage(sessionId, beforeIndex, limit, projectSlug, loaded) {
|
|
696
|
-
|
|
698
|
+
let cached = historyCache.get(sessionId);
|
|
697
699
|
if (!cached && projectSlug) {
|
|
698
|
-
|
|
699
|
-
|
|
700
|
+
const projectPath = getProjectPath(projectSlug);
|
|
701
|
+
const options = projectPath ? { dir: projectPath } : undefined;
|
|
700
702
|
try {
|
|
701
|
-
|
|
702
|
-
|
|
703
|
+
const rawMessages = await getSessionMessages(sessionId, options);
|
|
704
|
+
const allMessages = convertSessionMessages(rawMessages);
|
|
703
705
|
historyCache.set(sessionId, { messages: allMessages, title: null });
|
|
704
706
|
touchCache(sessionId);
|
|
705
707
|
evictOldest();
|
|
@@ -712,8 +714,8 @@ export async function getSessionHistoryPage(sessionId, beforeIndex, limit, proje
|
|
|
712
714
|
}
|
|
713
715
|
if (!cached)
|
|
714
716
|
return { messages: [], hasMore: false, totalMessages: 0 };
|
|
715
|
-
|
|
716
|
-
|
|
717
|
+
const total = cached.messages.length;
|
|
718
|
+
let endIdx;
|
|
717
719
|
if (loaded !== undefined) {
|
|
718
720
|
endIdx = Math.max(0, total - loaded);
|
|
719
721
|
}
|
|
@@ -723,13 +725,13 @@ export async function getSessionHistoryPage(sessionId, beforeIndex, limit, proje
|
|
|
723
725
|
else {
|
|
724
726
|
endIdx = total;
|
|
725
727
|
}
|
|
726
|
-
|
|
727
|
-
|
|
728
|
+
const startIdx = Math.max(0, endIdx - limit);
|
|
729
|
+
const page = cached.messages.slice(startIdx, endIdx);
|
|
728
730
|
return { messages: page, hasMore: startIdx > 0, totalMessages: total };
|
|
729
731
|
}
|
|
730
732
|
export function createSession(projectSlug, sessionType) {
|
|
731
|
-
|
|
732
|
-
|
|
733
|
+
const sessionId = randomUUID();
|
|
734
|
+
const now = Date.now();
|
|
733
735
|
return {
|
|
734
736
|
id: sessionId,
|
|
735
737
|
projectSlug,
|
|
@@ -740,8 +742,8 @@ export function createSession(projectSlug, sessionType) {
|
|
|
740
742
|
};
|
|
741
743
|
}
|
|
742
744
|
export async function renameSession(projectSlug, sessionId, title) {
|
|
743
|
-
|
|
744
|
-
|
|
745
|
+
const projectPath = getProjectPath(projectSlug);
|
|
746
|
+
const options = projectPath ? { dir: projectPath } : undefined;
|
|
745
747
|
try {
|
|
746
748
|
await sdkRenameSession(sessionId, title, options);
|
|
747
749
|
return true;
|
|
@@ -752,12 +754,12 @@ export async function renameSession(projectSlug, sessionId, title) {
|
|
|
752
754
|
}
|
|
753
755
|
}
|
|
754
756
|
export async function deleteSession(projectSlug, sessionId) {
|
|
755
|
-
|
|
757
|
+
const projectPath = getProjectPath(projectSlug);
|
|
756
758
|
if (!projectPath) {
|
|
757
759
|
return false;
|
|
758
760
|
}
|
|
759
|
-
|
|
760
|
-
|
|
761
|
+
const hash = projectPathToHash(projectPath);
|
|
762
|
+
const sessionFile = join(homedir(), ".claude", "projects", hash, sessionId + ".jsonl");
|
|
761
763
|
if (!existsSync(sessionFile)) {
|
|
762
764
|
return false;
|
|
763
765
|
}
|
|
@@ -772,12 +774,12 @@ export async function deleteSession(projectSlug, sessionId) {
|
|
|
772
774
|
}
|
|
773
775
|
export async function findProjectSlugForSession(sessionId) {
|
|
774
776
|
try {
|
|
775
|
-
|
|
777
|
+
const info = await getSessionInfo(sessionId);
|
|
776
778
|
if (!info || !info.cwd) {
|
|
777
779
|
return null;
|
|
778
780
|
}
|
|
779
|
-
|
|
780
|
-
for (
|
|
781
|
+
const config = loadConfig();
|
|
782
|
+
for (let i = 0; i < config.projects.length; i++) {
|
|
781
783
|
if (info.cwd.startsWith(config.projects[i].path)) {
|
|
782
784
|
return config.projects[i].slug;
|
|
783
785
|
}
|