clauder-hub 0.1.2 → 0.1.4
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/README.md +1 -1
- package/package.json +1 -1
- package/server/dist/index.js +139 -9
- package/web/dist/assets/{MonacoEditor-Bdblyy-v.js → MonacoEditor-7a1aZ6Pf.js} +3 -3
- package/web/dist/assets/{cssMode-DOakqY50.js → cssMode-D42SG8q2.js} +1 -1
- package/web/dist/assets/{freemarker2-WsMzPf-p.js → freemarker2-HuLVqAyk.js} +1 -1
- package/web/dist/assets/{handlebars-CFf3afIj.js → handlebars-CQbtHmDj.js} +1 -1
- package/web/dist/assets/{html-Duk6L3Ci.js → html-DCQkaSoP.js} +1 -1
- package/web/dist/assets/{htmlMode-CuaMCYCU.js → htmlMode-Occ0oux7.js} +1 -1
- package/web/dist/assets/{index-DpS67tuQ.js → index-382ZKQ6T.js} +132 -127
- package/web/dist/assets/{index-CGbzqzmX.js → index-DB6lAoRD.js} +1 -1
- package/web/dist/assets/index-YQ0zZelJ.css +32 -0
- package/web/dist/assets/{javascript-57wNn1QZ.js → javascript-DkkoffJP.js} +1 -1
- package/web/dist/assets/{jsonMode-DPgMOUkd.js → jsonMode-CvTmblXx.js} +1 -1
- package/web/dist/assets/{liquid-DHlnUOel.js → liquid-DhsiR38F.js} +1 -1
- package/web/dist/assets/{lspLanguageFeatures-BIy_BVAi.js → lspLanguageFeatures-lfpHn74b.js} +1 -1
- package/web/dist/assets/{mdx-SCDblWWe.js → mdx-LuMf9AxQ.js} +1 -1
- package/web/dist/assets/{python-DcjgL-6k.js → python-BkVjahJB.js} +1 -1
- package/web/dist/assets/{razor-q_Zkc8t3.js → razor-CoVA4mXW.js} +1 -1
- package/web/dist/assets/{tsMode-N3PGleJF.js → tsMode-C3jqF6o4.js} +1 -1
- package/web/dist/assets/{typescript-UQxkBsMs.js → typescript-DzEhu0NR.js} +1 -1
- package/web/dist/assets/{workers-BVzxuZVI.js → workers-DO15KvMQ.js} +1 -1
- package/web/dist/assets/{xml-BDNQC6ch.js → xml-Dnv-hWWk.js} +1 -1
- package/web/dist/assets/{yaml-FyKAiCgl.js → yaml-hUd7D5AP.js} +1 -1
- package/web/dist/index.html +2 -2
- package/web/dist/assets/index-DI4uTQnz.css +0 -32
package/README.md
CHANGED
|
@@ -59,7 +59,7 @@ Clauder Hub runs the real `claude` you already use, in real terminals, and adds
|
|
|
59
59
|
<tr>
|
|
60
60
|
<td>
|
|
61
61
|
<h3>Answer from your phone</h3>
|
|
62
|
-
Telegram and Slack bots relay every question with inline buttons, and BotFather-style commands (<code>/new</code>, <code>/sessions</code>, <code>/summary</code>, <code>/stop</code>) drive sessions from chat. No public URL needed.
|
|
62
|
+
Telegram and Slack bots relay every question with inline buttons, and BotFather-style commands (<code>/new</code>, <code>/jobs</code>, <code>/sessions</code>, <code>/summary</code>, <code>/stop</code>) drive sessions from chat. No public URL needed.
|
|
63
63
|
</td>
|
|
64
64
|
<td>
|
|
65
65
|
<picture>
|
package/package.json
CHANGED
package/server/dist/index.js
CHANGED
|
@@ -27214,6 +27214,8 @@ CREATE INDEX IF NOT EXISTS idx_runs_status ON runs(status, created_at);
|
|
|
27214
27214
|
ensureColumn("sessions", "setup", "TEXT");
|
|
27215
27215
|
ensureColumn("channel_refs", "project_id", "TEXT");
|
|
27216
27216
|
ensureColumn("channel_threads", "project_id", "TEXT");
|
|
27217
|
+
ensureColumn("runs", "resume", "INTEGER NOT NULL DEFAULT 0");
|
|
27218
|
+
ensureColumn("runs", "queued_at", "TEXT");
|
|
27217
27219
|
}
|
|
27218
27220
|
function now() {
|
|
27219
27221
|
return (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -28743,6 +28745,14 @@ If the category was given as "auto", choose the one that fits the task best. Do
|
|
|
28743
28745
|
|
|
28744
28746
|
Finish what is missing, then send the done block again.`
|
|
28745
28747
|
},
|
|
28748
|
+
{
|
|
28749
|
+
key: "typed.resumeRun",
|
|
28750
|
+
label: "First message of a resumed run",
|
|
28751
|
+
audience: "claude-typed",
|
|
28752
|
+
description: "Typed into a stopped run's own session when it is resumed from the queue (Resume on the Automation page, /resume on a channel).",
|
|
28753
|
+
placeholders: [],
|
|
28754
|
+
text: "You were stopped part-way through this task. Check what is already done, then carry on from where you left off, without redoing finished work."
|
|
28755
|
+
},
|
|
28746
28756
|
{
|
|
28747
28757
|
key: "typed.addMcp",
|
|
28748
28758
|
label: "First message of an Add MCP server task",
|
|
@@ -28893,6 +28903,8 @@ Reply *install* to run it (project scope) and resume the session, or *skip* to c
|
|
|
28893
28903
|
|
|
28894
28904
|
{{p}}new - start a session (with a prompt, or I ask for one)
|
|
28895
28905
|
{{p}}run - queue an unattended run
|
|
28906
|
+
{{p}}jobs - the saved jobs, to run one ({{p}}job <name> runs one by name)
|
|
28907
|
+
{{p}}resume - carry on a stopped run
|
|
28896
28908
|
{{p}}sessions - the sessions, to pick one
|
|
28897
28909
|
{{p}}summary - what a session did
|
|
28898
28910
|
{{p}}stop - stop a running session
|
|
@@ -28947,7 +28959,7 @@ Reply to this message to talk to the session. Its questions come here too.`
|
|
|
28947
28959
|
{{p}}projects - the projects, to pick one
|
|
28948
28960
|
{{p}}use <project> - work on a project
|
|
28949
28961
|
{{p}}addproject <words> - add a project: describe it, I find the folder
|
|
28950
|
-
Then, on that project: {{p}}new, {{p}}run, {{p}}sessions, {{p}}summary, {{p}}stop, {{p}}queue, {{p}}cancel.
|
|
28962
|
+
Then, on that project: {{p}}new, {{p}}run, {{p}}jobs, {{p}}resume, {{p}}sessions, {{p}}summary, {{p}}stop, {{p}}queue, {{p}}cancel.
|
|
28951
28963
|
|
|
28952
28964
|
Working on: {{current}}`
|
|
28953
28965
|
},
|
|
@@ -28967,13 +28979,29 @@ Working on: {{current}}`
|
|
|
28967
28979
|
placeholders: ["request", "p"],
|
|
28968
28980
|
text: 'I found no project folder that looks like "{{request}}". Describe it another way (its name, what it does), or send its full path. Or {{p}}cancel.'
|
|
28969
28981
|
},
|
|
28982
|
+
{
|
|
28983
|
+
key: "channel.jobConfirm",
|
|
28984
|
+
label: "Channel command: confirming a job run",
|
|
28985
|
+
audience: "channel",
|
|
28986
|
+
description: "Asked before a saved job is queued from the channel ({{p}}job, or a button from {{p}}jobs).",
|
|
28987
|
+
placeholders: ["job", "prompt"],
|
|
28988
|
+
text: "Run {{job}} now? It joins the queue as an unattended run; its questions and its result come here.\n\n{{prompt}}"
|
|
28989
|
+
},
|
|
28990
|
+
{
|
|
28991
|
+
key: "channel.resumeConfirm",
|
|
28992
|
+
label: "Channel command: confirming a resume",
|
|
28993
|
+
audience: "channel",
|
|
28994
|
+
description: "Asked before a stopped run is queued again to carry on its own session ({{p}}resume).",
|
|
28995
|
+
placeholders: ["run"],
|
|
28996
|
+
text: "Resume {{run}}? It joins the back of the queue and carries on its own conversation from where it stopped."
|
|
28997
|
+
},
|
|
28970
28998
|
{
|
|
28971
28999
|
key: "channel.stopConfirm",
|
|
28972
29000
|
label: "Channel command: confirming a stop",
|
|
28973
29001
|
audience: "channel",
|
|
28974
29002
|
description: "Asked before a session is stopped from the channel.",
|
|
28975
29003
|
placeholders: ["session"],
|
|
28976
|
-
text: "Stop {{session}}? Its terminal closes; the conversation is kept and can be resumed from the dashboard."
|
|
29004
|
+
text: "Stop {{session}}? Its terminal closes; the conversation is kept and can be resumed from the dashboard, or with resume for a run."
|
|
28977
29005
|
}
|
|
28978
29006
|
];
|
|
28979
29007
|
}
|
|
@@ -30365,6 +30393,23 @@ function queueFromChannel(prompt) {
|
|
|
30365
30393
|
const { position } = enqueue({ source: "manual", prompt, category: "auto" });
|
|
30366
30394
|
return { title: "\u{1F5C2} Run queued", lines: [renderPrompt("channel.queued", { position: String(position) })], buttons: [[{ label: "\u{1F5C2} Queue", command: "queue" }]] };
|
|
30367
30395
|
}
|
|
30396
|
+
function findJob(arg) {
|
|
30397
|
+
const wanted = arg.trim().toLowerCase();
|
|
30398
|
+
if (!wanted) return null;
|
|
30399
|
+
const jobs = listJobs();
|
|
30400
|
+
const exact = getJob(arg.trim()) ?? jobs.find((job) => job.name.toLowerCase() === wanted);
|
|
30401
|
+
if (exact) return exact;
|
|
30402
|
+
const matches = jobs.filter((job) => job.name.toLowerCase().includes(wanted));
|
|
30403
|
+
return matches.length === 1 ? matches[0] : null;
|
|
30404
|
+
}
|
|
30405
|
+
function jobPicker(title, jobs, empty) {
|
|
30406
|
+
if (!jobs.length) return empty;
|
|
30407
|
+
return {
|
|
30408
|
+
title,
|
|
30409
|
+
lines: jobs.slice(0, 15).map((job, index) => `${index + 1}. ${job.name} \xB7 ${job.kind === "cron" ? `cron ${job.schedule}` : "manual / webhook"}${job.enabled ? "" : " \xB7 disabled"}`),
|
|
30410
|
+
buttons: jobs.slice(0, 15).map((job, index) => [{ label: short(`\u25B6\uFE0F ${index + 1}. ${job.name}`, 60), command: `job ${job.id}` }])
|
|
30411
|
+
};
|
|
30412
|
+
}
|
|
30368
30413
|
function queueCard() {
|
|
30369
30414
|
const runs = listRuns(50).filter((run4) => run4.status === "queued" || run4.status === "running");
|
|
30370
30415
|
if (!runs.length) return "The queue is empty.";
|
|
@@ -30450,6 +30495,58 @@ async function runCommand(channelId, input, from, options = {}) {
|
|
|
30450
30495
|
if (arg) return name === "new" ? startFromChannel(channelId, arg, from, options) : queueFromChannel(arg);
|
|
30451
30496
|
awaiting.set(key, name);
|
|
30452
30497
|
return { title: name === "new" ? "\u25B6\uFE0F New session" : "\u{1F5C2} New run", lines: [renderPrompt(name === "new" ? "channel.askNew" : "channel.askRun", { p: p2 })], buttons: [[{ label: "Cancel", command: "cancel" }]] };
|
|
30498
|
+
case "jobs":
|
|
30499
|
+
return jobPicker("\u2699\uFE0F Jobs: pick one to run", listJobs(), "No jobs yet. Create one on the Automation page.");
|
|
30500
|
+
case "job": {
|
|
30501
|
+
if (!arg) return jobPicker("\u2699\uFE0F Run which job?", listJobs(), "No jobs yet. Create one on the Automation page.");
|
|
30502
|
+
const job = findJob(arg);
|
|
30503
|
+
if (!job) {
|
|
30504
|
+
const like = listJobs().filter((item) => item.name.toLowerCase().includes(arg.toLowerCase()));
|
|
30505
|
+
return jobPicker(`\u{1F914} Which job did you mean?`, like.length ? like : listJobs(), `No job like "${short(arg, 60)}". ${p2}jobs lists them.`);
|
|
30506
|
+
}
|
|
30507
|
+
return {
|
|
30508
|
+
title: "\u2699\uFE0F Run the job?",
|
|
30509
|
+
lines: [renderPrompt("channel.jobConfirm", { job: job.name, prompt: short(flat(job.prompt), 300) })],
|
|
30510
|
+
buttons: [
|
|
30511
|
+
[
|
|
30512
|
+
{ label: "Yes, run it", command: `job! ${job.id}`, tone: "primary" },
|
|
30513
|
+
{ label: "No", command: "cancel" }
|
|
30514
|
+
]
|
|
30515
|
+
]
|
|
30516
|
+
};
|
|
30517
|
+
}
|
|
30518
|
+
case "job!": {
|
|
30519
|
+
const job = getJob(arg);
|
|
30520
|
+
if (!job) return `That job is gone. ${p2}jobs lists them.`;
|
|
30521
|
+
const { position } = enqueue({ job, source: "manual" });
|
|
30522
|
+
return { title: `\u{1F5C2} ${job.name} queued`, lines: [renderPrompt("channel.queued", { position: String(position) })], buttons: [[{ label: "\u{1F5C2} Queue", command: "queue" }]] };
|
|
30523
|
+
}
|
|
30524
|
+
case "resume": {
|
|
30525
|
+
const runs = resumable();
|
|
30526
|
+
const run4 = arg ? runs.find((item) => item.id === arg) ?? runs[Number(arg) - 1] : void 0;
|
|
30527
|
+
if (!run4) {
|
|
30528
|
+
if (!runs.length) return "No stopped run to resume.";
|
|
30529
|
+
return {
|
|
30530
|
+
title: "\u23EF Resume which run?",
|
|
30531
|
+
lines: runs.map((item, index) => `${index + 1}. ${short(flat(item.jobName ?? item.prompt), 80)} \xB7 stopped ${item.finishedAt ? new Date(item.finishedAt).toLocaleString() : ""}`),
|
|
30532
|
+
buttons: runs.map((item, index) => [{ label: short(`\u23EF ${index + 1}. ${flat(item.jobName ?? item.prompt)}`, 60), command: `resume ${item.id}` }])
|
|
30533
|
+
};
|
|
30534
|
+
}
|
|
30535
|
+
return {
|
|
30536
|
+
title: "\u23EF Resume the run?",
|
|
30537
|
+
lines: [renderPrompt("channel.resumeConfirm", { run: flat(run4.jobName ?? short(run4.prompt, 80)) })],
|
|
30538
|
+
buttons: [
|
|
30539
|
+
[
|
|
30540
|
+
{ label: "Yes, resume it", command: `resume! ${run4.id}`, tone: "primary" },
|
|
30541
|
+
{ label: "No", command: "cancel" }
|
|
30542
|
+
]
|
|
30543
|
+
]
|
|
30544
|
+
};
|
|
30545
|
+
}
|
|
30546
|
+
case "resume!": {
|
|
30547
|
+
const { run: run4, position } = resumeRun(arg);
|
|
30548
|
+
return { title: `\u23EF ${flat(run4.jobName ?? short(run4.prompt, 60))} queued again`, lines: [renderPrompt("channel.queued", { position: String(position) })], buttons: [[{ label: "\u{1F5C2} Queue", command: "queue" }]] };
|
|
30549
|
+
}
|
|
30453
30550
|
case "cancel":
|
|
30454
30551
|
suggested.delete(suggestKey(channelId, from));
|
|
30455
30552
|
return focused.delete(key) ? "Cancelled. Plain messages go nowhere until you pick a session." : "Cancelled.";
|
|
@@ -30509,7 +30606,7 @@ async function runCommand(channelId, input, from, options = {}) {
|
|
|
30509
30606
|
return await interpret(channelId, from, input, options) ?? `I don't know ${p2}${name}. ${p2}help lists what I can do.`;
|
|
30510
30607
|
}
|
|
30511
30608
|
}
|
|
30512
|
-
var COMMAND, awaiting, focused, FOCUS_IDLE_MS, numbered, keyOf, HUB_COMMANDS, short, flat, STATUS, suggested, suggestKey, sessionLine, COMMAND_GUIDE, GUESSABLE, GLOBAL_COMMAND_MENU, COMMAND_MENU;
|
|
30609
|
+
var COMMAND, awaiting, focused, FOCUS_IDLE_MS, numbered, keyOf, HUB_COMMANDS, short, flat, STATUS, resumable, suggested, suggestKey, sessionLine, COMMAND_GUIDE, GUESSABLE, GLOBAL_COMMAND_MENU, COMMAND_MENU;
|
|
30513
30610
|
var init_commands = __esm({
|
|
30514
30611
|
"server/src/channels/commands.ts"() {
|
|
30515
30612
|
"use strict";
|
|
@@ -30527,16 +30624,20 @@ var init_commands = __esm({
|
|
|
30527
30624
|
FOCUS_IDLE_MS = 60 * 6e4;
|
|
30528
30625
|
numbered = /* @__PURE__ */ new Map();
|
|
30529
30626
|
keyOf = (channelId) => `${currentProject().id}:${channelId}`;
|
|
30530
|
-
HUB_COMMANDS = /* @__PURE__ */ new Set(["start", "help", "new", "run", "cancel", "leave", "sessions", "session", "summary", "stop", "stop!", "queue", "cancelrun"]);
|
|
30627
|
+
HUB_COMMANDS = /* @__PURE__ */ new Set(["start", "help", "new", "run", "jobs", "job", "job!", "resume", "resume!", "cancel", "leave", "sessions", "session", "summary", "stop", "stop!", "queue", "cancelrun"]);
|
|
30531
30628
|
short = (text, length) => text.length > length ? `${text.slice(0, length - 1)}\u2026` : text;
|
|
30532
30629
|
flat = (text) => text.replace(/\s+/g, " ").trim();
|
|
30533
30630
|
STATUS = { waiting: "\u23F3 waiting for you", running: "\u25B6\uFE0F running", done: "\u2705 done", exited: "\u23F9 stopped", external: "outside the hub" };
|
|
30631
|
+
resumable = () => listRuns(50).filter((run4) => run4.sessionId && run4.status === "exited").slice(0, 10);
|
|
30534
30632
|
suggested = /* @__PURE__ */ new Map();
|
|
30535
30633
|
suggestKey = (channelId, from) => `${keyOf(channelId)}:${from}`;
|
|
30536
30634
|
sessionLine = (session, index) => `${index + 1}. ${session.title} \xB7 ${STATUS[session.status] ?? session.status}${session.firstPrompt ? ` \xB7 asked: ${short(flat(session.firstPrompt), 120)}` : ""}`;
|
|
30537
30635
|
COMMAND_GUIDE = [
|
|
30538
30636
|
"new <what the session should do> - start a session",
|
|
30539
30637
|
"run <what to do> - queue an unattended run",
|
|
30638
|
+
"jobs - list the saved jobs, to run one",
|
|
30639
|
+
"job <name> - run a saved job now (asks first)",
|
|
30640
|
+
"resume - carry on a stopped run (asks first)",
|
|
30540
30641
|
"sessions - list the sessions",
|
|
30541
30642
|
"session <number> - pick a session, to talk to it",
|
|
30542
30643
|
"summary <number> - what a session did",
|
|
@@ -30544,13 +30645,15 @@ var init_commands = __esm({
|
|
|
30544
30645
|
"queue - the run queue",
|
|
30545
30646
|
"help - the commands"
|
|
30546
30647
|
].join("\n");
|
|
30547
|
-
GUESSABLE = /* @__PURE__ */ new Set(["new", "run", "sessions", "session", "summary", "stop", "queue", "help"]);
|
|
30648
|
+
GUESSABLE = /* @__PURE__ */ new Set(["new", "run", "jobs", "job", "resume", "sessions", "session", "summary", "stop", "queue", "help"]);
|
|
30548
30649
|
GLOBAL_COMMAND_MENU = [
|
|
30549
30650
|
{ command: "projects", description: "Pick a project" },
|
|
30550
30651
|
{ command: "use", description: "Work on a project: /use <name>" },
|
|
30551
30652
|
{ command: "addproject", description: "Add a project: describe it, I find it" },
|
|
30552
30653
|
{ command: "new", description: "Start a session in the picked project" },
|
|
30553
30654
|
{ command: "run", description: "Queue a run in the picked project" },
|
|
30655
|
+
{ command: "jobs", description: "The picked project's jobs, to run one" },
|
|
30656
|
+
{ command: "resume", description: "Carry on a stopped run in the picked project" },
|
|
30554
30657
|
{ command: "sessions", description: "The picked project's sessions" },
|
|
30555
30658
|
{ command: "summary", description: "What a session did" },
|
|
30556
30659
|
{ command: "stop", description: "Stop a running session" },
|
|
@@ -30561,6 +30664,8 @@ var init_commands = __esm({
|
|
|
30561
30664
|
COMMAND_MENU = [
|
|
30562
30665
|
{ command: "new", description: "Start a session" },
|
|
30563
30666
|
{ command: "run", description: "Queue an unattended run" },
|
|
30667
|
+
{ command: "jobs", description: "Run a saved job" },
|
|
30668
|
+
{ command: "resume", description: "Carry on a stopped run" },
|
|
30564
30669
|
{ command: "sessions", description: "Pick a session" },
|
|
30565
30670
|
{ command: "summary", description: "What a session did" },
|
|
30566
30671
|
{ command: "stop", description: "Stop a running session" },
|
|
@@ -33148,11 +33253,32 @@ function enqueue(input) {
|
|
|
33148
33253
|
if (input.job) db.prepare("UPDATE jobs SET last_run_at = ? WHERE id = ?").run(now(), input.job.id);
|
|
33149
33254
|
broadcast("runs", {});
|
|
33150
33255
|
broadcast("jobs", {});
|
|
33151
|
-
const position =
|
|
33256
|
+
const position = queuePosition(id);
|
|
33152
33257
|
advance();
|
|
33153
33258
|
const run4 = listRuns(500).find((item) => item.id === id);
|
|
33154
33259
|
return { run: run4, position };
|
|
33155
33260
|
}
|
|
33261
|
+
function queuePosition(id) {
|
|
33262
|
+
return db.prepare(`SELECT COUNT(*) AS n FROM runs WHERE status = 'queued' AND ${IN_LINE} <= (SELECT ${IN_LINE} FROM runs WHERE id = ?)`).get(id).n;
|
|
33263
|
+
}
|
|
33264
|
+
function stopRun(id) {
|
|
33265
|
+
const run4 = listRuns(500).find((item) => item.id === id);
|
|
33266
|
+
if (!run4 || run4.status !== "running" || !run4.sessionId) throw new Error("That run is not running.");
|
|
33267
|
+
const terminal = terminalForSession(run4.sessionId);
|
|
33268
|
+
if (!terminal) throw new Error("That run has no running terminal.");
|
|
33269
|
+
killTerminal(terminal);
|
|
33270
|
+
}
|
|
33271
|
+
function resumeRun(id) {
|
|
33272
|
+
const run4 = listRuns(500).find((item) => item.id === id);
|
|
33273
|
+
if (!run4?.sessionId) throw new Error("That run never started, so there is nothing to resume.");
|
|
33274
|
+
if (run4.status === "queued" || run4.status === "running") throw new Error("That run is already queued or running.");
|
|
33275
|
+
if (terminalForSession(run4.sessionId)) throw new Error("Its session is already open.");
|
|
33276
|
+
db.prepare("UPDATE runs SET status = 'queued', resume = 1, queued_at = ?, finished_at = NULL WHERE id = ?").run(now(), id);
|
|
33277
|
+
broadcast("runs", {});
|
|
33278
|
+
const position = queuePosition(id);
|
|
33279
|
+
advance();
|
|
33280
|
+
return { run: listRuns(500).find((item) => item.id === id), position };
|
|
33281
|
+
}
|
|
33156
33282
|
function cancelRun(id) {
|
|
33157
33283
|
db.prepare("UPDATE runs SET status = 'cancelled', finished_at = ? WHERE id = ? AND status = 'queued'").run(now(), id);
|
|
33158
33284
|
broadcast("runs", {});
|
|
@@ -33177,11 +33303,11 @@ function advance() {
|
|
|
33177
33303
|
try {
|
|
33178
33304
|
const running = db.prepare("SELECT id FROM runs WHERE status = 'running' LIMIT 1").get();
|
|
33179
33305
|
if (running) return;
|
|
33180
|
-
const next = db.prepare(
|
|
33306
|
+
const next = db.prepare(`SELECT * FROM runs WHERE status = 'queued' ORDER BY ${IN_LINE} LIMIT 1`).get();
|
|
33181
33307
|
if (!next) return;
|
|
33182
33308
|
const job = next.job_id ? getJob(next.job_id) : null;
|
|
33183
33309
|
try {
|
|
33184
|
-
const terminal = startSession({
|
|
33310
|
+
const terminal = next.resume && next.session_id ? startSession({ resumeId: next.session_id, prompt: promptText("typed.resumeRun"), runId: next.id, permissionMode: claudeSettings().runPermissionMode }) : startSession({
|
|
33185
33311
|
prompt: next.prompt,
|
|
33186
33312
|
origin: next.source === "manual" ? "queue" : next.source,
|
|
33187
33313
|
runId: next.id,
|
|
@@ -33236,7 +33362,7 @@ function recoverRuns() {
|
|
|
33236
33362
|
db.prepare("UPDATE interactions SET status = 'closed', answered_by = 'hub restart' WHERE status = 'open'").run();
|
|
33237
33363
|
return stranded;
|
|
33238
33364
|
}
|
|
33239
|
-
var crons, cronKey, toJob, toRun, advancing, waitingForClaude;
|
|
33365
|
+
var crons, cronKey, toJob, toRun, IN_LINE, advancing, waitingForClaude;
|
|
33240
33366
|
var init_jobs = __esm({
|
|
33241
33367
|
"server/src/jobs.ts"() {
|
|
33242
33368
|
"use strict";
|
|
@@ -33276,10 +33402,12 @@ var init_jobs = __esm({
|
|
|
33276
33402
|
category: row.category,
|
|
33277
33403
|
status: row.status,
|
|
33278
33404
|
sessionId: row.session_id,
|
|
33405
|
+
resume: Boolean(row.resume),
|
|
33279
33406
|
createdAt: row.created_at,
|
|
33280
33407
|
startedAt: row.started_at,
|
|
33281
33408
|
finishedAt: row.finished_at
|
|
33282
33409
|
});
|
|
33410
|
+
IN_LINE = "COALESCE(queued_at, created_at)";
|
|
33283
33411
|
advancing = false;
|
|
33284
33412
|
waitingForClaude = /* @__PURE__ */ new Set();
|
|
33285
33413
|
}
|
|
@@ -34349,6 +34477,8 @@ api.post(
|
|
|
34349
34477
|
})
|
|
34350
34478
|
);
|
|
34351
34479
|
api.get("/runs", wrap2(() => listRuns()));
|
|
34480
|
+
api.post("/runs/:id/stop", wrap2((req) => stopRun(req.params.id)));
|
|
34481
|
+
api.post("/runs/:id/resume", wrap2((req) => resumeRun(req.params.id)));
|
|
34352
34482
|
api.post("/runs", wrap2((req) => enqueue({ source: "manual", prompt: String(req.body?.prompt ?? ""), category: req.body?.category || "auto" })));
|
|
34353
34483
|
api.post("/runs/:id/cancel", wrap2((req) => cancelRun(req.params.id)));
|
|
34354
34484
|
api.get("/mcp/suggestions", wrap2(() => listSuggestions()));
|