clauder-hub 0.1.4 → 0.1.5
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/package.json +1 -1
- package/server/dist/index.js +126 -11
package/package.json
CHANGED
package/server/dist/index.js
CHANGED
|
@@ -28560,14 +28560,14 @@ function claudeSettings() {
|
|
|
28560
28560
|
}
|
|
28561
28561
|
function saveClaudeSettings(input) {
|
|
28562
28562
|
const next = { ...claudeSettings(), ...input };
|
|
28563
|
-
const
|
|
28563
|
+
const check3 = (ok, message) => {
|
|
28564
28564
|
if (!ok) throw new Error(message);
|
|
28565
28565
|
};
|
|
28566
|
-
|
|
28567
|
-
|
|
28568
|
-
|
|
28569
|
-
|
|
28570
|
-
|
|
28566
|
+
check3(!next.permissionMode || PERMISSION_MODES.includes(next.permissionMode), `Unknown permission mode '${next.permissionMode}'.`);
|
|
28567
|
+
check3(!next.runPermissionMode || PERMISSION_MODES.includes(next.runPermissionMode), `Unknown permission mode '${next.runPermissionMode}'.`);
|
|
28568
|
+
check3(!next.effort || EFFORTS.includes(next.effort), `Effort is one of ${EFFORTS.join(", ")}.`);
|
|
28569
|
+
check3(word.test(next.model) && word.test(next.fallbackModel), "A model is one word, like opus or claude-sonnet-5-5.");
|
|
28570
|
+
check3(["", "on", "off"].includes(next.chrome), "Claude in Chrome is on, off or your own setting.");
|
|
28571
28571
|
next.addDirs = (Array.isArray(next.addDirs) ? next.addDirs : []).map((dir) => String(dir).trim()).filter(Boolean);
|
|
28572
28572
|
db.prepare("INSERT INTO settings (key, value, updated_at) VALUES ('claude', ?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at").run(
|
|
28573
28573
|
JSON.stringify(next),
|
|
@@ -29102,6 +29102,7 @@ function spawnTerminal(input) {
|
|
|
29102
29102
|
clients: /* @__PURE__ */ new Set(),
|
|
29103
29103
|
backlog: [],
|
|
29104
29104
|
backlogSize: 0,
|
|
29105
|
+
lastOutputAt: Date.now(),
|
|
29105
29106
|
cols,
|
|
29106
29107
|
rows
|
|
29107
29108
|
};
|
|
@@ -29109,6 +29110,7 @@ function spawnTerminal(input) {
|
|
|
29109
29110
|
child.onData(bindProject((data) => {
|
|
29110
29111
|
terminal.backlog.push(data);
|
|
29111
29112
|
terminal.backlogSize += data.length;
|
|
29113
|
+
terminal.lastOutputAt = Date.now();
|
|
29112
29114
|
while (terminal.backlogSize > BACKLOG_LIMIT && terminal.backlog.length > 1) {
|
|
29113
29115
|
terminal.backlogSize -= terminal.backlog.shift().length;
|
|
29114
29116
|
}
|
|
@@ -29195,11 +29197,11 @@ function killTerminal(terminal) {
|
|
|
29195
29197
|
function waitForExit(terminal, timeoutMs = 1e4) {
|
|
29196
29198
|
return new Promise((resolve) => {
|
|
29197
29199
|
const started = Date.now();
|
|
29198
|
-
const
|
|
29200
|
+
const check3 = () => {
|
|
29199
29201
|
if (!terminals.has(terminal.id) || Date.now() - started > timeoutMs) return resolve();
|
|
29200
|
-
setTimeout(
|
|
29202
|
+
setTimeout(check3, 100);
|
|
29201
29203
|
};
|
|
29202
|
-
|
|
29204
|
+
check3();
|
|
29203
29205
|
});
|
|
29204
29206
|
}
|
|
29205
29207
|
function killAll() {
|
|
@@ -31276,6 +31278,113 @@ var init_telegram = __esm({
|
|
|
31276
31278
|
}
|
|
31277
31279
|
});
|
|
31278
31280
|
|
|
31281
|
+
// server/src/watchdog.ts
|
|
31282
|
+
var watchdog_exports = {};
|
|
31283
|
+
__export(watchdog_exports, {
|
|
31284
|
+
answerTrust: () => answerTrust,
|
|
31285
|
+
startWatchdog: () => startWatchdog
|
|
31286
|
+
});
|
|
31287
|
+
function screenTail(terminal) {
|
|
31288
|
+
let text = "";
|
|
31289
|
+
for (let index = terminal.backlog.length - 1; index >= 0 && text.length < SCREEN_TAIL * 2; index -= 1) text = terminal.backlog[index] + text;
|
|
31290
|
+
return text.slice(-SCREEN_TAIL * 2).replace(ANSI, "").slice(-SCREEN_TAIL);
|
|
31291
|
+
}
|
|
31292
|
+
async function answerTrust(terminal, trust) {
|
|
31293
|
+
if (!trust) return pressKeys(terminal, "\x1B");
|
|
31294
|
+
for (let step = 0; step < 4; step += 1) {
|
|
31295
|
+
const flat2 = squeeze(screenTail(terminal).slice(-1500));
|
|
31296
|
+
const cursor = flat2.lastIndexOf("\u276F");
|
|
31297
|
+
const after = cursor >= 0 ? flat2.slice(cursor + 1) : "";
|
|
31298
|
+
if (after.startsWith("yes")) return pressKeys(terminal, "\r");
|
|
31299
|
+
const yesAt = flat2.lastIndexOf("yes,itrust");
|
|
31300
|
+
await pressKeys(terminal, yesAt > cursor ? "\x1B[B" : "\x1B[A");
|
|
31301
|
+
await sleep2(300);
|
|
31302
|
+
}
|
|
31303
|
+
throw new Error("Could not find the trust option on the screen.");
|
|
31304
|
+
}
|
|
31305
|
+
function lastLines(screen) {
|
|
31306
|
+
const lines2 = screen.split(/\r?\n/).map((line) => line.replace(/\s+/g, " ").trim()).filter(Boolean);
|
|
31307
|
+
return lines2.slice(-14).join("\n").slice(-1200);
|
|
31308
|
+
}
|
|
31309
|
+
function check2() {
|
|
31310
|
+
for (const terminal of listTerminals()) {
|
|
31311
|
+
if (terminal.finished || terminal.restarting) continue;
|
|
31312
|
+
const open = listInteractions({ sessionId: terminal.sessionId, status: "open" });
|
|
31313
|
+
const screen = screenTail(terminal);
|
|
31314
|
+
const trustOpen = open.find((item) => item.data?.trust);
|
|
31315
|
+
if (showsTrustDialog(screen)) {
|
|
31316
|
+
if (!trustOpen) {
|
|
31317
|
+
createInteraction({
|
|
31318
|
+
sessionId: terminal.sessionId,
|
|
31319
|
+
kind: "question",
|
|
31320
|
+
title: "Trust this folder?",
|
|
31321
|
+
body: "Claude Code asks whether to trust the files in this project's folder before it starts. Nothing runs until it is answered.",
|
|
31322
|
+
options: [
|
|
31323
|
+
{ key: "1", label: "Yes, trust this folder" },
|
|
31324
|
+
{ key: "2", label: "No, exit" }
|
|
31325
|
+
],
|
|
31326
|
+
data: { trust: true, questions: [{ question: "Trust this folder?", options: ["Yes, trust this folder", "No, exit"] }] }
|
|
31327
|
+
});
|
|
31328
|
+
}
|
|
31329
|
+
continue;
|
|
31330
|
+
}
|
|
31331
|
+
if (trustOpen) closeOpen(terminal.sessionId, "terminal", ["question"]);
|
|
31332
|
+
const stalled = open.find((item) => item.data?.stalled);
|
|
31333
|
+
if (stalled && terminal.lastOutputAt > Date.parse(stalled.createdAt) + 5e3) {
|
|
31334
|
+
closeOpen(terminal.sessionId, "terminal", ["turn"]);
|
|
31335
|
+
setSessionStatus(terminal.sessionId, "running");
|
|
31336
|
+
continue;
|
|
31337
|
+
}
|
|
31338
|
+
const quietFor = Date.now() - terminal.lastOutputAt;
|
|
31339
|
+
if (quietFor < STALL_MS || open.length || reported.get(terminal.id) === terminal.lastOutputAt) continue;
|
|
31340
|
+
if (!getSessionRow(terminal.sessionId)?.run_id) continue;
|
|
31341
|
+
reported.set(terminal.id, terminal.lastOutputAt);
|
|
31342
|
+
createInteraction({
|
|
31343
|
+
sessionId: terminal.sessionId,
|
|
31344
|
+
kind: "turn",
|
|
31345
|
+
title: "This run looks stuck",
|
|
31346
|
+
body: `Nothing has changed on its screen for ${Math.round(quietFor / 6e4)} minutes. It may be waiting on a dialog, or it stopped without reporting done. Reply to type into it, or stop it.
|
|
31347
|
+
|
|
31348
|
+
${lastLines(screen)}`,
|
|
31349
|
+
data: { stalled: true }
|
|
31350
|
+
});
|
|
31351
|
+
}
|
|
31352
|
+
for (const id of reported.keys()) if (!allTerminals().some((terminal) => terminal.id === id)) reported.delete(id);
|
|
31353
|
+
}
|
|
31354
|
+
function startWatchdog(intervalMs = 3e3) {
|
|
31355
|
+
const timer = setInterval(bindProject(() => {
|
|
31356
|
+
try {
|
|
31357
|
+
check2();
|
|
31358
|
+
} catch (error) {
|
|
31359
|
+
console.error("[watchdog]", error.message);
|
|
31360
|
+
}
|
|
31361
|
+
}), intervalMs);
|
|
31362
|
+
return () => clearInterval(timer);
|
|
31363
|
+
}
|
|
31364
|
+
var TRUST_MARKERS, STALL_MS, SCREEN_TAIL, ANSI, squeeze, showsTrustDialog, sleep2, reported;
|
|
31365
|
+
var init_watchdog = __esm({
|
|
31366
|
+
"server/src/watchdog.ts"() {
|
|
31367
|
+
"use strict";
|
|
31368
|
+
init_context();
|
|
31369
|
+
init_interactions();
|
|
31370
|
+
init_sessions();
|
|
31371
|
+
init_terminals();
|
|
31372
|
+
TRUST_MARKERS = ["doyoutrustthefilesinthisfolder", "isthisaprojectyoucreatedoroneyoutrust", "yes,itrustthisfolder"];
|
|
31373
|
+
STALL_MS = Number(process.env.CLAUDER_HUB_STALL_MS) || 8 * 6e4;
|
|
31374
|
+
SCREEN_TAIL = 8e3;
|
|
31375
|
+
ANSI = /\x1b\[[0-9;?]*[ -/]*[@-~]|\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)|\x1b[()][A-Za-z0-9]|\x1b[=>78]/g;
|
|
31376
|
+
squeeze = (text) => text.toLowerCase().replace(/\s+/g, "");
|
|
31377
|
+
showsTrustDialog = (screen) => {
|
|
31378
|
+
const flat2 = squeeze(screen.slice(-3e3));
|
|
31379
|
+
if (!TRUST_MARKERS.some((marker) => flat2.includes(marker))) return false;
|
|
31380
|
+
const end = Math.max(flat2.lastIndexOf("entertoconfirm"), flat2.lastIndexOf("itrustthisfolder"), flat2.lastIndexOf("no,exit"));
|
|
31381
|
+
return end >= 0 && flat2.length - end < 120;
|
|
31382
|
+
};
|
|
31383
|
+
sleep2 = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
31384
|
+
reported = /* @__PURE__ */ new Map();
|
|
31385
|
+
}
|
|
31386
|
+
});
|
|
31387
|
+
|
|
31279
31388
|
// server/src/mcpSuggestions.ts
|
|
31280
31389
|
var mcpSuggestions_exports = {};
|
|
31281
31390
|
__export(mcpSuggestions_exports, {
|
|
@@ -31500,6 +31609,11 @@ function closeOpen(sessionId, by, kinds) {
|
|
|
31500
31609
|
}
|
|
31501
31610
|
async function answerQuestion(interaction, answer) {
|
|
31502
31611
|
const terminal = terminalForSession(interaction.sessionId);
|
|
31612
|
+
if (interaction.data.trust) {
|
|
31613
|
+
const { answerTrust: answerTrust2 } = await Promise.resolve().then(() => (init_watchdog(), watchdog_exports));
|
|
31614
|
+
await answerTrust2(terminal, answer.trim() !== "2");
|
|
31615
|
+
return;
|
|
31616
|
+
}
|
|
31503
31617
|
const questions = interaction.data.questions ?? [];
|
|
31504
31618
|
const split = answer.includes("\n") ? answer.split("\n") : answer.split(/[;,]/);
|
|
31505
31619
|
const parts = questions.length > 1 ? split.map((part) => part.trim()) : [answer.trim()];
|
|
@@ -31701,7 +31815,7 @@ function bootGlobal() {
|
|
|
31701
31815
|
};
|
|
31702
31816
|
const stops = withProject(project, () => {
|
|
31703
31817
|
recoverRuns();
|
|
31704
|
-
const list = [startTranscriptWatcher(), startInteractionSweeper(), stopChannels];
|
|
31818
|
+
const list = [startTranscriptWatcher(), startInteractionSweeper(), startWatchdog(), stopChannels];
|
|
31705
31819
|
startChannels({ device: false });
|
|
31706
31820
|
return list;
|
|
31707
31821
|
});
|
|
@@ -31759,7 +31873,7 @@ function boot(row) {
|
|
|
31759
31873
|
withProject(project, () => {
|
|
31760
31874
|
const stranded = recoverRuns();
|
|
31761
31875
|
if (stranded) console.log(` note : ${row.name}: ${stranded} queued run(s) were running when the hub last stopped`);
|
|
31762
|
-
stops.push(startTranscriptWatcher(), startInteractionSweeper());
|
|
31876
|
+
stops.push(startTranscriptWatcher(), startInteractionSweeper(), startWatchdog());
|
|
31763
31877
|
stops.push(stopChannels);
|
|
31764
31878
|
startChannels();
|
|
31765
31879
|
stops.push(stopScheduler);
|
|
@@ -31840,6 +31954,7 @@ var init_projects = __esm({
|
|
|
31840
31954
|
init_db();
|
|
31841
31955
|
init_events();
|
|
31842
31956
|
init_interactions();
|
|
31957
|
+
init_watchdog();
|
|
31843
31958
|
init_jobs();
|
|
31844
31959
|
init_paths();
|
|
31845
31960
|
init_terminals();
|