@threadbase-sh/streamer 1.39.1 → 1.40.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 +747 -341
- package/dist/cli.cjs.map +1 -1
- package/dist/index.cjs +575 -169
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +518 -112
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -3061,10 +3061,10 @@ var import_client = require("@temporalio/client");
|
|
|
3061
3061
|
var import_scanner3 = require("@threadbase-sh/scanner");
|
|
3062
3062
|
var import_crypto11 = require("crypto");
|
|
3063
3063
|
var import_events = require("events");
|
|
3064
|
-
var
|
|
3064
|
+
var import_fs19 = require("fs");
|
|
3065
3065
|
var import_promises7 = require("fs/promises");
|
|
3066
3066
|
var import_http = require("http");
|
|
3067
|
-
var
|
|
3067
|
+
var import_os10 = require("os");
|
|
3068
3068
|
var import_path18 = require("path");
|
|
3069
3069
|
var import_readline = require("readline");
|
|
3070
3070
|
|
|
@@ -3324,7 +3324,7 @@ async function handleStartAgentSession(body, deps) {
|
|
|
3324
3324
|
}
|
|
3325
3325
|
|
|
3326
3326
|
// src/api/app.ts
|
|
3327
|
-
var
|
|
3327
|
+
var import_hono18 = require("hono");
|
|
3328
3328
|
|
|
3329
3329
|
// src/db/repositories/devices.repository.ts
|
|
3330
3330
|
var import_crypto5 = require("crypto");
|
|
@@ -3644,12 +3644,222 @@ var errorMiddleware = (err, c) => {
|
|
|
3644
3644
|
return c.json({ error: message }, 500);
|
|
3645
3645
|
};
|
|
3646
3646
|
|
|
3647
|
-
// src/api/routes/
|
|
3647
|
+
// src/api/routes/backup.routes.ts
|
|
3648
3648
|
var import_hono2 = require("hono");
|
|
3649
|
+
var import_os5 = require("os");
|
|
3650
|
+
|
|
3651
|
+
// src/services/backup/backup.ts
|
|
3652
|
+
var BACKUP_FORMAT_VERSION = 1;
|
|
3653
|
+
var BackupError = class extends Error {
|
|
3654
|
+
constructor(message, code) {
|
|
3655
|
+
super(message);
|
|
3656
|
+
this.code = code;
|
|
3657
|
+
}
|
|
3658
|
+
code;
|
|
3659
|
+
};
|
|
3660
|
+
function validateArchive(input) {
|
|
3661
|
+
if (!input || typeof input !== "object") {
|
|
3662
|
+
throw new BackupError("Backup is not an object", "INVALID_ARCHIVE");
|
|
3663
|
+
}
|
|
3664
|
+
const archive = input;
|
|
3665
|
+
const manifest = archive.manifest;
|
|
3666
|
+
if (!manifest || typeof manifest !== "object") {
|
|
3667
|
+
throw new BackupError("Backup is missing its manifest", "INVALID_ARCHIVE");
|
|
3668
|
+
}
|
|
3669
|
+
if (manifest.formatVersion !== BACKUP_FORMAT_VERSION) {
|
|
3670
|
+
throw new BackupError(
|
|
3671
|
+
`Unsupported backup format version ${String(manifest.formatVersion)}; this build reads version ${BACKUP_FORMAT_VERSION}`,
|
|
3672
|
+
"UNSUPPORTED_VERSION"
|
|
3673
|
+
);
|
|
3674
|
+
}
|
|
3675
|
+
if (!Array.isArray(archive.projects)) {
|
|
3676
|
+
throw new BackupError("Backup is missing its projects array", "INVALID_ARCHIVE");
|
|
3677
|
+
}
|
|
3678
|
+
for (const [i, p] of archive.projects.entries()) {
|
|
3679
|
+
if (!p || typeof p !== "object") {
|
|
3680
|
+
throw new BackupError(`Project at index ${i} is not an object`, "INVALID_ARCHIVE");
|
|
3681
|
+
}
|
|
3682
|
+
if (typeof p.id !== "string" || p.id.length === 0) {
|
|
3683
|
+
throw new BackupError(`Project at index ${i} has no id`, "INVALID_ARCHIVE");
|
|
3684
|
+
}
|
|
3685
|
+
if (typeof p.path !== "string" || p.path.length === 0) {
|
|
3686
|
+
throw new BackupError(`Project at index ${i} has no path`, "INVALID_ARCHIVE");
|
|
3687
|
+
}
|
|
3688
|
+
}
|
|
3689
|
+
const ids = new Set(archive.projects.map((p) => p.id));
|
|
3690
|
+
if (ids.size !== archive.projects.length) {
|
|
3691
|
+
throw new BackupError("Backup contains duplicate project ids", "INVALID_ARCHIVE");
|
|
3692
|
+
}
|
|
3693
|
+
return archive;
|
|
3694
|
+
}
|
|
3695
|
+
function remapPaths(projects, rules) {
|
|
3696
|
+
const ordered = [...rules].sort((a, b) => b.from.length - a.from.length);
|
|
3697
|
+
return projects.map((p) => {
|
|
3698
|
+
const rule = ordered.find((r) => p.path === r.from || p.path.startsWith(`${r.from}/`));
|
|
3699
|
+
if (!rule) return p;
|
|
3700
|
+
return { ...p, path: `${rule.to}${p.path.slice(rule.from.length)}` };
|
|
3701
|
+
});
|
|
3702
|
+
}
|
|
3703
|
+
function planRestore(incoming, existing) {
|
|
3704
|
+
const byId = new Map(existing.map((e) => [e.id, e]));
|
|
3705
|
+
const byPath = new Map(existing.map((e) => [e.path, e]));
|
|
3706
|
+
const plan = { create: [], update: [], conflict: [] };
|
|
3707
|
+
for (const p of incoming) {
|
|
3708
|
+
const sameId = byId.get(p.id);
|
|
3709
|
+
if (sameId) {
|
|
3710
|
+
if (sameId.path !== p.path) plan.update.push(p);
|
|
3711
|
+
continue;
|
|
3712
|
+
}
|
|
3713
|
+
const samePath = byPath.get(p.path);
|
|
3714
|
+
if (samePath) {
|
|
3715
|
+
plan.conflict.push({ incoming: p, existingId: samePath.id });
|
|
3716
|
+
continue;
|
|
3717
|
+
}
|
|
3718
|
+
plan.create.push(p);
|
|
3719
|
+
}
|
|
3720
|
+
return plan;
|
|
3721
|
+
}
|
|
3722
|
+
|
|
3723
|
+
// src/version.ts
|
|
3724
|
+
var import_node_fs2 = require("fs");
|
|
3725
|
+
var import_node_path3 = require("path");
|
|
3726
|
+
var cached;
|
|
3727
|
+
function getVersion() {
|
|
3728
|
+
if (cached !== void 0) return cached;
|
|
3729
|
+
cached = resolveVersion();
|
|
3730
|
+
return cached;
|
|
3731
|
+
}
|
|
3732
|
+
function resolveVersion() {
|
|
3733
|
+
const scriptPath = process.argv[1] ?? "";
|
|
3734
|
+
const here = scriptPath ? (0, import_node_path3.dirname)(scriptPath) : process.cwd();
|
|
3735
|
+
let realHere = here;
|
|
3736
|
+
try {
|
|
3737
|
+
realHere = (0, import_node_path3.dirname)((0, import_node_fs2.realpathSync)(scriptPath));
|
|
3738
|
+
} catch {
|
|
3739
|
+
}
|
|
3740
|
+
const searchDirs = realHere === here ? [here, (0, import_node_path3.join)(here, "..")] : [here, (0, import_node_path3.join)(here, ".."), realHere, (0, import_node_path3.join)(realHere, "..")];
|
|
3741
|
+
for (const dir of searchDirs) {
|
|
3742
|
+
try {
|
|
3743
|
+
const v = (0, import_node_fs2.readFileSync)((0, import_node_path3.join)(dir, "version.txt"), "utf8").trim();
|
|
3744
|
+
if (v) return v;
|
|
3745
|
+
} catch {
|
|
3746
|
+
}
|
|
3747
|
+
}
|
|
3748
|
+
try {
|
|
3749
|
+
const pkg = JSON.parse((0, import_node_fs2.readFileSync)((0, import_node_path3.join)(here, "..", "package.json"), "utf8"));
|
|
3750
|
+
if (pkg.version) return `${pkg.version}+source`;
|
|
3751
|
+
} catch {
|
|
3752
|
+
}
|
|
3753
|
+
return "0.0.0+unknown";
|
|
3754
|
+
}
|
|
3755
|
+
|
|
3756
|
+
// src/api/routes/backup.routes.ts
|
|
3757
|
+
function readBody(c) {
|
|
3758
|
+
return new Promise((resolve2, reject) => {
|
|
3759
|
+
const chunks = [];
|
|
3760
|
+
c.env.incoming.on("data", (chunk) => chunks.push(chunk));
|
|
3761
|
+
c.env.incoming.on("end", () => {
|
|
3762
|
+
try {
|
|
3763
|
+
const raw = Buffer.concat(chunks).toString("utf-8");
|
|
3764
|
+
resolve2(raw ? JSON.parse(raw) : {});
|
|
3765
|
+
} catch {
|
|
3766
|
+
reject(new Error("Invalid JSON body"));
|
|
3767
|
+
}
|
|
3768
|
+
});
|
|
3769
|
+
c.env.incoming.on("error", reject);
|
|
3770
|
+
});
|
|
3771
|
+
}
|
|
3772
|
+
var createBackupRoutes = (deps) => {
|
|
3773
|
+
const app = new import_hono2.Hono();
|
|
3774
|
+
app.get("/export", (c) => {
|
|
3775
|
+
const repo = deps.projectsRepo();
|
|
3776
|
+
if (!repo) {
|
|
3777
|
+
return c.json({ error: "Project store is unavailable", code: "STORE_UNAVAILABLE" }, 503);
|
|
3778
|
+
}
|
|
3779
|
+
const projects = repo.listProjects().map((p) => ({
|
|
3780
|
+
id: p.id,
|
|
3781
|
+
path: p.path,
|
|
3782
|
+
name: p.name ?? null,
|
|
3783
|
+
createdAt: p.createdAt,
|
|
3784
|
+
updatedAt: p.updatedAt
|
|
3785
|
+
}));
|
|
3786
|
+
return c.json({
|
|
3787
|
+
manifest: {
|
|
3788
|
+
formatVersion: BACKUP_FORMAT_VERSION,
|
|
3789
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3790
|
+
streamerVersion: getVersion(),
|
|
3791
|
+
sourceHost: (0, import_os5.hostname)(),
|
|
3792
|
+
// No endpoint here exports the API key. The flag is recorded so an
|
|
3793
|
+
// archive is self-describing about its own sensitivity rather than
|
|
3794
|
+
// requiring a reader to infer it.
|
|
3795
|
+
includesSecrets: false,
|
|
3796
|
+
counts: { projects: projects.length }
|
|
3797
|
+
},
|
|
3798
|
+
projects
|
|
3799
|
+
});
|
|
3800
|
+
});
|
|
3801
|
+
app.post("/restore", async (c) => {
|
|
3802
|
+
const repo = deps.projectsRepo();
|
|
3803
|
+
if (!repo) {
|
|
3804
|
+
return c.json({ error: "Project store is unavailable", code: "STORE_UNAVAILABLE" }, 503);
|
|
3805
|
+
}
|
|
3806
|
+
let body;
|
|
3807
|
+
try {
|
|
3808
|
+
body = await readBody(c);
|
|
3809
|
+
} catch {
|
|
3810
|
+
return c.json({ error: "Invalid JSON body", code: "INVALID_BODY" }, 400);
|
|
3811
|
+
}
|
|
3812
|
+
let archive;
|
|
3813
|
+
try {
|
|
3814
|
+
archive = validateArchive(body.archive);
|
|
3815
|
+
} catch (err) {
|
|
3816
|
+
if (err instanceof BackupError) {
|
|
3817
|
+
return c.json({ error: err.message, code: err.code }, 400);
|
|
3818
|
+
}
|
|
3819
|
+
throw err;
|
|
3820
|
+
}
|
|
3821
|
+
const rules = Array.isArray(body.pathMap) ? body.pathMap.filter((r) => typeof r?.from === "string" && typeof r?.to === "string").map((r) => ({ from: r.from, to: r.to })) : [];
|
|
3822
|
+
const incoming = rules.length > 0 ? remapPaths(archive.projects, rules) : archive.projects;
|
|
3823
|
+
const existing = repo.listProjects().map((p) => ({ id: p.id, path: p.path }));
|
|
3824
|
+
const plan = planRestore(incoming, existing);
|
|
3825
|
+
const summary = {
|
|
3826
|
+
create: plan.create.length,
|
|
3827
|
+
update: plan.update.length,
|
|
3828
|
+
conflict: plan.conflict.length
|
|
3829
|
+
};
|
|
3830
|
+
if (body.apply !== true) {
|
|
3831
|
+
return c.json({ applied: false, summary, plan });
|
|
3832
|
+
}
|
|
3833
|
+
if (plan.conflict.length > 0) {
|
|
3834
|
+
return c.json(
|
|
3835
|
+
{
|
|
3836
|
+
error: "Restore has unresolved conflicts",
|
|
3837
|
+
code: "RESTORE_CONFLICT",
|
|
3838
|
+
summary,
|
|
3839
|
+
plan
|
|
3840
|
+
},
|
|
3841
|
+
409
|
|
3842
|
+
);
|
|
3843
|
+
}
|
|
3844
|
+
let applied = 0;
|
|
3845
|
+
for (const p of [...plan.create, ...plan.update]) {
|
|
3846
|
+
try {
|
|
3847
|
+
repo.upsertProjectByPath(p.path, { name: p.name });
|
|
3848
|
+
applied++;
|
|
3849
|
+
} catch {
|
|
3850
|
+
}
|
|
3851
|
+
}
|
|
3852
|
+
return c.json({ applied: true, summary, appliedCount: applied });
|
|
3853
|
+
});
|
|
3854
|
+
return app;
|
|
3855
|
+
};
|
|
3856
|
+
|
|
3857
|
+
// src/api/routes/browse.routes.ts
|
|
3858
|
+
var import_hono3 = require("hono");
|
|
3649
3859
|
var ALREADY_HANDLED = 597;
|
|
3650
3860
|
var alreadyHandled = () => new Response(null, { status: ALREADY_HANDLED });
|
|
3651
3861
|
var createBrowseRoutes = (deps) => {
|
|
3652
|
-
const app = new
|
|
3862
|
+
const app = new import_hono3.Hono();
|
|
3653
3863
|
app.get("/browse", async (c) => {
|
|
3654
3864
|
const url = new URL(c.req.url);
|
|
3655
3865
|
await deps.handleBrowse(url, c.env.outgoing);
|
|
@@ -3663,7 +3873,7 @@ var createBrowseRoutes = (deps) => {
|
|
|
3663
3873
|
};
|
|
3664
3874
|
|
|
3665
3875
|
// src/api/routes/cacheAlert.routes.ts
|
|
3666
|
-
var
|
|
3876
|
+
var import_hono4 = require("hono");
|
|
3667
3877
|
|
|
3668
3878
|
// src/schemas/cacheAlert.schema.ts
|
|
3669
3879
|
var import_zod = require("zod");
|
|
@@ -3686,7 +3896,7 @@ function readRawBody2(req) {
|
|
|
3686
3896
|
});
|
|
3687
3897
|
}
|
|
3688
3898
|
var createCacheAlertRoutes = (deps) => {
|
|
3689
|
-
const app = new
|
|
3899
|
+
const app = new import_hono4.Hono();
|
|
3690
3900
|
app.get("/", (c) => {
|
|
3691
3901
|
const monitor = deps.cacheMonitor();
|
|
3692
3902
|
return c.json({ pending: monitor?.pending ?? null });
|
|
@@ -3723,7 +3933,7 @@ var createCacheAlertRoutes = (deps) => {
|
|
|
3723
3933
|
};
|
|
3724
3934
|
|
|
3725
3935
|
// src/api/routes/config.routes.ts
|
|
3726
|
-
var
|
|
3936
|
+
var import_hono5 = require("hono");
|
|
3727
3937
|
|
|
3728
3938
|
// src/schemas/claudeFlags.schema.ts
|
|
3729
3939
|
var import_zod2 = require("zod");
|
|
@@ -3744,7 +3954,7 @@ function readRawBody3(req) {
|
|
|
3744
3954
|
});
|
|
3745
3955
|
}
|
|
3746
3956
|
var createConfigRoutes = (deps) => {
|
|
3747
|
-
const app = new
|
|
3957
|
+
const app = new import_hono5.Hono();
|
|
3748
3958
|
app.get("/claude-flags", (c) => c.json(deps.claudeFlagsConfig()));
|
|
3749
3959
|
app.get("/feature-flags", (c) => c.json(deps.featureFlagsConfig()));
|
|
3750
3960
|
app.put("/claude-flags", async (c) => {
|
|
@@ -3779,11 +3989,11 @@ var createConfigRoutes = (deps) => {
|
|
|
3779
3989
|
};
|
|
3780
3990
|
|
|
3781
3991
|
// src/api/routes/conversations.routes.ts
|
|
3782
|
-
var
|
|
3992
|
+
var import_hono6 = require("hono");
|
|
3783
3993
|
var ALREADY_HANDLED2 = 597;
|
|
3784
3994
|
var alreadyHandled2 = () => new Response(null, { status: ALREADY_HANDLED2 });
|
|
3785
3995
|
var createConversationRoutes = (deps) => {
|
|
3786
|
-
const app = new
|
|
3996
|
+
const app = new import_hono6.Hono();
|
|
3787
3997
|
app.get("/count", async (c) => {
|
|
3788
3998
|
const url = new URL(c.req.url);
|
|
3789
3999
|
await deps.handleConversationsCount(url, c.env.outgoing);
|
|
@@ -3810,9 +4020,9 @@ var createConversationRoutes = (deps) => {
|
|
|
3810
4020
|
};
|
|
3811
4021
|
|
|
3812
4022
|
// src/api/routes/devices.routes.ts
|
|
3813
|
-
var
|
|
4023
|
+
var import_hono7 = require("hono");
|
|
3814
4024
|
var createDeviceRoutes = (deps) => {
|
|
3815
|
-
const app = new
|
|
4025
|
+
const app = new import_hono7.Hono();
|
|
3816
4026
|
app.get("/", (c) => {
|
|
3817
4027
|
const repo = deps.devicesRepo();
|
|
3818
4028
|
if (!repo) return c.json({ devices: [], available: false });
|
|
@@ -3835,45 +4045,134 @@ var createDeviceRoutes = (deps) => {
|
|
|
3835
4045
|
return app;
|
|
3836
4046
|
};
|
|
3837
4047
|
|
|
3838
|
-
// src/api/routes/
|
|
3839
|
-
var
|
|
4048
|
+
// src/api/routes/diagnostics.routes.ts
|
|
4049
|
+
var import_fs7 = require("fs");
|
|
4050
|
+
var import_hono8 = require("hono");
|
|
3840
4051
|
|
|
3841
|
-
// src/
|
|
3842
|
-
var
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
|
|
3846
|
-
if (
|
|
3847
|
-
|
|
3848
|
-
|
|
4052
|
+
// src/services/diagnostics/diagnostics.ts
|
|
4053
|
+
var DIAGNOSTICS_CONTRACT_VERSION = 1;
|
|
4054
|
+
function redactPath(path) {
|
|
4055
|
+
if (!path) return null;
|
|
4056
|
+
const parts = path.split(/[/\\]/).filter(Boolean);
|
|
4057
|
+
if (parts.length <= 2) return parts.join("/");
|
|
4058
|
+
return `\u2026/${parts.slice(-2).join("/")}`;
|
|
4059
|
+
}
|
|
4060
|
+
function worstStatus(checks) {
|
|
4061
|
+
const rank = { ok: 0, unknown: 1, degraded: 2, failed: 3 };
|
|
4062
|
+
return checks.reduce(
|
|
4063
|
+
(worst, c) => rank[c.status] > rank[worst] ? c.status : worst,
|
|
4064
|
+
"ok"
|
|
4065
|
+
);
|
|
3849
4066
|
}
|
|
3850
|
-
function
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
3854
|
-
|
|
3855
|
-
|
|
3856
|
-
}
|
|
4067
|
+
function buildReport(checks, now = /* @__PURE__ */ new Date()) {
|
|
4068
|
+
return {
|
|
4069
|
+
contractVersion: DIAGNOSTICS_CONTRACT_VERSION,
|
|
4070
|
+
generatedAt: now.toISOString(),
|
|
4071
|
+
overall: worstStatus(checks),
|
|
4072
|
+
checks
|
|
4073
|
+
};
|
|
4074
|
+
}
|
|
4075
|
+
var SECRET_KEY_RE = /(key|token|secret|password|passwd|credential|authorization|cookie)/i;
|
|
4076
|
+
function redactValue(value) {
|
|
4077
|
+
if (Array.isArray(value)) {
|
|
4078
|
+
return value.map((v) => redactValue(v));
|
|
3857
4079
|
}
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
if (v) return v;
|
|
3863
|
-
} catch {
|
|
4080
|
+
if (value && typeof value === "object") {
|
|
4081
|
+
const out = {};
|
|
4082
|
+
for (const [k, v] of Object.entries(value)) {
|
|
4083
|
+
out[k] = SECRET_KEY_RE.test(k) ? "[redacted]" : redactValue(v);
|
|
3864
4084
|
}
|
|
4085
|
+
return out;
|
|
3865
4086
|
}
|
|
4087
|
+
return value;
|
|
4088
|
+
}
|
|
4089
|
+
|
|
4090
|
+
// src/api/routes/diagnostics.routes.ts
|
|
4091
|
+
function providerCheck(name, resolve2) {
|
|
3866
4092
|
try {
|
|
3867
|
-
const
|
|
3868
|
-
|
|
4093
|
+
const exe = resolve2();
|
|
4094
|
+
return {
|
|
4095
|
+
id: `provider:${name}`,
|
|
4096
|
+
status: "ok",
|
|
4097
|
+
summary: `${name} CLI is installed.`,
|
|
4098
|
+
remediation: "NONE",
|
|
4099
|
+
detail: { location: redactPath(exe) }
|
|
4100
|
+
};
|
|
3869
4101
|
} catch {
|
|
4102
|
+
return {
|
|
4103
|
+
id: `provider:${name}`,
|
|
4104
|
+
status: "failed",
|
|
4105
|
+
summary: `${name} CLI could not be located. Sessions for this provider cannot start.`,
|
|
4106
|
+
remediation: "PROVIDER_NOT_INSTALLED"
|
|
4107
|
+
};
|
|
3870
4108
|
}
|
|
3871
|
-
return "0.0.0+unknown";
|
|
3872
4109
|
}
|
|
4110
|
+
var createDiagnosticsRoutes = (deps) => {
|
|
4111
|
+
const app = new import_hono8.Hono();
|
|
4112
|
+
app.get("/", (c) => {
|
|
4113
|
+
const checks = [];
|
|
4114
|
+
checks.push({
|
|
4115
|
+
id: "streamer",
|
|
4116
|
+
status: "ok",
|
|
4117
|
+
summary: "Streamer is running.",
|
|
4118
|
+
remediation: "NONE",
|
|
4119
|
+
detail: { version: getVersion(), uptimeSeconds: Math.floor(process.uptime()) }
|
|
4120
|
+
});
|
|
4121
|
+
checks.push(providerCheck("claude-code", resolveClaudeExe));
|
|
4122
|
+
checks.push(providerCheck("codex-cli", resolveCodexExe));
|
|
4123
|
+
const cacheAlert = deps.cacheMonitor()?.healthzField();
|
|
4124
|
+
checks.push(
|
|
4125
|
+
cacheAlert ? {
|
|
4126
|
+
id: "cache",
|
|
4127
|
+
status: "degraded",
|
|
4128
|
+
summary: "Conversation cache reported an integrity alert.",
|
|
4129
|
+
remediation: "CACHE_DEGRADED"
|
|
4130
|
+
} : {
|
|
4131
|
+
id: "cache",
|
|
4132
|
+
status: "ok",
|
|
4133
|
+
summary: "Conversation cache is healthy.",
|
|
4134
|
+
remediation: "NONE"
|
|
4135
|
+
}
|
|
4136
|
+
);
|
|
4137
|
+
let ptyOk = true;
|
|
4138
|
+
try {
|
|
4139
|
+
require.resolve("node-pty");
|
|
4140
|
+
} catch {
|
|
4141
|
+
ptyOk = false;
|
|
4142
|
+
}
|
|
4143
|
+
checks.push(
|
|
4144
|
+
ptyOk ? { id: "pty", status: "ok", summary: "PTY subsystem is available.", remediation: "NONE" } : {
|
|
4145
|
+
id: "pty",
|
|
4146
|
+
status: "failed",
|
|
4147
|
+
summary: "node-pty failed to load, so no managed session can start.",
|
|
4148
|
+
remediation: "PTY_UNAVAILABLE"
|
|
4149
|
+
}
|
|
4150
|
+
);
|
|
4151
|
+
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
4152
|
+
const claudeProjects = home ? `${home}/.claude/projects` : "";
|
|
4153
|
+
checks.push(
|
|
4154
|
+
claudeProjects && (0, import_fs7.existsSync)(claudeProjects) ? {
|
|
4155
|
+
id: "filesystem",
|
|
4156
|
+
status: "ok",
|
|
4157
|
+
summary: "Provider history directory is present.",
|
|
4158
|
+
remediation: "NONE",
|
|
4159
|
+
detail: { location: redactPath(claudeProjects) }
|
|
4160
|
+
} : {
|
|
4161
|
+
id: "filesystem",
|
|
4162
|
+
status: "degraded",
|
|
4163
|
+
summary: "Provider history directory was not found; history may be unavailable.",
|
|
4164
|
+
remediation: "FS_SCOPE_MISSING"
|
|
4165
|
+
}
|
|
4166
|
+
);
|
|
4167
|
+
return c.json(redactValue(buildReport(checks)));
|
|
4168
|
+
});
|
|
4169
|
+
return app;
|
|
4170
|
+
};
|
|
3873
4171
|
|
|
3874
4172
|
// src/api/routes/health.routes.ts
|
|
4173
|
+
var import_hono9 = require("hono");
|
|
3875
4174
|
var createHealthRoutes = (deps) => {
|
|
3876
|
-
const app = new
|
|
4175
|
+
const app = new import_hono9.Hono();
|
|
3877
4176
|
app.get("/", (c) => {
|
|
3878
4177
|
const cacheAlert = deps.cacheMonitor()?.healthzField();
|
|
3879
4178
|
return c.json({ ok: true, version: getVersion(), ...cacheAlert ? { cacheAlert } : {} });
|
|
@@ -3884,7 +4183,7 @@ var createHealthRoutes = (deps) => {
|
|
|
3884
4183
|
// src/api/routes/logs.routes.ts
|
|
3885
4184
|
var import_node_fs3 = require("fs");
|
|
3886
4185
|
var import_node_path5 = require("path");
|
|
3887
|
-
var
|
|
4186
|
+
var import_hono10 = require("hono");
|
|
3888
4187
|
|
|
3889
4188
|
// src/lifecycle/constants.ts
|
|
3890
4189
|
var import_node_os = require("os");
|
|
@@ -3942,7 +4241,7 @@ function readLogLines(filePath, sinceOffset, limit) {
|
|
|
3942
4241
|
}
|
|
3943
4242
|
}
|
|
3944
4243
|
function createLogsRoutes() {
|
|
3945
|
-
const app = new
|
|
4244
|
+
const app = new import_hono10.Hono();
|
|
3946
4245
|
app.get("/", (c) => {
|
|
3947
4246
|
try {
|
|
3948
4247
|
const sourceParam = (c.req.query("source") || "").toLowerCase();
|
|
@@ -4013,8 +4312,8 @@ function createLogsRoutes() {
|
|
|
4013
4312
|
// src/api/routes/misc.routes.ts
|
|
4014
4313
|
var import_node_child_process = require("child_process");
|
|
4015
4314
|
var import_node_crypto2 = require("crypto");
|
|
4016
|
-
var
|
|
4017
|
-
var
|
|
4315
|
+
var import_hono11 = require("hono");
|
|
4316
|
+
var import_os6 = require("os");
|
|
4018
4317
|
|
|
4019
4318
|
// src/config/update-config.ts
|
|
4020
4319
|
var import_node_fs4 = require("fs");
|
|
@@ -4344,12 +4643,12 @@ function verifyWebhookSignature(body, header, secret) {
|
|
|
4344
4643
|
}
|
|
4345
4644
|
var clientLog = getLogger("client");
|
|
4346
4645
|
var createMiscRoutes = (deps) => {
|
|
4347
|
-
const app = new
|
|
4646
|
+
const app = new import_hono11.Hono();
|
|
4348
4647
|
app.get("/api/info", (c) => {
|
|
4349
4648
|
const ptyIds = deps.ptyAttachedIds();
|
|
4350
4649
|
return c.json({
|
|
4351
4650
|
version: getVersion(),
|
|
4352
|
-
machineName: (0,
|
|
4651
|
+
machineName: (0, import_os6.hostname)(),
|
|
4353
4652
|
platform: process.platform,
|
|
4354
4653
|
activeSessions: deps.sessionStore.list(ptyIds).filter((s) => s.status === "running").length,
|
|
4355
4654
|
publicUrl: deps.publicUrl,
|
|
@@ -4475,11 +4774,11 @@ var createMiscRoutes = (deps) => {
|
|
|
4475
4774
|
};
|
|
4476
4775
|
|
|
4477
4776
|
// src/api/routes/pair.routes.ts
|
|
4478
|
-
var
|
|
4777
|
+
var import_hono12 = require("hono");
|
|
4479
4778
|
var ALREADY_HANDLED3 = 597;
|
|
4480
4779
|
var alreadyHandled3 = () => new Response(null, { status: ALREADY_HANDLED3 });
|
|
4481
4780
|
var createPairRoutes = (deps) => {
|
|
4482
|
-
const app = new
|
|
4781
|
+
const app = new import_hono12.Hono();
|
|
4483
4782
|
app.post("/start", (c) => {
|
|
4484
4783
|
deps.handlePairStart(c.env.outgoing);
|
|
4485
4784
|
return alreadyHandled3();
|
|
@@ -4492,11 +4791,11 @@ var createPairRoutes = (deps) => {
|
|
|
4492
4791
|
};
|
|
4493
4792
|
|
|
4494
4793
|
// src/api/routes/projects.routes.ts
|
|
4495
|
-
var
|
|
4794
|
+
var import_hono13 = require("hono");
|
|
4496
4795
|
var ALREADY_HANDLED4 = 597;
|
|
4497
4796
|
var alreadyHandled4 = () => new Response(null, { status: ALREADY_HANDLED4 });
|
|
4498
4797
|
var createProjectRoutes = (deps) => {
|
|
4499
|
-
const app = new
|
|
4798
|
+
const app = new import_hono13.Hono();
|
|
4500
4799
|
app.get("/", (c) => {
|
|
4501
4800
|
const url = new URL(c.req.url);
|
|
4502
4801
|
deps.handleListProjects(url, c.env.outgoing);
|
|
@@ -4511,7 +4810,7 @@ var createProjectRoutes = (deps) => {
|
|
|
4511
4810
|
};
|
|
4512
4811
|
|
|
4513
4812
|
// src/api/routes/providers.routes.ts
|
|
4514
|
-
var
|
|
4813
|
+
var import_hono14 = require("hono");
|
|
4515
4814
|
|
|
4516
4815
|
// src/services/providers/providerHealth.ts
|
|
4517
4816
|
var import_child_process3 = require("child_process");
|
|
@@ -4634,7 +4933,7 @@ async function providerHealth(name, resolveExe, detect = runVersion) {
|
|
|
4634
4933
|
|
|
4635
4934
|
// src/api/routes/providers.routes.ts
|
|
4636
4935
|
var createProviderRoutes = () => {
|
|
4637
|
-
const app = new
|
|
4936
|
+
const app = new import_hono14.Hono();
|
|
4638
4937
|
app.get("/", async (c) => {
|
|
4639
4938
|
const providers = await Promise.all([
|
|
4640
4939
|
providerHealth(CLAUDE_CODE_PROVIDER, resolveClaudeExe),
|
|
@@ -4646,11 +4945,11 @@ var createProviderRoutes = () => {
|
|
|
4646
4945
|
};
|
|
4647
4946
|
|
|
4648
4947
|
// src/api/routes/scanner.routes.ts
|
|
4649
|
-
var
|
|
4948
|
+
var import_hono15 = require("hono");
|
|
4650
4949
|
var ALREADY_HANDLED5 = 597;
|
|
4651
4950
|
var alreadyHandled5 = () => new Response(null, { status: ALREADY_HANDLED5 });
|
|
4652
4951
|
var createScannerRoutes = (deps) => {
|
|
4653
|
-
const app = new
|
|
4952
|
+
const app = new import_hono15.Hono();
|
|
4654
4953
|
app.get("/api/search", async (c) => {
|
|
4655
4954
|
const url = new URL(c.req.url);
|
|
4656
4955
|
await deps.handleSearch(url, c.env.outgoing);
|
|
@@ -4660,11 +4959,11 @@ var createScannerRoutes = (deps) => {
|
|
|
4660
4959
|
};
|
|
4661
4960
|
|
|
4662
4961
|
// src/api/routes/sessions.routes.ts
|
|
4663
|
-
var
|
|
4962
|
+
var import_hono16 = require("hono");
|
|
4664
4963
|
var ALREADY_HANDLED6 = 597;
|
|
4665
4964
|
var alreadyHandled6 = () => new Response(null, { status: ALREADY_HANDLED6 });
|
|
4666
4965
|
var createSessionRoutes = (deps) => {
|
|
4667
|
-
const app = new
|
|
4966
|
+
const app = new import_hono16.Hono();
|
|
4668
4967
|
app.get("/count", (c) => {
|
|
4669
4968
|
deps.handleSessionsCount(c.env.outgoing);
|
|
4670
4969
|
return alreadyHandled6();
|
|
@@ -4739,9 +5038,9 @@ var createSessionRoutes = (deps) => {
|
|
|
4739
5038
|
};
|
|
4740
5039
|
|
|
4741
5040
|
// src/api/routes/ws.routes.ts
|
|
4742
|
-
var
|
|
5041
|
+
var import_hono17 = require("hono");
|
|
4743
5042
|
var createWsRoutes = (deps, upgradeWebSocket) => {
|
|
4744
|
-
const app = new
|
|
5043
|
+
const app = new import_hono17.Hono();
|
|
4745
5044
|
app.get(
|
|
4746
5045
|
"/ws",
|
|
4747
5046
|
upgradeWebSocket(() => {
|
|
@@ -4767,7 +5066,7 @@ var createWsRoutes = (deps, upgradeWebSocket) => {
|
|
|
4767
5066
|
|
|
4768
5067
|
// src/api/app.ts
|
|
4769
5068
|
var createHonoApp = (deps, upgradeWebSocket) => {
|
|
4770
|
-
const app = new
|
|
5069
|
+
const app = new import_hono18.Hono();
|
|
4771
5070
|
const httpLog = getLogger("http");
|
|
4772
5071
|
app.use("*", async (c, next) => {
|
|
4773
5072
|
const start = Date.now();
|
|
@@ -4788,6 +5087,7 @@ var createHonoApp = (deps, upgradeWebSocket) => {
|
|
|
4788
5087
|
app.use("*", authMiddleware(deps));
|
|
4789
5088
|
app.onError(errorMiddleware);
|
|
4790
5089
|
app.route("/healthz", createHealthRoutes(deps));
|
|
5090
|
+
app.route("/api/diagnostics", createDiagnosticsRoutes(deps));
|
|
4791
5091
|
app.route("/", createMiscRoutes(deps));
|
|
4792
5092
|
app.route("/api/sessions", createSessionRoutes(deps));
|
|
4793
5093
|
app.route("/api/conversations", createConversationRoutes(deps));
|
|
@@ -4796,6 +5096,7 @@ var createHonoApp = (deps, upgradeWebSocket) => {
|
|
|
4796
5096
|
app.route("/api/projects", createProjectRoutes(deps));
|
|
4797
5097
|
app.route("/api/providers", createProviderRoutes());
|
|
4798
5098
|
app.route("/api/devices", createDeviceRoutes(deps));
|
|
5099
|
+
app.route("/api/backup", createBackupRoutes(deps));
|
|
4799
5100
|
app.route("/api/pair", createPairRoutes(deps));
|
|
4800
5101
|
app.route("/api", createBrowseRoutes(deps));
|
|
4801
5102
|
app.route("/", createScannerRoutes(deps));
|
|
@@ -4861,13 +5162,13 @@ async function createDirectory(parentAbsolutePath, name) {
|
|
|
4861
5162
|
// src/conversation-cache.ts
|
|
4862
5163
|
var import_scanner2 = require("@threadbase-sh/scanner");
|
|
4863
5164
|
var import_better_sqlite3 = __toESM(require("better-sqlite3"), 1);
|
|
4864
|
-
var
|
|
5165
|
+
var import_fs10 = require("fs");
|
|
4865
5166
|
var import_promises3 = require("fs/promises");
|
|
4866
5167
|
var import_path12 = require("path");
|
|
4867
5168
|
var import_promises4 = require("timers/promises");
|
|
4868
5169
|
|
|
4869
5170
|
// src/db/sqlite-migrate.ts
|
|
4870
|
-
var
|
|
5171
|
+
var import_fs8 = require("fs");
|
|
4871
5172
|
var import_path10 = require("path");
|
|
4872
5173
|
var import_url2 = require("url");
|
|
4873
5174
|
var import_meta2 = {};
|
|
@@ -4886,7 +5187,7 @@ CREATE TABLE IF NOT EXISTS schema_migrations (
|
|
|
4886
5187
|
function runSqliteMigrations(db, migrationsDir) {
|
|
4887
5188
|
db.exec(SCHEMA_MIGRATIONS_SQL);
|
|
4888
5189
|
const dir = migrationsDir ?? (0, import_path10.join)(getMigrationsDir2(), "migrations");
|
|
4889
|
-
const files = (0,
|
|
5190
|
+
const files = (0, import_fs8.readdirSync)(dir).filter((f) => f.endsWith(".sql")).sort();
|
|
4890
5191
|
const appliedRows = db.prepare("SELECT id FROM schema_migrations").all();
|
|
4891
5192
|
const appliedSet = new Set(appliedRows.map((r) => r.id));
|
|
4892
5193
|
const recordApplied = db.prepare("INSERT INTO schema_migrations (id, applied_at) VALUES (?, ?)");
|
|
@@ -4897,7 +5198,7 @@ function runSqliteMigrations(db, migrationsDir) {
|
|
|
4897
5198
|
skipped.push(file);
|
|
4898
5199
|
continue;
|
|
4899
5200
|
}
|
|
4900
|
-
const sql = (0,
|
|
5201
|
+
const sql = (0, import_fs8.readFileSync)((0, import_path10.join)(dir, file), "utf-8");
|
|
4901
5202
|
const tx = db.transaction(() => {
|
|
4902
5203
|
db.exec(sql);
|
|
4903
5204
|
recordApplied.run(file, (/* @__PURE__ */ new Date()).toISOString());
|
|
@@ -4909,7 +5210,7 @@ function runSqliteMigrations(db, migrationsDir) {
|
|
|
4909
5210
|
}
|
|
4910
5211
|
|
|
4911
5212
|
// src/services/conversations/isAgentConversation.ts
|
|
4912
|
-
var
|
|
5213
|
+
var import_fs9 = require("fs");
|
|
4913
5214
|
var DEFAULT_AGENT_ENTRYPOINTS = /* @__PURE__ */ new Set(["sdk-cli", "claude-vscode"]);
|
|
4914
5215
|
var CHUNK_BYTES = 64 * 1024;
|
|
4915
5216
|
var ENTRYPOINT_PROBE = `"entrypoint":`;
|
|
@@ -4931,12 +5232,12 @@ function isAgentFile(filePath, entrypoints = DEFAULT_AGENT_ENTRYPOINTS) {
|
|
|
4931
5232
|
if (cached2 !== void 0) return cached2;
|
|
4932
5233
|
let fd;
|
|
4933
5234
|
try {
|
|
4934
|
-
fd = (0,
|
|
5235
|
+
fd = (0, import_fs9.openSync)(filePath, "r");
|
|
4935
5236
|
} catch {
|
|
4936
5237
|
return false;
|
|
4937
5238
|
}
|
|
4938
5239
|
try {
|
|
4939
|
-
const fileSize = (0,
|
|
5240
|
+
const fileSize = (0, import_fs9.statSync)(filePath).size;
|
|
4940
5241
|
if (fileSize === 0) {
|
|
4941
5242
|
fileDecisionCache.set(key, false);
|
|
4942
5243
|
return false;
|
|
@@ -4947,7 +5248,7 @@ function isAgentFile(filePath, entrypoints = DEFAULT_AGENT_ENTRYPOINTS) {
|
|
|
4947
5248
|
let carry = "";
|
|
4948
5249
|
while (offset < fileSize) {
|
|
4949
5250
|
const toRead = Math.min(CHUNK_BYTES, fileSize - offset);
|
|
4950
|
-
const got = (0,
|
|
5251
|
+
const got = (0, import_fs9.readSync)(fd, buf, 0, toRead, offset);
|
|
4951
5252
|
if (got <= 0) break;
|
|
4952
5253
|
const chunk = carry + buf.toString("utf8", 0, got);
|
|
4953
5254
|
for (const marker of markers) {
|
|
@@ -4968,7 +5269,7 @@ function isAgentFile(filePath, entrypoints = DEFAULT_AGENT_ENTRYPOINTS) {
|
|
|
4968
5269
|
} catch {
|
|
4969
5270
|
return false;
|
|
4970
5271
|
} finally {
|
|
4971
|
-
(0,
|
|
5272
|
+
(0, import_fs9.closeSync)(fd);
|
|
4972
5273
|
}
|
|
4973
5274
|
}
|
|
4974
5275
|
function parseAgentEntrypointsEnv(raw) {
|
|
@@ -5543,7 +5844,7 @@ var ConversationCache = class _ConversationCache {
|
|
|
5543
5844
|
if (!fileState) return null;
|
|
5544
5845
|
let stat3;
|
|
5545
5846
|
try {
|
|
5546
|
-
stat3 = (0,
|
|
5847
|
+
stat3 = (0, import_fs10.statSync)(filePath);
|
|
5547
5848
|
} catch {
|
|
5548
5849
|
return null;
|
|
5549
5850
|
}
|
|
@@ -5564,17 +5865,17 @@ var ConversationCache = class _ConversationCache {
|
|
|
5564
5865
|
);
|
|
5565
5866
|
if (rows.length === 0) return { messages: [], total, fromIndex: from };
|
|
5566
5867
|
const messages = [];
|
|
5567
|
-
const fd = (0,
|
|
5868
|
+
const fd = (0, import_fs10.openSync)(filePath, "r");
|
|
5568
5869
|
try {
|
|
5569
5870
|
const state = (0, import_scanner2.createJsonlParseState)();
|
|
5570
5871
|
for (const row of rows) {
|
|
5571
5872
|
const buf = Buffer.alloc(row.byte_length);
|
|
5572
|
-
(0,
|
|
5873
|
+
(0, import_fs10.readSync)(fd, buf, 0, row.byte_length, row.byte_offset);
|
|
5573
5874
|
const msg = (0, import_scanner2.parseJsonlLine)(buf.toString("utf-8"), state);
|
|
5574
5875
|
if (msg) messages.push(msg);
|
|
5575
5876
|
}
|
|
5576
5877
|
} finally {
|
|
5577
|
-
(0,
|
|
5878
|
+
(0, import_fs10.closeSync)(fd);
|
|
5578
5879
|
}
|
|
5579
5880
|
return { messages, total, fromIndex: from };
|
|
5580
5881
|
}
|
|
@@ -5602,14 +5903,14 @@ var ConversationCache = class _ConversationCache {
|
|
|
5602
5903
|
isAgentFileCached(filePath) {
|
|
5603
5904
|
let s;
|
|
5604
5905
|
try {
|
|
5605
|
-
s = (0,
|
|
5906
|
+
s = (0, import_fs10.statSync)(filePath);
|
|
5606
5907
|
} catch {
|
|
5607
5908
|
return false;
|
|
5608
5909
|
}
|
|
5609
5910
|
return this.classifyAgentFile(filePath, s.mtimeMs, s.size);
|
|
5610
5911
|
}
|
|
5611
5912
|
static open(dbPath, tailSize = 10, migrationsDir, options) {
|
|
5612
|
-
(0,
|
|
5913
|
+
(0, import_fs10.mkdirSync)((0, import_path12.dirname)(dbPath), { recursive: true });
|
|
5613
5914
|
const db = new import_better_sqlite3.default(dbPath);
|
|
5614
5915
|
db.pragma("journal_mode = WAL");
|
|
5615
5916
|
db.pragma("foreign_keys = ON");
|
|
@@ -5796,7 +6097,7 @@ var ConversationCache = class _ConversationCache {
|
|
|
5796
6097
|
let mtimeMs = null;
|
|
5797
6098
|
let fileSize = null;
|
|
5798
6099
|
try {
|
|
5799
|
-
const s = (0,
|
|
6100
|
+
const s = (0, import_fs10.statSync)(m.filePath);
|
|
5800
6101
|
mtimeMs = s.mtimeMs;
|
|
5801
6102
|
fileSize = s.size;
|
|
5802
6103
|
} catch {
|
|
@@ -5856,8 +6157,8 @@ var ConversationCache = class _ConversationCache {
|
|
|
5856
6157
|
let fileSize;
|
|
5857
6158
|
let fd;
|
|
5858
6159
|
try {
|
|
5859
|
-
fileSize = (0,
|
|
5860
|
-
fd = (0,
|
|
6160
|
+
fileSize = (0, import_fs10.statSync)(filePath).size;
|
|
6161
|
+
fd = (0, import_fs10.openSync)(filePath, "r");
|
|
5861
6162
|
} catch {
|
|
5862
6163
|
return false;
|
|
5863
6164
|
}
|
|
@@ -5870,7 +6171,7 @@ var ConversationCache = class _ConversationCache {
|
|
|
5870
6171
|
while (pos > 0 && lines.length < this.tailSize * 4) {
|
|
5871
6172
|
const toRead = Math.min(CHUNK, pos);
|
|
5872
6173
|
pos -= toRead;
|
|
5873
|
-
(0,
|
|
6174
|
+
(0, import_fs10.readSync)(fd, buf, 0, toRead, pos);
|
|
5874
6175
|
const chunk = buf.subarray(0, toRead).toString("utf8");
|
|
5875
6176
|
const combined = chunk + partial;
|
|
5876
6177
|
const parts = combined.split("\n");
|
|
@@ -5881,7 +6182,7 @@ var ConversationCache = class _ConversationCache {
|
|
|
5881
6182
|
}
|
|
5882
6183
|
if (partial) lines.push(partial);
|
|
5883
6184
|
} finally {
|
|
5884
|
-
(0,
|
|
6185
|
+
(0, import_fs10.closeSync)(fd);
|
|
5885
6186
|
}
|
|
5886
6187
|
const msgs = [];
|
|
5887
6188
|
for (let i = 0; i < lines.length && msgs.length < this.tailSize; i++) {
|
|
@@ -6094,7 +6395,7 @@ var ConversationCache = class _ConversationCache {
|
|
|
6094
6395
|
* `handleGetConversation` can still serve the cached tail even when the
|
|
6095
6396
|
* JSONL has been deleted.
|
|
6096
6397
|
*/
|
|
6097
|
-
pruneGhostFiles(exists =
|
|
6398
|
+
pruneGhostFiles(exists = import_fs10.existsSync) {
|
|
6098
6399
|
const rows = this.stmts.allFilePaths.all();
|
|
6099
6400
|
const ghosts = [];
|
|
6100
6401
|
const prune = this.db.transaction((ids) => {
|
|
@@ -6149,7 +6450,7 @@ var ConversationCache = class _ConversationCache {
|
|
|
6149
6450
|
* Returns the removed IDs.
|
|
6150
6451
|
*/
|
|
6151
6452
|
reconcileDeletions(livePaths, opts) {
|
|
6152
|
-
const exists = opts?.exists ??
|
|
6453
|
+
const exists = opts?.exists ?? import_fs10.existsSync;
|
|
6153
6454
|
const rows = this.stmts.allFilePaths.all();
|
|
6154
6455
|
const removed = [];
|
|
6155
6456
|
const drop = this.db.transaction((ids) => {
|
|
@@ -6184,7 +6485,7 @@ var ConversationCache = class _ConversationCache {
|
|
|
6184
6485
|
* reports drift for the CacheIntegrityMonitor to classify. `tailed` flags
|
|
6185
6486
|
* rows that still have cached history (which pruneGhostFiles would keep).
|
|
6186
6487
|
*/
|
|
6187
|
-
listMissingFiles(exists =
|
|
6488
|
+
listMissingFiles(exists = import_fs10.existsSync) {
|
|
6188
6489
|
const rows = this.stmts.allFilePathsWithTitle.all();
|
|
6189
6490
|
const missing = [];
|
|
6190
6491
|
for (const row of rows) {
|
|
@@ -6554,8 +6855,8 @@ async function recordUpload(pool2, instanceId, row) {
|
|
|
6554
6855
|
}
|
|
6555
6856
|
|
|
6556
6857
|
// src/handlers/handleListProjects.ts
|
|
6557
|
-
var
|
|
6558
|
-
var
|
|
6858
|
+
var import_fs11 = require("fs");
|
|
6859
|
+
var import_os7 = require("os");
|
|
6559
6860
|
var import_path13 = require("path");
|
|
6560
6861
|
function decodeProjectPath(dirName) {
|
|
6561
6862
|
return dirName.replace(/-/g, "/");
|
|
@@ -6563,14 +6864,14 @@ function decodeProjectPath(dirName) {
|
|
|
6563
6864
|
function handleListProjects(url, res) {
|
|
6564
6865
|
const limit = Math.max(1, parseInt(url.searchParams.get("limit") ?? "50", 10) || 50);
|
|
6565
6866
|
const offset = Math.max(0, parseInt(url.searchParams.get("offset") ?? "0", 10) || 0);
|
|
6566
|
-
const projectsDir = (0, import_path13.join)((0,
|
|
6867
|
+
const projectsDir = (0, import_path13.join)((0, import_os7.homedir)(), ".claude", "projects");
|
|
6567
6868
|
let entries;
|
|
6568
6869
|
try {
|
|
6569
|
-
entries = (0,
|
|
6870
|
+
entries = (0, import_fs11.readdirSync)(projectsDir).map((dirName) => {
|
|
6570
6871
|
const fullPath = (0, import_path13.join)(projectsDir, dirName);
|
|
6571
6872
|
let mtime = 0;
|
|
6572
6873
|
try {
|
|
6573
|
-
mtime = (0,
|
|
6874
|
+
mtime = (0, import_fs11.statSync)(fullPath).mtimeMs;
|
|
6574
6875
|
} catch {
|
|
6575
6876
|
}
|
|
6576
6877
|
const path = decodeProjectPath(dirName);
|
|
@@ -6685,19 +6986,19 @@ function setCacheMetadata(repo, key, value) {
|
|
|
6685
6986
|
|
|
6686
6987
|
// src/services/cache-integrity/cacheIntegrityMonitor.ts
|
|
6687
6988
|
var import_crypto9 = require("crypto");
|
|
6688
|
-
var
|
|
6989
|
+
var import_fs14 = require("fs");
|
|
6689
6990
|
|
|
6690
6991
|
// src/services/cache-integrity/alertStore.ts
|
|
6691
|
-
var
|
|
6692
|
-
var
|
|
6992
|
+
var import_fs12 = require("fs");
|
|
6993
|
+
var import_os8 = require("os");
|
|
6693
6994
|
var import_path14 = require("path");
|
|
6694
6995
|
function alertStatePath() {
|
|
6695
|
-
const dir = process.env.THREADBASE_CONFIG_DIR ?? (0, import_path14.join)((0,
|
|
6996
|
+
const dir = process.env.THREADBASE_CONFIG_DIR ?? (0, import_path14.join)((0, import_os8.homedir)(), ".threadbase");
|
|
6696
6997
|
return (0, import_path14.join)(dir, "cache-alert.json");
|
|
6697
6998
|
}
|
|
6698
6999
|
function loadAlertState() {
|
|
6699
7000
|
try {
|
|
6700
|
-
const parsed = JSON.parse((0,
|
|
7001
|
+
const parsed = JSON.parse((0, import_fs12.readFileSync)(alertStatePath(), "utf-8"));
|
|
6701
7002
|
return parsed && typeof parsed === "object" ? parsed : {};
|
|
6702
7003
|
} catch {
|
|
6703
7004
|
return {};
|
|
@@ -6705,13 +7006,13 @@ function loadAlertState() {
|
|
|
6705
7006
|
}
|
|
6706
7007
|
function saveAlertState(state) {
|
|
6707
7008
|
const path = alertStatePath();
|
|
6708
|
-
(0,
|
|
6709
|
-
(0,
|
|
7009
|
+
(0, import_fs12.mkdirSync)((0, import_path14.dirname)(path), { recursive: true });
|
|
7010
|
+
(0, import_fs12.writeFileSync)(path, `${JSON.stringify(state, null, 2)}
|
|
6710
7011
|
`);
|
|
6711
7012
|
}
|
|
6712
7013
|
|
|
6713
7014
|
// src/services/cache-integrity/backup.ts
|
|
6714
|
-
var
|
|
7015
|
+
var import_fs13 = require("fs");
|
|
6715
7016
|
var import_path15 = require("path");
|
|
6716
7017
|
var DEFAULT_RETAIN = 3;
|
|
6717
7018
|
function retainCount() {
|
|
@@ -6724,16 +7025,16 @@ function timestamp(d) {
|
|
|
6724
7025
|
}
|
|
6725
7026
|
async function backupCacheDb(db, cacheDir) {
|
|
6726
7027
|
const backupsDir = (0, import_path15.join)(cacheDir, "backups");
|
|
6727
|
-
(0,
|
|
7028
|
+
(0, import_fs13.mkdirSync)(backupsDir, { recursive: true });
|
|
6728
7029
|
const destPath = (0, import_path15.join)(backupsDir, `cache-${timestamp(/* @__PURE__ */ new Date())}.db`);
|
|
6729
7030
|
await db.backup(destPath);
|
|
6730
7031
|
const retain = retainCount();
|
|
6731
|
-
const backups = (0,
|
|
7032
|
+
const backups = (0, import_fs13.readdirSync)(backupsDir).filter((f) => f.startsWith("cache-") && f.endsWith(".db")).map((f) => {
|
|
6732
7033
|
const full = (0, import_path15.join)(backupsDir, f);
|
|
6733
|
-
return { full, mtime: (0,
|
|
7034
|
+
return { full, mtime: (0, import_fs13.statSync)(full).mtimeMs };
|
|
6734
7035
|
}).sort((a, b) => b.mtime - a.mtime);
|
|
6735
7036
|
for (const stale of backups.slice(retain)) {
|
|
6736
|
-
if ((0,
|
|
7037
|
+
if ((0, import_fs13.existsSync)(stale.full)) (0, import_fs13.unlinkSync)(stale.full);
|
|
6737
7038
|
}
|
|
6738
7039
|
return destPath;
|
|
6739
7040
|
}
|
|
@@ -6830,7 +7131,7 @@ var CacheIntegrityMonitor = class {
|
|
|
6830
7131
|
* the pending record, back up on high severity, and broadcast the alert.
|
|
6831
7132
|
*/
|
|
6832
7133
|
async runDetection(detectedAt = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
6833
|
-
const all = this.cache.listMissingFiles(
|
|
7134
|
+
const all = this.cache.listMissingFiles(import_fs14.existsSync);
|
|
6834
7135
|
const missing = all.filter((m) => !this.ignoredIds.has(m.id));
|
|
6835
7136
|
if (missing.length === 0) {
|
|
6836
7137
|
if (this._pending) {
|
|
@@ -6934,7 +7235,7 @@ var CacheIntegrityMonitor = class {
|
|
|
6934
7235
|
case "prune_all": {
|
|
6935
7236
|
await this.ensureBackup(pending);
|
|
6936
7237
|
const backupPath = pending.backupPath;
|
|
6937
|
-
const stillMissing = pending.missing.filter((m) => !(0,
|
|
7238
|
+
const stillMissing = pending.missing.filter((m) => !(0, import_fs14.existsSync)(m.filePath)).map((m) => m.id);
|
|
6938
7239
|
const pruned = this.cache.dropRowsById(stillMissing);
|
|
6939
7240
|
this.applyDeferredUnlinks();
|
|
6940
7241
|
this.clearPending();
|
|
@@ -6996,7 +7297,7 @@ var CacheIntegrityMonitor = class {
|
|
|
6996
7297
|
|
|
6997
7298
|
// src/services/conversations/conversationWatcher.ts
|
|
6998
7299
|
var import_chokidar = __toESM(require("chokidar"), 1);
|
|
6999
|
-
var
|
|
7300
|
+
var import_fs15 = require("fs");
|
|
7000
7301
|
var import_promises5 = require("fs/promises");
|
|
7001
7302
|
var ConversationWatcher = class {
|
|
7002
7303
|
files = /* @__PURE__ */ new Map();
|
|
@@ -7022,7 +7323,7 @@ var ConversationWatcher = class {
|
|
|
7022
7323
|
if (this.files.has(key)) return;
|
|
7023
7324
|
let offset;
|
|
7024
7325
|
try {
|
|
7025
|
-
offset = (0,
|
|
7326
|
+
offset = (0, import_fs15.statSync)(filePath).size;
|
|
7026
7327
|
} catch {
|
|
7027
7328
|
offset = 0;
|
|
7028
7329
|
}
|
|
@@ -7204,14 +7505,14 @@ function findSearchTarget(messages, query) {
|
|
|
7204
7505
|
}
|
|
7205
7506
|
|
|
7206
7507
|
// src/services/conversations/pruneAgentConversations.ts
|
|
7207
|
-
var
|
|
7508
|
+
var import_fs16 = require("fs");
|
|
7208
7509
|
function pruneAgentConversations(cache) {
|
|
7209
7510
|
const db = cache.getDatabase();
|
|
7210
7511
|
const rows = db.prepare("SELECT id, file_path FROM conversation_meta").all();
|
|
7211
7512
|
let pruned = 0;
|
|
7212
7513
|
let missing = 0;
|
|
7213
7514
|
for (const row of rows) {
|
|
7214
|
-
if (!(0,
|
|
7515
|
+
if (!(0, import_fs16.existsSync)(row.file_path)) {
|
|
7215
7516
|
missing += 1;
|
|
7216
7517
|
continue;
|
|
7217
7518
|
}
|
|
@@ -7312,22 +7613,22 @@ function refreshConversationCache(deps) {
|
|
|
7312
7613
|
}
|
|
7313
7614
|
|
|
7314
7615
|
// src/services/conversations/shouldRefreshProjectsFromHdd.ts
|
|
7315
|
-
var
|
|
7316
|
-
var
|
|
7616
|
+
var import_fs17 = require("fs");
|
|
7617
|
+
var import_os9 = require("os");
|
|
7317
7618
|
var import_path16 = require("path");
|
|
7318
|
-
var DEFAULT_PROJECTS_DIR = (0, import_path16.join)((0,
|
|
7619
|
+
var DEFAULT_PROJECTS_DIR = (0, import_path16.join)((0, import_os9.homedir)(), ".claude", "projects");
|
|
7319
7620
|
function maxProjectsTreeMtimeMs(projectsDir) {
|
|
7320
7621
|
let maxMs;
|
|
7321
7622
|
try {
|
|
7322
|
-
maxMs = (0,
|
|
7623
|
+
maxMs = (0, import_fs17.statSync)(projectsDir).mtimeMs;
|
|
7323
7624
|
} catch {
|
|
7324
7625
|
return null;
|
|
7325
7626
|
}
|
|
7326
7627
|
try {
|
|
7327
|
-
for (const ent of (0,
|
|
7628
|
+
for (const ent of (0, import_fs17.readdirSync)(projectsDir, { withFileTypes: true })) {
|
|
7328
7629
|
if (!ent.isDirectory()) continue;
|
|
7329
7630
|
try {
|
|
7330
|
-
const childMs = (0,
|
|
7631
|
+
const childMs = (0, import_fs17.statSync)((0, import_path16.join)(projectsDir, ent.name)).mtimeMs;
|
|
7331
7632
|
if (childMs > maxMs) maxMs = childMs;
|
|
7332
7633
|
} catch {
|
|
7333
7634
|
}
|
|
@@ -8088,8 +8389,90 @@ function resolveAnswer(pending, body) {
|
|
|
8088
8389
|
}
|
|
8089
8390
|
}
|
|
8090
8391
|
|
|
8392
|
+
// src/services/search/searchQuery.ts
|
|
8393
|
+
var DEFAULT_SEARCH_LIMIT = 50;
|
|
8394
|
+
var MAX_SEARCH_LIMIT = 200;
|
|
8395
|
+
var MAX_QUERY_LENGTH = 256;
|
|
8396
|
+
var SearchQueryError = class extends Error {
|
|
8397
|
+
constructor(message, code) {
|
|
8398
|
+
super(message);
|
|
8399
|
+
this.code = code;
|
|
8400
|
+
}
|
|
8401
|
+
code;
|
|
8402
|
+
};
|
|
8403
|
+
function intOr(raw, fallback) {
|
|
8404
|
+
if (raw === null) return fallback;
|
|
8405
|
+
const n = Number.parseInt(raw, 10);
|
|
8406
|
+
return Number.isFinite(n) ? n : fallback;
|
|
8407
|
+
}
|
|
8408
|
+
function parseSearchQuery(params) {
|
|
8409
|
+
const q = (params.get("q") ?? "").trim();
|
|
8410
|
+
if (!q) {
|
|
8411
|
+
throw new SearchQueryError("Missing query parameter: q", "invalid_query");
|
|
8412
|
+
}
|
|
8413
|
+
if (q.length > MAX_QUERY_LENGTH) {
|
|
8414
|
+
throw new SearchQueryError(`Query exceeds ${MAX_QUERY_LENGTH} characters`, "query_too_long");
|
|
8415
|
+
}
|
|
8416
|
+
const limit = Math.min(
|
|
8417
|
+
Math.max(intOr(params.get("limit"), DEFAULT_SEARCH_LIMIT), 1),
|
|
8418
|
+
MAX_SEARCH_LIMIT
|
|
8419
|
+
);
|
|
8420
|
+
const offset = Math.max(intOr(params.get("offset"), 0), 0);
|
|
8421
|
+
const filters = {};
|
|
8422
|
+
const provider = params.get("provider");
|
|
8423
|
+
if (provider !== null) {
|
|
8424
|
+
if (!isProviderName(provider)) {
|
|
8425
|
+
throw new SearchQueryError(`Unknown provider: ${provider}`, "invalid_filter");
|
|
8426
|
+
}
|
|
8427
|
+
filters.provider = provider;
|
|
8428
|
+
}
|
|
8429
|
+
const projectPath = params.get("projectPath");
|
|
8430
|
+
if (projectPath) filters.projectPath = projectPath;
|
|
8431
|
+
const branch = params.get("branch");
|
|
8432
|
+
if (branch) filters.branch = branch;
|
|
8433
|
+
for (const [key, field] of [
|
|
8434
|
+
["since", "since"],
|
|
8435
|
+
["until", "until"]
|
|
8436
|
+
]) {
|
|
8437
|
+
const raw = params.get(key);
|
|
8438
|
+
if (raw === null) continue;
|
|
8439
|
+
const ms = Date.parse(raw);
|
|
8440
|
+
if (Number.isNaN(ms)) {
|
|
8441
|
+
throw new SearchQueryError(`Invalid ${key}: expected an ISO 8601 date`, "invalid_filter");
|
|
8442
|
+
}
|
|
8443
|
+
filters[field] = ms;
|
|
8444
|
+
}
|
|
8445
|
+
if (filters.since != null && filters.until != null && filters.since > filters.until) {
|
|
8446
|
+
throw new SearchQueryError("`since` must not be after `until`", "invalid_filter");
|
|
8447
|
+
}
|
|
8448
|
+
return { q, limit, offset, filters };
|
|
8449
|
+
}
|
|
8450
|
+
function applyFilters(results, filters) {
|
|
8451
|
+
return results.filter((r) => {
|
|
8452
|
+
if (filters.provider && r.provider !== filters.provider) return false;
|
|
8453
|
+
if (filters.projectPath && r.projectPath !== filters.projectPath) return false;
|
|
8454
|
+
if (filters.branch && r.branch !== filters.branch) return false;
|
|
8455
|
+
if (filters.since != null || filters.until != null) {
|
|
8456
|
+
const ts = r.lastActivity == null ? Number.NaN : new Date(r.lastActivity).getTime();
|
|
8457
|
+
if (Number.isNaN(ts)) return false;
|
|
8458
|
+
if (filters.since != null && ts < filters.since) return false;
|
|
8459
|
+
if (filters.until != null && ts > filters.until) return false;
|
|
8460
|
+
}
|
|
8461
|
+
return true;
|
|
8462
|
+
});
|
|
8463
|
+
}
|
|
8464
|
+
function paginate(results, offset, limit) {
|
|
8465
|
+
const items = results.slice(offset, offset + limit);
|
|
8466
|
+
return {
|
|
8467
|
+
items,
|
|
8468
|
+
total: results.length,
|
|
8469
|
+
offset,
|
|
8470
|
+
hasMore: offset + items.length < results.length
|
|
8471
|
+
};
|
|
8472
|
+
}
|
|
8473
|
+
|
|
8091
8474
|
// src/services/sessions/conversationBusy.ts
|
|
8092
|
-
var
|
|
8475
|
+
var import_fs18 = require("fs");
|
|
8093
8476
|
var RESUME_BUSY_WINDOW_MS = 12e4;
|
|
8094
8477
|
function resolveResumeBusyWindowMs(env = process.env) {
|
|
8095
8478
|
const raw = env.THREADBASE_RESUME_BUSY_WINDOW_MS;
|
|
@@ -8106,7 +8489,7 @@ function conversationBusy(input) {
|
|
|
8106
8489
|
let lastActivityMs = null;
|
|
8107
8490
|
if (input.jsonlPath) {
|
|
8108
8491
|
try {
|
|
8109
|
-
const mtimeMs = (0,
|
|
8492
|
+
const mtimeMs = (0, import_fs18.statSync)(input.jsonlPath).mtimeMs;
|
|
8110
8493
|
const age = now - mtimeMs;
|
|
8111
8494
|
lastActivityMs = Math.max(0, age);
|
|
8112
8495
|
const isSelfEcho = input.selfPtyEndedAt != null && mtimeMs <= input.selfPtyEndedAt + SELF_ACTIVITY_SKEW_MS;
|
|
@@ -8790,6 +9173,8 @@ var DEFAULT_PTY_GRACE_PERIOD_MS = 27e4;
|
|
|
8790
9173
|
var GRACE_MAX_DEFERS = 4;
|
|
8791
9174
|
var IDLE_REAP_AFTER_MS = 6 * 60 * 60 * 1e3;
|
|
8792
9175
|
var IDLE_REAP_SWEEP_MS = 5 * 60 * 1e3;
|
|
9176
|
+
var SEARCH_OVERFETCH = 4;
|
|
9177
|
+
var SEARCH_MAX_SCAN = 1e3;
|
|
8793
9178
|
var RESUME_DISCOVERY_TIMEOUT_MS = 750;
|
|
8794
9179
|
var DISCOVERY_TTL_MS = 15e3;
|
|
8795
9180
|
var ADOPT_KILL_TIMEOUT_MS = 5e3;
|
|
@@ -9030,7 +9415,7 @@ var StreamerServer = class {
|
|
|
9030
9415
|
this.skipStartupWarmup = config.skipStartupWarmup ?? false;
|
|
9031
9416
|
this.scannerPersistenceDisabled = config.scannerPersistent === false;
|
|
9032
9417
|
this.scanProfiles = config.scanProfiles;
|
|
9033
|
-
this.codexRoots = config.codexRoots ?? [(0, import_path18.join)((0,
|
|
9418
|
+
this.codexRoots = config.codexRoots ?? [(0, import_path18.join)((0, import_os10.homedir)(), ".codex", "sessions")];
|
|
9034
9419
|
this.ptyGracePeriodMs = config.ptyGracePeriodMs ?? DEFAULT_PTY_GRACE_PERIOD_MS;
|
|
9035
9420
|
this.defaultSystemPrompt = config.defaultSystemPrompt ?? DEFAULT_SYSTEM_PROMPT;
|
|
9036
9421
|
this.featureFlags = resolveFeatureFlags({ cli: config.featureFlags, yaml: loadFeatureFlags() });
|
|
@@ -9044,7 +9429,7 @@ var StreamerServer = class {
|
|
|
9044
9429
|
this.claudeFlagsPersistable = config.claudeFlags === void 0;
|
|
9045
9430
|
this.claudeFlags = config.claudeFlags ?? loadClaudeFlags();
|
|
9046
9431
|
this.claudeExtraArgs = config.claudeExtraArgs ?? loadClaudeExtraArgs();
|
|
9047
|
-
this.cacheDir = config.cacheDir ?? loadCacheDir() ?? (0, import_path18.join)((0,
|
|
9432
|
+
this.cacheDir = config.cacheDir ?? loadCacheDir() ?? (0, import_path18.join)((0, import_os10.homedir)(), ".threadbase", "cache");
|
|
9048
9433
|
this.tailSize = config.tailSize ?? loadTailSize() ?? 10;
|
|
9049
9434
|
this.directoryDebounceMs = parseDirScanDebounceEnv(process.env.THREADBASE_DIR_SCAN_DEBOUNCE_MS) ?? config.directoryScanDebounceMs ?? 1e3;
|
|
9050
9435
|
this.markScannerStaleDebounced = debounce(() => {
|
|
@@ -9095,7 +9480,7 @@ var StreamerServer = class {
|
|
|
9095
9480
|
const seqs = cache.extendMessageIndex(
|
|
9096
9481
|
filePath,
|
|
9097
9482
|
spans,
|
|
9098
|
-
(0,
|
|
9483
|
+
(0, import_fs19.statSync)(filePath),
|
|
9099
9484
|
readFrom,
|
|
9100
9485
|
endOffset
|
|
9101
9486
|
);
|
|
@@ -9558,14 +9943,14 @@ var StreamerServer = class {
|
|
|
9558
9943
|
}
|
|
9559
9944
|
this.apnsClient = new ApnsClient(creds);
|
|
9560
9945
|
const sender = new LiveActivitySender(this.apnsClient, pushRepo);
|
|
9561
|
-
const serverId = process.env.THREADBASE_INSTANCE_ID ?? (0,
|
|
9562
|
-
this.liveActivityNotifier = new LiveActivityNotifier(sender, serverId, (0,
|
|
9946
|
+
const serverId = process.env.THREADBASE_INSTANCE_ID ?? (0, import_os10.hostname)();
|
|
9947
|
+
this.liveActivityNotifier = new LiveActivityNotifier(sender, serverId, (0, import_os10.hostname)());
|
|
9563
9948
|
this.liveActivityRenewal = new LiveActivityRenewalScheduler({
|
|
9564
9949
|
repo: pushRepo,
|
|
9565
9950
|
sender,
|
|
9566
9951
|
sessionStore: this.sessionStore,
|
|
9567
9952
|
serverId,
|
|
9568
|
-
serverLabel: (0,
|
|
9953
|
+
serverLabel: (0, import_os10.hostname)()
|
|
9569
9954
|
});
|
|
9570
9955
|
this.liveActivityRenewal.start();
|
|
9571
9956
|
this.log.info("Live Activity push enabled", {
|
|
@@ -9904,7 +10289,7 @@ var StreamerServer = class {
|
|
|
9904
10289
|
this.fileWatcher.watchDirectory(dir);
|
|
9905
10290
|
}
|
|
9906
10291
|
for (const dir of this.codexRoots) {
|
|
9907
|
-
if (!(0,
|
|
10292
|
+
if (!(0, import_fs19.existsSync)(dir)) continue;
|
|
9908
10293
|
this.fileWatcher.watchDirectory(dir);
|
|
9909
10294
|
}
|
|
9910
10295
|
} catch (err) {
|
|
@@ -10177,7 +10562,7 @@ var StreamerServer = class {
|
|
|
10177
10562
|
}
|
|
10178
10563
|
let body;
|
|
10179
10564
|
try {
|
|
10180
|
-
body = await
|
|
10565
|
+
body = await readBody2(req);
|
|
10181
10566
|
} catch (err) {
|
|
10182
10567
|
const message = err instanceof Error ? err.message : "Invalid body";
|
|
10183
10568
|
json(res, 400, { error: message });
|
|
@@ -10223,7 +10608,7 @@ var StreamerServer = class {
|
|
|
10223
10608
|
nonce: sealed.nonce,
|
|
10224
10609
|
ephemeralPublicKey: sealed.ephemeralPublicKey,
|
|
10225
10610
|
publicUrl: this.publicUrl,
|
|
10226
|
-
machineName: (0,
|
|
10611
|
+
machineName: (0, import_os10.hostname)(),
|
|
10227
10612
|
...device && {
|
|
10228
10613
|
deviceId: device.deviceId,
|
|
10229
10614
|
deviceToken: device.deviceToken,
|
|
@@ -10351,7 +10736,7 @@ var StreamerServer = class {
|
|
|
10351
10736
|
if (this.scanProfiles && this.scanProfiles.length > 0) {
|
|
10352
10737
|
return this.scanProfiles.filter((p) => p.enabled).map((p) => (0, import_path18.join)(p.configDir, "projects"));
|
|
10353
10738
|
}
|
|
10354
|
-
return [(0, import_path18.join)((0,
|
|
10739
|
+
return [(0, import_path18.join)((0, import_os10.homedir)(), ".claude", "projects")];
|
|
10355
10740
|
}
|
|
10356
10741
|
/**
|
|
10357
10742
|
* Full-glob scan + cache upsert/delete reconcile. Used by ?refresh=1 and by
|
|
@@ -10753,20 +11138,20 @@ var StreamerServer = class {
|
|
|
10753
11138
|
if (this.scanProfiles && this.scanProfiles.length > 0) {
|
|
10754
11139
|
return this.scanProfiles.filter((p) => p.enabled).map((p) => (0, import_path18.join)(p.configDir, "projects"));
|
|
10755
11140
|
}
|
|
10756
|
-
return [(0, import_path18.join)((0,
|
|
11141
|
+
return [(0, import_path18.join)((0, import_os10.homedir)(), ".claude", "projects")];
|
|
10757
11142
|
}
|
|
10758
11143
|
findJsonlPath(uuid) {
|
|
10759
11144
|
const filename = `${uuid}.jsonl`;
|
|
10760
11145
|
for (const projectsDir of this.projectsDirs()) {
|
|
10761
|
-
if (!(0,
|
|
10762
|
-
for (const dir of (0,
|
|
11146
|
+
if (!(0, import_fs19.existsSync)(projectsDir)) continue;
|
|
11147
|
+
for (const dir of (0, import_fs19.readdirSync)(projectsDir)) {
|
|
10763
11148
|
const fp = (0, import_path18.join)(projectsDir, dir, filename);
|
|
10764
|
-
if ((0,
|
|
11149
|
+
if ((0, import_fs19.existsSync)(fp)) return fp;
|
|
10765
11150
|
const projectDir = (0, import_path18.join)(projectsDir, dir);
|
|
10766
11151
|
try {
|
|
10767
|
-
for (const sub of (0,
|
|
11152
|
+
for (const sub of (0, import_fs19.readdirSync)(projectDir)) {
|
|
10768
11153
|
const subagentPath = (0, import_path18.join)(projectDir, sub, "subagents", filename);
|
|
10769
|
-
if ((0,
|
|
11154
|
+
if ((0, import_fs19.existsSync)(subagentPath)) return subagentPath;
|
|
10770
11155
|
}
|
|
10771
11156
|
} catch {
|
|
10772
11157
|
}
|
|
@@ -10776,7 +11161,7 @@ var StreamerServer = class {
|
|
|
10776
11161
|
}
|
|
10777
11162
|
async readCwdFromJsonl(filePath) {
|
|
10778
11163
|
return new Promise((resolve2) => {
|
|
10779
|
-
const rl = (0, import_readline.createInterface)({ input: (0,
|
|
11164
|
+
const rl = (0, import_readline.createInterface)({ input: (0, import_fs19.createReadStream)(filePath), crlfDelay: Infinity });
|
|
10780
11165
|
let found = false;
|
|
10781
11166
|
rl.on("line", (line) => {
|
|
10782
11167
|
if (found) return;
|
|
@@ -10866,7 +11251,7 @@ var StreamerServer = class {
|
|
|
10866
11251
|
if (this.isManagedTailPath(key)) return;
|
|
10867
11252
|
let mtimeMs;
|
|
10868
11253
|
try {
|
|
10869
|
-
mtimeMs = (0,
|
|
11254
|
+
mtimeMs = (0, import_fs19.statSync)(filePath).mtimeMs;
|
|
10870
11255
|
} catch {
|
|
10871
11256
|
return;
|
|
10872
11257
|
}
|
|
@@ -11048,7 +11433,7 @@ var StreamerServer = class {
|
|
|
11048
11433
|
if (!conv.filePath) return false;
|
|
11049
11434
|
let mtimeMs = null;
|
|
11050
11435
|
try {
|
|
11051
|
-
mtimeMs = (0,
|
|
11436
|
+
mtimeMs = (0, import_fs19.statSync)(conv.filePath).mtimeMs;
|
|
11052
11437
|
} catch {
|
|
11053
11438
|
return false;
|
|
11054
11439
|
}
|
|
@@ -11303,7 +11688,7 @@ var StreamerServer = class {
|
|
|
11303
11688
|
}
|
|
11304
11689
|
let body;
|
|
11305
11690
|
try {
|
|
11306
|
-
body = await
|
|
11691
|
+
body = await readBody2(req);
|
|
11307
11692
|
} catch {
|
|
11308
11693
|
res.setHeader("Accept-Query", "application/json");
|
|
11309
11694
|
json(res, 422, { error: "Malformed JSON body", code: "invalid_query" });
|
|
@@ -11341,17 +11726,27 @@ var StreamerServer = class {
|
|
|
11341
11726
|
});
|
|
11342
11727
|
}
|
|
11343
11728
|
async handleSearch(url, res) {
|
|
11344
|
-
|
|
11345
|
-
|
|
11346
|
-
|
|
11347
|
-
|
|
11729
|
+
let parsed;
|
|
11730
|
+
try {
|
|
11731
|
+
parsed = parseSearchQuery(url.searchParams);
|
|
11732
|
+
} catch (err) {
|
|
11733
|
+
if (err instanceof SearchQueryError) {
|
|
11734
|
+
json(res, 400, { error: err.message, code: err.code });
|
|
11735
|
+
return;
|
|
11736
|
+
}
|
|
11737
|
+
throw err;
|
|
11348
11738
|
}
|
|
11349
|
-
const
|
|
11739
|
+
const { q, limit, offset, filters } = parsed;
|
|
11740
|
+
const startedAt = Date.now();
|
|
11350
11741
|
const scanner = await this.getScanner();
|
|
11351
11742
|
const results = await (0, import_scanner3.search)(
|
|
11352
11743
|
q,
|
|
11353
11744
|
{
|
|
11354
|
-
|
|
11745
|
+
// Fetch beyond the requested page: filters below are applied AFTER the
|
|
11746
|
+
// scanner returns, so slicing at `limit` here would drop results that a
|
|
11747
|
+
// later page should contain. Bounded so a broad query cannot pull an
|
|
11748
|
+
// unbounded set into memory.
|
|
11749
|
+
limit: Math.min(offset + limit * SEARCH_OVERFETCH, SEARCH_MAX_SCAN),
|
|
11355
11750
|
include: "conversations",
|
|
11356
11751
|
...this.scanProfiles ? { profiles: this.scanProfiles } : {},
|
|
11357
11752
|
...this.codexScanOpts()
|
|
@@ -11375,13 +11770,24 @@ var StreamerServer = class {
|
|
|
11375
11770
|
lastActivity: r.meta.timestamp,
|
|
11376
11771
|
firstMessage: r.meta.firstMessage ?? void 0,
|
|
11377
11772
|
lastMessage: r.meta.lastMessage ?? void 0,
|
|
11378
|
-
provider: r.meta.provider ?? CLAUDE_CODE_PROVIDER
|
|
11773
|
+
provider: r.meta.provider ?? CLAUDE_CODE_PROVIDER,
|
|
11774
|
+
// The scanner already computes relevance and match snippets; the previous
|
|
11775
|
+
// adapter discarded both, so results arrived in an unexplained order with
|
|
11776
|
+
// no indication of WHY anything matched.
|
|
11777
|
+
score: r.score,
|
|
11778
|
+
matches: Array.isArray(r.matches) ? r.matches.map((m) => ({
|
|
11779
|
+
field: m.field,
|
|
11780
|
+
snippet: m.snippet
|
|
11781
|
+
})) : []
|
|
11379
11782
|
}));
|
|
11783
|
+
const page = paginate(applyFilters(adapted, filters), offset, limit);
|
|
11380
11784
|
json(res, 200, {
|
|
11381
|
-
conversations:
|
|
11382
|
-
hasMore:
|
|
11383
|
-
offset:
|
|
11384
|
-
total:
|
|
11785
|
+
conversations: page.items,
|
|
11786
|
+
hasMore: page.hasMore,
|
|
11787
|
+
offset: page.offset,
|
|
11788
|
+
total: page.total,
|
|
11789
|
+
// Query timing, so a slow search is diagnosable rather than merely felt.
|
|
11790
|
+
tookMs: Date.now() - startedAt
|
|
11385
11791
|
});
|
|
11386
11792
|
}
|
|
11387
11793
|
async handleListSessions(url, res) {
|
|
@@ -11427,7 +11833,7 @@ var StreamerServer = class {
|
|
|
11427
11833
|
if (this.rejectIfWarmingUp(res)) return;
|
|
11428
11834
|
const session = this.sessionStore.get(sessionId, this.ptyAttachedIds());
|
|
11429
11835
|
if (session) {
|
|
11430
|
-
if (!(0,
|
|
11836
|
+
if (!(0, import_fs19.existsSync)(session.projectPath)) {
|
|
11431
11837
|
session.failureReason = `Project directory not found: ${session.projectPath}`;
|
|
11432
11838
|
}
|
|
11433
11839
|
const reconciled = this.withReconciledLifecycle([session])[0];
|
|
@@ -11454,7 +11860,7 @@ var StreamerServer = class {
|
|
|
11454
11860
|
json(res, 404, { error: "Session not found" });
|
|
11455
11861
|
}
|
|
11456
11862
|
async handleResume(req, res) {
|
|
11457
|
-
const body = await
|
|
11863
|
+
const body = await readBody2(req);
|
|
11458
11864
|
const sessionId = body.sessionId ?? body.conversationId;
|
|
11459
11865
|
if (!sessionId) {
|
|
11460
11866
|
json(res, 400, { error: "Missing sessionId" });
|
|
@@ -11586,7 +11992,7 @@ var StreamerServer = class {
|
|
|
11586
11992
|
return;
|
|
11587
11993
|
}
|
|
11588
11994
|
if (this.agentConfig.enabled) {
|
|
11589
|
-
const body2 = await
|
|
11995
|
+
const body2 = await readBody2(req);
|
|
11590
11996
|
const cache = this.cache;
|
|
11591
11997
|
if (!cache) {
|
|
11592
11998
|
json(res, 503, {
|
|
@@ -11605,7 +12011,7 @@ var StreamerServer = class {
|
|
|
11605
12011
|
json(res, result.status, result.body);
|
|
11606
12012
|
return;
|
|
11607
12013
|
}
|
|
11608
|
-
const body = await
|
|
12014
|
+
const body = await readBody2(req);
|
|
11609
12015
|
const { input, keys } = body;
|
|
11610
12016
|
let idempotencyKey;
|
|
11611
12017
|
try {
|
|
@@ -11769,7 +12175,7 @@ var StreamerServer = class {
|
|
|
11769
12175
|
});
|
|
11770
12176
|
}
|
|
11771
12177
|
async handleSendAnswer(sessionId, req, res) {
|
|
11772
|
-
const body = await
|
|
12178
|
+
const body = await readBody2(req);
|
|
11773
12179
|
const pending = this.pendingQuestions.get(sessionId);
|
|
11774
12180
|
const resolution = resolveAnswer(pending, body);
|
|
11775
12181
|
if (!resolution.ok) {
|
|
@@ -11798,7 +12204,7 @@ var StreamerServer = class {
|
|
|
11798
12204
|
json(res, 400, { error: "Session has no project path" });
|
|
11799
12205
|
return;
|
|
11800
12206
|
}
|
|
11801
|
-
const body = await
|
|
12207
|
+
const body = await readBody2(req);
|
|
11802
12208
|
const { filename, mimeType, dataBase64 } = body ?? {};
|
|
11803
12209
|
if (typeof filename !== "string" || typeof mimeType !== "string" || typeof dataBase64 !== "string") {
|
|
11804
12210
|
json(res, 400, { error: "Missing filename, mimeType, or dataBase64" });
|
|
@@ -12000,7 +12406,7 @@ var StreamerServer = class {
|
|
|
12000
12406
|
return;
|
|
12001
12407
|
}
|
|
12002
12408
|
if (this.agentConfig.enabled) {
|
|
12003
|
-
const body2 = await
|
|
12409
|
+
const body2 = await readBody2(req);
|
|
12004
12410
|
const result = await handleStartAgentSession(body2, {
|
|
12005
12411
|
sessionStore: this.sessionStore,
|
|
12006
12412
|
// biome-ignore lint/style/noNonNullAssertion: agentClient is set when agentConfig.enabled is true
|
|
@@ -12014,7 +12420,7 @@ var StreamerServer = class {
|
|
|
12014
12420
|
}
|
|
12015
12421
|
return;
|
|
12016
12422
|
}
|
|
12017
|
-
const body = await
|
|
12423
|
+
const body = await readBody2(req);
|
|
12018
12424
|
const { path: relativePath, provider: requestedProvider, systemPrompt: clientPrompt } = body;
|
|
12019
12425
|
if (requestedProvider !== void 0 && !isProviderName(requestedProvider)) {
|
|
12020
12426
|
json(res, 400, { error: "Invalid provider" });
|
|
@@ -12151,7 +12557,7 @@ var StreamerServer = class {
|
|
|
12151
12557
|
// file isn't slurped in full.
|
|
12152
12558
|
readFirstLineSessionId(filePath) {
|
|
12153
12559
|
try {
|
|
12154
|
-
const content = (0,
|
|
12560
|
+
const content = (0, import_fs19.readFileSync)(filePath, "utf8");
|
|
12155
12561
|
const nl = content.indexOf("\n");
|
|
12156
12562
|
const firstLine = nl === -1 ? content : content.slice(0, nl);
|
|
12157
12563
|
if (!firstLine.trim()) return null;
|
|
@@ -12166,7 +12572,7 @@ var StreamerServer = class {
|
|
|
12166
12572
|
// was passed to Claude via --session-id so the filename matches from the start.
|
|
12167
12573
|
watchForJsonl(sessionId, projectPath) {
|
|
12168
12574
|
const encoded = projectPath.replace(/[/\\:.]/g, "-");
|
|
12169
|
-
const projectsDir = (0, import_path18.join)((0,
|
|
12575
|
+
const projectsDir = (0, import_path18.join)((0, import_os10.homedir)(), ".claude", "projects", encoded);
|
|
12170
12576
|
const expectedFile = `${sessionId}.jsonl`;
|
|
12171
12577
|
const filePath = (0, import_path18.join)(projectsDir, expectedFile);
|
|
12172
12578
|
const deadline = Date.now() + 12e4;
|
|
@@ -12186,11 +12592,11 @@ var StreamerServer = class {
|
|
|
12186
12592
|
cleanup();
|
|
12187
12593
|
return;
|
|
12188
12594
|
}
|
|
12189
|
-
let resolvedFilePath = (0,
|
|
12190
|
-
if (!resolvedFilePath && (0,
|
|
12595
|
+
let resolvedFilePath = (0, import_fs19.existsSync)(filePath) ? filePath : null;
|
|
12596
|
+
if (!resolvedFilePath && (0, import_fs19.existsSync)(projectsDir)) {
|
|
12191
12597
|
try {
|
|
12192
12598
|
const now = Date.now();
|
|
12193
|
-
const match = (0,
|
|
12599
|
+
const match = (0, import_fs19.readdirSync)(projectsDir).filter((f) => f.endsWith(".jsonl")).map((f) => ({ f, mtime: (0, import_fs19.statSync)((0, import_path18.join)(projectsDir, f)).mtimeMs })).filter(({ mtime }) => now - mtime < 5e3).filter(
|
|
12194
12600
|
({ f }) => (0, import_path18.basename)(f, ".jsonl") === sessionId || this.readFirstLineSessionId((0, import_path18.join)(projectsDir, f)) === sessionId
|
|
12195
12601
|
).sort((a, b) => b.mtime - a.mtime)[0];
|
|
12196
12602
|
if (match) resolvedFilePath = (0, import_path18.join)(projectsDir, match.f);
|
|
@@ -12201,7 +12607,7 @@ var StreamerServer = class {
|
|
|
12201
12607
|
cleanup();
|
|
12202
12608
|
this.sessionFileMap.set(sessionId, resolvedFilePath);
|
|
12203
12609
|
try {
|
|
12204
|
-
const existing = (0,
|
|
12610
|
+
const existing = (0, import_fs19.readFileSync)(resolvedFilePath, "utf8").split("\n").filter(Boolean);
|
|
12205
12611
|
if (existing.length > 0) {
|
|
12206
12612
|
this.broadcastConversationLines(sessionId, existing);
|
|
12207
12613
|
}
|
|
@@ -12225,7 +12631,7 @@ var StreamerServer = class {
|
|
|
12225
12631
|
if (this.sessionFileMap.has(sessionId)) return;
|
|
12226
12632
|
try {
|
|
12227
12633
|
require("fs").mkdirSync(projectsDir, { recursive: true });
|
|
12228
|
-
watcher = (0,
|
|
12634
|
+
watcher = (0, import_fs19.watch)(projectsDir, tryWire);
|
|
12229
12635
|
watcher.on("error", cleanup);
|
|
12230
12636
|
} catch {
|
|
12231
12637
|
}
|
|
@@ -12254,7 +12660,7 @@ var StreamerServer = class {
|
|
|
12254
12660
|
};
|
|
12255
12661
|
const matchesProjectPath = (candidatePath) => {
|
|
12256
12662
|
try {
|
|
12257
|
-
const firstLine = (0,
|
|
12663
|
+
const firstLine = (0, import_fs19.readFileSync)(candidatePath, "utf8").split("\n", 1)[0];
|
|
12258
12664
|
if (!firstLine) return null;
|
|
12259
12665
|
const parsed = JSON.parse(firstLine);
|
|
12260
12666
|
if (parsed?.type !== "session_meta") return null;
|
|
@@ -12283,15 +12689,15 @@ var StreamerServer = class {
|
|
|
12283
12689
|
);
|
|
12284
12690
|
for (const root of this.codexRoots) {
|
|
12285
12691
|
const sessionsDir = (0, import_path18.join)(root, dateDir);
|
|
12286
|
-
if (!(0,
|
|
12692
|
+
if (!(0, import_fs19.existsSync)(sessionsDir)) continue;
|
|
12287
12693
|
let candidateFiles;
|
|
12288
12694
|
try {
|
|
12289
|
-
candidateFiles = (0,
|
|
12695
|
+
candidateFiles = (0, import_fs19.readdirSync)(sessionsDir).filter((f) => f.endsWith(".jsonl"));
|
|
12290
12696
|
} catch {
|
|
12291
12697
|
continue;
|
|
12292
12698
|
}
|
|
12293
12699
|
const nowMs = Date.now();
|
|
12294
|
-
const recentCandidates = candidateFiles.map((f) => ({ f, mtime: (0,
|
|
12700
|
+
const recentCandidates = candidateFiles.map((f) => ({ f, mtime: (0, import_fs19.statSync)((0, import_path18.join)(sessionsDir, f)).mtimeMs })).filter(({ mtime }) => nowMs - mtime < 1e4).sort((a, b) => b.mtime - a.mtime);
|
|
12295
12701
|
for (const { f } of recentCandidates) {
|
|
12296
12702
|
const candidatePath = (0, import_path18.join)(sessionsDir, f);
|
|
12297
12703
|
const match = matchesProjectPath(candidatePath);
|
|
@@ -12303,7 +12709,7 @@ var StreamerServer = class {
|
|
|
12303
12709
|
this.sessionFileMap.set(sessionId, candidatePath);
|
|
12304
12710
|
this.fileWatcher.watch(candidatePath);
|
|
12305
12711
|
try {
|
|
12306
|
-
const existing = (0,
|
|
12712
|
+
const existing = (0, import_fs19.readFileSync)(candidatePath, "utf8").split("\n").filter(Boolean);
|
|
12307
12713
|
if (existing.length > 0) {
|
|
12308
12714
|
this.broadcastConversationLines(sessionId, existing);
|
|
12309
12715
|
}
|
|
@@ -12372,7 +12778,7 @@ var StreamerServer = class {
|
|
|
12372
12778
|
});
|
|
12373
12779
|
return;
|
|
12374
12780
|
}
|
|
12375
|
-
const body = await
|
|
12781
|
+
const body = await readBody2(req);
|
|
12376
12782
|
const { path: relativePath, name } = body;
|
|
12377
12783
|
if (!name || typeof name !== "string") {
|
|
12378
12784
|
json(res, 400, { error: "Missing name field" });
|
|
@@ -12402,7 +12808,7 @@ var StreamerServer = class {
|
|
|
12402
12808
|
}
|
|
12403
12809
|
let parsed;
|
|
12404
12810
|
try {
|
|
12405
|
-
parsed = await
|
|
12811
|
+
parsed = await readBody2(req);
|
|
12406
12812
|
} catch {
|
|
12407
12813
|
json(res, 400, { error: "Invalid JSON" });
|
|
12408
12814
|
return;
|
|
@@ -12459,7 +12865,7 @@ var StreamerServer = class {
|
|
|
12459
12865
|
}
|
|
12460
12866
|
let parsed;
|
|
12461
12867
|
try {
|
|
12462
|
-
parsed = await
|
|
12868
|
+
parsed = await readBody2(req);
|
|
12463
12869
|
} catch {
|
|
12464
12870
|
json(res, 400, { error: "Invalid JSON" });
|
|
12465
12871
|
return;
|
|
@@ -12518,7 +12924,7 @@ async function waitForProcessExit(pid, timeoutMs, pollMs = ADOPT_KILL_POLL_MS) {
|
|
|
12518
12924
|
}
|
|
12519
12925
|
function classifyResumability(cwd) {
|
|
12520
12926
|
if (!cwd) return { resumable: true };
|
|
12521
|
-
if ((0,
|
|
12927
|
+
if ((0, import_fs19.existsSync)(cwd)) return { resumable: true };
|
|
12522
12928
|
const ranInWorktree = /\/\.worktrees\//.test(cwd) || /\/\.claude\/worktrees\//.test(cwd);
|
|
12523
12929
|
return {
|
|
12524
12930
|
resumable: false,
|
|
@@ -12636,7 +13042,7 @@ function parseSessionListQuery(url) {
|
|
|
12636
13042
|
const cursor = url.searchParams.get("cursor") ?? void 0;
|
|
12637
13043
|
return { query: { limit, sortBy, order, status, cursor } };
|
|
12638
13044
|
}
|
|
12639
|
-
function
|
|
13045
|
+
function readBody2(req) {
|
|
12640
13046
|
return new Promise((resolve2, reject) => {
|
|
12641
13047
|
const chunks = [];
|
|
12642
13048
|
req.on("data", (chunk) => chunks.push(chunk));
|