@threadbase-sh/streamer 1.54.2 → 1.55.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/cli.cjs +433 -351
- package/dist/cli.cjs.map +1 -1
- package/dist/index.cjs +252 -171
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +160 -79
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3934,8 +3934,8 @@ import { EventEmitter } from "events";
|
|
|
3934
3934
|
import { existsSync as existsSync16 } from "fs";
|
|
3935
3935
|
import { realpath as realpath2 } from "fs/promises";
|
|
3936
3936
|
import { createServer as createServer2 } from "http";
|
|
3937
|
-
import { homedir as
|
|
3938
|
-
import { dirname as
|
|
3937
|
+
import { homedir as homedir14, hostname as hostname3 } from "os";
|
|
3938
|
+
import { dirname as dirname11, join as join25 } from "path";
|
|
3939
3939
|
|
|
3940
3940
|
// src/api/app.ts
|
|
3941
3941
|
import { Hono as Hono18 } from "hono";
|
|
@@ -5399,6 +5399,66 @@ var PushRepository = class {
|
|
|
5399
5399
|
}
|
|
5400
5400
|
};
|
|
5401
5401
|
|
|
5402
|
+
// src/server-identity.ts
|
|
5403
|
+
import { createPrivateKey, createPublicKey, generateKeyPairSync } from "crypto";
|
|
5404
|
+
import { chmodSync as chmodSync2, mkdirSync as mkdirSync3, readFileSync as readFileSync7, renameSync as renameSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
5405
|
+
import { homedir as homedir6 } from "os";
|
|
5406
|
+
import { dirname as dirname6, join as join10 } from "path";
|
|
5407
|
+
var IDENTITY_FILE_VERSION = 1;
|
|
5408
|
+
function serverIdentityKeyPath() {
|
|
5409
|
+
const dir = process.env.THREADBASE_CONFIG_DIR ?? join10(homedir6(), ".threadbase");
|
|
5410
|
+
return join10(dir, "keys", "server-identity.key");
|
|
5411
|
+
}
|
|
5412
|
+
function loadOrCreateServerIdentity() {
|
|
5413
|
+
const path = serverIdentityKeyPath();
|
|
5414
|
+
let raw;
|
|
5415
|
+
try {
|
|
5416
|
+
raw = readFileSync7(path, "utf-8");
|
|
5417
|
+
} catch (err) {
|
|
5418
|
+
if (err.code !== "ENOENT") throw err;
|
|
5419
|
+
return generateIdentity(path);
|
|
5420
|
+
}
|
|
5421
|
+
let privateKey;
|
|
5422
|
+
try {
|
|
5423
|
+
privateKey = createPrivateKey({ key: JSON.parse(raw).key, format: "jwk" });
|
|
5424
|
+
} catch {
|
|
5425
|
+
throw new Error(
|
|
5426
|
+
`Server identity key at ${path} could not be read. Refusing to generate a new one \u2014 that would invalidate every paired device. Repair or delete the file deliberately.`
|
|
5427
|
+
);
|
|
5428
|
+
}
|
|
5429
|
+
return { publicKey: publicKeyOf(privateKey), privateKey };
|
|
5430
|
+
}
|
|
5431
|
+
function serverIdentityPublicKey() {
|
|
5432
|
+
return loadOrCreateServerIdentity().publicKey;
|
|
5433
|
+
}
|
|
5434
|
+
function generateIdentity(path) {
|
|
5435
|
+
const { privateKey } = generateKeyPairSync("x25519");
|
|
5436
|
+
const file = {
|
|
5437
|
+
v: IDENTITY_FILE_VERSION,
|
|
5438
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5439
|
+
key: privateKey.export({ format: "jwk" })
|
|
5440
|
+
};
|
|
5441
|
+
mkdirSync3(dirname6(path), { recursive: true, mode: 448 });
|
|
5442
|
+
const tmp = `${path}.tmp`;
|
|
5443
|
+
writeFileSync3(tmp, `${JSON.stringify(file)}
|
|
5444
|
+
`, { encoding: "utf-8", mode: 384 });
|
|
5445
|
+
chmodSync2(tmp, 384);
|
|
5446
|
+
renameSync2(tmp, path);
|
|
5447
|
+
const publicKey = publicKeyOf(privateKey);
|
|
5448
|
+
getLogger("identity").info(`Generated server identity key ${publicKey}`, {
|
|
5449
|
+
event: "identity.key_generated",
|
|
5450
|
+
path
|
|
5451
|
+
});
|
|
5452
|
+
return { publicKey, privateKey };
|
|
5453
|
+
}
|
|
5454
|
+
function publicKeyOf(privateKey) {
|
|
5455
|
+
const jwk = createPublicKey(privateKey).export({ format: "jwk" });
|
|
5456
|
+
if (jwk.crv !== "X25519" || typeof jwk.x !== "string") {
|
|
5457
|
+
throw new Error(`Server identity key at ${serverIdentityKeyPath()} is not an X25519 key`);
|
|
5458
|
+
}
|
|
5459
|
+
return jwk.x;
|
|
5460
|
+
}
|
|
5461
|
+
|
|
5402
5462
|
// src/services/push/apnsClient.ts
|
|
5403
5463
|
import { createSign } from "crypto";
|
|
5404
5464
|
import { connect, constants } from "http2";
|
|
@@ -5607,6 +5667,21 @@ function describePushCapability(liveActivityEnabled, env = process.env) {
|
|
|
5607
5667
|
liveActivityReason: describeMissingApnsCredentials(env) ?? "APNs credentials are set but the push token store is unavailable, so Live Activity push is disabled."
|
|
5608
5668
|
};
|
|
5609
5669
|
}
|
|
5670
|
+
var identityKeyFailureLogged = false;
|
|
5671
|
+
function describeServerIdentityKey() {
|
|
5672
|
+
try {
|
|
5673
|
+
return serverIdentityPublicKey();
|
|
5674
|
+
} catch (err) {
|
|
5675
|
+
if (!identityKeyFailureLogged) {
|
|
5676
|
+
identityKeyFailureLogged = true;
|
|
5677
|
+
getLogger("identity").error(
|
|
5678
|
+
`Server identity key unavailable, so /api/info will omit it: ${err instanceof Error ? err.message : String(err)}`,
|
|
5679
|
+
{ event: "identity.unavailable" }
|
|
5680
|
+
);
|
|
5681
|
+
}
|
|
5682
|
+
return void 0;
|
|
5683
|
+
}
|
|
5684
|
+
}
|
|
5610
5685
|
var clientLog = getLogger("client");
|
|
5611
5686
|
var createMiscRoutes = (deps) => {
|
|
5612
5687
|
const app = new Hono11();
|
|
@@ -5640,7 +5715,13 @@ var createMiscRoutes = (deps) => {
|
|
|
5640
5715
|
// actually send a push, so mobile can hide an affordance instead of
|
|
5641
5716
|
// registering tokens nothing will ever send to. Absent on older servers,
|
|
5642
5717
|
// which a client should read as "unknown", not "unavailable".
|
|
5643
|
-
push: describePushCapability(deps.liveActivityPushEnabled())
|
|
5718
|
+
push: describePushCapability(deps.liveActivityPushEnabled()),
|
|
5719
|
+
// This server's long-term X25519 public key, base64url. The same value the
|
|
5720
|
+
// pair QR carries as `spk`, served here so an already-paired client can
|
|
5721
|
+
// learn it without re-scanning. Additive: absent means a server with no
|
|
5722
|
+
// readable identity key, which a client must read as "cannot verify this
|
|
5723
|
+
// server" — never as a reason to fail the rest of this response.
|
|
5724
|
+
serverIdentityKey: describeServerIdentityKey()
|
|
5644
5725
|
});
|
|
5645
5726
|
});
|
|
5646
5727
|
app.get("/api/profiles", (c) => c.json([]));
|
|
@@ -6158,7 +6239,7 @@ import {
|
|
|
6158
6239
|
search
|
|
6159
6240
|
} from "@threadbase-sh/scanner";
|
|
6160
6241
|
import { createReadStream, existsSync as existsSync8, readdirSync as readdirSync3 } from "fs";
|
|
6161
|
-
import { join as
|
|
6242
|
+
import { join as join12 } from "path";
|
|
6162
6243
|
import { createInterface } from "readline";
|
|
6163
6244
|
|
|
6164
6245
|
// src/conversation-cache.ts
|
|
@@ -6167,9 +6248,9 @@ import {
|
|
|
6167
6248
|
parseJsonlLine
|
|
6168
6249
|
} from "@threadbase-sh/scanner";
|
|
6169
6250
|
import Database from "better-sqlite3";
|
|
6170
|
-
import { closeSync as closeSync3, existsSync as existsSync6, mkdirSync as
|
|
6251
|
+
import { closeSync as closeSync3, existsSync as existsSync6, mkdirSync as mkdirSync4, openSync as openSync3, readSync as readSync3, statSync as statSync3 } from "fs";
|
|
6171
6252
|
import { open as openAsync } from "fs/promises";
|
|
6172
|
-
import { dirname as
|
|
6253
|
+
import { dirname as dirname8 } from "path";
|
|
6173
6254
|
import { setImmediate as yieldToEventLoop } from "timers/promises";
|
|
6174
6255
|
|
|
6175
6256
|
// src/db/query-timing.ts
|
|
@@ -6241,17 +6322,17 @@ function labelStatements(statements) {
|
|
|
6241
6322
|
}
|
|
6242
6323
|
|
|
6243
6324
|
// src/db/sqlite-migrate.ts
|
|
6244
|
-
import { readdirSync as readdirSync2, readFileSync as
|
|
6245
|
-
import { dirname as
|
|
6325
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync8 } from "fs";
|
|
6326
|
+
import { dirname as dirname7, join as join11 } from "path";
|
|
6246
6327
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
6247
6328
|
function getMigrationsDir2() {
|
|
6248
6329
|
if (typeof import.meta !== "undefined" && import.meta.url) {
|
|
6249
|
-
return
|
|
6330
|
+
return dirname7(fileURLToPath2(import.meta.url));
|
|
6250
6331
|
}
|
|
6251
6332
|
return __dirname;
|
|
6252
6333
|
}
|
|
6253
6334
|
function resolveMigrationsDir(name = "migrations") {
|
|
6254
|
-
return
|
|
6335
|
+
return join11(getMigrationsDir2(), name);
|
|
6255
6336
|
}
|
|
6256
6337
|
var SCHEMA_MIGRATIONS_SQL = `
|
|
6257
6338
|
CREATE TABLE IF NOT EXISTS schema_migrations (
|
|
@@ -6273,7 +6354,7 @@ function runSqliteMigrations(db, migrationsDir) {
|
|
|
6273
6354
|
skipped.push(file);
|
|
6274
6355
|
continue;
|
|
6275
6356
|
}
|
|
6276
|
-
const sql =
|
|
6357
|
+
const sql = readFileSync8(join11(dir, file), "utf-8");
|
|
6277
6358
|
const tx = db.transaction(() => {
|
|
6278
6359
|
db.exec(sql);
|
|
6279
6360
|
recordApplied.run(file, (/* @__PURE__ */ new Date()).toISOString());
|
|
@@ -7016,7 +7097,7 @@ var ConversationCache = class _ConversationCache {
|
|
|
7016
7097
|
return this.classifyAgentFile(filePath, s.mtimeMs, s.size);
|
|
7017
7098
|
}
|
|
7018
7099
|
static open(dbPath, tailSize = 10, migrationsDir, options) {
|
|
7019
|
-
|
|
7100
|
+
mkdirSync4(dirname8(dbPath), { recursive: true });
|
|
7020
7101
|
const db = instrumentDatabase(new Database(dbPath));
|
|
7021
7102
|
db.pragma("journal_mode = WAL");
|
|
7022
7103
|
db.pragma("foreign_keys = ON");
|
|
@@ -8309,12 +8390,12 @@ var ConversationHandlers = class {
|
|
|
8309
8390
|
for (const projectsDir of this.scannerManager.projectsDirs()) {
|
|
8310
8391
|
if (!existsSync8(projectsDir)) continue;
|
|
8311
8392
|
for (const dir of readdirSync3(projectsDir)) {
|
|
8312
|
-
const fp =
|
|
8393
|
+
const fp = join12(projectsDir, dir, filename);
|
|
8313
8394
|
if (existsSync8(fp)) return fp;
|
|
8314
|
-
const projectDir =
|
|
8395
|
+
const projectDir = join12(projectsDir, dir);
|
|
8315
8396
|
try {
|
|
8316
8397
|
for (const sub of readdirSync3(projectDir)) {
|
|
8317
|
-
const subagentPath =
|
|
8398
|
+
const subagentPath = join12(projectDir, sub, "subagents", filename);
|
|
8318
8399
|
if (existsSync8(subagentPath)) return subagentPath;
|
|
8319
8400
|
}
|
|
8320
8401
|
} catch {
|
|
@@ -8789,7 +8870,7 @@ var ConversationHandlers = class {
|
|
|
8789
8870
|
|
|
8790
8871
|
// src/api/handlers/sessions.handlers.ts
|
|
8791
8872
|
import { existsSync as existsSync10 } from "fs";
|
|
8792
|
-
import { basename as basename5, dirname as
|
|
8873
|
+
import { basename as basename5, dirname as dirname9, join as join16 } from "path";
|
|
8793
8874
|
|
|
8794
8875
|
// node_modules/nanoid/index.js
|
|
8795
8876
|
import crypto2 from "crypto";
|
|
@@ -8972,7 +9053,7 @@ async function handleSendAgentInput(sessionId, body, deps) {
|
|
|
8972
9053
|
|
|
8973
9054
|
// src/agent/handle-start-agent-session.ts
|
|
8974
9055
|
import { existsSync as existsSync9 } from "fs";
|
|
8975
|
-
import { join as
|
|
9056
|
+
import { join as join13 } from "path";
|
|
8976
9057
|
function validateBody(body) {
|
|
8977
9058
|
if (body === null || body === void 0 || typeof body !== "object") {
|
|
8978
9059
|
return { ok: false };
|
|
@@ -9002,7 +9083,7 @@ async function handleStartAgentSession(body, deps) {
|
|
|
9002
9083
|
}
|
|
9003
9084
|
let conversationId = parsed.conversationId;
|
|
9004
9085
|
if (conversationId) {
|
|
9005
|
-
const jsonlPath =
|
|
9086
|
+
const jsonlPath = join13(deps.conversationsDir, `${conversationId}.jsonl`);
|
|
9006
9087
|
if (!existsSync9(jsonlPath)) {
|
|
9007
9088
|
return {
|
|
9008
9089
|
status: 404,
|
|
@@ -9048,7 +9129,7 @@ async function handleStartAgentSession(body, deps) {
|
|
|
9048
9129
|
|
|
9049
9130
|
// src/browse.ts
|
|
9050
9131
|
import { mkdir as mkdir2, readdir, realpath, stat } from "fs/promises";
|
|
9051
|
-
import { join as
|
|
9132
|
+
import { join as join14, resolve, sep } from "path";
|
|
9052
9133
|
var BrowsePathNotFoundError = class extends Error {
|
|
9053
9134
|
constructor(message) {
|
|
9054
9135
|
super(message);
|
|
@@ -9090,7 +9171,7 @@ async function createDirectory(parentAbsolutePath, name) {
|
|
|
9090
9171
|
if (name.includes("/") || name.includes("\\") || name === ".." || name === ".") {
|
|
9091
9172
|
throw new Error("Invalid directory name");
|
|
9092
9173
|
}
|
|
9093
|
-
const target =
|
|
9174
|
+
const target = join14(parentAbsolutePath, name);
|
|
9094
9175
|
try {
|
|
9095
9176
|
const s = await stat(target);
|
|
9096
9177
|
if (s.isDirectory()) throw new Error("Directory already exists");
|
|
@@ -9365,7 +9446,7 @@ function readIdempotencyKey(body) {
|
|
|
9365
9446
|
import { randomBytes as randomBytes3 } from "crypto";
|
|
9366
9447
|
import { mkdir as mkdir3, writeFile } from "fs/promises";
|
|
9367
9448
|
import heicConvert from "heic-convert";
|
|
9368
|
-
import { join as
|
|
9449
|
+
import { join as join15 } from "path";
|
|
9369
9450
|
var UPLOAD_DIR_NAME = ".threadbase-uploads";
|
|
9370
9451
|
var MAX_BYTES = 25 * 1024 * 1024;
|
|
9371
9452
|
var HEIC_MIMES = /* @__PURE__ */ new Set(["image/heic", "image/heif"]);
|
|
@@ -9398,9 +9479,9 @@ async function saveUploadFile(input) {
|
|
|
9398
9479
|
}
|
|
9399
9480
|
const id = `up_${randomBytes3(8).toString("hex")}`;
|
|
9400
9481
|
const safeName = sanitizeFilename(originalName) || `file${MIME_TO_EXT[mimeType] ?? ""}`;
|
|
9401
|
-
const dir =
|
|
9482
|
+
const dir = join15(input.projectPath, UPLOAD_DIR_NAME, input.sessionId);
|
|
9402
9483
|
await mkdir3(dir, { recursive: true });
|
|
9403
|
-
const filePath =
|
|
9484
|
+
const filePath = join15(dir, `${Date.now()}-${id}-${safeName}`);
|
|
9404
9485
|
await writeFile(filePath, buffer);
|
|
9405
9486
|
return {
|
|
9406
9487
|
id,
|
|
@@ -10321,7 +10402,7 @@ var SessionHandlers = class {
|
|
|
10321
10402
|
sessionStore: this.sessionStore,
|
|
10322
10403
|
// biome-ignore lint/style/noNonNullAssertion: agentClient is set when agentConfig.enabled is true
|
|
10323
10404
|
agentClient: this.agentClient,
|
|
10324
|
-
conversationsDir: this.cacheDir ?
|
|
10405
|
+
conversationsDir: this.cacheDir ? join16(dirname9(this.cacheDir), "conversations") : "",
|
|
10325
10406
|
agentConfig: this.agentConfig
|
|
10326
10407
|
});
|
|
10327
10408
|
json(res, result.status, result.body);
|
|
@@ -10798,10 +10879,10 @@ var SessionsRepository = class {
|
|
|
10798
10879
|
|
|
10799
10880
|
// src/db/runtime-store.ts
|
|
10800
10881
|
import Database2 from "better-sqlite3";
|
|
10801
|
-
import { homedir as
|
|
10802
|
-
import { join as
|
|
10882
|
+
import { homedir as homedir7 } from "os";
|
|
10883
|
+
import { join as join17 } from "path";
|
|
10803
10884
|
function resolveRuntimeDbPath(override) {
|
|
10804
|
-
return override ?? process.env.THREADBASE_RUNTIME_DB ??
|
|
10885
|
+
return override ?? process.env.THREADBASE_RUNTIME_DB ?? join17(process.env.THREADBASE_CONFIG_DIR ?? join17(homedir7(), ".threadbase"), "runtime.db");
|
|
10805
10886
|
}
|
|
10806
10887
|
var RuntimeStore = class _RuntimeStore {
|
|
10807
10888
|
constructor(db) {
|
|
@@ -11119,14 +11200,14 @@ import { spawn as spawn2 } from "child_process";
|
|
|
11119
11200
|
|
|
11120
11201
|
// src/pty-host/socket.ts
|
|
11121
11202
|
import { createConnection, createServer } from "net";
|
|
11122
|
-
import { homedir as
|
|
11123
|
-
import { join as
|
|
11203
|
+
import { homedir as homedir8 } from "os";
|
|
11204
|
+
import { join as join18 } from "path";
|
|
11124
11205
|
function hostSocketPath(instanceId) {
|
|
11125
11206
|
if (process.platform === "win32") {
|
|
11126
11207
|
return `\\\\.\\pipe\\threadbase-pty-host-${instanceId}`;
|
|
11127
11208
|
}
|
|
11128
|
-
const dir = process.env.THREADBASE_CONFIG_DIR ??
|
|
11129
|
-
return
|
|
11209
|
+
const dir = process.env.THREADBASE_CONFIG_DIR ?? join18(homedir8(), ".threadbase");
|
|
11210
|
+
return join18(dir, "run", `pty-host-${instanceId}.sock`);
|
|
11130
11211
|
}
|
|
11131
11212
|
function socketTransport(socket) {
|
|
11132
11213
|
socket.setEncoding("utf8");
|
|
@@ -11201,8 +11282,8 @@ import {
|
|
|
11201
11282
|
ConversationScanner
|
|
11202
11283
|
} from "@threadbase-sh/scanner";
|
|
11203
11284
|
import { statSync as statSync7 } from "fs";
|
|
11204
|
-
import { homedir as
|
|
11205
|
-
import { join as
|
|
11285
|
+
import { homedir as homedir10 } from "os";
|
|
11286
|
+
import { join as join20 } from "path";
|
|
11206
11287
|
|
|
11207
11288
|
// src/services/cache/cacheMetadata.ts
|
|
11208
11289
|
function getCacheMetadata(repo, key) {
|
|
@@ -11302,9 +11383,9 @@ function refreshConversationCache(deps) {
|
|
|
11302
11383
|
|
|
11303
11384
|
// src/services/conversations/shouldRefreshProjectsFromHdd.ts
|
|
11304
11385
|
import { readdirSync as readdirSync4, statSync as statSync6 } from "fs";
|
|
11305
|
-
import { homedir as
|
|
11306
|
-
import { join as
|
|
11307
|
-
var DEFAULT_PROJECTS_DIR =
|
|
11386
|
+
import { homedir as homedir9 } from "os";
|
|
11387
|
+
import { join as join19 } from "path";
|
|
11388
|
+
var DEFAULT_PROJECTS_DIR = join19(homedir9(), ".claude", "projects");
|
|
11308
11389
|
function maxProjectsTreeMtimeMs(projectsDir) {
|
|
11309
11390
|
let maxMs;
|
|
11310
11391
|
try {
|
|
@@ -11316,7 +11397,7 @@ function maxProjectsTreeMtimeMs(projectsDir) {
|
|
|
11316
11397
|
for (const ent of readdirSync4(projectsDir, { withFileTypes: true })) {
|
|
11317
11398
|
if (!ent.isDirectory()) continue;
|
|
11318
11399
|
try {
|
|
11319
|
-
const childMs = statSync6(
|
|
11400
|
+
const childMs = statSync6(join19(projectsDir, ent.name)).mtimeMs;
|
|
11320
11401
|
if (childMs > maxMs) maxMs = childMs;
|
|
11321
11402
|
} catch {
|
|
11322
11403
|
}
|
|
@@ -11488,9 +11569,9 @@ var ScannerManager = class {
|
|
|
11488
11569
|
projectsDirs() {
|
|
11489
11570
|
const profiles = this.deps.scanProfiles;
|
|
11490
11571
|
if (profiles && profiles.length > 0) {
|
|
11491
|
-
return profiles.filter((p) => p.enabled).map((p) =>
|
|
11572
|
+
return profiles.filter((p) => p.enabled).map((p) => join20(p.configDir, "projects"));
|
|
11492
11573
|
}
|
|
11493
|
-
return [
|
|
11574
|
+
return [join20(homedir10(), ".claude", "projects")];
|
|
11494
11575
|
}
|
|
11495
11576
|
// ─── staleness ────────────────────────────────────────────────────
|
|
11496
11577
|
// Drain the stale set and disarm the flag together. The caller owns the
|
|
@@ -11771,8 +11852,8 @@ import { statSync as statSync9 } from "fs";
|
|
|
11771
11852
|
|
|
11772
11853
|
// src/handlers/handleListProjects.ts
|
|
11773
11854
|
import { closeSync as closeSync4, openSync as openSync4, readdirSync as readdirSync5, readSync as readSync4, statSync as statSync8 } from "fs";
|
|
11774
|
-
import { homedir as
|
|
11775
|
-
import { join as
|
|
11855
|
+
import { homedir as homedir11 } from "os";
|
|
11856
|
+
import { join as join21 } from "path";
|
|
11776
11857
|
var HEAD_BYTES = 64 * 1024;
|
|
11777
11858
|
var MAX_FILES_PROBED = 3;
|
|
11778
11859
|
function readRecordedCwd(dir) {
|
|
@@ -11785,7 +11866,7 @@ function readRecordedCwd(dir) {
|
|
|
11785
11866
|
for (const file of files.slice(0, MAX_FILES_PROBED)) {
|
|
11786
11867
|
let fd;
|
|
11787
11868
|
try {
|
|
11788
|
-
fd = openSync4(
|
|
11869
|
+
fd = openSync4(join21(dir, file), "r");
|
|
11789
11870
|
const buf = Buffer.alloc(HEAD_BYTES);
|
|
11790
11871
|
const bytes = readSync4(fd, buf, 0, HEAD_BYTES, 0);
|
|
11791
11872
|
for (const line of buf.subarray(0, bytes).toString("utf8").split("\n")) {
|
|
@@ -11809,11 +11890,11 @@ function decodeProjectPath(dirName) {
|
|
|
11809
11890
|
function handleListProjects(url, res) {
|
|
11810
11891
|
const limit = Math.max(1, parseInt(url.searchParams.get("limit") ?? "50", 10) || 50);
|
|
11811
11892
|
const offset = Math.max(0, parseInt(url.searchParams.get("offset") ?? "0", 10) || 0);
|
|
11812
|
-
const projectsDir =
|
|
11893
|
+
const projectsDir = join21(homedir11(), ".claude", "projects");
|
|
11813
11894
|
let entries;
|
|
11814
11895
|
try {
|
|
11815
11896
|
entries = readdirSync5(projectsDir).map((dirName) => {
|
|
11816
|
-
const fullPath =
|
|
11897
|
+
const fullPath = join21(projectsDir, dirName);
|
|
11817
11898
|
let mtime = 0;
|
|
11818
11899
|
try {
|
|
11819
11900
|
mtime = statSync8(fullPath).mtimeMs;
|
|
@@ -11828,7 +11909,7 @@ function handleListProjects(url, res) {
|
|
|
11828
11909
|
}
|
|
11829
11910
|
const total = entries.length;
|
|
11830
11911
|
const page = entries.slice(offset, offset + limit).map(({ dirName }) => {
|
|
11831
|
-
const path = readRecordedCwd(
|
|
11912
|
+
const path = readRecordedCwd(join21(projectsDir, dirName)) ?? decodeProjectPath(String(dirName));
|
|
11832
11913
|
const name = path.split(/[\\/]/).filter(Boolean).pop() ?? dirName;
|
|
11833
11914
|
return { name, path, dirName };
|
|
11834
11915
|
});
|
|
@@ -12230,16 +12311,16 @@ import { createHash as createHash4 } from "crypto";
|
|
|
12230
12311
|
import { existsSync as existsSync12 } from "fs";
|
|
12231
12312
|
|
|
12232
12313
|
// src/services/cache-integrity/alertStore.ts
|
|
12233
|
-
import { mkdirSync as
|
|
12234
|
-
import { homedir as
|
|
12235
|
-
import { dirname as
|
|
12314
|
+
import { mkdirSync as mkdirSync5, readFileSync as readFileSync9, writeFileSync as writeFileSync4 } from "fs";
|
|
12315
|
+
import { homedir as homedir12 } from "os";
|
|
12316
|
+
import { dirname as dirname10, join as join22 } from "path";
|
|
12236
12317
|
function alertStatePath() {
|
|
12237
|
-
const dir = process.env.THREADBASE_CONFIG_DIR ??
|
|
12238
|
-
return
|
|
12318
|
+
const dir = process.env.THREADBASE_CONFIG_DIR ?? join22(homedir12(), ".threadbase");
|
|
12319
|
+
return join22(dir, "cache-alert.json");
|
|
12239
12320
|
}
|
|
12240
12321
|
function loadAlertState() {
|
|
12241
12322
|
try {
|
|
12242
|
-
const parsed = JSON.parse(
|
|
12323
|
+
const parsed = JSON.parse(readFileSync9(alertStatePath(), "utf-8"));
|
|
12243
12324
|
return parsed && typeof parsed === "object" ? parsed : {};
|
|
12244
12325
|
} catch {
|
|
12245
12326
|
return {};
|
|
@@ -12247,14 +12328,14 @@ function loadAlertState() {
|
|
|
12247
12328
|
}
|
|
12248
12329
|
function saveAlertState(state) {
|
|
12249
12330
|
const path = alertStatePath();
|
|
12250
|
-
|
|
12251
|
-
|
|
12331
|
+
mkdirSync5(dirname10(path), { recursive: true });
|
|
12332
|
+
writeFileSync4(path, `${JSON.stringify(state, null, 2)}
|
|
12252
12333
|
`);
|
|
12253
12334
|
}
|
|
12254
12335
|
|
|
12255
12336
|
// src/services/cache-integrity/backup.ts
|
|
12256
|
-
import { existsSync as existsSync11, mkdirSync as
|
|
12257
|
-
import { join as
|
|
12337
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync6, readdirSync as readdirSync6, statSync as statSync10, unlinkSync } from "fs";
|
|
12338
|
+
import { join as join23 } from "path";
|
|
12258
12339
|
var DEFAULT_RETAIN = 3;
|
|
12259
12340
|
function retainCount() {
|
|
12260
12341
|
const parsed = Number.parseInt(process.env.THREADBASE_CACHE_BACKUP_RETAIN ?? "", 10);
|
|
@@ -12265,13 +12346,13 @@ function timestamp(d) {
|
|
|
12265
12346
|
return `${d.getFullYear()}${p(d.getMonth() + 1)}${p(d.getDate())}-${p(d.getHours())}${p(d.getMinutes())}${p(d.getSeconds())}`;
|
|
12266
12347
|
}
|
|
12267
12348
|
async function backupCacheDb(db, cacheDir) {
|
|
12268
|
-
const backupsDir =
|
|
12269
|
-
|
|
12270
|
-
const destPath =
|
|
12349
|
+
const backupsDir = join23(cacheDir, "backups");
|
|
12350
|
+
mkdirSync6(backupsDir, { recursive: true });
|
|
12351
|
+
const destPath = join23(backupsDir, `cache-${timestamp(/* @__PURE__ */ new Date())}.db`);
|
|
12271
12352
|
await db.backup(destPath);
|
|
12272
12353
|
const retain = retainCount();
|
|
12273
12354
|
const backups = readdirSync6(backupsDir).filter((f) => f.startsWith("cache-") && f.endsWith(".db")).map((f) => {
|
|
12274
|
-
const full =
|
|
12355
|
+
const full = join23(backupsDir, f);
|
|
12275
12356
|
return { full, mtime: statSync10(full).mtimeMs };
|
|
12276
12357
|
}).sort((a, b) => b.mtime - a.mtime);
|
|
12277
12358
|
for (const stale of backups.slice(retain)) {
|
|
@@ -14239,9 +14320,9 @@ function discoveredToResponse(d, conversationId) {
|
|
|
14239
14320
|
}
|
|
14240
14321
|
|
|
14241
14322
|
// src/session-watchers.ts
|
|
14242
|
-
import { existsSync as existsSync15, watch as fsWatch, readdirSync as readdirSync7, readFileSync as
|
|
14243
|
-
import { homedir as
|
|
14244
|
-
import { basename as basename6, join as
|
|
14323
|
+
import { existsSync as existsSync15, watch as fsWatch, readdirSync as readdirSync7, readFileSync as readFileSync10, statSync as statSync12 } from "fs";
|
|
14324
|
+
import { homedir as homedir13 } from "os";
|
|
14325
|
+
import { basename as basename6, join as join24 } from "path";
|
|
14245
14326
|
var SessionWatchers = class {
|
|
14246
14327
|
constructor(deps) {
|
|
14247
14328
|
this.deps = deps;
|
|
@@ -14307,7 +14388,7 @@ var SessionWatchers = class {
|
|
|
14307
14388
|
// file isn't slurped in full.
|
|
14308
14389
|
readFirstLineSessionId(filePath) {
|
|
14309
14390
|
try {
|
|
14310
|
-
const content =
|
|
14391
|
+
const content = readFileSync10(filePath, "utf8");
|
|
14311
14392
|
const nl = content.indexOf("\n");
|
|
14312
14393
|
const firstLine = nl === -1 ? content : content.slice(0, nl);
|
|
14313
14394
|
if (!firstLine.trim()) return null;
|
|
@@ -14322,9 +14403,9 @@ var SessionWatchers = class {
|
|
|
14322
14403
|
// was passed to Claude via --session-id so the filename matches from the start.
|
|
14323
14404
|
watchForJsonl(sessionId, projectPath) {
|
|
14324
14405
|
const encoded = projectPath.replace(/[/\\:.]/g, "-");
|
|
14325
|
-
const projectsDir =
|
|
14406
|
+
const projectsDir = join24(homedir13(), ".claude", "projects", encoded);
|
|
14326
14407
|
const expectedFile = `${sessionId}.jsonl`;
|
|
14327
|
-
const filePath =
|
|
14408
|
+
const filePath = join24(projectsDir, expectedFile);
|
|
14328
14409
|
const deadline = Date.now() + 12e4;
|
|
14329
14410
|
let watcher = null;
|
|
14330
14411
|
const cleanup = () => {
|
|
@@ -14346,10 +14427,10 @@ var SessionWatchers = class {
|
|
|
14346
14427
|
if (!resolvedFilePath && existsSync15(projectsDir)) {
|
|
14347
14428
|
try {
|
|
14348
14429
|
const now = Date.now();
|
|
14349
|
-
const match = readdirSync7(projectsDir).filter((f) => f.endsWith(".jsonl")).map((f) => ({ f, mtime: statSync12(
|
|
14350
|
-
({ f }) => basename6(f, ".jsonl") === sessionId || this.readFirstLineSessionId(
|
|
14430
|
+
const match = readdirSync7(projectsDir).filter((f) => f.endsWith(".jsonl")).map((f) => ({ f, mtime: statSync12(join24(projectsDir, f)).mtimeMs })).filter(({ mtime }) => now - mtime < 5e3).filter(
|
|
14431
|
+
({ f }) => basename6(f, ".jsonl") === sessionId || this.readFirstLineSessionId(join24(projectsDir, f)) === sessionId
|
|
14351
14432
|
).sort((a, b) => b.mtime - a.mtime)[0];
|
|
14352
|
-
if (match) resolvedFilePath =
|
|
14433
|
+
if (match) resolvedFilePath = join24(projectsDir, match.f);
|
|
14353
14434
|
} catch {
|
|
14354
14435
|
}
|
|
14355
14436
|
}
|
|
@@ -14357,7 +14438,7 @@ var SessionWatchers = class {
|
|
|
14357
14438
|
cleanup();
|
|
14358
14439
|
this.deps.sessionFileMap.set(sessionId, resolvedFilePath);
|
|
14359
14440
|
try {
|
|
14360
|
-
const existing =
|
|
14441
|
+
const existing = readFileSync10(resolvedFilePath, "utf8").split("\n").filter(Boolean);
|
|
14361
14442
|
if (existing.length > 0) {
|
|
14362
14443
|
this.deps.broadcastConversationLines(sessionId, existing);
|
|
14363
14444
|
}
|
|
@@ -14393,7 +14474,7 @@ var SessionWatchers = class {
|
|
|
14393
14474
|
watchForCodexRollout(sessionId, projectPath) {
|
|
14394
14475
|
const deadline = Date.now() + 12e4;
|
|
14395
14476
|
const now = /* @__PURE__ */ new Date();
|
|
14396
|
-
const dateDir =
|
|
14477
|
+
const dateDir = join24(
|
|
14397
14478
|
String(now.getFullYear()),
|
|
14398
14479
|
String(now.getMonth() + 1).padStart(2, "0"),
|
|
14399
14480
|
String(now.getDate()).padStart(2, "0")
|
|
@@ -14406,7 +14487,7 @@ var SessionWatchers = class {
|
|
|
14406
14487
|
};
|
|
14407
14488
|
const matchesProjectPath = (candidatePath) => {
|
|
14408
14489
|
try {
|
|
14409
|
-
const firstLine =
|
|
14490
|
+
const firstLine = readFileSync10(candidatePath, "utf8").split("\n", 1)[0];
|
|
14410
14491
|
if (!firstLine) return null;
|
|
14411
14492
|
const parsed = JSON.parse(firstLine);
|
|
14412
14493
|
if (parsed?.type !== "session_meta") return null;
|
|
@@ -14434,7 +14515,7 @@ var SessionWatchers = class {
|
|
|
14434
14515
|
this.deps.sessionStore.listManaged().filter((s) => s.id !== sessionId && s.boundConversationId != null).map((s) => s.boundConversationId)
|
|
14435
14516
|
);
|
|
14436
14517
|
for (const root of this.deps.codexRoots) {
|
|
14437
|
-
const sessionsDir =
|
|
14518
|
+
const sessionsDir = join24(root, dateDir);
|
|
14438
14519
|
if (!existsSync15(sessionsDir)) continue;
|
|
14439
14520
|
let candidateFiles;
|
|
14440
14521
|
try {
|
|
@@ -14443,9 +14524,9 @@ var SessionWatchers = class {
|
|
|
14443
14524
|
continue;
|
|
14444
14525
|
}
|
|
14445
14526
|
const nowMs = Date.now();
|
|
14446
|
-
const recentCandidates = candidateFiles.map((f) => ({ f, mtime: statSync12(
|
|
14527
|
+
const recentCandidates = candidateFiles.map((f) => ({ f, mtime: statSync12(join24(sessionsDir, f)).mtimeMs })).filter(({ mtime }) => nowMs - mtime < 1e4).sort((a, b) => b.mtime - a.mtime);
|
|
14447
14528
|
for (const { f } of recentCandidates) {
|
|
14448
|
-
const candidatePath =
|
|
14529
|
+
const candidatePath = join24(sessionsDir, f);
|
|
14449
14530
|
const match = matchesProjectPath(candidatePath);
|
|
14450
14531
|
if (!match) continue;
|
|
14451
14532
|
if (boundElsewhere.has(match.id)) continue;
|
|
@@ -14466,7 +14547,7 @@ var SessionWatchers = class {
|
|
|
14466
14547
|
this.deps.sessionFileMap.set(sessionId, candidatePath);
|
|
14467
14548
|
this.deps.fileWatcher.watch(candidatePath);
|
|
14468
14549
|
try {
|
|
14469
|
-
const existing =
|
|
14550
|
+
const existing = readFileSync10(candidatePath, "utf8").split("\n").filter(Boolean);
|
|
14470
14551
|
if (existing.length > 0) {
|
|
14471
14552
|
this.deps.broadcastConversationLines(sessionId, existing);
|
|
14472
14553
|
}
|
|
@@ -14849,7 +14930,7 @@ var StreamerServer = class {
|
|
|
14849
14930
|
this.skipStartupWarmup = config.skipStartupWarmup ?? false;
|
|
14850
14931
|
this.autoResumeOnBoot = config.autoResumeOnBoot ?? false;
|
|
14851
14932
|
this.scanProfiles = config.scanProfiles;
|
|
14852
|
-
this.codexRoots = config.codexRoots ?? [
|
|
14933
|
+
this.codexRoots = config.codexRoots ?? [join25(homedir14(), ".codex", "sessions")];
|
|
14853
14934
|
this.ptyGracePeriodMs = config.ptyGracePeriodMs ?? DEFAULT_PTY_GRACE_PERIOD_MS;
|
|
14854
14935
|
this.defaultSystemPrompt = config.defaultSystemPrompt ?? DEFAULT_SYSTEM_PROMPT;
|
|
14855
14936
|
const flagResolution = resolveFeatureFlags({
|
|
@@ -14866,7 +14947,7 @@ var StreamerServer = class {
|
|
|
14866
14947
|
this.claudeFlagsPersistable = config.claudeFlags === void 0;
|
|
14867
14948
|
this.claudeFlags = config.claudeFlags ?? loadClaudeFlags();
|
|
14868
14949
|
this.claudeExtraArgs = config.claudeExtraArgs ?? loadClaudeExtraArgs();
|
|
14869
|
-
this.cacheDir = config.cacheDir ?? loadCacheDir() ??
|
|
14950
|
+
this.cacheDir = config.cacheDir ?? loadCacheDir() ?? join25(homedir14(), ".threadbase", "cache");
|
|
14870
14951
|
this.runtimeDbPath = resolveRuntimeDbPath(config.runtimeDbPath);
|
|
14871
14952
|
this.tailSize = config.tailSize ?? loadTailSize() ?? 10;
|
|
14872
14953
|
this.directoryDebounceMs = parseDirScanDebounceEnv(process.env.THREADBASE_DIR_SCAN_DEBOUNCE_MS) ?? config.directoryScanDebounceMs ?? 1e3;
|
|
@@ -15043,7 +15124,7 @@ var StreamerServer = class {
|
|
|
15043
15124
|
temporalClient,
|
|
15044
15125
|
taskQueue: agentConfig.temporal.taskQueue
|
|
15045
15126
|
});
|
|
15046
|
-
const conversationsBaseDir = agentConfig.conversationsDir ||
|
|
15127
|
+
const conversationsBaseDir = agentConfig.conversationsDir || join25(dirname11(this.cacheDir), "conversations");
|
|
15047
15128
|
conversationWriter = createConversationWriter({
|
|
15048
15129
|
baseDir: conversationsBaseDir
|
|
15049
15130
|
});
|
|
@@ -15536,7 +15617,7 @@ var StreamerServer = class {
|
|
|
15536
15617
|
}
|
|
15537
15618
|
try {
|
|
15538
15619
|
this.cache = ConversationCache.open(
|
|
15539
|
-
|
|
15620
|
+
join25(this.cacheDir, "cache.db"),
|
|
15540
15621
|
this.tailSize,
|
|
15541
15622
|
void 0,
|
|
15542
15623
|
{
|