clay-server 2.7.1 → 2.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/cli.js +1 -2
- package/lib/config.js +4 -7
- package/lib/project.js +5 -177
- package/lib/public/app.js +50 -204
- package/lib/public/css/loop.css +1 -62
- package/lib/public/index.html +10 -83
- package/lib/public/modules/markdown.js +0 -10
- package/lib/public/style.css +0 -2
- package/lib/sessions.js +1 -1
- package/package.json +1 -1
- package/lib/public/css/scheduler-modal.css +0 -546
- package/lib/public/css/scheduler.css +0 -944
- package/lib/public/modules/scheduler.js +0 -1240
- package/lib/scheduler.js +0 -362
package/bin/cli.js
CHANGED
|
@@ -21,9 +21,8 @@ var { execSync, execFileSync, spawn } = require("child_process");
|
|
|
21
21
|
var qrcode = require("qrcode-terminal");
|
|
22
22
|
var net = require("net");
|
|
23
23
|
|
|
24
|
-
// Detect dev mode — dev and prod
|
|
24
|
+
// Detect dev mode (no separate storage — dev and prod share ~/.clay)
|
|
25
25
|
var _isDev = (process.argv[1] && path.basename(process.argv[1]) === "clay-dev") || process.argv.includes("--dev");
|
|
26
|
-
if (_isDev) process.env.CLAY_DEV = "1";
|
|
27
26
|
|
|
28
27
|
var { loadConfig, saveConfig, configPath, socketPath, logPath, ensureConfigDir, isDaemonAlive, isDaemonAliveAsync, generateSlug, clearStaleConfig, loadClayrc, saveClayrc, readCrashInfo } = require("../lib/config");
|
|
29
28
|
var { sendIPCCommand } = require("../lib/ipc");
|
package/lib/config.js
CHANGED
|
@@ -70,23 +70,20 @@ var CONFIG_DIR = CLAY_HOME;
|
|
|
70
70
|
var CLAYRC_PATH = path.join(os.homedir(), ".clayrc");
|
|
71
71
|
var CRASH_INFO_PATH = path.join(CONFIG_DIR, "crash.json");
|
|
72
72
|
|
|
73
|
-
// Dev mode uses separate daemon files so dev and prod can run simultaneously
|
|
74
|
-
var _devMode = !!process.env.CLAY_DEV;
|
|
75
|
-
|
|
76
73
|
function configPath() {
|
|
77
|
-
return path.join(CONFIG_DIR,
|
|
74
|
+
return path.join(CONFIG_DIR, "daemon.json");
|
|
78
75
|
}
|
|
79
76
|
|
|
80
77
|
function socketPath() {
|
|
81
78
|
if (process.platform === "win32") {
|
|
82
|
-
var pipeName =
|
|
79
|
+
var pipeName = "clay-daemon";
|
|
83
80
|
return "\\\\.\\pipe\\" + pipeName;
|
|
84
81
|
}
|
|
85
|
-
return path.join(CONFIG_DIR,
|
|
82
|
+
return path.join(CONFIG_DIR, "daemon.sock");
|
|
86
83
|
}
|
|
87
84
|
|
|
88
85
|
function logPath() {
|
|
89
|
-
return path.join(CONFIG_DIR,
|
|
86
|
+
return path.join(CONFIG_DIR, "daemon.log");
|
|
90
87
|
}
|
|
91
88
|
|
|
92
89
|
function ensureConfigDir() {
|
package/lib/project.js
CHANGED
|
@@ -8,7 +8,6 @@ var { createTerminalManager } = require("./terminal-manager");
|
|
|
8
8
|
var { createNotesManager } = require("./notes");
|
|
9
9
|
var { fetchLatestVersion, isNewer } = require("./updater");
|
|
10
10
|
var { execFileSync, spawn } = require("child_process");
|
|
11
|
-
var { createLoopRegistry } = require("./scheduler");
|
|
12
11
|
|
|
13
12
|
var MAX_UPLOAD_BYTES = 50 * 1024 * 1024; // 50 MB
|
|
14
13
|
|
|
@@ -396,71 +395,17 @@ function createProjectContext(opts) {
|
|
|
396
395
|
judgeReady: hasJudge,
|
|
397
396
|
loopJsonReady: hasLoopJson,
|
|
398
397
|
bothReady: hasPrompt && hasJudge,
|
|
399
|
-
taskId: loopState.loopId,
|
|
400
398
|
});
|
|
401
399
|
// Auto-transition to approval phase when both files appear
|
|
402
400
|
if (hasPrompt && hasJudge && loopState.phase === "crafting") {
|
|
403
401
|
loopState.phase = "approval";
|
|
404
402
|
saveLoopState();
|
|
405
|
-
|
|
406
|
-
// Parse recommended title from crafting session conversation
|
|
407
|
-
if (loopState.craftingSessionId && loopState.loopId) {
|
|
408
|
-
var craftSess = sm.sessions.get(loopState.craftingSessionId);
|
|
409
|
-
if (craftSess && craftSess.history) {
|
|
410
|
-
for (var hi = craftSess.history.length - 1; hi >= 0; hi--) {
|
|
411
|
-
var entry = craftSess.history[hi];
|
|
412
|
-
var entryText = entry.text || "";
|
|
413
|
-
var titleMatch = entryText.match(/\[\[LOOP_TITLE:\s*(.+?)\]\]/);
|
|
414
|
-
if (titleMatch) {
|
|
415
|
-
var suggestedTitle = titleMatch[1].trim();
|
|
416
|
-
if (suggestedTitle) {
|
|
417
|
-
loopRegistry.updateRecord(loopState.loopId, { name: suggestedTitle });
|
|
418
|
-
}
|
|
419
|
-
break;
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
403
|
}
|
|
425
404
|
}
|
|
426
405
|
|
|
427
406
|
// Load persisted state on startup
|
|
428
407
|
loadLoopState();
|
|
429
408
|
|
|
430
|
-
// --- Loop Registry (unified one-off + scheduled) ---
|
|
431
|
-
var activeRegistryId = null; // track which registry record triggered current loop
|
|
432
|
-
|
|
433
|
-
var loopRegistry = createLoopRegistry({
|
|
434
|
-
cwd: cwd,
|
|
435
|
-
onTrigger: function (record) {
|
|
436
|
-
// Only trigger if no loop is currently active
|
|
437
|
-
if (loopState.active || loopState.phase === "executing") {
|
|
438
|
-
console.log("[loop-registry] Skipping trigger — loop already active");
|
|
439
|
-
return;
|
|
440
|
-
}
|
|
441
|
-
// Verify the loop directory and files exist
|
|
442
|
-
var recDir = path.join(cwd, ".claude", "loops", record.id);
|
|
443
|
-
try {
|
|
444
|
-
fs.accessSync(path.join(recDir, "PROMPT.md"));
|
|
445
|
-
fs.accessSync(path.join(recDir, "JUDGE.md"));
|
|
446
|
-
} catch (e) {
|
|
447
|
-
console.error("[loop-registry] Loop files missing for " + record.id);
|
|
448
|
-
return;
|
|
449
|
-
}
|
|
450
|
-
// Set the loopId and start
|
|
451
|
-
loopState.loopId = record.id;
|
|
452
|
-
activeRegistryId = record.id;
|
|
453
|
-
console.log("[loop-registry] Auto-starting loop: " + record.name);
|
|
454
|
-
send({ type: "schedule_run_started", recordId: record.id });
|
|
455
|
-
startLoop();
|
|
456
|
-
},
|
|
457
|
-
onChange: function (records) {
|
|
458
|
-
send({ type: "loop_registry_updated", records: records });
|
|
459
|
-
},
|
|
460
|
-
});
|
|
461
|
-
loopRegistry.load();
|
|
462
|
-
loopRegistry.startTimer();
|
|
463
|
-
|
|
464
409
|
function startLoop(opts) {
|
|
465
410
|
var loopOpts = opts || {};
|
|
466
411
|
var dir = loopDir();
|
|
@@ -709,19 +654,6 @@ function createProjectContext(opts) {
|
|
|
709
654
|
results: loopState.results,
|
|
710
655
|
});
|
|
711
656
|
|
|
712
|
-
// Record result in loop registry
|
|
713
|
-
if (loopState.loopId) {
|
|
714
|
-
loopRegistry.recordRun(loopState.loopId, {
|
|
715
|
-
reason: reason,
|
|
716
|
-
startedAt: loopState.startedAt,
|
|
717
|
-
iterations: loopState.iteration,
|
|
718
|
-
});
|
|
719
|
-
}
|
|
720
|
-
if (activeRegistryId) {
|
|
721
|
-
send({ type: "schedule_run_finished", recordId: activeRegistryId, reason: reason, iterations: loopState.iteration });
|
|
722
|
-
activeRegistryId = null;
|
|
723
|
-
}
|
|
724
|
-
|
|
725
657
|
if (pushModule) {
|
|
726
658
|
var body = reason === "pass"
|
|
727
659
|
? "Task completed after " + loopState.iteration + " iteration(s)"
|
|
@@ -841,7 +773,6 @@ function createProjectContext(opts) {
|
|
|
841
773
|
sendTo(ws, { type: "config_state", model: sm.currentModel || "", mode: sm.currentPermissionMode || "default", effort: sm.currentEffort || "medium", betas: sm.currentBetas || [] });
|
|
842
774
|
sendTo(ws, { type: "term_list", terminals: tm.list() });
|
|
843
775
|
sendTo(ws, { type: "notes_list", notes: nm.list() });
|
|
844
|
-
sendTo(ws, { type: "loop_registry_updated", records: loopRegistry.getAll() });
|
|
845
776
|
|
|
846
777
|
// Ralph Loop availability
|
|
847
778
|
var hasLoopFiles = false;
|
|
@@ -878,7 +809,6 @@ function createProjectContext(opts) {
|
|
|
878
809
|
promptReady: _hasPrompt,
|
|
879
810
|
judgeReady: _hasJudge,
|
|
880
811
|
bothReady: _hasPrompt && _hasJudge,
|
|
881
|
-
taskId: loopState.loopId,
|
|
882
812
|
});
|
|
883
813
|
}
|
|
884
814
|
|
|
@@ -2089,16 +2019,6 @@ function createProjectContext(opts) {
|
|
|
2089
2019
|
}
|
|
2090
2020
|
|
|
2091
2021
|
if (msg.type === "loop_start") {
|
|
2092
|
-
// If this loop has a cron schedule, don't run immediately — just confirm registration
|
|
2093
|
-
if (loopState.wizardData && loopState.wizardData.cron) {
|
|
2094
|
-
loopState.active = false;
|
|
2095
|
-
loopState.phase = "done";
|
|
2096
|
-
saveLoopState();
|
|
2097
|
-
send({ type: "loop_finished", reason: "scheduled", iterations: 0, results: [] });
|
|
2098
|
-
send({ type: "ralph_phase", phase: "idle", wizardData: null });
|
|
2099
|
-
send({ type: "loop_scheduled", recordId: loopState.loopId, cron: loopState.wizardData.cron });
|
|
2100
|
-
return;
|
|
2101
|
-
}
|
|
2102
2022
|
startLoop();
|
|
2103
2023
|
return;
|
|
2104
2024
|
}
|
|
@@ -2111,29 +2031,17 @@ function createProjectContext(opts) {
|
|
|
2111
2031
|
if (msg.type === "ralph_wizard_complete") {
|
|
2112
2032
|
var wData = msg.data || {};
|
|
2113
2033
|
var maxIter = wData.maxIterations || 25;
|
|
2114
|
-
var wizardCron = wData.cron || null;
|
|
2115
2034
|
var newLoopId = generateLoopId();
|
|
2116
2035
|
loopState.loopId = newLoopId;
|
|
2117
2036
|
loopState.wizardData = {
|
|
2118
|
-
name: wData.name ||
|
|
2037
|
+
name: (wData.name || "").replace(/[^a-zA-Z0-9_-]/g, "") || "ralph",
|
|
2119
2038
|
task: wData.task || "",
|
|
2120
2039
|
maxIterations: maxIter,
|
|
2121
|
-
cron: wizardCron,
|
|
2122
2040
|
};
|
|
2123
2041
|
loopState.phase = "crafting";
|
|
2124
2042
|
loopState.startedAt = Date.now();
|
|
2125
2043
|
saveLoopState();
|
|
2126
2044
|
|
|
2127
|
-
// Register in loop registry
|
|
2128
|
-
loopRegistry.register({
|
|
2129
|
-
id: newLoopId,
|
|
2130
|
-
name: loopState.wizardData.name,
|
|
2131
|
-
task: wData.task || "",
|
|
2132
|
-
cron: wizardCron,
|
|
2133
|
-
enabled: wizardCron ? true : false,
|
|
2134
|
-
maxIterations: maxIter,
|
|
2135
|
-
});
|
|
2136
|
-
|
|
2137
2045
|
// Create loop directory and write LOOP.json
|
|
2138
2046
|
var lDir = loopDir();
|
|
2139
2047
|
try { fs.mkdirSync(lDir, { recursive: true }); } catch (e) {}
|
|
@@ -2143,26 +2051,19 @@ function createProjectContext(opts) {
|
|
|
2143
2051
|
fs.renameSync(tmpLoopJson, loopJsonPath);
|
|
2144
2052
|
|
|
2145
2053
|
// Assemble prompt for clay-ralph skill (include loop dir path so skill knows where to write)
|
|
2146
|
-
var craftingPrompt = "
|
|
2147
|
-
"
|
|
2148
|
-
"Your job is to interview me, then create PROMPT.md and JUDGE.md files " +
|
|
2149
|
-
"that a future autonomous session will execute.\n\n" +
|
|
2150
|
-
"## Task\n" + (wData.task || "") +
|
|
2151
|
-
"\n\n## Loop Directory\n" + lDir;
|
|
2054
|
+
var craftingPrompt = "/clay-ralph\n## Task\n" + (wData.task || "") +
|
|
2055
|
+
"\n## Loop Directory\n" + lDir;
|
|
2152
2056
|
|
|
2153
2057
|
// Create a new session for crafting
|
|
2154
2058
|
var craftingSession = sm.createSession();
|
|
2155
2059
|
var craftName = (loopState.wizardData && loopState.wizardData.name) || "";
|
|
2156
2060
|
craftingSession.title = "Ralph" + (craftName ? " " + craftName : "") + " Crafting";
|
|
2157
2061
|
craftingSession.ralphCraftingMode = true;
|
|
2158
|
-
craftingSession.hidden = true;
|
|
2159
2062
|
sm.saveSessionFile(craftingSession);
|
|
2160
2063
|
sm.switchSession(craftingSession.localId);
|
|
2064
|
+
sm.broadcastSessionList();
|
|
2161
2065
|
loopState.craftingSessionId = craftingSession.localId;
|
|
2162
2066
|
|
|
2163
|
-
// Store crafting session ID in the registry record
|
|
2164
|
-
loopRegistry.updateRecord(newLoopId, { craftingSessionId: craftingSession.localId });
|
|
2165
|
-
|
|
2166
2067
|
// Start .claude/ directory watcher
|
|
2167
2068
|
startClaudeDirWatch();
|
|
2168
2069
|
|
|
@@ -2176,27 +2077,11 @@ function createProjectContext(opts) {
|
|
|
2176
2077
|
send({ type: "status", status: "processing" });
|
|
2177
2078
|
sdk.startQuery(craftingSession, craftingPrompt);
|
|
2178
2079
|
|
|
2179
|
-
send({ type: "ralph_crafting_started", sessionId: craftingSession.localId
|
|
2080
|
+
send({ type: "ralph_crafting_started", sessionId: craftingSession.localId });
|
|
2180
2081
|
send({ type: "ralph_phase", phase: "crafting", wizardData: loopState.wizardData, craftingSessionId: craftingSession.localId });
|
|
2181
2082
|
return;
|
|
2182
2083
|
}
|
|
2183
2084
|
|
|
2184
|
-
if (msg.type === "loop_registry_files") {
|
|
2185
|
-
var recId = msg.id;
|
|
2186
|
-
var lDir = path.join(cwd, ".claude", "loops", recId);
|
|
2187
|
-
var promptContent = "";
|
|
2188
|
-
var judgeContent = "";
|
|
2189
|
-
try { promptContent = fs.readFileSync(path.join(lDir, "PROMPT.md"), "utf8"); } catch (e) {}
|
|
2190
|
-
try { judgeContent = fs.readFileSync(path.join(lDir, "JUDGE.md"), "utf8"); } catch (e) {}
|
|
2191
|
-
send({
|
|
2192
|
-
type: "loop_registry_files_content",
|
|
2193
|
-
id: recId,
|
|
2194
|
-
prompt: promptContent,
|
|
2195
|
-
judge: judgeContent,
|
|
2196
|
-
});
|
|
2197
|
-
return;
|
|
2198
|
-
}
|
|
2199
|
-
|
|
2200
2085
|
if (msg.type === "ralph_preview_files") {
|
|
2201
2086
|
var promptContent = "";
|
|
2202
2087
|
var judgeContent = "";
|
|
@@ -2244,62 +2129,6 @@ function createProjectContext(opts) {
|
|
|
2244
2129
|
return;
|
|
2245
2130
|
}
|
|
2246
2131
|
|
|
2247
|
-
// --- Loop Registry messages ---
|
|
2248
|
-
if (msg.type === "loop_registry_list") {
|
|
2249
|
-
sendTo(ws, { type: "loop_registry_updated", records: loopRegistry.getAll() });
|
|
2250
|
-
return;
|
|
2251
|
-
}
|
|
2252
|
-
|
|
2253
|
-
if (msg.type === "loop_registry_update") {
|
|
2254
|
-
var updatedRec = loopRegistry.update(msg.id, msg.data || {});
|
|
2255
|
-
if (!updatedRec) {
|
|
2256
|
-
sendTo(ws, { type: "loop_registry_error", text: "Record not found" });
|
|
2257
|
-
}
|
|
2258
|
-
return;
|
|
2259
|
-
}
|
|
2260
|
-
|
|
2261
|
-
if (msg.type === "loop_registry_remove") {
|
|
2262
|
-
var removedRec = loopRegistry.remove(msg.id);
|
|
2263
|
-
if (!removedRec) {
|
|
2264
|
-
sendTo(ws, { type: "loop_registry_error", text: "Record not found" });
|
|
2265
|
-
}
|
|
2266
|
-
return;
|
|
2267
|
-
}
|
|
2268
|
-
|
|
2269
|
-
if (msg.type === "loop_registry_toggle") {
|
|
2270
|
-
var toggledRec = loopRegistry.toggleEnabled(msg.id);
|
|
2271
|
-
if (!toggledRec) {
|
|
2272
|
-
sendTo(ws, { type: "loop_registry_error", text: "Record not found or not scheduled" });
|
|
2273
|
-
}
|
|
2274
|
-
return;
|
|
2275
|
-
}
|
|
2276
|
-
|
|
2277
|
-
if (msg.type === "loop_registry_rerun") {
|
|
2278
|
-
// Re-run an existing job (one-off from library)
|
|
2279
|
-
if (loopState.active || loopState.phase === "executing") {
|
|
2280
|
-
sendTo(ws, { type: "loop_registry_error", text: "A loop is already running" });
|
|
2281
|
-
return;
|
|
2282
|
-
}
|
|
2283
|
-
var rerunRec = loopRegistry.getById(msg.id);
|
|
2284
|
-
if (!rerunRec) {
|
|
2285
|
-
sendTo(ws, { type: "loop_registry_error", text: "Record not found" });
|
|
2286
|
-
return;
|
|
2287
|
-
}
|
|
2288
|
-
var rerunDir = path.join(cwd, ".claude", "loops", rerunRec.id);
|
|
2289
|
-
try {
|
|
2290
|
-
fs.accessSync(path.join(rerunDir, "PROMPT.md"));
|
|
2291
|
-
fs.accessSync(path.join(rerunDir, "JUDGE.md"));
|
|
2292
|
-
} catch (e) {
|
|
2293
|
-
sendTo(ws, { type: "loop_registry_error", text: "Loop files missing for " + rerunRec.id });
|
|
2294
|
-
return;
|
|
2295
|
-
}
|
|
2296
|
-
loopState.loopId = rerunRec.id;
|
|
2297
|
-
activeRegistryId = null; // not a scheduled trigger
|
|
2298
|
-
send({ type: "loop_rerun_started", recordId: rerunRec.id });
|
|
2299
|
-
startLoop();
|
|
2300
|
-
return;
|
|
2301
|
-
}
|
|
2302
|
-
|
|
2303
2132
|
if (msg.type !== "message") return;
|
|
2304
2133
|
if (!msg.text && (!msg.images || msg.images.length === 0) && (!msg.pastes || msg.pastes.length === 0)) return;
|
|
2305
2134
|
|
|
@@ -2662,7 +2491,6 @@ function createProjectContext(opts) {
|
|
|
2662
2491
|
|
|
2663
2492
|
// --- Destroy ---
|
|
2664
2493
|
function destroy() {
|
|
2665
|
-
loopRegistry.stopTimer();
|
|
2666
2494
|
stopFileWatch();
|
|
2667
2495
|
stopAllDirWatches();
|
|
2668
2496
|
// Abort all active sessions
|