chat-recall 0.7.0 → 0.7.2
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/auto-update.d.ts +34 -0
- package/dist/auto-update.d.ts.map +1 -1
- package/dist/auto-update.js +51 -8
- package/dist/auto-update.js.map +1 -1
- package/dist/cli.js +463 -244
- package/dist/cli.js.map +1 -1
- package/dist/code-index-cursor.d.ts +20 -3
- package/dist/code-index-cursor.d.ts.map +1 -1
- package/dist/code-index-cursor.js +53 -8
- package/dist/code-index-cursor.js.map +1 -1
- package/dist/mcp-relay.js +76 -13
- package/dist/mcp-relay.js.map +1 -1
- package/dist/mcp.js +777 -471
- package/dist/mcp.js.map +1 -1
- package/dist/relay-lifecycle.d.ts +69 -0
- package/dist/relay-lifecycle.d.ts.map +1 -0
- package/dist/relay-lifecycle.js +117 -0
- package/dist/relay-lifecycle.js.map +1 -0
- package/dist/repair.d.ts +2 -0
- package/dist/repair.d.ts.map +1 -1
- package/dist/repair.js +23 -3
- package/dist/repair.js.map +1 -1
- package/dist/scan-worker.js +122 -65
- package/dist/service-installer.d.ts.map +1 -1
- package/dist/service-installer.js +7 -2
- package/dist/service-installer.js.map +1 -1
- package/dist/sync-client.d.ts +38 -5
- package/dist/sync-client.d.ts.map +1 -1
- package/dist/sync-client.js +194 -61
- package/dist/sync-client.js.map +1 -1
- package/dist/sync-supervisor.d.ts +43 -0
- package/dist/sync-supervisor.d.ts.map +1 -0
- package/dist/sync-supervisor.js +84 -0
- package/dist/sync-supervisor.js.map +1 -0
- package/dist/watch.js +470 -213
- package/package.json +1 -1
package/dist/mcp.js
CHANGED
|
@@ -4585,12 +4585,13 @@ function resolveProjectId(inputPath) {
|
|
|
4585
4585
|
if (!isAbsolute(inputPath)) {
|
|
4586
4586
|
return { id: `path:${inputPath}`, displayName: inputPath, source: "path" };
|
|
4587
4587
|
}
|
|
4588
|
+
const known = inputCache.get(inputPath);
|
|
4589
|
+
if (known)
|
|
4590
|
+
return known;
|
|
4588
4591
|
const realPath = safeRealpath(inputPath);
|
|
4589
|
-
const
|
|
4590
|
-
if (cached)
|
|
4591
|
-
return cached;
|
|
4592
|
-
const result = doResolve(realPath);
|
|
4592
|
+
const result = resolveCache.get(realPath) ?? doResolve(realPath);
|
|
4593
4593
|
resolveCache.set(realPath, result);
|
|
4594
|
+
inputCache.set(inputPath, result);
|
|
4594
4595
|
return result;
|
|
4595
4596
|
}
|
|
4596
4597
|
function resolveWorkspaceId(inputPath) {
|
|
@@ -4891,7 +4892,7 @@ function globToRegex(glob) {
|
|
|
4891
4892
|
function escapeRe(s) {
|
|
4892
4893
|
return s.replace(/[.+^${}()|[\]\\]/g, "\\$&");
|
|
4893
4894
|
}
|
|
4894
|
-
var DEFAULT_AUTO_WORKSPACE_MIN_REPOS, BOT_WORKTREE_MARKERS, resolveCache, gitCache, workspaceCache, wsIdCache, cachedConfig, GIT_TIMEOUT_MS, AUTO_WORKSPACE_MAX_DEPTH;
|
|
4895
|
+
var DEFAULT_AUTO_WORKSPACE_MIN_REPOS, BOT_WORKTREE_MARKERS, resolveCache, inputCache, gitCache, workspaceCache, wsIdCache, cachedConfig, GIT_TIMEOUT_MS, AUTO_WORKSPACE_MAX_DEPTH;
|
|
4895
4896
|
var init_project_resolver = __esm({
|
|
4896
4897
|
"../engine/dist/core/project-resolver.js"() {
|
|
4897
4898
|
"use strict";
|
|
@@ -4899,6 +4900,7 @@ var init_project_resolver = __esm({
|
|
|
4899
4900
|
DEFAULT_AUTO_WORKSPACE_MIN_REPOS = 3;
|
|
4900
4901
|
BOT_WORKTREE_MARKERS = [".claude-pr-bot"];
|
|
4901
4902
|
resolveCache = /* @__PURE__ */ new Map();
|
|
4903
|
+
inputCache = /* @__PURE__ */ new Map();
|
|
4902
4904
|
gitCache = /* @__PURE__ */ new Map();
|
|
4903
4905
|
workspaceCache = /* @__PURE__ */ new Map();
|
|
4904
4906
|
wsIdCache = /* @__PURE__ */ new Map();
|
|
@@ -7156,7 +7158,7 @@ var init_raw = __esm({
|
|
|
7156
7158
|
// ../engine/dist/transcript/shadow.js
|
|
7157
7159
|
import { gzipSync as gzipSync2, gunzipSync as gunzipSync2 } from "zlib";
|
|
7158
7160
|
import { createHash as createHash4 } from "crypto";
|
|
7159
|
-
import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync2, renameSync as renameSync2, statSync as statSync7 } from "fs";
|
|
7161
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync2, renameSync as renameSync2, statSync as statSync7, openSync as openSync2, readSync as readSync2, closeSync as closeSync2 } from "fs";
|
|
7160
7162
|
import { dirname as dirname9, join as join11 } from "path";
|
|
7161
7163
|
function shadowRoot() {
|
|
7162
7164
|
return join11(getDataDir(), "shadow");
|
|
@@ -7332,6 +7334,29 @@ function updateShadow(sessionId, exp, prebuilt) {
|
|
|
7332
7334
|
path: path2
|
|
7333
7335
|
};
|
|
7334
7336
|
}
|
|
7337
|
+
function shadowUncompressedBytes(tool, rawId) {
|
|
7338
|
+
const path2 = shadowFileFor(tool, rawId);
|
|
7339
|
+
let fd = null;
|
|
7340
|
+
try {
|
|
7341
|
+
const size = statSync7(path2).size;
|
|
7342
|
+
if (size < 18)
|
|
7343
|
+
return 0;
|
|
7344
|
+
fd = openSync2(path2, "r");
|
|
7345
|
+
const tail = Buffer.alloc(4);
|
|
7346
|
+
if (readSync2(fd, tail, 0, 4, size - 4) !== 4)
|
|
7347
|
+
return 0;
|
|
7348
|
+
return tail.readUInt32LE(0);
|
|
7349
|
+
} catch {
|
|
7350
|
+
return 0;
|
|
7351
|
+
} finally {
|
|
7352
|
+
if (fd !== null) {
|
|
7353
|
+
try {
|
|
7354
|
+
closeSync2(fd);
|
|
7355
|
+
} catch {
|
|
7356
|
+
}
|
|
7357
|
+
}
|
|
7358
|
+
}
|
|
7359
|
+
}
|
|
7335
7360
|
var WHOLE_FILE_TOOLS, SINGLETON_META;
|
|
7336
7361
|
var init_shadow = __esm({
|
|
7337
7362
|
"../engine/dist/transcript/shadow.js"() {
|
|
@@ -7344,7 +7369,7 @@ var init_shadow = __esm({
|
|
|
7344
7369
|
});
|
|
7345
7370
|
|
|
7346
7371
|
// ../engine/dist/core/live-session-scan.js
|
|
7347
|
-
import { existsSync as existsSync8, readdirSync as readdirSync8, readFileSync as readFileSync6, statSync as statSync8, openSync as
|
|
7372
|
+
import { existsSync as existsSync8, readdirSync as readdirSync8, readFileSync as readFileSync6, statSync as statSync8, openSync as openSync3, readSync as readSync3, closeSync as closeSync3 } from "fs";
|
|
7348
7373
|
import { join as join12, basename as basename7 } from "path";
|
|
7349
7374
|
function detectTool(sessionId) {
|
|
7350
7375
|
return getBackendForId(sessionId)?.id ?? "claude";
|
|
@@ -7371,6 +7396,15 @@ function sessionFileIndex() {
|
|
|
7371
7396
|
return indexCache;
|
|
7372
7397
|
const byId = /* @__PURE__ */ new Map();
|
|
7373
7398
|
const seenRoots = /* @__PURE__ */ new Set();
|
|
7399
|
+
const projectPaths = /* @__PURE__ */ new Map();
|
|
7400
|
+
const projectPathOf = (dirName) => {
|
|
7401
|
+
let p = projectPaths.get(dirName);
|
|
7402
|
+
if (p === void 0) {
|
|
7403
|
+
p = resolveProjectDirName(dirName);
|
|
7404
|
+
projectPaths.set(dirName, p);
|
|
7405
|
+
}
|
|
7406
|
+
return p;
|
|
7407
|
+
};
|
|
7374
7408
|
for (const root of claudeProjectDirs()) {
|
|
7375
7409
|
if (seenRoots.has(root) || !existsSync8(root))
|
|
7376
7410
|
continue;
|
|
@@ -7398,7 +7432,7 @@ function sessionFileIndex() {
|
|
|
7398
7432
|
const located = {
|
|
7399
7433
|
path: join12(dir, name),
|
|
7400
7434
|
projectDir: entry.name,
|
|
7401
|
-
projectPath:
|
|
7435
|
+
projectPath: projectPathOf(entry.name)
|
|
7402
7436
|
};
|
|
7403
7437
|
const prior = byId.get(id);
|
|
7404
7438
|
if (prior)
|
|
@@ -8080,7 +8114,7 @@ var init_flat_string = __esm({
|
|
|
8080
8114
|
});
|
|
8081
8115
|
|
|
8082
8116
|
// ../engine/dist/core/first-prompt.js
|
|
8083
|
-
import { createReadStream as createReadStream2, existsSync as existsSync10, openSync as
|
|
8117
|
+
import { createReadStream as createReadStream2, existsSync as existsSync10, openSync as openSync4, readSync as readSync4, closeSync as closeSync4 } from "fs";
|
|
8084
8118
|
import { StringDecoder } from "string_decoder";
|
|
8085
8119
|
function stripBanners2(s) {
|
|
8086
8120
|
let out = s;
|
|
@@ -8134,7 +8168,7 @@ function extractFirstUserPromptSync(filePath, options = {}) {
|
|
|
8134
8168
|
const maxBytes = options.maxBytes ?? 4 * 1024 * 1024;
|
|
8135
8169
|
let fd;
|
|
8136
8170
|
try {
|
|
8137
|
-
fd =
|
|
8171
|
+
fd = openSync4(filePath, "r");
|
|
8138
8172
|
} catch {
|
|
8139
8173
|
return "";
|
|
8140
8174
|
}
|
|
@@ -8146,7 +8180,7 @@ function extractFirstUserPromptSync(filePath, options = {}) {
|
|
|
8146
8180
|
let pos = 0;
|
|
8147
8181
|
let lineNo = 0;
|
|
8148
8182
|
for (; ; ) {
|
|
8149
|
-
const n =
|
|
8183
|
+
const n = readSync4(fd, buf, 0, CHUNK, pos);
|
|
8150
8184
|
if (n <= 0)
|
|
8151
8185
|
break;
|
|
8152
8186
|
pos += n;
|
|
@@ -8172,7 +8206,7 @@ function extractFirstUserPromptSync(filePath, options = {}) {
|
|
|
8172
8206
|
return "";
|
|
8173
8207
|
} finally {
|
|
8174
8208
|
try {
|
|
8175
|
-
|
|
8209
|
+
closeSync4(fd);
|
|
8176
8210
|
} catch {
|
|
8177
8211
|
}
|
|
8178
8212
|
}
|
|
@@ -8191,8 +8225,8 @@ var init_first_prompt = __esm({
|
|
|
8191
8225
|
});
|
|
8192
8226
|
|
|
8193
8227
|
// ../engine/dist/core/backends/tail-read.js
|
|
8194
|
-
import { openSync as
|
|
8195
|
-
function readTailFromOffset(path2, offset) {
|
|
8228
|
+
import { openSync as openSync5, readSync as readSync5, closeSync as closeSync5, statSync as statSync9 } from "node:fs";
|
|
8229
|
+
function readTailFromOffset(path2, offset, maxBytes = TAIL_READ_MAX_BYTES) {
|
|
8196
8230
|
let size = 0;
|
|
8197
8231
|
try {
|
|
8198
8232
|
size = statSync9(path2).size;
|
|
@@ -8201,14 +8235,14 @@ function readTailFromOffset(path2, offset) {
|
|
|
8201
8235
|
}
|
|
8202
8236
|
if (size <= offset)
|
|
8203
8237
|
return { text: "", newOffset: offset };
|
|
8204
|
-
const len = Math.min(size - offset,
|
|
8238
|
+
const len = Math.min(size - offset, Math.max(1, maxBytes));
|
|
8205
8239
|
const buf = Buffer.allocUnsafe(len);
|
|
8206
8240
|
let fd = null;
|
|
8207
8241
|
try {
|
|
8208
|
-
fd =
|
|
8242
|
+
fd = openSync5(path2, "r");
|
|
8209
8243
|
let got = 0;
|
|
8210
8244
|
while (got < len) {
|
|
8211
|
-
const n =
|
|
8245
|
+
const n = readSync5(fd, buf, got, len - got, offset + got);
|
|
8212
8246
|
if (n <= 0)
|
|
8213
8247
|
break;
|
|
8214
8248
|
got += n;
|
|
@@ -8217,25 +8251,47 @@ function readTailFromOffset(path2, offset) {
|
|
|
8217
8251
|
return { text: "", newOffset: offset };
|
|
8218
8252
|
const slice = got < len ? buf.subarray(0, got) : buf;
|
|
8219
8253
|
const lastNl = slice.lastIndexOf(10);
|
|
8220
|
-
if (lastNl
|
|
8221
|
-
|
|
8254
|
+
if (lastNl >= 0) {
|
|
8255
|
+
const text = slice.subarray(0, lastNl + 1).toString("utf-8");
|
|
8256
|
+
return { text, newOffset: offset + lastNl + 1 };
|
|
8222
8257
|
}
|
|
8223
|
-
|
|
8224
|
-
|
|
8258
|
+
if (offset + got >= size)
|
|
8259
|
+
return { text: "", newOffset: offset };
|
|
8260
|
+
const end = findNewline(fd, offset + got, size);
|
|
8261
|
+
if (end < 0)
|
|
8262
|
+
return { text: "", newOffset: offset };
|
|
8263
|
+
return { text: "", newOffset: end + 1, skippedBytes: end + 1 - offset };
|
|
8225
8264
|
} catch {
|
|
8226
8265
|
return { text: "", newOffset: offset };
|
|
8227
8266
|
} finally {
|
|
8228
8267
|
if (fd !== null) {
|
|
8229
8268
|
try {
|
|
8230
|
-
|
|
8269
|
+
closeSync5(fd);
|
|
8231
8270
|
} catch {
|
|
8232
8271
|
}
|
|
8233
8272
|
}
|
|
8234
8273
|
}
|
|
8235
8274
|
}
|
|
8275
|
+
function findNewline(fd, from, size) {
|
|
8276
|
+
const block = Buffer.allocUnsafe(SKIP_SCAN_BYTES);
|
|
8277
|
+
let pos = from;
|
|
8278
|
+
while (pos < size) {
|
|
8279
|
+
const n = readSync5(fd, block, 0, Math.min(SKIP_SCAN_BYTES, size - pos), pos);
|
|
8280
|
+
if (n <= 0)
|
|
8281
|
+
return -1;
|
|
8282
|
+
const i = block.subarray(0, n).indexOf(10);
|
|
8283
|
+
if (i >= 0)
|
|
8284
|
+
return pos + i;
|
|
8285
|
+
pos += n;
|
|
8286
|
+
}
|
|
8287
|
+
return -1;
|
|
8288
|
+
}
|
|
8289
|
+
var TAIL_READ_MAX_BYTES, SKIP_SCAN_BYTES;
|
|
8236
8290
|
var init_tail_read = __esm({
|
|
8237
8291
|
"../engine/dist/core/backends/tail-read.js"() {
|
|
8238
8292
|
"use strict";
|
|
8293
|
+
TAIL_READ_MAX_BYTES = 16 * 1024 * 1024;
|
|
8294
|
+
SKIP_SCAN_BYTES = 1024 * 1024;
|
|
8239
8295
|
}
|
|
8240
8296
|
});
|
|
8241
8297
|
|
|
@@ -8512,7 +8568,7 @@ var init_generic_engine = __esm({
|
|
|
8512
8568
|
});
|
|
8513
8569
|
|
|
8514
8570
|
// ../engine/dist/core/backends/claude.js
|
|
8515
|
-
import { existsSync as existsSync11, readdirSync as readdirSync9, readFileSync as readFileSync7, statSync as statSync10, openSync as
|
|
8571
|
+
import { existsSync as existsSync11, readdirSync as readdirSync9, readFileSync as readFileSync7, statSync as statSync10, openSync as openSync6, readSync as readSync6, closeSync as closeSync6 } from "fs";
|
|
8516
8572
|
import { join as join13, basename as basename10 } from "path";
|
|
8517
8573
|
function stripReminders(text) {
|
|
8518
8574
|
return text.replace(/<system-reminder>[\s\S]*?<\/system-reminder>/g, "").trim();
|
|
@@ -8609,13 +8665,13 @@ var init_claude2 = __esm({
|
|
|
8609
8665
|
return null;
|
|
8610
8666
|
let head;
|
|
8611
8667
|
try {
|
|
8612
|
-
const fd =
|
|
8668
|
+
const fd = openSync6(located.path, "r");
|
|
8613
8669
|
try {
|
|
8614
8670
|
const buf = Buffer.alloc(65536);
|
|
8615
|
-
const n =
|
|
8671
|
+
const n = readSync6(fd, buf, 0, buf.length, 0);
|
|
8616
8672
|
head = buf.toString("utf-8", 0, n);
|
|
8617
8673
|
} finally {
|
|
8618
|
-
|
|
8674
|
+
closeSync6(fd);
|
|
8619
8675
|
}
|
|
8620
8676
|
} catch {
|
|
8621
8677
|
return null;
|
|
@@ -8660,12 +8716,12 @@ var init_claude2 = __esm({
|
|
|
8660
8716
|
const seen = /* @__PURE__ */ new Set();
|
|
8661
8717
|
const out = [];
|
|
8662
8718
|
for (const root of claudeProjectDirs()) {
|
|
8663
|
-
this.collectSessionsFromRoot(root, cutoff, filter, seen, out);
|
|
8719
|
+
this.collectSessionsFromRoot(root, cutoff, filter, seen, out, opts.previews !== false);
|
|
8664
8720
|
}
|
|
8665
8721
|
out.sort((a, b) => b.mtime - a.mtime);
|
|
8666
8722
|
return opts.limit ? out.slice(0, opts.limit) : out;
|
|
8667
8723
|
}
|
|
8668
|
-
collectSessionsFromRoot(root, cutoff, filter, seen, out) {
|
|
8724
|
+
collectSessionsFromRoot(root, cutoff, filter, seen, out, previews) {
|
|
8669
8725
|
if (!existsSync11(root))
|
|
8670
8726
|
return;
|
|
8671
8727
|
for (const proj of readdirSync9(root, { withFileTypes: true })) {
|
|
@@ -8724,7 +8780,7 @@ var init_claude2 = __esm({
|
|
|
8724
8780
|
}
|
|
8725
8781
|
if (stat3.mtimeMs < cutoff)
|
|
8726
8782
|
continue;
|
|
8727
|
-
const firstPrompt = extractFirstUserPromptSync(fullPath, { maxLength: 200 });
|
|
8783
|
+
const firstPrompt = previews ? extractFirstUserPromptSync(fullPath, { maxLength: 200 }) : "";
|
|
8728
8784
|
out.push({
|
|
8729
8785
|
toolId: "claude",
|
|
8730
8786
|
rawId: sessionId,
|
|
@@ -9087,14 +9143,14 @@ var init_claude2 = __esm({
|
|
|
9087
9143
|
spansMultipleSources(prefixedId) {
|
|
9088
9144
|
return findSessionFiles(this.toRawId(prefixedId)).length > 1;
|
|
9089
9145
|
}
|
|
9090
|
-
async readFromOffset(prefixedId, offset) {
|
|
9146
|
+
async readFromOffset(prefixedId, offset, maxBytes) {
|
|
9091
9147
|
const rawId = this.toRawId(prefixedId);
|
|
9092
9148
|
const copies = findSessionFiles(rawId);
|
|
9093
9149
|
if (copies.length === 0)
|
|
9094
9150
|
return { text: "", newOffset: offset };
|
|
9095
9151
|
if (copies.length > 1)
|
|
9096
9152
|
return { text: "", newOffset: offset };
|
|
9097
|
-
return readTailFromOffset(copies[0].path, offset);
|
|
9153
|
+
return readTailFromOffset(copies[0].path, offset, maxBytes);
|
|
9098
9154
|
}
|
|
9099
9155
|
};
|
|
9100
9156
|
claudeBackend = new ClaudeBackend();
|
|
@@ -9322,23 +9378,8 @@ var init_opencode = __esm({
|
|
|
9322
9378
|
const projectPath = row.project_path || row.directory || "";
|
|
9323
9379
|
if (filter && !projectPath.toLowerCase().includes(filter))
|
|
9324
9380
|
continue;
|
|
9325
|
-
const
|
|
9326
|
-
|
|
9327
|
-
FROM part p JOIN message m ON m.id = p.message_id
|
|
9328
|
-
WHERE p.session_id = ?
|
|
9329
|
-
AND p.data LIKE '%"type":"text"%'
|
|
9330
|
-
AND m.data LIKE '%"role":"user"%'
|
|
9331
|
-
ORDER BY p.time_created ASC LIMIT 1
|
|
9332
|
-
`).get(row.id);
|
|
9333
|
-
let firstPrompt = "";
|
|
9334
|
-
if (firstUser) {
|
|
9335
|
-
try {
|
|
9336
|
-
const parsed = JSON.parse(firstUser.data);
|
|
9337
|
-
firstPrompt = flatString(String(parsed?.text || "").slice(0, 200));
|
|
9338
|
-
} catch {
|
|
9339
|
-
}
|
|
9340
|
-
}
|
|
9341
|
-
const messageCount = db.prepare("SELECT COUNT(*) as c FROM message WHERE session_id = ?").get(row.id)?.c ?? 0;
|
|
9381
|
+
const firstPrompt = opts.previews === false ? "" : this.firstUserPrompt(db, row.id);
|
|
9382
|
+
const messageCount = opts.previews === false ? 0 : db.prepare("SELECT COUNT(*) as c FROM message WHERE session_id = ?").get(row.id)?.c ?? 0;
|
|
9342
9383
|
const created = new Date(row.time_created || row.time_updated).toISOString();
|
|
9343
9384
|
const modified = new Date(row.time_updated).toISOString();
|
|
9344
9385
|
out.push({
|
|
@@ -9362,6 +9403,45 @@ var init_opencode = __esm({
|
|
|
9362
9403
|
db.close();
|
|
9363
9404
|
}
|
|
9364
9405
|
}
|
|
9406
|
+
/**
|
|
9407
|
+
* The first user text part of a session.
|
|
9408
|
+
*
|
|
9409
|
+
* Walks the session's user messages oldest first through the
|
|
9410
|
+
* (session_id, time_created, id) index and stops at the first one with a
|
|
9411
|
+
* text part. The role and the text are read by SQLite: a first message can
|
|
9412
|
+
* carry whole diffs in `summary` (107 MB on one row), and parsing that in JS
|
|
9413
|
+
* to read one field took the process to 510 MB. The earlier query filtered
|
|
9414
|
+
* every part and message with LIKE and then sorted, so it read all of a
|
|
9415
|
+
* session's data: 423 MB for a 236-session listing.
|
|
9416
|
+
*/
|
|
9417
|
+
firstUserPrompt(db, sessionId) {
|
|
9418
|
+
try {
|
|
9419
|
+
const has = (table, col) => !!db.prepare(`SELECT 1 FROM pragma_table_info(?) WHERE name = ?`).get(table, col);
|
|
9420
|
+
const msgOrder = has("message", "time_created") ? "time_created, id" : "rowid";
|
|
9421
|
+
const partOrder = has("part", "time_created") ? "time_created, id" : "rowid";
|
|
9422
|
+
const nextUser = db.prepare(`SELECT id, ${msgOrder === "rowid" ? "rowid AS k1, 0 AS k2" : "time_created AS k1, id AS k2"} FROM message
|
|
9423
|
+
WHERE session_id = ? AND (${msgOrder === "rowid" ? "rowid, 0" : "time_created, id"}) > (?, ?)
|
|
9424
|
+
AND json_extract(data, '$.role') = 'user'
|
|
9425
|
+
ORDER BY ${msgOrder} LIMIT 1`);
|
|
9426
|
+
const firstText = db.prepare(`SELECT substr(json_extract(data, '$.text'), 1, 400) AS text FROM part
|
|
9427
|
+
WHERE message_id = ? AND json_extract(data, '$.type') = 'text'
|
|
9428
|
+
AND length(trim(json_extract(data, '$.text'))) > 0
|
|
9429
|
+
ORDER BY ${partOrder} LIMIT 1`);
|
|
9430
|
+
let k1 = Number.MIN_SAFE_INTEGER, k2 = msgOrder === "rowid" ? 0 : "";
|
|
9431
|
+
for (; ; ) {
|
|
9432
|
+
const m = nextUser.get(sessionId, k1, k2);
|
|
9433
|
+
if (!m)
|
|
9434
|
+
return "";
|
|
9435
|
+
k1 = m.k1;
|
|
9436
|
+
k2 = m.k2;
|
|
9437
|
+
const t = firstText.get(m.id);
|
|
9438
|
+
if (t?.text)
|
|
9439
|
+
return flatString(t.text.slice(0, 200));
|
|
9440
|
+
}
|
|
9441
|
+
} catch {
|
|
9442
|
+
return "";
|
|
9443
|
+
}
|
|
9444
|
+
}
|
|
9365
9445
|
/** OpenCode auto-generates a session title (model-written, distinct from the
|
|
9366
9446
|
* first prompt — e.g. "Replacing Claude with Gemini CLI"). It's a plain
|
|
9367
9447
|
* column on the session row. */
|
|
@@ -9678,7 +9758,7 @@ var init_opencode = __esm({
|
|
|
9678
9758
|
});
|
|
9679
9759
|
|
|
9680
9760
|
// ../engine/dist/core/backends/codex.js
|
|
9681
|
-
import { readdirSync as readdirSync10, readFileSync as readFileSync8, statSync as statSync11, openSync as
|
|
9761
|
+
import { readdirSync as readdirSync10, readFileSync as readFileSync8, statSync as statSync11, openSync as openSync7, readSync as readSync7, closeSync as closeSync7 } from "fs";
|
|
9682
9762
|
import { basename as basename11, dirname as dirname12, join as join15 } from "path";
|
|
9683
9763
|
function parseShellWrites(cmd) {
|
|
9684
9764
|
const out = [];
|
|
@@ -9924,28 +10004,29 @@ var init_codex2 = __esm({
|
|
|
9924
10004
|
continue;
|
|
9925
10005
|
let firstPrompt = "";
|
|
9926
10006
|
let messageCount = 0;
|
|
9927
|
-
|
|
9928
|
-
|
|
9929
|
-
|
|
9930
|
-
|
|
9931
|
-
|
|
9932
|
-
|
|
9933
|
-
|
|
9934
|
-
|
|
9935
|
-
|
|
9936
|
-
|
|
9937
|
-
|
|
9938
|
-
|
|
9939
|
-
|
|
9940
|
-
|
|
9941
|
-
firstPrompt
|
|
10007
|
+
if (opts.previews !== false)
|
|
10008
|
+
try {
|
|
10009
|
+
const lines = readFileSync8(filePath, "utf-8").split("\n");
|
|
10010
|
+
for (const line of lines) {
|
|
10011
|
+
if (!line.trim())
|
|
10012
|
+
continue;
|
|
10013
|
+
let ev;
|
|
10014
|
+
try {
|
|
10015
|
+
ev = JSON.parse(line);
|
|
10016
|
+
} catch {
|
|
10017
|
+
continue;
|
|
10018
|
+
}
|
|
10019
|
+
if (ev.type === "event_msg" && ev.payload?.type === "user_message") {
|
|
10020
|
+
messageCount++;
|
|
10021
|
+
if (!firstPrompt && ev.payload.message) {
|
|
10022
|
+
firstPrompt = flatString(String(ev.payload.message).slice(0, 200));
|
|
10023
|
+
}
|
|
10024
|
+
} else if (ev.type === "response_item" && ev.payload?.type === "message") {
|
|
10025
|
+
messageCount++;
|
|
9942
10026
|
}
|
|
9943
|
-
} else if (ev.type === "response_item" && ev.payload?.type === "message") {
|
|
9944
|
-
messageCount++;
|
|
9945
10027
|
}
|
|
10028
|
+
} catch {
|
|
9946
10029
|
}
|
|
9947
|
-
} catch {
|
|
9948
|
-
}
|
|
9949
10030
|
const prior = seen.get(rawId);
|
|
9950
10031
|
if (prior) {
|
|
9951
10032
|
if (stat3.mtimeMs > prior.mtime) {
|
|
@@ -10352,10 +10433,10 @@ var init_codex2 = __esm({
|
|
|
10352
10433
|
if (p === loc.path)
|
|
10353
10434
|
continue;
|
|
10354
10435
|
try {
|
|
10355
|
-
const fd =
|
|
10436
|
+
const fd = openSync7(p, "r");
|
|
10356
10437
|
const buf = Buffer.alloc(4096);
|
|
10357
|
-
const n =
|
|
10358
|
-
|
|
10438
|
+
const n = readSync7(fd, buf, 0, 4096, 0);
|
|
10439
|
+
closeSync7(fd);
|
|
10359
10440
|
const head = buf.toString("utf-8", 0, n);
|
|
10360
10441
|
if (head.includes(parentId) && head.includes("parent_thread_id"))
|
|
10361
10442
|
push(p, `subagents/${f}`);
|
|
@@ -10380,11 +10461,11 @@ var init_codex2 = __esm({
|
|
|
10380
10461
|
return 0;
|
|
10381
10462
|
}
|
|
10382
10463
|
}
|
|
10383
|
-
async readFromOffset(prefixedId, offset) {
|
|
10464
|
+
async readFromOffset(prefixedId, offset, maxBytes) {
|
|
10384
10465
|
const loc = this.findSession(prefixedId);
|
|
10385
10466
|
if (!loc)
|
|
10386
10467
|
return { text: "", newOffset: offset };
|
|
10387
|
-
return readTailFromOffset(loc.path, offset);
|
|
10468
|
+
return readTailFromOffset(loc.path, offset, maxBytes);
|
|
10388
10469
|
}
|
|
10389
10470
|
};
|
|
10390
10471
|
codexBackend = new CodexBackend();
|
|
@@ -10635,21 +10716,22 @@ var init_agy = __esm({
|
|
|
10635
10716
|
const projectDir = projectPath ? basename12(projectPath) : "";
|
|
10636
10717
|
let firstPrompt = "";
|
|
10637
10718
|
let messageCount = 0;
|
|
10638
|
-
|
|
10639
|
-
|
|
10640
|
-
|
|
10641
|
-
|
|
10642
|
-
const
|
|
10643
|
-
|
|
10644
|
-
|
|
10645
|
-
|
|
10646
|
-
|
|
10647
|
-
|
|
10648
|
-
|
|
10719
|
+
if (opts.previews !== false)
|
|
10720
|
+
try {
|
|
10721
|
+
const lines = readFileSync9(filePath, "utf-8").split("\n").filter(Boolean);
|
|
10722
|
+
messageCount = lines.length;
|
|
10723
|
+
for (const line of lines) {
|
|
10724
|
+
const obj = JSON.parse(line);
|
|
10725
|
+
if (obj.source === "USER_EXPLICIT" && obj.type === "USER_INPUT" && typeof obj.content === "string") {
|
|
10726
|
+
const content = obj.content;
|
|
10727
|
+
const match = content.match(/<USER_REQUEST>([\s\S]*?)<\/USER_REQUEST>/);
|
|
10728
|
+
const rawText = match ? match[1].trim() : content.trim();
|
|
10729
|
+
firstPrompt = flatString(rawText.slice(0, 200));
|
|
10730
|
+
break;
|
|
10731
|
+
}
|
|
10649
10732
|
}
|
|
10733
|
+
} catch {
|
|
10650
10734
|
}
|
|
10651
|
-
} catch {
|
|
10652
|
-
}
|
|
10653
10735
|
if (seenIds.has(rawId))
|
|
10654
10736
|
continue;
|
|
10655
10737
|
seenIds.add(rawId);
|
|
@@ -10945,11 +11027,11 @@ var init_agy = __esm({
|
|
|
10945
11027
|
return 0;
|
|
10946
11028
|
}
|
|
10947
11029
|
}
|
|
10948
|
-
async readFromOffset(prefixedId, offset) {
|
|
11030
|
+
async readFromOffset(prefixedId, offset, maxBytes) {
|
|
10949
11031
|
const loc = this.findSession(prefixedId);
|
|
10950
11032
|
if (!loc)
|
|
10951
11033
|
return { text: "", newOffset: offset };
|
|
10952
|
-
return readTailFromOffset(loc.path, offset);
|
|
11034
|
+
return readTailFromOffset(loc.path, offset, maxBytes);
|
|
10953
11035
|
}
|
|
10954
11036
|
};
|
|
10955
11037
|
agyBackend = new AgyBackend();
|
|
@@ -13340,7 +13422,10 @@ async function resolveCodeindexBin(autoInstall = true) {
|
|
|
13340
13422
|
}
|
|
13341
13423
|
function runMcp(binPath, workspace, calls, timeoutMs = 18e4) {
|
|
13342
13424
|
return new Promise((resolve3, reject) => {
|
|
13343
|
-
const p = spawn(binPath, ["--mcp", "--workspace", workspace], {
|
|
13425
|
+
const p = spawn(binPath, ["--mcp", "--workspace", workspace], {
|
|
13426
|
+
stdio: ["pipe", "pipe", "ignore"],
|
|
13427
|
+
env: { ...process.env, CODEINDEX_NO_DAEMON: "1" }
|
|
13428
|
+
});
|
|
13344
13429
|
const res = /* @__PURE__ */ new Map();
|
|
13345
13430
|
let pending2 = "";
|
|
13346
13431
|
let failed = false;
|
|
@@ -14715,10 +14800,10 @@ var init_types = __esm({
|
|
|
14715
14800
|
|
|
14716
14801
|
// ../engine/dist/transcript/opencode.js
|
|
14717
14802
|
async function parseOpenCodeSubagents(parentSessionId) {
|
|
14718
|
-
const { existsSync:
|
|
14803
|
+
const { existsSync: existsSync48 } = await import("fs");
|
|
14719
14804
|
const { opencodeBackend: opencodeBackend2 } = await Promise.resolve().then(() => (init_opencode(), opencode_exports));
|
|
14720
14805
|
const dbPath = opencodeBackend2.dbPath();
|
|
14721
|
-
if (!
|
|
14806
|
+
if (!existsSync48(dbPath))
|
|
14722
14807
|
return [];
|
|
14723
14808
|
const realParent = parentSessionId.replace("opencode_", "");
|
|
14724
14809
|
const db = openSqliteReadonlyOrThrow(dbPath);
|
|
@@ -14759,10 +14844,10 @@ async function parseOpenCodeSubagents(parentSessionId) {
|
|
|
14759
14844
|
}
|
|
14760
14845
|
}
|
|
14761
14846
|
async function parseOpenCodeTranscript(sessionId) {
|
|
14762
|
-
const { existsSync:
|
|
14847
|
+
const { existsSync: existsSync48 } = await import("fs");
|
|
14763
14848
|
const { opencodeBackend: opencodeBackend2 } = await Promise.resolve().then(() => (init_opencode(), opencode_exports));
|
|
14764
14849
|
const dbPath = opencodeBackend2.dbPath();
|
|
14765
|
-
if (!
|
|
14850
|
+
if (!existsSync48(dbPath))
|
|
14766
14851
|
return [];
|
|
14767
14852
|
const realId = sessionId.replace("opencode_", "");
|
|
14768
14853
|
const db = openSqliteReadonlyOrThrow(dbPath);
|
|
@@ -19248,7 +19333,7 @@ var init_sync_fields = __esm({
|
|
|
19248
19333
|
});
|
|
19249
19334
|
|
|
19250
19335
|
// ../engine/dist/core/index-lock.js
|
|
19251
|
-
import { existsSync as existsSync43, mkdirSync as mkdirSync11, openSync as
|
|
19336
|
+
import { existsSync as existsSync43, mkdirSync as mkdirSync11, openSync as openSync9, writeFileSync as writeFileSync8, readFileSync as readFileSync35, rmSync as rmSync4, statSync as statSync40, closeSync as closeSync9 } from "fs";
|
|
19252
19337
|
import { dirname as dirname18, join as join47 } from "path";
|
|
19253
19338
|
function acquireIndexLock(opts) {
|
|
19254
19339
|
const lockPath = lockFilePath();
|
|
@@ -19260,7 +19345,7 @@ function acquireIndexLock(opts) {
|
|
|
19260
19345
|
}
|
|
19261
19346
|
let fd;
|
|
19262
19347
|
try {
|
|
19263
|
-
fd =
|
|
19348
|
+
fd = openSync9(lockPath, "wx", 384);
|
|
19264
19349
|
} catch (err) {
|
|
19265
19350
|
if (err.code === "EEXIST")
|
|
19266
19351
|
return null;
|
|
@@ -19270,7 +19355,7 @@ function acquireIndexLock(opts) {
|
|
|
19270
19355
|
try {
|
|
19271
19356
|
writeFileSync8(fd, JSON.stringify(holder));
|
|
19272
19357
|
} finally {
|
|
19273
|
-
|
|
19358
|
+
closeSync9(fd);
|
|
19274
19359
|
}
|
|
19275
19360
|
let released = false;
|
|
19276
19361
|
return {
|
|
@@ -19364,7 +19449,7 @@ var init_http = __esm({
|
|
|
19364
19449
|
CLI_VERSION = JSON.parse(readFileSync36(new URL("../package.json", import.meta.url), "utf-8")).version || CLI_VERSION;
|
|
19365
19450
|
} catch {
|
|
19366
19451
|
try {
|
|
19367
|
-
if (true) CLI_VERSION = "0.7.
|
|
19452
|
+
if (true) CLI_VERSION = "0.7.2";
|
|
19368
19453
|
} catch {
|
|
19369
19454
|
}
|
|
19370
19455
|
}
|
|
@@ -19863,7 +19948,7 @@ var init_telemetry = __esm({
|
|
|
19863
19948
|
init_sync_client();
|
|
19864
19949
|
cliVersion2 = () => {
|
|
19865
19950
|
try {
|
|
19866
|
-
return true ? "0.7.
|
|
19951
|
+
return true ? "0.7.2" : "dev";
|
|
19867
19952
|
} catch {
|
|
19868
19953
|
return "dev";
|
|
19869
19954
|
}
|
|
@@ -20023,16 +20108,17 @@ __export(auto_update_exports, {
|
|
|
20023
20108
|
noteUpdateFailure: () => noteUpdateFailure,
|
|
20024
20109
|
planAutoUpdate: () => planAutoUpdate,
|
|
20025
20110
|
readUpdateState: () => readUpdateState,
|
|
20111
|
+
resolveNpm: () => resolveNpm,
|
|
20026
20112
|
runAutoUpdate: () => runAutoUpdate,
|
|
20027
20113
|
runningPrefix: () => runningPrefix,
|
|
20028
20114
|
sweepStaleStaging: () => sweepStaleStaging,
|
|
20029
20115
|
tarballUrl: () => tarballUrl
|
|
20030
20116
|
});
|
|
20031
|
-
import { execSync as execSync4 } from "node:child_process";
|
|
20032
|
-
import { readFileSync as readFileSync37 } from "node:fs";
|
|
20117
|
+
import { execSync as execSync4, execFileSync as execFileSync3 } from "node:child_process";
|
|
20118
|
+
import { readFileSync as readFileSync37, existsSync as existsSync45 } from "node:fs";
|
|
20033
20119
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
20034
20120
|
import { writeFileSync as writeFileSync9, mkdtempSync as mkdtempSync3, rmSync as rmSync5, readdirSync as readdirSync32, statSync as statSync41, mkdirSync as mkdirSync12 } from "node:fs";
|
|
20035
|
-
import { join as join48, dirname as dirname19, sep as sep3 } from "node:path";
|
|
20121
|
+
import { join as join48, dirname as dirname19, sep as sep3, posix as pathPosix, win32 as pathWin32 } from "node:path";
|
|
20036
20122
|
import { tmpdir as tmpdir3, homedir as homedir17 } from "node:os";
|
|
20037
20123
|
import { createHash as createHash10 } from "node:crypto";
|
|
20038
20124
|
function compareVersions(a, b) {
|
|
@@ -20147,25 +20233,46 @@ function runningPrefix(moduleUrl = import.meta.url) {
|
|
|
20147
20233
|
const beforeNodeModules = here.slice(0, at);
|
|
20148
20234
|
return beforeNodeModules.endsWith(`${sep3}lib`) ? beforeNodeModules.slice(0, -4) : beforeNodeModules;
|
|
20149
20235
|
}
|
|
20150
|
-
function npmInstall(
|
|
20236
|
+
function npmInstall(npm, args) {
|
|
20151
20237
|
try {
|
|
20152
|
-
|
|
20238
|
+
const opts = { stdio: ["ignore", "ignore", "pipe"], env: npm.env };
|
|
20239
|
+
if (npm.shell) execSync4([npm.file, ...npm.args, ...args].map((a) => `"${a}"`).join(" "), opts);
|
|
20240
|
+
else execFileSync3(npm.file, [...npm.args, ...args], opts);
|
|
20153
20241
|
} catch (e) {
|
|
20154
20242
|
const err = e;
|
|
20155
20243
|
const detail = String(err.stderr ?? "").trim().split("\n").filter((l) => l.trim() && !/^npm (notice|warn)\b/i.test(l)).slice(-4).join(" | ").slice(0, 400);
|
|
20156
20244
|
throw new Error(detail ? `${err.message ?? "install failed"} \u2014 ${detail}` : err.message ?? "install failed");
|
|
20157
20245
|
}
|
|
20158
20246
|
}
|
|
20247
|
+
function resolveNpm(loc) {
|
|
20248
|
+
const win = loc.platform === "win32";
|
|
20249
|
+
const p = win ? pathWin32 : pathPosix;
|
|
20250
|
+
const nodeDir = p.dirname(loc.execPath);
|
|
20251
|
+
const cliUnder = (root) => win ? p.join(root, "node_modules", "npm", "bin", "npm-cli.js") : p.join(root, "lib", "node_modules", "npm", "bin", "npm-cli.js");
|
|
20252
|
+
const candidates2 = [
|
|
20253
|
+
cliUnder(win ? nodeDir : p.join(nodeDir, "..")),
|
|
20254
|
+
...loc.prefix ? [cliUnder(loc.prefix)] : []
|
|
20255
|
+
];
|
|
20256
|
+
const pathKey = Object.keys(loc.env).find((k) => k.toUpperCase() === "PATH") ?? "PATH";
|
|
20257
|
+
const binDirs = [nodeDir, ...loc.prefix ? [win ? loc.prefix : p.join(loc.prefix, "bin")] : []];
|
|
20258
|
+
const delimiter3 = win ? ";" : ":";
|
|
20259
|
+
const current = loc.env[pathKey] ?? "";
|
|
20260
|
+
const env = { ...loc.env, [pathKey]: [...binDirs, ...current ? [current] : []].join(delimiter3) };
|
|
20261
|
+
const cli = candidates2.find((c) => loc.exists(c));
|
|
20262
|
+
if (cli) return { file: loc.execPath, args: [cli], env, shell: false };
|
|
20263
|
+
return { file: "npm", args: [], env, shell: true };
|
|
20264
|
+
}
|
|
20159
20265
|
function realInstall(tgz) {
|
|
20160
20266
|
const prefix = runningPrefix();
|
|
20267
|
+
const npm = resolveNpm({ execPath: process.execPath, prefix, platform: process.platform, env: process.env, exists: existsSync45 });
|
|
20161
20268
|
if (prefix) {
|
|
20162
|
-
npmInstall(
|
|
20269
|
+
npmInstall(npm, ["install", "-g", "--prefix", prefix, tgz]);
|
|
20163
20270
|
return;
|
|
20164
20271
|
}
|
|
20165
20272
|
try {
|
|
20166
|
-
npmInstall(
|
|
20273
|
+
npmInstall(npm, ["install", "-g", tgz]);
|
|
20167
20274
|
} catch {
|
|
20168
|
-
npmInstall(
|
|
20275
|
+
npmInstall(npm, ["install", "-g", "--prefix", join48(homedir17(), ".local"), tgz]);
|
|
20169
20276
|
}
|
|
20170
20277
|
}
|
|
20171
20278
|
function realRestart(platform2) {
|
|
@@ -20266,7 +20373,9 @@ var init_auto_update = __esm({
|
|
|
20266
20373
|
var sync_client_exports = {};
|
|
20267
20374
|
__export(sync_client_exports, {
|
|
20268
20375
|
BATCH_SCAN_MAX_BYTES: () => BATCH_SCAN_MAX_BYTES,
|
|
20376
|
+
DERIVED_MAX_BYTES: () => DERIVED_MAX_BYTES,
|
|
20269
20377
|
FULL_BUILD_MAX_BYTES: () => FULL_BUILD_MAX_BYTES,
|
|
20378
|
+
SYNC_CHUNK_BYTES: () => SYNC_CHUNK_BYTES,
|
|
20270
20379
|
_acceptedUploads: () => _acceptedUploads,
|
|
20271
20380
|
_fetchTenantSyncConfigForTests: () => _fetchTenantSyncConfigForTests,
|
|
20272
20381
|
_resetCapsCacheForTests: () => _resetCapsCacheForTests,
|
|
@@ -20285,8 +20394,10 @@ __export(sync_client_exports, {
|
|
|
20285
20394
|
reconcileFields: () => reconcileFields,
|
|
20286
20395
|
removeCredentials: () => removeCredentials,
|
|
20287
20396
|
saveCredentials: () => saveCredentials,
|
|
20397
|
+
scanTranscriptFindingsChunked: () => scanTranscriptFindingsChunked,
|
|
20288
20398
|
serverAcceptsGzipBody: () => serverAcceptsGzipBody,
|
|
20289
20399
|
serverIngestConcurrency: () => serverIngestConcurrency,
|
|
20400
|
+
sessionBuildBytes: () => sessionBuildBytes,
|
|
20290
20401
|
sessionFileBytes: () => sessionFileBytes,
|
|
20291
20402
|
setServerIngestConcurrency: () => setServerIngestConcurrency,
|
|
20292
20403
|
syncIncremental: () => syncIncremental,
|
|
@@ -20676,7 +20787,7 @@ async function reconcileFields(opts = {}) {
|
|
|
20676
20787
|
let cached = null;
|
|
20677
20788
|
const fullRefs = () => cached ??= listAvailableBackends().flatMap((b) => {
|
|
20678
20789
|
try {
|
|
20679
|
-
return b.listSessions({});
|
|
20790
|
+
return b.listSessions({ previews: false });
|
|
20680
20791
|
} catch {
|
|
20681
20792
|
return [];
|
|
20682
20793
|
}
|
|
@@ -20839,7 +20950,7 @@ async function syncToTargetInScope(cred, opts = {}) {
|
|
|
20839
20950
|
const listSinceMs = changedWalk ? Math.max(0, (opts.sinceMs ?? 0) - CHANGED_WALK_OVERLAP_MS) : opts.useLedger ? void 0 : opts.sinceMs;
|
|
20840
20951
|
const refs = listAvailableBackends().flatMap((b) => {
|
|
20841
20952
|
try {
|
|
20842
|
-
return b.listSessions({ sinceMs: listSinceMs });
|
|
20953
|
+
return b.listSessions({ sinceMs: listSinceMs, previews: false });
|
|
20843
20954
|
} catch {
|
|
20844
20955
|
return [];
|
|
20845
20956
|
}
|
|
@@ -21058,16 +21169,53 @@ async function syncToTargetInScope(cred, opts = {}) {
|
|
|
21058
21169
|
clearTimeout(timer);
|
|
21059
21170
|
}
|
|
21060
21171
|
};
|
|
21172
|
+
const postChunkedHead = async (conv, fileSize) => {
|
|
21173
|
+
const headers = { "content-type": "application/json" };
|
|
21174
|
+
if (cred.token) headers.authorization = `Bearer ${cred.token}`;
|
|
21175
|
+
const ac = new AbortController();
|
|
21176
|
+
const timer = setTimeout(() => ac.abort(new Error(`upload timed out after ${UPLOAD_TIMEOUT_MS}ms`)), UPLOAD_TIMEOUT_MS);
|
|
21177
|
+
try {
|
|
21178
|
+
const res = await fetch(`${base}/api/sync`, {
|
|
21179
|
+
method: "POST",
|
|
21180
|
+
headers,
|
|
21181
|
+
body: JSON.stringify({ conversations: [conv] }),
|
|
21182
|
+
signal: ac.signal
|
|
21183
|
+
});
|
|
21184
|
+
if (!res.ok) throw new Error(`chunked head sync failed: HTTP ${res.status} ${await res.text().catch(() => "")}`);
|
|
21185
|
+
const body = await res.json().catch(() => ({}));
|
|
21186
|
+
const id = conv.session_id;
|
|
21187
|
+
const guarded = (body.shrink_guarded ?? []).find((g) => g.session_id === id);
|
|
21188
|
+
const mtime = conv.mtime;
|
|
21189
|
+
try {
|
|
21190
|
+
if (!guarded) {
|
|
21191
|
+
markSynced(base, [{ id, mtime, offset: conv.from_offset, size: conv.from_offset, acked: true }]);
|
|
21192
|
+
} else if (typeof guarded.o === "number" && guarded.o > 0 && guarded.o <= fileSize) {
|
|
21193
|
+
markSynced(base, [{ id, mtime, offset: guarded.o, size: guarded.o, acked: true }]);
|
|
21194
|
+
} else {
|
|
21195
|
+
markSynced(base, [{ id, mtime }]);
|
|
21196
|
+
}
|
|
21197
|
+
} catch {
|
|
21198
|
+
}
|
|
21199
|
+
appendResults.uploaded++;
|
|
21200
|
+
} finally {
|
|
21201
|
+
clearTimeout(timer);
|
|
21202
|
+
}
|
|
21203
|
+
};
|
|
21061
21204
|
let appendBatch = [];
|
|
21205
|
+
let appendBatchBytes = 0;
|
|
21062
21206
|
const APPEND_BATCH_SIZE = 50;
|
|
21207
|
+
const APPEND_BATCH_MAX_BYTES = SYNC_CHUNK_BYTES;
|
|
21063
21208
|
const appendFlush = async () => {
|
|
21064
21209
|
if (appendBatch.length === 0) return;
|
|
21065
21210
|
const batch = appendBatch;
|
|
21066
21211
|
appendBatch = [];
|
|
21212
|
+
appendBatchBytes = 0;
|
|
21067
21213
|
await postAppend(batch);
|
|
21068
21214
|
};
|
|
21069
|
-
const appendAdd = async (conv) => {
|
|
21215
|
+
const appendAdd = async (conv, bytes = 0) => {
|
|
21216
|
+
if (appendBatch.length > 0 && appendBatchBytes + bytes > APPEND_BATCH_MAX_BYTES) await appendFlush();
|
|
21070
21217
|
appendBatch.push(conv);
|
|
21218
|
+
appendBatchBytes += bytes;
|
|
21071
21219
|
if (appendBatch.length >= APPEND_BATCH_SIZE) await appendFlush();
|
|
21072
21220
|
};
|
|
21073
21221
|
const itemsBatch = makeBatcher("items", 100);
|
|
@@ -21117,7 +21265,7 @@ async function syncToTargetInScope(cred, opts = {}) {
|
|
|
21117
21265
|
cred,
|
|
21118
21266
|
() => listAvailableBackends().flatMap((b) => {
|
|
21119
21267
|
try {
|
|
21120
|
-
return b.listSessions({});
|
|
21268
|
+
return b.listSessions({ previews: false });
|
|
21121
21269
|
} catch {
|
|
21122
21270
|
return [];
|
|
21123
21271
|
}
|
|
@@ -21146,7 +21294,7 @@ async function syncToTargetInScope(cred, opts = {}) {
|
|
|
21146
21294
|
let externalFindings = null;
|
|
21147
21295
|
if (scanSecrets) {
|
|
21148
21296
|
externalFindings = /* @__PURE__ */ new Map();
|
|
21149
|
-
const toScan = slice.filter((ref) => modeOf(ref) === "full" &&
|
|
21297
|
+
const toScan = slice.filter((ref) => modeOf(ref) === "full" && sessionBuildBytes(ref) <= FULL_BUILD_MAX_BYTES);
|
|
21150
21298
|
trace?.(`batch secret scan start: ${toScan.length} session(s), \u2264${(BATCH_SCAN_MAX_BYTES / 1048576).toFixed(0)}MB per slice`);
|
|
21151
21299
|
const batch = batchScanExternal(
|
|
21152
21300
|
toScan.map((ref) => ({
|
|
@@ -21190,11 +21338,17 @@ async function syncToTargetInScope(cred, opts = {}) {
|
|
|
21190
21338
|
const ack = ledger.get(ref.prefixedId);
|
|
21191
21339
|
try {
|
|
21192
21340
|
const tail = await buildConversationTail(ref, ack.o ?? 0, { mapPath, tenantRules });
|
|
21193
|
-
|
|
21341
|
+
const chunked = sessionBuildBytes(ref) > FULL_BUILD_MAX_BYTES;
|
|
21342
|
+
if (tail && tail.findings.length > 0 && !chunked) {
|
|
21194
21343
|
promotedForFindings = true;
|
|
21195
21344
|
} else if (tail) {
|
|
21196
21345
|
redactions += tail.redactions;
|
|
21197
|
-
await appendAdd(tail.conv);
|
|
21346
|
+
await appendAdd(tail.conv, tail.bytes);
|
|
21347
|
+
if (tail.findings.length > 0) {
|
|
21348
|
+
await appendFlush();
|
|
21349
|
+
const all = await scanTranscriptFindingsChunked(ref, tenantRules);
|
|
21350
|
+
if (all.length > 0) await findingsBatch.add(all);
|
|
21351
|
+
}
|
|
21198
21352
|
walked++;
|
|
21199
21353
|
if (walked % 250 === 0) console.error(`[sync] ${walked}/${slice.length} sessions\u2026`);
|
|
21200
21354
|
continue;
|
|
@@ -21242,12 +21396,13 @@ async function syncToTargetInScope(cred, opts = {}) {
|
|
|
21242
21396
|
}
|
|
21243
21397
|
redactions += built.redactions;
|
|
21244
21398
|
if (built.srcHash) shippedHash.set(ref.prefixedId, built.srcHash);
|
|
21245
|
-
await
|
|
21399
|
+
if (built.conv.chunked) await postChunkedHead(built.conv, sessionFileBytes(ref));
|
|
21400
|
+
else await convBatch.add(built.conv);
|
|
21246
21401
|
if (built.findings.length > 0) await findingsBatch.add(built.findings);
|
|
21247
21402
|
if (upload.sessionMeta) {
|
|
21248
21403
|
await accumulateKg(extractEntities(built.kgText, { projectPath: built.projectPath, sourceType: "session", sessionId: ref.prefixedId, validFrom: ref.mtime ? new Date(ref.mtime).toISOString().slice(0, 10) : void 0 }), ref.prefixedId);
|
|
21249
21404
|
}
|
|
21250
|
-
if (upload.sessionMeta && sessionFileBytes(ref) <=
|
|
21405
|
+
if (upload.sessionMeta && sessionFileBytes(ref) <= DERIVED_MAX_BYTES) {
|
|
21251
21406
|
try {
|
|
21252
21407
|
const count = { redactions: 0 };
|
|
21253
21408
|
const offloaded = await scanPool().runDerived({
|
|
@@ -21427,35 +21582,70 @@ async function syncToTargetInScope(cred, opts = {}) {
|
|
|
21427
21582
|
scanMs: Math.round(scanMs)
|
|
21428
21583
|
};
|
|
21429
21584
|
}
|
|
21585
|
+
async function scanTranscriptFindingsChunked(ref, tenantRules) {
|
|
21586
|
+
const backend = getBackendForId(ref.prefixedId);
|
|
21587
|
+
const findings = [];
|
|
21588
|
+
if (!backend?.readFromOffset) return findings;
|
|
21589
|
+
let offset = 0;
|
|
21590
|
+
let lineBase = 0;
|
|
21591
|
+
for (; ; ) {
|
|
21592
|
+
const chunk = await backend.readFromOffset(ref.prefixedId, offset, SYNC_CHUNK_BYTES);
|
|
21593
|
+
if (chunk.newOffset <= offset) break;
|
|
21594
|
+
offset = chunk.newOffset;
|
|
21595
|
+
if (chunk.skippedBytes) {
|
|
21596
|
+
lineBase += 1;
|
|
21597
|
+
continue;
|
|
21598
|
+
}
|
|
21599
|
+
try {
|
|
21600
|
+
for (const f of scanTextForFindings(chunk.text)) {
|
|
21601
|
+
findings.push({ session_id: ref.prefixedId, detector: "builtin", rule: f.rule, line: lineBase + f.line, preview: f.preview });
|
|
21602
|
+
}
|
|
21603
|
+
if (tenantRules && tenantRules.length > 0) {
|
|
21604
|
+
for (const f of scanTenantRules(chunk.text, tenantRules)) {
|
|
21605
|
+
findings.push({ session_id: ref.prefixedId, detector: f.detector, rule: f.rule, line: lineBase + f.line, preview: f.preview });
|
|
21606
|
+
}
|
|
21607
|
+
}
|
|
21608
|
+
} catch {
|
|
21609
|
+
}
|
|
21610
|
+
for (let i = chunk.text.indexOf("\n"); i >= 0; i = chunk.text.indexOf("\n", i + 1)) lineBase++;
|
|
21611
|
+
}
|
|
21612
|
+
return dropFuzzyFindings(findings, (f) => ({ detector: f.detector, rule: f.rule }));
|
|
21613
|
+
}
|
|
21430
21614
|
function sessionFileBytes(ref) {
|
|
21431
21615
|
const backend = getBackendForId(ref.prefixedId);
|
|
21432
21616
|
if (!backend?.isAppendOnly?.()) return 0;
|
|
21433
21617
|
return backend.fileSize?.(ref.prefixedId) ?? 0;
|
|
21434
21618
|
}
|
|
21619
|
+
function sessionBuildBytes(ref) {
|
|
21620
|
+
const main2 = sessionFileBytes(ref);
|
|
21621
|
+
if (main2 === 0) return 0;
|
|
21622
|
+
return Math.max(main2, shadowUncompressedBytes(ref.toolId, ref.rawId));
|
|
21623
|
+
}
|
|
21435
21624
|
async function buildConversationSync(ref, mtime, opts = {}) {
|
|
21436
21625
|
const mapPath = opts.mapPath ?? ((p) => p);
|
|
21437
21626
|
let includeRaw = opts.includeRaw !== false;
|
|
21438
21627
|
let includeMeta = opts.includeMeta !== false;
|
|
21439
21628
|
let srcHash;
|
|
21440
|
-
const
|
|
21441
|
-
|
|
21442
|
-
let
|
|
21443
|
-
let tailOffsetEnd = 0;
|
|
21629
|
+
const oversized = sessionBuildBytes(ref) > FULL_BUILD_MAX_BYTES;
|
|
21630
|
+
let headOffsetEnd = 0;
|
|
21631
|
+
let oversizedFindings = null;
|
|
21444
21632
|
let transcript = null;
|
|
21445
21633
|
let fullestContainer = null;
|
|
21446
21634
|
if (oversized) {
|
|
21447
21635
|
const backend = getBackendForId(ref.prefixedId);
|
|
21448
21636
|
if (!backend?.readFromOffset) return null;
|
|
21449
21637
|
try {
|
|
21450
|
-
|
|
21451
|
-
|
|
21452
|
-
|
|
21453
|
-
|
|
21638
|
+
let head = await backend.readFromOffset(ref.prefixedId, 0, SYNC_CHUNK_BYTES);
|
|
21639
|
+
for (let hops = 0; head.skippedBytes && hops < 8; hops++) {
|
|
21640
|
+
head = await backend.readFromOffset(ref.prefixedId, head.newOffset, SYNC_CHUNK_BYTES);
|
|
21641
|
+
}
|
|
21642
|
+
headOffsetEnd = head.newOffset;
|
|
21643
|
+
const messages = parseTailMessages(ref.toolId, head.text);
|
|
21454
21644
|
if (messages.length > 0) transcript = { messages, subagents: [] };
|
|
21455
21645
|
} catch {
|
|
21456
21646
|
}
|
|
21457
21647
|
includeRaw = false;
|
|
21458
|
-
|
|
21648
|
+
oversizedFindings = await scanTranscriptFindingsChunked(ref, opts.tenantRules);
|
|
21459
21649
|
} else {
|
|
21460
21650
|
try {
|
|
21461
21651
|
const backend = getBackendForId(ref.prefixedId) ?? getBackend("claude");
|
|
@@ -21516,7 +21706,6 @@ async function buildConversationSync(ref, mtime, opts = {}) {
|
|
|
21516
21706
|
);
|
|
21517
21707
|
let projectPath = ref.projectPath;
|
|
21518
21708
|
const meta = { messageCount: textMessages.length };
|
|
21519
|
-
if (oversized) meta.truncated = true;
|
|
21520
21709
|
if (textMessages.filter((m) => m.role === "user").length <= 1) meta.oneShot = true;
|
|
21521
21710
|
try {
|
|
21522
21711
|
const cwd = ref.fullPath ? readCwdFromJsonl(ref.fullPath) : "";
|
|
@@ -21630,21 +21819,7 @@ async function buildConversationSync(ref, mtime, opts = {}) {
|
|
|
21630
21819
|
}
|
|
21631
21820
|
scanMs = performance.now() - t0;
|
|
21632
21821
|
}
|
|
21633
|
-
if (
|
|
21634
|
-
const t0 = performance.now();
|
|
21635
|
-
try {
|
|
21636
|
-
for (const f of scanTextForFindings(tailText)) {
|
|
21637
|
-
findings.push({ session_id: ref.prefixedId, detector: "builtin", rule: f.rule, line: f.line, preview: f.preview });
|
|
21638
|
-
}
|
|
21639
|
-
if (opts.tenantRules && opts.tenantRules.length > 0) {
|
|
21640
|
-
for (const f of scanTenantRules(tailText, opts.tenantRules)) {
|
|
21641
|
-
findings.push({ session_id: ref.prefixedId, detector: f.detector, rule: f.rule, line: f.line, preview: f.preview });
|
|
21642
|
-
}
|
|
21643
|
-
}
|
|
21644
|
-
} catch {
|
|
21645
|
-
}
|
|
21646
|
-
scanMs = performance.now() - t0;
|
|
21647
|
-
}
|
|
21822
|
+
if (oversizedFindings) findings.push(...oversizedFindings);
|
|
21648
21823
|
const keptFindings = dropFuzzyFindings(findings, (f) => ({ detector: f.detector, rule: f.rule }));
|
|
21649
21824
|
findings.length = 0;
|
|
21650
21825
|
findings.push(...keptFindings);
|
|
@@ -21690,7 +21865,10 @@ async function buildConversationSync(ref, mtime, opts = {}) {
|
|
|
21690
21865
|
// (convBatch onFlush uses this, not a recomputed size) so the next
|
|
21691
21866
|
// append's base_offset matches exactly — no spurious continuity misses on
|
|
21692
21867
|
// an actively-growing session. undefined for non-append-only backends.
|
|
21693
|
-
from_offset: oversized ?
|
|
21868
|
+
from_offset: oversized ? headOffsetEnd : getBackendForId(ref.prefixedId)?.fileSize?.(ref.rawId),
|
|
21869
|
+
// The head of a chunked session. The server reports back when its shrink
|
|
21870
|
+
// guard keeps a fuller stored copy, so the client appends from there.
|
|
21871
|
+
...oversized ? { chunked: true } : {},
|
|
21694
21872
|
meta,
|
|
21695
21873
|
mtime
|
|
21696
21874
|
},
|
|
@@ -21712,12 +21890,30 @@ async function buildConversationTail(ref, fromOffset, opts = {}) {
|
|
|
21712
21890
|
const backend = getBackendForId(ref.prefixedId);
|
|
21713
21891
|
if (!backend?.readFromOffset) return null;
|
|
21714
21892
|
const mapPath = opts.mapPath ?? ((p) => p);
|
|
21715
|
-
const { text: tailText, newOffset } = await backend.readFromOffset(ref.prefixedId, fromOffset);
|
|
21716
|
-
if (
|
|
21717
|
-
const messages = parseTailMessages(ref.toolId, tailText);
|
|
21718
|
-
if (messages.length === 0) return null;
|
|
21893
|
+
const { text: tailText, newOffset, skippedBytes } = await backend.readFromOffset(ref.prefixedId, fromOffset, SYNC_CHUNK_BYTES);
|
|
21894
|
+
if (newOffset <= fromOffset) return null;
|
|
21895
|
+
const messages = skippedBytes ? [] : parseTailMessages(ref.toolId, tailText);
|
|
21719
21896
|
const textMessages = messages.filter((m) => m.role !== "summary" && m.content?.trim());
|
|
21720
|
-
|
|
21897
|
+
const hasContent = textMessages.length > 0 || messages.some((m) => m.toolCalls?.length);
|
|
21898
|
+
if (!hasContent) {
|
|
21899
|
+
return {
|
|
21900
|
+
conv: {
|
|
21901
|
+
session_id: ref.prefixedId,
|
|
21902
|
+
tool: ref.toolId,
|
|
21903
|
+
project_path: mapPath(ref.projectPath),
|
|
21904
|
+
append: true,
|
|
21905
|
+
base_offset: fromOffset,
|
|
21906
|
+
from_offset: newOffset,
|
|
21907
|
+
redacted_text: "",
|
|
21908
|
+
envelope: { v: TRANSCRIPT_VERSION, messages: [], subagents: [] },
|
|
21909
|
+
mtime: Math.floor(ref.mtime) || 0
|
|
21910
|
+
},
|
|
21911
|
+
redactions: 0,
|
|
21912
|
+
newOffset,
|
|
21913
|
+
findings: [],
|
|
21914
|
+
bytes: 0
|
|
21915
|
+
};
|
|
21916
|
+
}
|
|
21721
21917
|
const firstUser = textMessages.find((m) => m.role === "user");
|
|
21722
21918
|
if (isInternalToolPrompt(firstUser?.content)) return null;
|
|
21723
21919
|
const count = { redactions: 0 };
|
|
@@ -21760,7 +21956,8 @@ async function buildConversationTail(ref, fromOffset, opts = {}) {
|
|
|
21760
21956
|
},
|
|
21761
21957
|
redactions: count.redactions,
|
|
21762
21958
|
newOffset,
|
|
21763
|
-
findings
|
|
21959
|
+
findings,
|
|
21960
|
+
bytes: Buffer.byteLength(tailText)
|
|
21764
21961
|
};
|
|
21765
21962
|
}
|
|
21766
21963
|
function isSyncSkip(r) {
|
|
@@ -21790,7 +21987,7 @@ async function syncIncremental(opts = {}) {
|
|
|
21790
21987
|
lock.release();
|
|
21791
21988
|
}
|
|
21792
21989
|
}
|
|
21793
|
-
var ownVersion, ERROR_CLASS, STANDING_FAILURE, walkProgressForUpload, lastServerPackSpec, credPath, _securityConfigCache, SECURITY_CONFIG_TTL_MS, _syncConfigCache, EMPTY_SYNC_CONFIG, _fetchTenantSyncConfigForTests, SYNC_RULES_REFUSAL, hashPath, acceptedUploads, MAX_DERIVED_ROW_BYTES, isLocalHost, CAPS_TTL_MS, capsOk, advertisedIngestConc, advertisedGzipBody, FULL_BUILD_MAX_BYTES,
|
|
21990
|
+
var ownVersion, ERROR_CLASS, STANDING_FAILURE, walkProgressForUpload, lastServerPackSpec, credPath, _securityConfigCache, SECURITY_CONFIG_TTL_MS, _syncConfigCache, EMPTY_SYNC_CONFIG, _fetchTenantSyncConfigForTests, SYNC_RULES_REFUSAL, hashPath, acceptedUploads, MAX_DERIVED_ROW_BYTES, isLocalHost, CAPS_TTL_MS, capsOk, advertisedIngestConc, advertisedGzipBody, FULL_BUILD_MAX_BYTES, DERIVED_MAX_BYTES, SYNC_CHUNK_BYTES, BATCH_SCAN_MAX_BYTES;
|
|
21794
21991
|
var init_sync_client = __esm({
|
|
21795
21992
|
"src/sync-client.ts"() {
|
|
21796
21993
|
"use strict";
|
|
@@ -21835,7 +22032,7 @@ var init_sync_client = __esm({
|
|
|
21835
22032
|
ownVersion = JSON.parse(readFileSync38(join49(dir, "../package.json"), "utf-8")).version || ownVersion;
|
|
21836
22033
|
} catch {
|
|
21837
22034
|
try {
|
|
21838
|
-
if (true) ownVersion = "0.7.
|
|
22035
|
+
if (true) ownVersion = "0.7.2";
|
|
21839
22036
|
} catch {
|
|
21840
22037
|
}
|
|
21841
22038
|
}
|
|
@@ -21862,8 +22059,9 @@ var init_sync_client = __esm({
|
|
|
21862
22059
|
capsOk = /* @__PURE__ */ new Map();
|
|
21863
22060
|
advertisedIngestConc = /* @__PURE__ */ new Map();
|
|
21864
22061
|
advertisedGzipBody = /* @__PURE__ */ new Set();
|
|
21865
|
-
FULL_BUILD_MAX_BYTES = Math.max(8, parseInt(process.env.CHAT_RECALL_FULL_BUILD_MAX_MB || "
|
|
21866
|
-
|
|
22062
|
+
FULL_BUILD_MAX_BYTES = Math.max(8, parseInt(process.env.CHAT_RECALL_FULL_BUILD_MAX_MB || "24", 10)) * 1024 * 1024;
|
|
22063
|
+
DERIVED_MAX_BYTES = Math.max(8, parseInt(process.env.CHAT_RECALL_DERIVED_MAX_MB || "64", 10)) * 1024 * 1024;
|
|
22064
|
+
SYNC_CHUNK_BYTES = 8 * 1024 * 1024;
|
|
21867
22065
|
BATCH_SCAN_MAX_BYTES = Math.max(1, parseInt(process.env.CHAT_RECALL_BATCHSCAN_MAX_MB || "64", 10)) * 1024 * 1024;
|
|
21868
22066
|
}
|
|
21869
22067
|
});
|
|
@@ -22048,306 +22246,101 @@ var init_update_notice = __esm({
|
|
|
22048
22246
|
}
|
|
22049
22247
|
});
|
|
22050
22248
|
|
|
22051
|
-
// src/
|
|
22052
|
-
var
|
|
22053
|
-
__export(
|
|
22054
|
-
|
|
22055
|
-
|
|
22056
|
-
|
|
22057
|
-
|
|
22058
|
-
|
|
22059
|
-
|
|
22060
|
-
|
|
22249
|
+
// src/service-installer.ts
|
|
22250
|
+
var service_installer_exports = {};
|
|
22251
|
+
__export(service_installer_exports, {
|
|
22252
|
+
ServiceInstallError: () => ServiceInstallError,
|
|
22253
|
+
installService: () => installService,
|
|
22254
|
+
isServiceRunning: () => isServiceRunning,
|
|
22255
|
+
platformName: () => platformName,
|
|
22256
|
+
refreshServiceDefinition: () => refreshServiceDefinition,
|
|
22257
|
+
renderLaunchdPlist: () => renderLaunchdPlist,
|
|
22258
|
+
renderSystemdUnit: () => renderSystemdUnit,
|
|
22259
|
+
renderWindowsTaskCommand: () => renderWindowsTaskCommand,
|
|
22260
|
+
uninstallService: () => uninstallService
|
|
22061
22261
|
});
|
|
22062
|
-
import {
|
|
22063
|
-
import {
|
|
22064
|
-
import {
|
|
22065
|
-
|
|
22066
|
-
|
|
22262
|
+
import { execSync as execSync5 } from "child_process";
|
|
22263
|
+
import { existsSync as existsSync46, mkdirSync as mkdirSync14, readFileSync as readFileSync41, rmSync as rmSync7, writeFileSync as writeFileSync12 } from "fs";
|
|
22264
|
+
import { homedir as homedir18 } from "os";
|
|
22265
|
+
import { join as join51 } from "path";
|
|
22266
|
+
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
22267
|
+
function resolveDaemonPaths() {
|
|
22268
|
+
const watchJs = fileURLToPath4(new URL("./watch.js", import.meta.url));
|
|
22269
|
+
const node = process.execPath;
|
|
22270
|
+
const dataDir = join51(homedir18(), ".chat-recall");
|
|
22271
|
+
const logFile = join51(dataDir, "watch.log");
|
|
22272
|
+
return { watchJs, node, logFile, dataDir };
|
|
22067
22273
|
}
|
|
22068
|
-
function
|
|
22274
|
+
function tryCmd(cmd) {
|
|
22069
22275
|
try {
|
|
22070
|
-
return
|
|
22276
|
+
return execSync5(cmd, { stdio: ["ignore", "pipe", "ignore"] }).toString();
|
|
22071
22277
|
} catch {
|
|
22072
|
-
return "
|
|
22278
|
+
return "";
|
|
22073
22279
|
}
|
|
22074
22280
|
}
|
|
22075
|
-
function
|
|
22076
|
-
|
|
22077
|
-
|
|
22078
|
-
|
|
22079
|
-
for (const rel of filesUnder(join51(src, name)).sort()) {
|
|
22080
|
-
h.update(name + "/" + rel);
|
|
22081
|
-
h.update(readFileSync41(join51(src, name, rel)));
|
|
22082
|
-
}
|
|
22281
|
+
function isServiceRunning() {
|
|
22282
|
+
if (process.platform === "win32") {
|
|
22283
|
+
if (/Running/i.test(tryCmd("schtasks /query /tn chat-recall-watch /fo LIST /v 2>nul"))) return true;
|
|
22284
|
+
return /watch\.js/i.test(tryCmd(`wmic process where "name='node.exe'" get commandline 2>nul`));
|
|
22083
22285
|
}
|
|
22084
|
-
|
|
22085
|
-
|
|
22086
|
-
function filesUnder(dir, prefix = "") {
|
|
22087
|
-
const out = [];
|
|
22088
|
-
for (const e of readdirSync34(dir, { withFileTypes: true })) {
|
|
22089
|
-
if (e.name === MARKER) continue;
|
|
22090
|
-
const rel = prefix ? `${prefix}/${e.name}` : e.name;
|
|
22091
|
-
if (e.isDirectory()) out.push(...filesUnder(join51(dir, e.name), rel));
|
|
22092
|
-
else out.push(rel);
|
|
22286
|
+
if (process.platform === "darwin") {
|
|
22287
|
+
return /com\.chat-recall\.watch/.test(tryCmd("launchctl list 2>/dev/null"));
|
|
22093
22288
|
}
|
|
22094
|
-
|
|
22095
|
-
|
|
22096
|
-
|
|
22097
|
-
return
|
|
22098
|
-
}
|
|
22099
|
-
function bundledSkillNames() {
|
|
22100
|
-
const src = skillsSourceDir();
|
|
22101
|
-
if (!existsSync45(src)) return [];
|
|
22102
|
-
return readdirSync34(src, { withFileTypes: true }).filter((e) => e.isDirectory() && existsSync45(join51(src, e.name, "SKILL.md"))).map((e) => e.name).sort();
|
|
22289
|
+
if (process.platform === "linux") {
|
|
22290
|
+
return /^active$/m.test(tryCmd("systemctl --user is-active chat-recall-watch.service 2>/dev/null"));
|
|
22291
|
+
}
|
|
22292
|
+
return false;
|
|
22103
22293
|
}
|
|
22104
|
-
function
|
|
22294
|
+
function refreshServiceDefinition() {
|
|
22295
|
+
if (process.platform !== "linux") return false;
|
|
22105
22296
|
try {
|
|
22106
|
-
|
|
22297
|
+
const { watchJs, node, logFile } = resolveDaemonPaths();
|
|
22298
|
+
const unitPath = join51(homedir18(), ".config", "systemd", "user", "chat-recall-watch.service");
|
|
22299
|
+
if (!existsSync46(unitPath)) return false;
|
|
22300
|
+
const current = readFileSync41(unitPath, "utf8");
|
|
22301
|
+
if (!current.includes(watchJs)) return false;
|
|
22302
|
+
const wanted = renderSystemdUnit(watchJs, node, logFile);
|
|
22303
|
+
if (readFileSync41(unitPath, "utf8") === wanted) return false;
|
|
22304
|
+
writeFileSync12(unitPath, wanted);
|
|
22305
|
+
execSync5("systemctl --user daemon-reload", { stdio: "ignore" });
|
|
22306
|
+
return true;
|
|
22107
22307
|
} catch {
|
|
22108
|
-
return
|
|
22308
|
+
return false;
|
|
22109
22309
|
}
|
|
22110
22310
|
}
|
|
22111
|
-
function
|
|
22112
|
-
const
|
|
22113
|
-
|
|
22114
|
-
|
|
22115
|
-
|
|
22116
|
-
const key2 = realPathOrSelf(dir);
|
|
22117
|
-
if (seen.has(key2)) continue;
|
|
22118
|
-
seen.add(key2);
|
|
22119
|
-
const primary = targets.length === 0;
|
|
22120
|
-
targets.push({
|
|
22121
|
-
id: primary ? "claude" : `claude:${basename23(home)}`,
|
|
22122
|
-
label: primary ? "Claude Code" : `Claude Code (${basename23(home)})`,
|
|
22123
|
-
dir,
|
|
22124
|
-
// Judge each profile on ITS OWN transcripts, not the primary's: a home
|
|
22125
|
-
// with no projects/ is a directory someone made, not a Claude install.
|
|
22126
|
-
available: existsSync45(join51(home, "projects"))
|
|
22127
|
-
});
|
|
22311
|
+
function installService() {
|
|
22312
|
+
const { watchJs, node, logFile, dataDir } = resolveDaemonPaths();
|
|
22313
|
+
mkdirSync14(dataDir, { recursive: true });
|
|
22314
|
+
if (process.platform === "linux") {
|
|
22315
|
+
return installSystemd(watchJs, node, logFile);
|
|
22128
22316
|
}
|
|
22129
|
-
if (
|
|
22130
|
-
|
|
22317
|
+
if (process.platform === "darwin") {
|
|
22318
|
+
return installLaunchd(watchJs, node, logFile);
|
|
22131
22319
|
}
|
|
22132
|
-
|
|
22133
|
-
|
|
22134
|
-
|
|
22135
|
-
|
|
22136
|
-
|
|
22137
|
-
|
|
22138
|
-
|
|
22139
|
-
// has its own dir, so per-tool coverage is complete.
|
|
22140
|
-
...claudeSkillTargets(),
|
|
22141
|
-
{ id: "agy", label: "Antigravity", dir: join51(agyBackend.homeDir(), "skills"), available: agyBackend.isAvailable() },
|
|
22142
|
-
{ id: "codex", label: "Codex", dir: codexBackend.skillsDir(), available: codexBackend.isAvailable() },
|
|
22143
|
-
{ id: "opencode", label: "OpenCode", dir: opencodeBackend.skillsDir(), available: opencodeBackend.isAvailable() },
|
|
22144
|
-
// Cursor DOES have its own dir (~/.cursor/skills). `skills-cursor` next to
|
|
22145
|
-
// it is Cursor's bundled set — read-only, never an install target.
|
|
22146
|
-
{ id: "cursor", label: "Cursor", dir: join51(cursorHomeDir(), "skills"), available: cursorBackend.isAvailable() }
|
|
22147
|
-
];
|
|
22320
|
+
if (process.platform === "win32") {
|
|
22321
|
+
return installWindowsTask(watchJs, node, logFile);
|
|
22322
|
+
}
|
|
22323
|
+
throw new ServiceInstallError(
|
|
22324
|
+
`--install-service: unsupported platform '${process.platform}'.`,
|
|
22325
|
+
"Run `chat-recall watch` in the foreground, or point your OS scheduler at `chat-recall-watch`."
|
|
22326
|
+
);
|
|
22148
22327
|
}
|
|
22149
|
-
function
|
|
22150
|
-
|
|
22151
|
-
|
|
22152
|
-
|
|
22153
|
-
|
|
22154
|
-
|
|
22155
|
-
|
|
22156
|
-
|
|
22157
|
-
|
|
22158
|
-
|
|
22159
|
-
|
|
22160
|
-
|
|
22161
|
-
|
|
22162
|
-
|
|
22163
|
-
|
|
22164
|
-
|
|
22165
|
-
}
|
|
22166
|
-
for (const name of names) {
|
|
22167
|
-
const dest = join51(t.dir, name);
|
|
22168
|
-
if (existsSync45(dest) && !isManaged(dest)) {
|
|
22169
|
-
skippedUserOwned.push(name);
|
|
22170
|
-
continue;
|
|
22171
|
-
}
|
|
22172
|
-
mkdirSync14(t.dir, { recursive: true });
|
|
22173
|
-
rmSync7(dest, { recursive: true, force: true });
|
|
22174
|
-
cpSync(join51(src, name), dest, { recursive: true });
|
|
22175
|
-
writeFileSync12(join51(dest, MARKER), `${stamp}
|
|
22176
|
-
`);
|
|
22177
|
-
installed.push(name);
|
|
22178
|
-
}
|
|
22179
|
-
perTarget.push({ ...t, installed, skippedUserOwned });
|
|
22180
|
-
}
|
|
22181
|
-
return { version: v, perTarget };
|
|
22182
|
-
}
|
|
22183
|
-
function uninstallSkills() {
|
|
22184
|
-
const names = new Set(bundledSkillNames());
|
|
22185
|
-
const perTarget = [];
|
|
22186
|
-
for (const t of skillTargets()) {
|
|
22187
|
-
const removed = [];
|
|
22188
|
-
if (!existsSync45(t.dir)) {
|
|
22189
|
-
perTarget.push({ id: t.id, label: t.label, removed });
|
|
22190
|
-
continue;
|
|
22191
|
-
}
|
|
22192
|
-
for (const entry of readdirSync34(t.dir, { withFileTypes: true })) {
|
|
22193
|
-
if (!entry.isDirectory()) continue;
|
|
22194
|
-
const dir = join51(t.dir, entry.name);
|
|
22195
|
-
if (isManaged(dir) && (names.has(entry.name) || entry.name.startsWith("chat-recall"))) {
|
|
22196
|
-
rmSync7(dir, { recursive: true, force: true });
|
|
22197
|
-
removed.push(entry.name);
|
|
22198
|
-
}
|
|
22199
|
-
}
|
|
22200
|
-
perTarget.push({ id: t.id, label: t.label, removed });
|
|
22201
|
-
}
|
|
22202
|
-
return { perTarget };
|
|
22203
|
-
}
|
|
22204
|
-
function skillsNeedRefresh() {
|
|
22205
|
-
const cur = skillStamp();
|
|
22206
|
-
const names = bundledSkillNames();
|
|
22207
|
-
if (names.length === 0) return false;
|
|
22208
|
-
for (const t of skillTargets()) {
|
|
22209
|
-
if (!t.available) continue;
|
|
22210
|
-
for (const name of names) {
|
|
22211
|
-
const marker = join51(t.dir, name, MARKER);
|
|
22212
|
-
if (!existsSync45(marker)) return true;
|
|
22213
|
-
try {
|
|
22214
|
-
if (readFileSync41(marker, "utf-8").trim() !== cur) return true;
|
|
22215
|
-
} catch {
|
|
22216
|
-
return true;
|
|
22217
|
-
}
|
|
22218
|
-
}
|
|
22219
|
-
}
|
|
22220
|
-
return false;
|
|
22221
|
-
}
|
|
22222
|
-
function skillStatus() {
|
|
22223
|
-
const names = bundledSkillNames();
|
|
22224
|
-
const cur = skillStamp();
|
|
22225
|
-
return skillTargets().map((t) => {
|
|
22226
|
-
let installed = 0;
|
|
22227
|
-
let ver = null;
|
|
22228
|
-
for (const name of names) {
|
|
22229
|
-
const marker = join51(t.dir, name, MARKER);
|
|
22230
|
-
if (existsSync45(marker)) {
|
|
22231
|
-
installed++;
|
|
22232
|
-
try {
|
|
22233
|
-
ver = readFileSync41(marker, "utf-8").trim();
|
|
22234
|
-
} catch {
|
|
22235
|
-
}
|
|
22236
|
-
}
|
|
22237
|
-
}
|
|
22238
|
-
return { id: t.id, label: t.label, available: t.available, installed, version: installed === names.length ? ver : installed ? `${ver} (partial)` : null };
|
|
22239
|
-
}).filter((s) => s.available || s.installed > 0);
|
|
22240
|
-
}
|
|
22241
|
-
var MARKER;
|
|
22242
|
-
var init_install_skills = __esm({
|
|
22243
|
-
"src/install-skills.ts"() {
|
|
22244
|
-
"use strict";
|
|
22245
|
-
init_tool_paths();
|
|
22246
|
-
init_claude2();
|
|
22247
|
-
init_opencode();
|
|
22248
|
-
init_codex2();
|
|
22249
|
-
init_agy();
|
|
22250
|
-
init_cursor();
|
|
22251
|
-
init_tool_paths();
|
|
22252
|
-
MARKER = ".chat-recall-managed";
|
|
22253
|
-
}
|
|
22254
|
-
});
|
|
22255
|
-
|
|
22256
|
-
// src/service-installer.ts
|
|
22257
|
-
var service_installer_exports = {};
|
|
22258
|
-
__export(service_installer_exports, {
|
|
22259
|
-
ServiceInstallError: () => ServiceInstallError,
|
|
22260
|
-
installService: () => installService,
|
|
22261
|
-
isServiceRunning: () => isServiceRunning,
|
|
22262
|
-
platformName: () => platformName,
|
|
22263
|
-
refreshServiceDefinition: () => refreshServiceDefinition,
|
|
22264
|
-
renderLaunchdPlist: () => renderLaunchdPlist,
|
|
22265
|
-
renderSystemdUnit: () => renderSystemdUnit,
|
|
22266
|
-
renderWindowsTaskCommand: () => renderWindowsTaskCommand,
|
|
22267
|
-
uninstallService: () => uninstallService
|
|
22268
|
-
});
|
|
22269
|
-
import { execSync as execSync5 } from "child_process";
|
|
22270
|
-
import { existsSync as existsSync46, mkdirSync as mkdirSync15, readFileSync as readFileSync42, rmSync as rmSync8, writeFileSync as writeFileSync13 } from "fs";
|
|
22271
|
-
import { homedir as homedir18 } from "os";
|
|
22272
|
-
import { join as join52 } from "path";
|
|
22273
|
-
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
22274
|
-
function resolveDaemonPaths() {
|
|
22275
|
-
const watchJs = fileURLToPath4(new URL("./watch.js", import.meta.url));
|
|
22276
|
-
const node = process.execPath;
|
|
22277
|
-
const dataDir = join52(homedir18(), ".chat-recall");
|
|
22278
|
-
const logFile = join52(dataDir, "watch.log");
|
|
22279
|
-
return { watchJs, node, logFile, dataDir };
|
|
22280
|
-
}
|
|
22281
|
-
function tryCmd(cmd) {
|
|
22282
|
-
try {
|
|
22283
|
-
return execSync5(cmd, { stdio: ["ignore", "pipe", "ignore"] }).toString();
|
|
22284
|
-
} catch {
|
|
22285
|
-
return "";
|
|
22286
|
-
}
|
|
22287
|
-
}
|
|
22288
|
-
function isServiceRunning() {
|
|
22289
|
-
if (process.platform === "win32") {
|
|
22290
|
-
if (/Running/i.test(tryCmd("schtasks /query /tn chat-recall-watch /fo LIST /v 2>nul"))) return true;
|
|
22291
|
-
return /watch\.js/i.test(tryCmd(`wmic process where "name='node.exe'" get commandline 2>nul`));
|
|
22292
|
-
}
|
|
22293
|
-
if (process.platform === "darwin") {
|
|
22294
|
-
return /com\.chat-recall\.watch/.test(tryCmd("launchctl list 2>/dev/null"));
|
|
22295
|
-
}
|
|
22296
|
-
if (process.platform === "linux") {
|
|
22297
|
-
return /active/.test(tryCmd("systemctl --user is-active chat-recall-watch.service 2>/dev/null"));
|
|
22298
|
-
}
|
|
22299
|
-
return false;
|
|
22300
|
-
}
|
|
22301
|
-
function refreshServiceDefinition() {
|
|
22302
|
-
if (process.platform !== "linux") return false;
|
|
22303
|
-
try {
|
|
22304
|
-
const { watchJs, node, logFile } = resolveDaemonPaths();
|
|
22305
|
-
const unitPath = join52(homedir18(), ".config", "systemd", "user", "chat-recall-watch.service");
|
|
22306
|
-
if (!existsSync46(unitPath)) return false;
|
|
22307
|
-
const current = readFileSync42(unitPath, "utf8");
|
|
22308
|
-
if (!current.includes(watchJs)) return false;
|
|
22309
|
-
const wanted = renderSystemdUnit(watchJs, node, logFile);
|
|
22310
|
-
if (readFileSync42(unitPath, "utf8") === wanted) return false;
|
|
22311
|
-
writeFileSync13(unitPath, wanted);
|
|
22312
|
-
execSync5("systemctl --user daemon-reload", { stdio: "ignore" });
|
|
22313
|
-
return true;
|
|
22314
|
-
} catch {
|
|
22315
|
-
return false;
|
|
22316
|
-
}
|
|
22317
|
-
}
|
|
22318
|
-
function installService() {
|
|
22319
|
-
const { watchJs, node, logFile, dataDir } = resolveDaemonPaths();
|
|
22320
|
-
mkdirSync15(dataDir, { recursive: true });
|
|
22321
|
-
if (process.platform === "linux") {
|
|
22322
|
-
return installSystemd(watchJs, node, logFile);
|
|
22323
|
-
}
|
|
22324
|
-
if (process.platform === "darwin") {
|
|
22325
|
-
return installLaunchd(watchJs, node, logFile);
|
|
22326
|
-
}
|
|
22327
|
-
if (process.platform === "win32") {
|
|
22328
|
-
return installWindowsTask(watchJs, node, logFile);
|
|
22329
|
-
}
|
|
22330
|
-
throw new ServiceInstallError(
|
|
22331
|
-
`--install-service: unsupported platform '${process.platform}'.`,
|
|
22332
|
-
"Run `chat-recall watch` in the foreground, or point your OS scheduler at `chat-recall-watch`."
|
|
22333
|
-
);
|
|
22334
|
-
}
|
|
22335
|
-
function installSystemd(watchJs, node, logFile) {
|
|
22336
|
-
const unitDir = join52(homedir18(), ".config", "systemd", "user");
|
|
22337
|
-
const unitPath = join52(unitDir, "chat-recall-watch.service");
|
|
22338
|
-
const unit = renderSystemdUnit(watchJs, node, logFile);
|
|
22339
|
-
mkdirSync15(unitDir, { recursive: true });
|
|
22340
|
-
writeFileSync13(unitPath, unit);
|
|
22341
|
-
try {
|
|
22342
|
-
execSync5("systemctl --user daemon-reload && systemctl --user enable chat-recall-watch.service && systemctl --user restart chat-recall-watch.service", { stdio: "inherit" });
|
|
22343
|
-
try {
|
|
22344
|
-
execSync5('loginctl enable-linger "$USER" 2>/dev/null', { stdio: "ignore" });
|
|
22345
|
-
} catch {
|
|
22346
|
-
}
|
|
22347
|
-
} catch (err) {
|
|
22348
|
-
throw new ServiceInstallError("Unit written but systemctl failed.", `Unit: ${unitPath}`);
|
|
22349
|
-
}
|
|
22350
|
-
return { definitionPath: unitPath, logFile };
|
|
22328
|
+
function installSystemd(watchJs, node, logFile) {
|
|
22329
|
+
const unitDir = join51(homedir18(), ".config", "systemd", "user");
|
|
22330
|
+
const unitPath = join51(unitDir, "chat-recall-watch.service");
|
|
22331
|
+
const unit = renderSystemdUnit(watchJs, node, logFile);
|
|
22332
|
+
mkdirSync14(unitDir, { recursive: true });
|
|
22333
|
+
writeFileSync12(unitPath, unit);
|
|
22334
|
+
try {
|
|
22335
|
+
execSync5("systemctl --user daemon-reload && systemctl --user enable chat-recall-watch.service && systemctl --user restart chat-recall-watch.service", { stdio: "inherit" });
|
|
22336
|
+
try {
|
|
22337
|
+
execSync5('loginctl enable-linger "$USER" 2>/dev/null', { stdio: "ignore" });
|
|
22338
|
+
} catch {
|
|
22339
|
+
}
|
|
22340
|
+
} catch (err) {
|
|
22341
|
+
throw new ServiceInstallError("Unit written but systemctl failed.", `Unit: ${unitPath}`);
|
|
22342
|
+
}
|
|
22343
|
+
return { definitionPath: unitPath, logFile };
|
|
22351
22344
|
}
|
|
22352
22345
|
function renderSystemdUnit(watchJs, node, logFile) {
|
|
22353
22346
|
return [
|
|
@@ -22370,7 +22363,12 @@ function renderSystemdUnit(watchJs, node, logFile) {
|
|
|
22370
22363
|
// sawtooth. 1536MB keeps GC honest (768 OOMed on the largest subagent-fanout session) with plenty of headroom for the
|
|
22371
22364
|
// largest transcripts.
|
|
22372
22365
|
`ExecStart=${node} --max-old-space-size=1536 ${watchJs}`,
|
|
22373
|
-
|
|
22366
|
+
// ALWAYS: the collector never ends its own work, so any exit systemd did
|
|
22367
|
+
// not ask for is a fault. Under on-failure a clean exit left it dead: on
|
|
22368
|
+
// 2026-09-16 the unit went "inactive (dead) … status=0/SUCCESS" and stayed
|
|
22369
|
+
// down for 8 days, so nothing synced and the auto-update tick, which runs
|
|
22370
|
+
// inside the daemon, never ran. `systemctl stop` still stops it.
|
|
22371
|
+
"Restart=always",
|
|
22374
22372
|
"RestartSec=10",
|
|
22375
22373
|
"Nice=10",
|
|
22376
22374
|
// NEVER STOP RETRYING. systemd's default start limiter gives up after a
|
|
@@ -22408,11 +22406,11 @@ function renderSystemdUnit(watchJs, node, logFile) {
|
|
|
22408
22406
|
}
|
|
22409
22407
|
function installLaunchd(watchJs, node, logFile) {
|
|
22410
22408
|
const label = "com.chat-recall.watch";
|
|
22411
|
-
const plistDir =
|
|
22412
|
-
const plistPath =
|
|
22409
|
+
const plistDir = join51(homedir18(), "Library", "LaunchAgents");
|
|
22410
|
+
const plistPath = join51(plistDir, `${label}.plist`);
|
|
22413
22411
|
const plist = renderLaunchdPlist(watchJs, node, logFile, label);
|
|
22414
|
-
|
|
22415
|
-
|
|
22412
|
+
mkdirSync14(plistDir, { recursive: true });
|
|
22413
|
+
writeFileSync12(plistPath, plist);
|
|
22416
22414
|
try {
|
|
22417
22415
|
try {
|
|
22418
22416
|
execSync5(`launchctl unload "${plistPath}"`, { stdio: "ignore" });
|
|
@@ -22465,9 +22463,9 @@ function uninstallService() {
|
|
|
22465
22463
|
removed = true;
|
|
22466
22464
|
} catch {
|
|
22467
22465
|
}
|
|
22468
|
-
const unitPath =
|
|
22466
|
+
const unitPath = join51(homedir18(), ".config", "systemd", "user", "chat-recall-watch.service");
|
|
22469
22467
|
if (existsSync46(unitPath)) {
|
|
22470
|
-
|
|
22468
|
+
rmSync7(unitPath, { force: true });
|
|
22471
22469
|
removed = true;
|
|
22472
22470
|
}
|
|
22473
22471
|
try {
|
|
@@ -22477,14 +22475,14 @@ function uninstallService() {
|
|
|
22477
22475
|
return removed;
|
|
22478
22476
|
}
|
|
22479
22477
|
if (process.platform === "darwin") {
|
|
22480
|
-
const plistPath =
|
|
22478
|
+
const plistPath = join51(homedir18(), "Library", "LaunchAgents", "com.chat-recall.watch.plist");
|
|
22481
22479
|
try {
|
|
22482
22480
|
execSync5(`launchctl unload "${plistPath}" 2>/dev/null`, { stdio: "ignore" });
|
|
22483
22481
|
} catch {
|
|
22484
22482
|
}
|
|
22485
22483
|
let removed = false;
|
|
22486
22484
|
if (existsSync46(plistPath)) {
|
|
22487
|
-
|
|
22485
|
+
rmSync7(plistPath, { force: true });
|
|
22488
22486
|
removed = true;
|
|
22489
22487
|
}
|
|
22490
22488
|
return removed;
|
|
@@ -22519,6 +22517,211 @@ var init_service_installer = __esm({
|
|
|
22519
22517
|
}
|
|
22520
22518
|
});
|
|
22521
22519
|
|
|
22520
|
+
// src/install-skills.ts
|
|
22521
|
+
var install_skills_exports = {};
|
|
22522
|
+
__export(install_skills_exports, {
|
|
22523
|
+
bundledSkillNames: () => bundledSkillNames,
|
|
22524
|
+
installSkills: () => installSkills,
|
|
22525
|
+
skillStatus: () => skillStatus,
|
|
22526
|
+
skillTargets: () => skillTargets,
|
|
22527
|
+
skillsNeedRefresh: () => skillsNeedRefresh,
|
|
22528
|
+
skillsSourceDir: () => skillsSourceDir,
|
|
22529
|
+
uninstallSkills: () => uninstallSkills
|
|
22530
|
+
});
|
|
22531
|
+
import { readdirSync as readdirSync34, existsSync as existsSync47, mkdirSync as mkdirSync15, rmSync as rmSync8, cpSync, writeFileSync as writeFileSync13, readFileSync as readFileSync42, statSync as statSync43, realpathSync as realpathSync4 } from "fs";
|
|
22532
|
+
import { createHash as createHash12 } from "crypto";
|
|
22533
|
+
import { join as join52, basename as basename23 } from "path";
|
|
22534
|
+
function skillsSourceDir() {
|
|
22535
|
+
return join52(import.meta.dirname, "..", "skills");
|
|
22536
|
+
}
|
|
22537
|
+
function version() {
|
|
22538
|
+
try {
|
|
22539
|
+
return JSON.parse(readFileSync42(new URL("../package.json", import.meta.url), "utf-8")).version || "0.0.0";
|
|
22540
|
+
} catch {
|
|
22541
|
+
return "0.0.0";
|
|
22542
|
+
}
|
|
22543
|
+
}
|
|
22544
|
+
function skillsFingerprint() {
|
|
22545
|
+
const src = skillsSourceDir();
|
|
22546
|
+
const h = createHash12("sha256");
|
|
22547
|
+
for (const name of bundledSkillNames()) {
|
|
22548
|
+
for (const rel of filesUnder(join52(src, name)).sort()) {
|
|
22549
|
+
h.update(name + "/" + rel);
|
|
22550
|
+
h.update(readFileSync42(join52(src, name, rel)));
|
|
22551
|
+
}
|
|
22552
|
+
}
|
|
22553
|
+
return h.digest("hex").slice(0, 12);
|
|
22554
|
+
}
|
|
22555
|
+
function filesUnder(dir, prefix = "") {
|
|
22556
|
+
const out = [];
|
|
22557
|
+
for (const e of readdirSync34(dir, { withFileTypes: true })) {
|
|
22558
|
+
if (e.name === MARKER) continue;
|
|
22559
|
+
const rel = prefix ? `${prefix}/${e.name}` : e.name;
|
|
22560
|
+
if (e.isDirectory()) out.push(...filesUnder(join52(dir, e.name), rel));
|
|
22561
|
+
else out.push(rel);
|
|
22562
|
+
}
|
|
22563
|
+
return out;
|
|
22564
|
+
}
|
|
22565
|
+
function skillStamp() {
|
|
22566
|
+
return `${version()} ${skillsFingerprint()}`;
|
|
22567
|
+
}
|
|
22568
|
+
function bundledSkillNames() {
|
|
22569
|
+
const src = skillsSourceDir();
|
|
22570
|
+
if (!existsSync47(src)) return [];
|
|
22571
|
+
return readdirSync34(src, { withFileTypes: true }).filter((e) => e.isDirectory() && existsSync47(join52(src, e.name, "SKILL.md"))).map((e) => e.name).sort();
|
|
22572
|
+
}
|
|
22573
|
+
function realPathOrSelf(p) {
|
|
22574
|
+
try {
|
|
22575
|
+
return realpathSync4(p);
|
|
22576
|
+
} catch {
|
|
22577
|
+
return p;
|
|
22578
|
+
}
|
|
22579
|
+
}
|
|
22580
|
+
function claudeSkillTargets() {
|
|
22581
|
+
const targets = [];
|
|
22582
|
+
const seen = /* @__PURE__ */ new Set();
|
|
22583
|
+
for (const home of claudeHomeDirs()) {
|
|
22584
|
+
const dir = join52(home, "skills");
|
|
22585
|
+
const key2 = realPathOrSelf(dir);
|
|
22586
|
+
if (seen.has(key2)) continue;
|
|
22587
|
+
seen.add(key2);
|
|
22588
|
+
const primary = targets.length === 0;
|
|
22589
|
+
targets.push({
|
|
22590
|
+
id: primary ? "claude" : `claude:${basename23(home)}`,
|
|
22591
|
+
label: primary ? "Claude Code" : `Claude Code (${basename23(home)})`,
|
|
22592
|
+
dir,
|
|
22593
|
+
// Judge each profile on ITS OWN transcripts, not the primary's: a home
|
|
22594
|
+
// with no projects/ is a directory someone made, not a Claude install.
|
|
22595
|
+
available: existsSync47(join52(home, "projects"))
|
|
22596
|
+
});
|
|
22597
|
+
}
|
|
22598
|
+
if (targets.length === 0) {
|
|
22599
|
+
targets.push({ id: "claude", label: "Claude Code", dir: claudeBackend.skillsDir(), available: claudeBackend.isAvailable() });
|
|
22600
|
+
}
|
|
22601
|
+
return targets;
|
|
22602
|
+
}
|
|
22603
|
+
function skillTargets() {
|
|
22604
|
+
return [
|
|
22605
|
+
// Per-tool dirs ONLY. We deliberately do NOT also write ~/.agents/skills:
|
|
22606
|
+
// some tools read BOTH ~/.agents/skills and their own dir, so installing to
|
|
22607
|
+
// both produces "skill conflict" duplicate warnings. Every tool we support
|
|
22608
|
+
// has its own dir, so per-tool coverage is complete.
|
|
22609
|
+
...claudeSkillTargets(),
|
|
22610
|
+
{ id: "agy", label: "Antigravity", dir: join52(agyBackend.homeDir(), "skills"), available: agyBackend.isAvailable() },
|
|
22611
|
+
{ id: "codex", label: "Codex", dir: codexBackend.skillsDir(), available: codexBackend.isAvailable() },
|
|
22612
|
+
{ id: "opencode", label: "OpenCode", dir: opencodeBackend.skillsDir(), available: opencodeBackend.isAvailable() },
|
|
22613
|
+
// Cursor DOES have its own dir (~/.cursor/skills). `skills-cursor` next to
|
|
22614
|
+
// it is Cursor's bundled set — read-only, never an install target.
|
|
22615
|
+
{ id: "cursor", label: "Cursor", dir: join52(cursorHomeDir(), "skills"), available: cursorBackend.isAvailable() }
|
|
22616
|
+
];
|
|
22617
|
+
}
|
|
22618
|
+
function isManaged(dir) {
|
|
22619
|
+
return existsSync47(join52(dir, MARKER));
|
|
22620
|
+
}
|
|
22621
|
+
function installSkills(opts = {}) {
|
|
22622
|
+
const onlyAvailable = opts.onlyAvailable ?? true;
|
|
22623
|
+
const src = skillsSourceDir();
|
|
22624
|
+
const names = bundledSkillNames();
|
|
22625
|
+
const v = version();
|
|
22626
|
+
const stamp = skillStamp();
|
|
22627
|
+
const perTarget = [];
|
|
22628
|
+
for (const t of skillTargets()) {
|
|
22629
|
+
const installed = [];
|
|
22630
|
+
const skippedUserOwned = [];
|
|
22631
|
+
if (onlyAvailable && !t.available) {
|
|
22632
|
+
perTarget.push({ ...t, installed, skippedUserOwned });
|
|
22633
|
+
continue;
|
|
22634
|
+
}
|
|
22635
|
+
for (const name of names) {
|
|
22636
|
+
const dest = join52(t.dir, name);
|
|
22637
|
+
if (existsSync47(dest) && !isManaged(dest)) {
|
|
22638
|
+
skippedUserOwned.push(name);
|
|
22639
|
+
continue;
|
|
22640
|
+
}
|
|
22641
|
+
mkdirSync15(t.dir, { recursive: true });
|
|
22642
|
+
rmSync8(dest, { recursive: true, force: true });
|
|
22643
|
+
cpSync(join52(src, name), dest, { recursive: true });
|
|
22644
|
+
writeFileSync13(join52(dest, MARKER), `${stamp}
|
|
22645
|
+
`);
|
|
22646
|
+
installed.push(name);
|
|
22647
|
+
}
|
|
22648
|
+
perTarget.push({ ...t, installed, skippedUserOwned });
|
|
22649
|
+
}
|
|
22650
|
+
return { version: v, perTarget };
|
|
22651
|
+
}
|
|
22652
|
+
function uninstallSkills() {
|
|
22653
|
+
const names = new Set(bundledSkillNames());
|
|
22654
|
+
const perTarget = [];
|
|
22655
|
+
for (const t of skillTargets()) {
|
|
22656
|
+
const removed = [];
|
|
22657
|
+
if (!existsSync47(t.dir)) {
|
|
22658
|
+
perTarget.push({ id: t.id, label: t.label, removed });
|
|
22659
|
+
continue;
|
|
22660
|
+
}
|
|
22661
|
+
for (const entry of readdirSync34(t.dir, { withFileTypes: true })) {
|
|
22662
|
+
if (!entry.isDirectory()) continue;
|
|
22663
|
+
const dir = join52(t.dir, entry.name);
|
|
22664
|
+
if (isManaged(dir) && (names.has(entry.name) || entry.name.startsWith("chat-recall"))) {
|
|
22665
|
+
rmSync8(dir, { recursive: true, force: true });
|
|
22666
|
+
removed.push(entry.name);
|
|
22667
|
+
}
|
|
22668
|
+
}
|
|
22669
|
+
perTarget.push({ id: t.id, label: t.label, removed });
|
|
22670
|
+
}
|
|
22671
|
+
return { perTarget };
|
|
22672
|
+
}
|
|
22673
|
+
function skillsNeedRefresh() {
|
|
22674
|
+
const cur = skillStamp();
|
|
22675
|
+
const names = bundledSkillNames();
|
|
22676
|
+
if (names.length === 0) return false;
|
|
22677
|
+
for (const t of skillTargets()) {
|
|
22678
|
+
if (!t.available) continue;
|
|
22679
|
+
for (const name of names) {
|
|
22680
|
+
const marker = join52(t.dir, name, MARKER);
|
|
22681
|
+
if (!existsSync47(marker)) return true;
|
|
22682
|
+
try {
|
|
22683
|
+
if (readFileSync42(marker, "utf-8").trim() !== cur) return true;
|
|
22684
|
+
} catch {
|
|
22685
|
+
return true;
|
|
22686
|
+
}
|
|
22687
|
+
}
|
|
22688
|
+
}
|
|
22689
|
+
return false;
|
|
22690
|
+
}
|
|
22691
|
+
function skillStatus() {
|
|
22692
|
+
const names = bundledSkillNames();
|
|
22693
|
+
const cur = skillStamp();
|
|
22694
|
+
return skillTargets().map((t) => {
|
|
22695
|
+
let installed = 0;
|
|
22696
|
+
let ver = null;
|
|
22697
|
+
for (const name of names) {
|
|
22698
|
+
const marker = join52(t.dir, name, MARKER);
|
|
22699
|
+
if (existsSync47(marker)) {
|
|
22700
|
+
installed++;
|
|
22701
|
+
try {
|
|
22702
|
+
ver = readFileSync42(marker, "utf-8").trim();
|
|
22703
|
+
} catch {
|
|
22704
|
+
}
|
|
22705
|
+
}
|
|
22706
|
+
}
|
|
22707
|
+
return { id: t.id, label: t.label, available: t.available, installed, version: installed === names.length ? ver : installed ? `${ver} (partial)` : null };
|
|
22708
|
+
}).filter((s) => s.available || s.installed > 0);
|
|
22709
|
+
}
|
|
22710
|
+
var MARKER;
|
|
22711
|
+
var init_install_skills = __esm({
|
|
22712
|
+
"src/install-skills.ts"() {
|
|
22713
|
+
"use strict";
|
|
22714
|
+
init_tool_paths();
|
|
22715
|
+
init_claude2();
|
|
22716
|
+
init_opencode();
|
|
22717
|
+
init_codex2();
|
|
22718
|
+
init_agy();
|
|
22719
|
+
init_cursor();
|
|
22720
|
+
init_tool_paths();
|
|
22721
|
+
MARKER = ".chat-recall-managed";
|
|
22722
|
+
}
|
|
22723
|
+
});
|
|
22724
|
+
|
|
22522
22725
|
// src/mcp.ts
|
|
22523
22726
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
22524
22727
|
import { spawn as spawn3 } from "child_process";
|
|
@@ -22639,6 +22842,107 @@ function socketPathFromArgv(argv) {
|
|
|
22639
22842
|
return v && !v.startsWith("--") ? v : void 0;
|
|
22640
22843
|
}
|
|
22641
22844
|
|
|
22845
|
+
// src/relay-lifecycle.ts
|
|
22846
|
+
var PARENT_POLL_MS = 5e3;
|
|
22847
|
+
function pidAlive(pid) {
|
|
22848
|
+
try {
|
|
22849
|
+
process.kill(pid, 0);
|
|
22850
|
+
return true;
|
|
22851
|
+
} catch (err) {
|
|
22852
|
+
return err.code === "EPERM";
|
|
22853
|
+
}
|
|
22854
|
+
}
|
|
22855
|
+
var processProbe = {
|
|
22856
|
+
ppid: () => process.ppid,
|
|
22857
|
+
alive: pidAlive
|
|
22858
|
+
};
|
|
22859
|
+
function parentGone(originalPpid, probe) {
|
|
22860
|
+
if (probe.ppid() !== originalPpid) return true;
|
|
22861
|
+
return !probe.alive(originalPpid);
|
|
22862
|
+
}
|
|
22863
|
+
function watchParent(onGone, opts = {}) {
|
|
22864
|
+
const probe = opts.probe ?? processProbe;
|
|
22865
|
+
const original = probe.ppid();
|
|
22866
|
+
if (original <= 1) return () => {
|
|
22867
|
+
};
|
|
22868
|
+
const timer = setInterval(() => {
|
|
22869
|
+
if (!parentGone(original, probe)) return;
|
|
22870
|
+
clearInterval(timer);
|
|
22871
|
+
onGone();
|
|
22872
|
+
}, opts.intervalMs ?? PARENT_POLL_MS);
|
|
22873
|
+
timer.unref?.();
|
|
22874
|
+
return () => clearInterval(timer);
|
|
22875
|
+
}
|
|
22876
|
+
function onInputFinished(input, onEnd) {
|
|
22877
|
+
let fired = false;
|
|
22878
|
+
const fire = () => {
|
|
22879
|
+
if (fired) return;
|
|
22880
|
+
fired = true;
|
|
22881
|
+
onEnd();
|
|
22882
|
+
};
|
|
22883
|
+
input.once("end", fire);
|
|
22884
|
+
input.once("close", fire);
|
|
22885
|
+
input.once("error", fire);
|
|
22886
|
+
}
|
|
22887
|
+
|
|
22888
|
+
// src/sync-supervisor.ts
|
|
22889
|
+
function createSyncSupervisor(opts) {
|
|
22890
|
+
let owner = null;
|
|
22891
|
+
let inFlight = false;
|
|
22892
|
+
let tookOver = false;
|
|
22893
|
+
const log6 = opts.log ?? (() => {
|
|
22894
|
+
});
|
|
22895
|
+
const serviceRunning = () => {
|
|
22896
|
+
try {
|
|
22897
|
+
return opts.serviceRunning();
|
|
22898
|
+
} catch {
|
|
22899
|
+
return false;
|
|
22900
|
+
}
|
|
22901
|
+
};
|
|
22902
|
+
const check = async () => {
|
|
22903
|
+
if (serviceRunning()) {
|
|
22904
|
+
if (owner !== "service") {
|
|
22905
|
+
log6(owner === "mcp" ? "watch service is running again \u2014 it owns sync; MCP background sync stands by" : "watch service active \u2014 MCP will not run background sync (the service syncs)");
|
|
22906
|
+
}
|
|
22907
|
+
owner = "service";
|
|
22908
|
+
return;
|
|
22909
|
+
}
|
|
22910
|
+
const scope = owner === "mcp" ? "changed" : "full";
|
|
22911
|
+
if (owner !== "mcp") {
|
|
22912
|
+
log6(owner === "service" ? "watch service is no longer running \u2014 MCP takes over background sync" : "no watch service running \u2014 MCP runs background sync");
|
|
22913
|
+
if (!tookOver) {
|
|
22914
|
+
tookOver = true;
|
|
22915
|
+
opts.onFirstTakeover?.();
|
|
22916
|
+
}
|
|
22917
|
+
}
|
|
22918
|
+
owner = "mcp";
|
|
22919
|
+
if (inFlight) return;
|
|
22920
|
+
inFlight = true;
|
|
22921
|
+
try {
|
|
22922
|
+
await opts.tick(scope);
|
|
22923
|
+
} catch {
|
|
22924
|
+
} finally {
|
|
22925
|
+
inFlight = false;
|
|
22926
|
+
}
|
|
22927
|
+
};
|
|
22928
|
+
const first = setTimeout(() => {
|
|
22929
|
+
void check();
|
|
22930
|
+
}, opts.firstCheckMs);
|
|
22931
|
+
first.unref?.();
|
|
22932
|
+
const every = setInterval(() => {
|
|
22933
|
+
void check();
|
|
22934
|
+
}, opts.intervalMs);
|
|
22935
|
+
every.unref?.();
|
|
22936
|
+
return {
|
|
22937
|
+
check,
|
|
22938
|
+
owner: () => owner,
|
|
22939
|
+
stop: () => {
|
|
22940
|
+
clearTimeout(first);
|
|
22941
|
+
clearInterval(every);
|
|
22942
|
+
}
|
|
22943
|
+
};
|
|
22944
|
+
}
|
|
22945
|
+
|
|
22642
22946
|
// src/mcp.ts
|
|
22643
22947
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
22644
22948
|
|
|
@@ -23553,7 +23857,7 @@ function sanitizeQuery(query) {
|
|
|
23553
23857
|
|
|
23554
23858
|
// ../engine/dist/core/write-ahead-log.js
|
|
23555
23859
|
init_paths();
|
|
23556
|
-
import { existsSync as existsSync17, mkdirSync as mkdirSync5, appendFileSync, openSync as
|
|
23860
|
+
import { existsSync as existsSync17, mkdirSync as mkdirSync5, appendFileSync, openSync as openSync8, closeSync as closeSync8, chmodSync as chmodSync3 } from "fs";
|
|
23557
23861
|
import { join as join20 } from "path";
|
|
23558
23862
|
var REDACT_KEYS = /* @__PURE__ */ new Set([
|
|
23559
23863
|
"content",
|
|
@@ -23579,8 +23883,8 @@ var WriteAheadLog = class {
|
|
|
23579
23883
|
}
|
|
23580
23884
|
this.walPath = join20(dir, "write_log.jsonl");
|
|
23581
23885
|
if (!existsSync17(this.walPath)) {
|
|
23582
|
-
const fd =
|
|
23583
|
-
|
|
23886
|
+
const fd = openSync8(this.walPath, "w", 384);
|
|
23887
|
+
closeSync8(fd);
|
|
23584
23888
|
}
|
|
23585
23889
|
}
|
|
23586
23890
|
/**
|
|
@@ -28371,12 +28675,6 @@ function startBackgroundSync() {
|
|
|
28371
28675
|
console.error("[mcp] background sync tick failed:", err instanceof Error ? err.message : err);
|
|
28372
28676
|
}
|
|
28373
28677
|
};
|
|
28374
|
-
setInterval(() => {
|
|
28375
|
-
void tick("changed");
|
|
28376
|
-
}, SYNC_TICK_MS).unref();
|
|
28377
|
-
setTimeout(() => {
|
|
28378
|
-
void tick("full");
|
|
28379
|
-
}, 15e3).unref();
|
|
28380
28678
|
const updateTick = async () => {
|
|
28381
28679
|
try {
|
|
28382
28680
|
const { refreshUpdateCheck: refreshUpdateCheck2, updateNotice: updateNotice3 } = await Promise.resolve().then(() => (init_update_notice(), update_notice_exports));
|
|
@@ -28386,12 +28684,27 @@ function startBackgroundSync() {
|
|
|
28386
28684
|
} catch {
|
|
28387
28685
|
}
|
|
28388
28686
|
};
|
|
28389
|
-
|
|
28390
|
-
|
|
28391
|
-
|
|
28392
|
-
|
|
28393
|
-
|
|
28394
|
-
|
|
28687
|
+
const startUpdateProbe = () => {
|
|
28688
|
+
setTimeout(() => {
|
|
28689
|
+
void updateTick();
|
|
28690
|
+
}, 5e3).unref();
|
|
28691
|
+
setInterval(() => {
|
|
28692
|
+
void updateTick();
|
|
28693
|
+
}, 6 * 60 * 60 * 1e3).unref();
|
|
28694
|
+
};
|
|
28695
|
+
let isServiceRunning2 = null;
|
|
28696
|
+
createSyncSupervisor({
|
|
28697
|
+
serviceRunning: () => isServiceRunning2?.() ?? false,
|
|
28698
|
+
tick,
|
|
28699
|
+
intervalMs: SYNC_TICK_MS,
|
|
28700
|
+
firstCheckMs: 15e3,
|
|
28701
|
+
onFirstTakeover: startUpdateProbe,
|
|
28702
|
+
log: (msg) => console.error(`[mcp] ${msg}`)
|
|
28703
|
+
});
|
|
28704
|
+
void Promise.resolve().then(() => (init_service_installer(), service_installer_exports)).then((m) => {
|
|
28705
|
+
isServiceRunning2 = m.isServiceRunning;
|
|
28706
|
+
}).catch(() => {
|
|
28707
|
+
});
|
|
28395
28708
|
}
|
|
28396
28709
|
function installCrashGuards() {
|
|
28397
28710
|
process.on("unhandledRejection", (reason) => {
|
|
@@ -28404,7 +28717,7 @@ function installCrashGuards() {
|
|
|
28404
28717
|
reportClientEvent2("mcp_crash", { message: err.stack || err.message });
|
|
28405
28718
|
});
|
|
28406
28719
|
}
|
|
28407
|
-
var RELAY_VERSION = true ? "0.7.
|
|
28720
|
+
var RELAY_VERSION = true ? "0.7.2" : "0.0.0";
|
|
28408
28721
|
function daemonIdleSecs() {
|
|
28409
28722
|
const raw = process.env.CHAT_RECALL_DAEMON_IDLE_SECS;
|
|
28410
28723
|
if (raw === void 0) return 900;
|
|
@@ -28509,6 +28822,9 @@ async function main() {
|
|
|
28509
28822
|
} else {
|
|
28510
28823
|
const transport2 = new StdioServerTransport();
|
|
28511
28824
|
await createMcpServer().connect(transport2);
|
|
28825
|
+
const leave = () => process.exit(0);
|
|
28826
|
+
onInputFinished(process.stdin, leave);
|
|
28827
|
+
watchParent(leave);
|
|
28512
28828
|
}
|
|
28513
28829
|
void (async () => {
|
|
28514
28830
|
try {
|
|
@@ -28529,16 +28845,6 @@ async function main() {
|
|
|
28529
28845
|
}
|
|
28530
28846
|
} catch {
|
|
28531
28847
|
}
|
|
28532
|
-
|
|
28533
|
-
try {
|
|
28534
|
-
const { isServiceRunning: isServiceRunning2 } = await Promise.resolve().then(() => (init_service_installer(), service_installer_exports));
|
|
28535
|
-
daemonRunning = isServiceRunning2();
|
|
28536
|
-
} catch {
|
|
28537
|
-
}
|
|
28538
|
-
if (daemonRunning) {
|
|
28539
|
-
console.error("[mcp] watch daemon active \u2014 MCP will not run background sync (keeps the tool server lightweight; the daemon syncs).");
|
|
28540
|
-
} else {
|
|
28541
|
-
startBackgroundSync();
|
|
28542
|
-
}
|
|
28848
|
+
startBackgroundSync();
|
|
28543
28849
|
}
|
|
28544
28850
|
main().catch(console.error);
|