clay-server 2.6.0 → 2.7.1
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 +53 -4
- package/lib/config.js +15 -6
- package/lib/daemon.js +47 -5
- package/lib/ipc.js +12 -0
- package/lib/notes.js +2 -2
- package/lib/project.js +883 -2
- package/lib/public/app.js +862 -14
- package/lib/public/css/diff.css +12 -0
- package/lib/public/css/filebrowser.css +1 -1
- package/lib/public/css/loop.css +841 -0
- package/lib/public/css/menus.css +5 -0
- package/lib/public/css/mobile-nav.css +15 -15
- package/lib/public/css/rewind.css +23 -0
- package/lib/public/css/scheduler-modal.css +546 -0
- package/lib/public/css/scheduler.css +944 -0
- package/lib/public/css/sidebar.css +1 -0
- package/lib/public/css/skills.css +59 -0
- package/lib/public/css/sticky-notes.css +486 -0
- package/lib/public/css/title-bar.css +83 -3
- package/lib/public/index.html +181 -3
- package/lib/public/modules/diff.js +3 -3
- package/lib/public/modules/filebrowser.js +169 -45
- package/lib/public/modules/input.js +17 -3
- package/lib/public/modules/markdown.js +10 -0
- package/lib/public/modules/qrcode.js +23 -26
- package/lib/public/modules/scheduler.js +1240 -0
- package/lib/public/modules/server-settings.js +40 -0
- package/lib/public/modules/sidebar.js +12 -0
- package/lib/public/modules/skills.js +84 -0
- package/lib/public/modules/sticky-notes.js +617 -52
- package/lib/public/modules/theme.js +9 -19
- package/lib/public/modules/tools.js +16 -2
- package/lib/public/style.css +3 -0
- package/lib/scheduler.js +362 -0
- package/lib/sdk-bridge.js +36 -0
- package/lib/sessions.js +9 -5
- package/lib/utils.js +49 -3
- package/package.json +1 -1
package/lib/public/app.js
CHANGED
|
@@ -6,14 +6,15 @@ import { initRewind, setRewindMode, showRewindModal, clearPendingRewindUuid, add
|
|
|
6
6
|
import { initNotifications, showDoneNotification, playDoneSound, isNotifAlertEnabled, isNotifSoundEnabled } from './modules/notifications.js';
|
|
7
7
|
import { initInput, clearPendingImages, handleInputSync, autoResize, builtinCommands, sendMessage } from './modules/input.js';
|
|
8
8
|
import { initQrCode } from './modules/qrcode.js';
|
|
9
|
-
import { initFileBrowser, loadRootDirectory, refreshTree, handleFsList, handleFsRead, handleDirChanged, refreshIfOpen, handleFileChanged, handleFileHistory, handleGitDiff, handleFileAt, getPendingNavigate, closeFileViewer } from './modules/filebrowser.js';
|
|
9
|
+
import { initFileBrowser, loadRootDirectory, refreshTree, handleFsList, handleFsRead, handleDirChanged, refreshIfOpen, handleFileChanged, handleFileHistory, handleGitDiff, handleFileAt, getPendingNavigate, closeFileViewer, resetFileBrowser } from './modules/filebrowser.js';
|
|
10
10
|
import { initTerminal, openTerminal, closeTerminal, resetTerminals, handleTermList, handleTermCreated, handleTermOutput, handleTermExited, handleTermClosed, sendTerminalCommand } from './modules/terminal.js';
|
|
11
|
-
import { initStickyNotes, handleNotesList, handleNoteCreated, handleNoteUpdated, handleNoteDeleted } from './modules/sticky-notes.js';
|
|
11
|
+
import { initStickyNotes, handleNotesList, handleNoteCreated, handleNoteUpdated, handleNoteDeleted, openArchive, closeArchive, isArchiveOpen } from './modules/sticky-notes.js';
|
|
12
12
|
import { initTheme, getThemeColor, getComputedVar, onThemeChange, getCurrentTheme } from './modules/theme.js';
|
|
13
13
|
import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUserQuestion, markAskUserAnswered, renderPermissionRequest, markPermissionResolved, markPermissionCancelled, renderPlanBanner, renderPlanCard, handleTodoWrite, handleTaskCreate, handleTaskUpdate, startThinking, appendThinking, stopThinking, resetThinkingGroup, createToolItem, updateToolExecuting, updateToolResult, markAllToolsDone, addTurnMeta, enableMainInput, getTools, getPlanContent, setPlanContent, isPlanFilePath, getTodoTools, updateSubagentActivity, addSubagentToolEntry, markSubagentDone, updateSubagentProgress, initSubagentStop, closeToolGroup, removeToolFromGroup } from './modules/tools.js';
|
|
14
|
-
import { initServerSettings, updateSettingsStats, updateSettingsModels, updateDaemonConfig, handleSetPinResult, handleKeepAwakeChanged, handleShutdownResult, handleSharedEnv, handleSharedEnvSaved, handleGlobalClaudeMdRead, handleGlobalClaudeMdWrite } from './modules/server-settings.js';
|
|
14
|
+
import { initServerSettings, updateSettingsStats, updateSettingsModels, updateDaemonConfig, handleSetPinResult, handleKeepAwakeChanged, handleRestartResult, handleShutdownResult, handleSharedEnv, handleSharedEnvSaved, handleGlobalClaudeMdRead, handleGlobalClaudeMdWrite } from './modules/server-settings.js';
|
|
15
15
|
import { initProjectSettings, handleInstructionsRead, handleInstructionsWrite, handleProjectEnv, handleProjectEnvSaved, isProjectSettingsOpen, handleProjectSharedEnv, handleProjectSharedEnvSaved } from './modules/project-settings.js';
|
|
16
16
|
import { initSkills, handleSkillInstalled, handleSkillUninstalled } from './modules/skills.js';
|
|
17
|
+
import { initScheduler, handleLoopRegistryUpdated, handleScheduleRunStarted, handleScheduleRunFinished, handleLoopScheduled, openSchedulerToTab, isSchedulerOpen, closeScheduler, enterCraftingMode, exitCraftingMode, handleLoopRegistryFiles } from './modules/scheduler.js';
|
|
17
18
|
|
|
18
19
|
// --- Base path for multi-project routing ---
|
|
19
20
|
var slugMatch = location.pathname.match(/^\/p\/([a-z0-9_-]+)/);
|
|
@@ -172,6 +173,16 @@ import { initSkills, handleSkillInstalled, handleSkillUninstalled } from './modu
|
|
|
172
173
|
var highlightTimer = null;
|
|
173
174
|
var activeSessionId = null;
|
|
174
175
|
var sessionDrafts = {};
|
|
176
|
+
var loopActive = false;
|
|
177
|
+
var loopAvailable = false;
|
|
178
|
+
var loopIteration = 0;
|
|
179
|
+
var loopMaxIterations = 0;
|
|
180
|
+
var ralphPhase = "idle"; // idle | wizard | crafting | approval | executing | done
|
|
181
|
+
var ralphCraftingSessionId = null;
|
|
182
|
+
var wizardStep = 1;
|
|
183
|
+
var wizardData = { name: "", task: "", maxIterations: 25, cron: null };
|
|
184
|
+
var ralphFilesReady = { promptReady: false, judgeReady: false, bothReady: false };
|
|
185
|
+
var ralphPreviewContent = { prompt: "", judge: "" };
|
|
175
186
|
var slashCommands = [];
|
|
176
187
|
// slashActiveIdx, slashFiltered, pendingImages, pendingPastes -> modules/input.js
|
|
177
188
|
// pendingPermissions -> modules/tools.js
|
|
@@ -550,7 +561,7 @@ import { initSkills, handleSkillInstalled, handleSkillUninstalled } from './modu
|
|
|
550
561
|
} else if (status === "processing") {
|
|
551
562
|
if (dot) { dot.classList.add("connected"); dot.classList.add("processing"); }
|
|
552
563
|
processing = true;
|
|
553
|
-
setSendBtnMode("stop");
|
|
564
|
+
setSendBtnMode(inputEl.value.trim() ? "send" : "stop");
|
|
554
565
|
} else {
|
|
555
566
|
connected = false;
|
|
556
567
|
sendBtn.disabled = true;
|
|
@@ -981,11 +992,12 @@ import { initSkills, handleSkillInstalled, handleSkillUninstalled } from './modu
|
|
|
981
992
|
};
|
|
982
993
|
|
|
983
994
|
function resolveContextWindow(model, sdkValue) {
|
|
995
|
+
if (sdkValue) return sdkValue;
|
|
984
996
|
var lc = (model || "").toLowerCase();
|
|
985
997
|
for (var key in KNOWN_CONTEXT_WINDOWS) {
|
|
986
|
-
if (lc.includes(key)) return
|
|
998
|
+
if (lc.includes(key)) return KNOWN_CONTEXT_WINDOWS[key];
|
|
987
999
|
}
|
|
988
|
-
return
|
|
1000
|
+
return 200000;
|
|
989
1001
|
}
|
|
990
1002
|
|
|
991
1003
|
function contextPctClass(pct) {
|
|
@@ -994,8 +1006,8 @@ import { initSkills, handleSkillInstalled, handleSkillUninstalled } from './modu
|
|
|
994
1006
|
|
|
995
1007
|
function updateContextPanel() {
|
|
996
1008
|
if (!contextUsedEl) return;
|
|
997
|
-
// Context window usage = input tokens (includes cache read/write)
|
|
998
|
-
var used = contextData.input
|
|
1009
|
+
// Context window usage = input tokens only (includes cache read/write)
|
|
1010
|
+
var used = contextData.input;
|
|
999
1011
|
var win = contextData.contextWindow;
|
|
1000
1012
|
var pct = win > 0 ? Math.min(100, (used / win) * 100) : 0;
|
|
1001
1013
|
var cls = contextPctClass(pct);
|
|
@@ -1041,13 +1053,21 @@ import { initSkills, handleSkillInstalled, handleSkillUninstalled } from './modu
|
|
|
1041
1053
|
contextTurnsEl.textContent = String(contextData.turns);
|
|
1042
1054
|
}
|
|
1043
1055
|
|
|
1044
|
-
function accumulateContext(cost, usage, modelUsage) {
|
|
1056
|
+
function accumulateContext(cost, usage, modelUsage, lastStreamInputTokens) {
|
|
1045
1057
|
if (cost != null) contextData.cost += cost;
|
|
1046
1058
|
// Use latest turn values (not cumulative) since each turn's input_tokens
|
|
1047
1059
|
// already includes the full conversation context up to that point
|
|
1048
1060
|
if (usage) {
|
|
1049
|
-
|
|
1050
|
-
|
|
1061
|
+
// Prefer per-call input_tokens from the last stream message_start event
|
|
1062
|
+
// when available — result.usage.input_tokens sums all API calls in a turn,
|
|
1063
|
+
// inflating context usage when tools are involved.
|
|
1064
|
+
// Falls back to the summed value for setups that don't emit message_start.
|
|
1065
|
+
if (lastStreamInputTokens) {
|
|
1066
|
+
contextData.input = lastStreamInputTokens;
|
|
1067
|
+
} else {
|
|
1068
|
+
contextData.input = (usage.input_tokens || usage.inputTokens || 0)
|
|
1069
|
+
+ (usage.cache_read_input_tokens || usage.cacheReadInputTokens || 0);
|
|
1070
|
+
}
|
|
1051
1071
|
contextData.output = usage.output_tokens || usage.outputTokens || 0;
|
|
1052
1072
|
contextData.cacheRead = usage.cache_read_input_tokens || usage.cacheReadInputTokens || 0;
|
|
1053
1073
|
contextData.cacheWrite = usage.cache_creation_input_tokens || usage.cacheCreationInputTokens || 0;
|
|
@@ -1194,7 +1214,7 @@ import { initSkills, handleSkillInstalled, handleSkillUninstalled } from './modu
|
|
|
1194
1214
|
setActivity: function(text) { setActivity(text); },
|
|
1195
1215
|
stopUrgentBlink: function() { stopUrgentBlink(); },
|
|
1196
1216
|
getContextPercent: function() {
|
|
1197
|
-
var used = contextData.input
|
|
1217
|
+
var used = contextData.input;
|
|
1198
1218
|
var win = contextData.contextWindow;
|
|
1199
1219
|
return win > 0 ? Math.round((used / win) * 100) : 0;
|
|
1200
1220
|
},
|
|
@@ -1723,6 +1743,9 @@ import { initSkills, handleSkillInstalled, handleSkillUninstalled } from './modu
|
|
|
1723
1743
|
// --- Project switching (no full reload) ---
|
|
1724
1744
|
function switchProject(slug) {
|
|
1725
1745
|
if (!slug || slug === currentSlug) return;
|
|
1746
|
+
resetFileBrowser();
|
|
1747
|
+
closeArchive();
|
|
1748
|
+
if (isSchedulerOpen()) closeScheduler();
|
|
1726
1749
|
currentSlug = slug;
|
|
1727
1750
|
basePath = "/p/" + slug + "/";
|
|
1728
1751
|
wsPath = "/p/" + slug + "/ws";
|
|
@@ -1735,6 +1758,9 @@ import { initSkills, handleSkillInstalled, handleSkillUninstalled } from './modu
|
|
|
1735
1758
|
var m = location.pathname.match(/^\/p\/([a-z0-9_-]+)/);
|
|
1736
1759
|
var newSlug = m ? m[1] : null;
|
|
1737
1760
|
if (newSlug && newSlug !== currentSlug) {
|
|
1761
|
+
resetFileBrowser();
|
|
1762
|
+
closeArchive();
|
|
1763
|
+
if (isSchedulerOpen()) closeScheduler();
|
|
1738
1764
|
currentSlug = newSlug;
|
|
1739
1765
|
basePath = "/p/" + newSlug + "/";
|
|
1740
1766
|
wsPath = "/p/" + newSlug + "/ws";
|
|
@@ -1865,7 +1891,7 @@ import { initSkills, handleSkillInstalled, handleSkillUninstalled } from './modu
|
|
|
1865
1891
|
replayingHistory = false;
|
|
1866
1892
|
// Restore accurate context data from the last result in full history
|
|
1867
1893
|
if (msg.lastUsage || msg.lastModelUsage) {
|
|
1868
|
-
accumulateContext(msg.lastCost, msg.lastUsage, msg.lastModelUsage);
|
|
1894
|
+
accumulateContext(msg.lastCost, msg.lastUsage, msg.lastModelUsage, msg.lastStreamInputTokens);
|
|
1869
1895
|
}
|
|
1870
1896
|
updateContextPanel();
|
|
1871
1897
|
updateUsagePanel();
|
|
@@ -2020,12 +2046,42 @@ import { initSkills, handleSkillInstalled, handleSkillUninstalled } from './modu
|
|
|
2020
2046
|
|
|
2021
2047
|
case "skill_installed":
|
|
2022
2048
|
handleSkillInstalled(msg);
|
|
2049
|
+
// Advance ralph wizard if we were installing clay-ralph
|
|
2050
|
+
if (msg.skill === "clay-ralph" && ralphSkillInstalling) {
|
|
2051
|
+
ralphSkillInstalling = false;
|
|
2052
|
+
ralphSkillInstalled = true;
|
|
2053
|
+
if (msg.success) {
|
|
2054
|
+
wizardStep = 2;
|
|
2055
|
+
updateWizardStep();
|
|
2056
|
+
} else {
|
|
2057
|
+
var rNextBtn = document.getElementById("ralph-wizard-next");
|
|
2058
|
+
if (rNextBtn) { rNextBtn.disabled = false; rNextBtn.textContent = "Get Started"; }
|
|
2059
|
+
var rStatusEl = document.getElementById("ralph-install-status");
|
|
2060
|
+
if (rStatusEl) { rStatusEl.innerHTML = "Failed to install skill. Try again."; }
|
|
2061
|
+
}
|
|
2062
|
+
}
|
|
2023
2063
|
break;
|
|
2024
2064
|
|
|
2025
2065
|
case "skill_uninstalled":
|
|
2026
2066
|
handleSkillUninstalled(msg);
|
|
2027
2067
|
break;
|
|
2028
2068
|
|
|
2069
|
+
case "loop_registry_updated":
|
|
2070
|
+
handleLoopRegistryUpdated(msg);
|
|
2071
|
+
break;
|
|
2072
|
+
|
|
2073
|
+
case "schedule_run_started":
|
|
2074
|
+
handleScheduleRunStarted(msg);
|
|
2075
|
+
break;
|
|
2076
|
+
|
|
2077
|
+
case "schedule_run_finished":
|
|
2078
|
+
handleScheduleRunFinished(msg);
|
|
2079
|
+
break;
|
|
2080
|
+
|
|
2081
|
+
case "loop_scheduled":
|
|
2082
|
+
handleLoopScheduled(msg);
|
|
2083
|
+
break;
|
|
2084
|
+
|
|
2029
2085
|
case "input_sync":
|
|
2030
2086
|
handleInputSync(msg.text);
|
|
2031
2087
|
break;
|
|
@@ -2056,6 +2112,8 @@ import { initSkills, handleSkillInstalled, handleSkillUninstalled } from './modu
|
|
|
2056
2112
|
activeSessionId = msg.id;
|
|
2057
2113
|
cliSessionId = msg.cliSessionId || null;
|
|
2058
2114
|
resetClientState();
|
|
2115
|
+
updateRalphBars();
|
|
2116
|
+
updateLoopInputVisibility(msg.loop);
|
|
2059
2117
|
// Restore draft for incoming session
|
|
2060
2118
|
var draft = sessionDrafts[activeSessionId] || "";
|
|
2061
2119
|
inputEl.value = draft;
|
|
@@ -2271,7 +2329,7 @@ import { initSkills, handleSkillInstalled, handleSkillUninstalled } from './modu
|
|
|
2271
2329
|
finalizeAssistantBlock();
|
|
2272
2330
|
addTurnMeta(msg.cost, msg.duration);
|
|
2273
2331
|
accumulateUsage(msg.cost, msg.usage);
|
|
2274
|
-
accumulateContext(msg.cost, msg.usage, msg.modelUsage);
|
|
2332
|
+
accumulateContext(msg.cost, msg.usage, msg.modelUsage, msg.lastStreamInputTokens);
|
|
2275
2333
|
break;
|
|
2276
2334
|
|
|
2277
2335
|
case "done":
|
|
@@ -2500,9 +2558,117 @@ import { initSkills, handleSkillInstalled, handleSkillUninstalled } from './modu
|
|
|
2500
2558
|
handleKeepAwakeChanged(msg);
|
|
2501
2559
|
break;
|
|
2502
2560
|
|
|
2561
|
+
case "restart_server_result":
|
|
2562
|
+
handleRestartResult(msg);
|
|
2563
|
+
break;
|
|
2564
|
+
|
|
2503
2565
|
case "shutdown_server_result":
|
|
2504
2566
|
handleShutdownResult(msg);
|
|
2505
2567
|
break;
|
|
2568
|
+
|
|
2569
|
+
// --- Ralph Loop ---
|
|
2570
|
+
case "loop_available":
|
|
2571
|
+
loopAvailable = msg.available;
|
|
2572
|
+
loopActive = msg.active;
|
|
2573
|
+
loopIteration = msg.iteration || 0;
|
|
2574
|
+
loopMaxIterations = msg.maxIterations || 20;
|
|
2575
|
+
updateLoopButton();
|
|
2576
|
+
if (loopActive) {
|
|
2577
|
+
showLoopBanner(true);
|
|
2578
|
+
if (loopIteration > 0) {
|
|
2579
|
+
updateLoopBanner(loopIteration, loopMaxIterations, "running");
|
|
2580
|
+
}
|
|
2581
|
+
}
|
|
2582
|
+
break;
|
|
2583
|
+
|
|
2584
|
+
case "loop_started":
|
|
2585
|
+
loopActive = true;
|
|
2586
|
+
ralphPhase = "executing";
|
|
2587
|
+
loopIteration = 0;
|
|
2588
|
+
loopMaxIterations = msg.maxIterations;
|
|
2589
|
+
showLoopBanner(true);
|
|
2590
|
+
updateLoopButton();
|
|
2591
|
+
addSystemMessage("Ralph Loop started (max " + msg.maxIterations + " iterations)", false);
|
|
2592
|
+
break;
|
|
2593
|
+
|
|
2594
|
+
case "loop_iteration":
|
|
2595
|
+
loopIteration = msg.iteration;
|
|
2596
|
+
loopMaxIterations = msg.maxIterations;
|
|
2597
|
+
updateLoopBanner(msg.iteration, msg.maxIterations, "running");
|
|
2598
|
+
updateLoopButton();
|
|
2599
|
+
addSystemMessage("Ralph Loop iteration #" + msg.iteration + " started", false);
|
|
2600
|
+
break;
|
|
2601
|
+
|
|
2602
|
+
case "loop_judging":
|
|
2603
|
+
updateLoopBanner(loopIteration, loopMaxIterations, "judging");
|
|
2604
|
+
addSystemMessage("Judging iteration #" + msg.iteration + "...", false);
|
|
2605
|
+
break;
|
|
2606
|
+
|
|
2607
|
+
case "loop_verdict":
|
|
2608
|
+
addSystemMessage("Judge: " + msg.verdict.toUpperCase() + " - " + (msg.summary || ""), false);
|
|
2609
|
+
break;
|
|
2610
|
+
|
|
2611
|
+
case "loop_stopping":
|
|
2612
|
+
updateLoopBanner(loopIteration, loopMaxIterations, "stopping");
|
|
2613
|
+
break;
|
|
2614
|
+
|
|
2615
|
+
case "loop_finished":
|
|
2616
|
+
loopActive = false;
|
|
2617
|
+
ralphPhase = "done";
|
|
2618
|
+
showLoopBanner(false);
|
|
2619
|
+
updateLoopButton();
|
|
2620
|
+
var finishMsg = msg.reason === "pass"
|
|
2621
|
+
? "Ralph Loop completed successfully after " + msg.iterations + " iteration(s)."
|
|
2622
|
+
: msg.reason === "max_iterations"
|
|
2623
|
+
? "Ralph Loop reached maximum iterations (" + msg.iterations + ")."
|
|
2624
|
+
: msg.reason === "stopped"
|
|
2625
|
+
? "Ralph Loop stopped."
|
|
2626
|
+
: "Ralph Loop ended with error.";
|
|
2627
|
+
addSystemMessage(finishMsg, false);
|
|
2628
|
+
break;
|
|
2629
|
+
|
|
2630
|
+
case "loop_error":
|
|
2631
|
+
addSystemMessage("Ralph Loop error: " + msg.text, true);
|
|
2632
|
+
break;
|
|
2633
|
+
|
|
2634
|
+
// --- Ralph Wizard / Crafting ---
|
|
2635
|
+
case "ralph_phase":
|
|
2636
|
+
ralphPhase = msg.phase || "idle";
|
|
2637
|
+
if (msg.craftingSessionId) ralphCraftingSessionId = msg.craftingSessionId;
|
|
2638
|
+
updateLoopButton();
|
|
2639
|
+
updateRalphBars();
|
|
2640
|
+
break;
|
|
2641
|
+
|
|
2642
|
+
case "ralph_crafting_started":
|
|
2643
|
+
ralphPhase = "crafting";
|
|
2644
|
+
ralphCraftingSessionId = msg.sessionId || activeSessionId;
|
|
2645
|
+
updateLoopButton();
|
|
2646
|
+
updateRalphBars();
|
|
2647
|
+
enterCraftingMode(ralphCraftingSessionId, msg.taskId || null);
|
|
2648
|
+
break;
|
|
2649
|
+
|
|
2650
|
+
case "ralph_files_status":
|
|
2651
|
+
ralphFilesReady = {
|
|
2652
|
+
promptReady: msg.promptReady,
|
|
2653
|
+
judgeReady: msg.judgeReady,
|
|
2654
|
+
bothReady: msg.bothReady,
|
|
2655
|
+
};
|
|
2656
|
+
if (msg.bothReady && (ralphPhase === "crafting" || ralphPhase === "approval")) {
|
|
2657
|
+
ralphPhase = "approval";
|
|
2658
|
+
showRalphApprovalBar(true);
|
|
2659
|
+
if (isSchedulerOpen()) exitCraftingMode(msg.taskId || null);
|
|
2660
|
+
}
|
|
2661
|
+
updateRalphApprovalStatus();
|
|
2662
|
+
break;
|
|
2663
|
+
|
|
2664
|
+
case "loop_registry_files_content":
|
|
2665
|
+
handleLoopRegistryFiles(msg);
|
|
2666
|
+
break;
|
|
2667
|
+
|
|
2668
|
+
case "ralph_files_content":
|
|
2669
|
+
ralphPreviewContent = { prompt: msg.prompt || "", judge: msg.judge || "" };
|
|
2670
|
+
openRalphPreviewModal();
|
|
2671
|
+
break;
|
|
2506
2672
|
}
|
|
2507
2673
|
}
|
|
2508
2674
|
|
|
@@ -2648,6 +2814,7 @@ import { initSkills, handleSkillInstalled, handleSkillUninstalled } from './modu
|
|
|
2648
2814
|
resetContextData: resetContextData,
|
|
2649
2815
|
showImageModal: showImageModal,
|
|
2650
2816
|
hideSuggestionChips: hideSuggestionChips,
|
|
2817
|
+
setSendBtnMode: setSendBtnMode,
|
|
2651
2818
|
});
|
|
2652
2819
|
|
|
2653
2820
|
// --- Notifications module (viewport, banners, notifications, debug, service worker) ---
|
|
@@ -2718,6 +2885,678 @@ import { initSkills, handleSkillInstalled, handleSkillUninstalled } from './modu
|
|
|
2718
2885
|
get connected() { return connected; },
|
|
2719
2886
|
});
|
|
2720
2887
|
|
|
2888
|
+
// --- Sticky Notes sidebar button (archive view) ---
|
|
2889
|
+
var stickyNotesSidebarBtn = $("sticky-notes-sidebar-btn");
|
|
2890
|
+
if (stickyNotesSidebarBtn) {
|
|
2891
|
+
stickyNotesSidebarBtn.addEventListener("click", function () {
|
|
2892
|
+
if (isSchedulerOpen()) closeScheduler();
|
|
2893
|
+
if (isArchiveOpen()) {
|
|
2894
|
+
closeArchive();
|
|
2895
|
+
} else {
|
|
2896
|
+
openArchive();
|
|
2897
|
+
}
|
|
2898
|
+
});
|
|
2899
|
+
}
|
|
2900
|
+
|
|
2901
|
+
// Close archive / scheduler panel when switching to other sidebar panels
|
|
2902
|
+
var fileBrowserBtn = $("file-browser-btn");
|
|
2903
|
+
var terminalSidebarBtn = $("terminal-sidebar-btn");
|
|
2904
|
+
if (fileBrowserBtn) fileBrowserBtn.addEventListener("click", function () { if (isArchiveOpen()) closeArchive(); if (isSchedulerOpen()) closeScheduler(); });
|
|
2905
|
+
if (terminalSidebarBtn) terminalSidebarBtn.addEventListener("click", function () { if (isArchiveOpen()) closeArchive(); if (isSchedulerOpen()) closeScheduler(); });
|
|
2906
|
+
|
|
2907
|
+
// --- Ralph Loop UI ---
|
|
2908
|
+
function updateLoopInputVisibility(loop) {
|
|
2909
|
+
var inputArea = document.getElementById("input-area");
|
|
2910
|
+
if (!inputArea) return;
|
|
2911
|
+
if (loop && loop.active) {
|
|
2912
|
+
inputArea.style.display = "none";
|
|
2913
|
+
} else {
|
|
2914
|
+
inputArea.style.display = "";
|
|
2915
|
+
}
|
|
2916
|
+
}
|
|
2917
|
+
|
|
2918
|
+
function updateLoopButton() {
|
|
2919
|
+
var section = document.getElementById("ralph-loop-section");
|
|
2920
|
+
if (!section) return;
|
|
2921
|
+
|
|
2922
|
+
var busy = loopActive || ralphPhase === "executing";
|
|
2923
|
+
var phase = busy ? "executing" : ralphPhase;
|
|
2924
|
+
|
|
2925
|
+
var statusHtml = "";
|
|
2926
|
+
var statusClass = "";
|
|
2927
|
+
var clickAction = "wizard"; // default
|
|
2928
|
+
|
|
2929
|
+
if (phase === "crafting") {
|
|
2930
|
+
statusHtml = '<span class="ralph-section-status crafting">' + iconHtml("loader", "icon-spin") + ' Crafting\u2026</span>';
|
|
2931
|
+
clickAction = "none";
|
|
2932
|
+
} else if (phase === "approval") {
|
|
2933
|
+
statusHtml = '<span class="ralph-section-status ready">Ready</span>';
|
|
2934
|
+
statusClass = "ralph-section-ready";
|
|
2935
|
+
clickAction = "none";
|
|
2936
|
+
} else if (phase === "executing") {
|
|
2937
|
+
var iterText = loopIteration > 0 ? "Running \u00b7 iteration " + loopIteration + "/" + loopMaxIterations : "Starting\u2026";
|
|
2938
|
+
statusHtml = '<span class="ralph-section-status running">' + iconHtml("loader", "icon-spin") + ' ' + iterText + '</span>';
|
|
2939
|
+
statusClass = "ralph-section-running";
|
|
2940
|
+
clickAction = "popover";
|
|
2941
|
+
} else if (phase === "done") {
|
|
2942
|
+
statusHtml = '<span class="ralph-section-status done">\u2713 Done</span>';
|
|
2943
|
+
statusHtml += '<a href="#" class="ralph-section-tasks-link">View in Scheduled Tasks</a>';
|
|
2944
|
+
statusClass = "ralph-section-done";
|
|
2945
|
+
clickAction = "wizard";
|
|
2946
|
+
} else {
|
|
2947
|
+
// idle
|
|
2948
|
+
statusHtml = '<span class="ralph-section-hint">Start a new loop</span>';
|
|
2949
|
+
}
|
|
2950
|
+
|
|
2951
|
+
section.className = "ralph-loop-section" + (statusClass ? " " + statusClass : "");
|
|
2952
|
+
section.innerHTML =
|
|
2953
|
+
'<div class="ralph-section-inner">' +
|
|
2954
|
+
'<div class="ralph-section-header">' +
|
|
2955
|
+
'<span class="ralph-section-icon">' + iconHtml("repeat") + '</span>' +
|
|
2956
|
+
'<span class="ralph-section-label">Ralph Loop</span>' +
|
|
2957
|
+
'<span class="loop-experimental"><i data-lucide="flask-conical"></i> experimental</span>' +
|
|
2958
|
+
'</div>' +
|
|
2959
|
+
'<div class="ralph-section-body">' + statusHtml + '</div>' +
|
|
2960
|
+
'</div>';
|
|
2961
|
+
|
|
2962
|
+
refreshIcons();
|
|
2963
|
+
|
|
2964
|
+
// Click handler on header
|
|
2965
|
+
var header = section.querySelector(".ralph-section-header");
|
|
2966
|
+
if (header) {
|
|
2967
|
+
header.style.cursor = clickAction === "none" ? "default" : "pointer";
|
|
2968
|
+
header.addEventListener("click", function() {
|
|
2969
|
+
if (clickAction === "popover") {
|
|
2970
|
+
toggleLoopPopover();
|
|
2971
|
+
} else if (clickAction === "wizard") {
|
|
2972
|
+
openRalphWizard();
|
|
2973
|
+
}
|
|
2974
|
+
});
|
|
2975
|
+
}
|
|
2976
|
+
|
|
2977
|
+
// "View in Scheduled Tasks" link
|
|
2978
|
+
var tasksLink = section.querySelector(".ralph-section-tasks-link");
|
|
2979
|
+
if (tasksLink) {
|
|
2980
|
+
tasksLink.addEventListener("click", function(e) {
|
|
2981
|
+
e.preventDefault();
|
|
2982
|
+
e.stopPropagation();
|
|
2983
|
+
openSchedulerToTab("library");
|
|
2984
|
+
});
|
|
2985
|
+
}
|
|
2986
|
+
}
|
|
2987
|
+
|
|
2988
|
+
function toggleLoopPopover() {
|
|
2989
|
+
var existing = document.getElementById("loop-status-modal");
|
|
2990
|
+
if (existing) {
|
|
2991
|
+
existing.remove();
|
|
2992
|
+
return;
|
|
2993
|
+
}
|
|
2994
|
+
|
|
2995
|
+
var taskPreview = wizardData.task || "—";
|
|
2996
|
+
if (taskPreview.length > 120) taskPreview = taskPreview.substring(0, 120) + "\u2026";
|
|
2997
|
+
var statusText = "Iteration #" + loopIteration + " / " + loopMaxIterations;
|
|
2998
|
+
|
|
2999
|
+
var modal = document.createElement("div");
|
|
3000
|
+
modal.id = "loop-status-modal";
|
|
3001
|
+
modal.className = "loop-status-modal";
|
|
3002
|
+
modal.innerHTML =
|
|
3003
|
+
'<div class="loop-status-backdrop"></div>' +
|
|
3004
|
+
'<div class="loop-status-dialog">' +
|
|
3005
|
+
'<div class="loop-status-dialog-header">' +
|
|
3006
|
+
'<span class="loop-status-dialog-icon">' + iconHtml("repeat") + '</span>' +
|
|
3007
|
+
'<span class="loop-status-dialog-title">Ralph Loop</span>' +
|
|
3008
|
+
'<button class="loop-status-dialog-close" title="Close">' + iconHtml("x") + '</button>' +
|
|
3009
|
+
'</div>' +
|
|
3010
|
+
'<div class="loop-status-dialog-body">' +
|
|
3011
|
+
'<div class="loop-status-dialog-row">' +
|
|
3012
|
+
'<span class="loop-status-dialog-label">Progress</span>' +
|
|
3013
|
+
'<span class="loop-status-dialog-value">' + escapeHtml(statusText) + '</span>' +
|
|
3014
|
+
'</div>' +
|
|
3015
|
+
'<div class="loop-status-dialog-row">' +
|
|
3016
|
+
'<span class="loop-status-dialog-label">Task</span>' +
|
|
3017
|
+
'<span class="loop-status-dialog-value loop-status-dialog-task">' + escapeHtml(taskPreview) + '</span>' +
|
|
3018
|
+
'</div>' +
|
|
3019
|
+
'</div>' +
|
|
3020
|
+
'<div class="loop-status-dialog-footer">' +
|
|
3021
|
+
'<button class="loop-status-dialog-stop">' + iconHtml("square") + ' Stop loop</button>' +
|
|
3022
|
+
'</div>' +
|
|
3023
|
+
'</div>';
|
|
3024
|
+
|
|
3025
|
+
document.body.appendChild(modal);
|
|
3026
|
+
refreshIcons();
|
|
3027
|
+
|
|
3028
|
+
function closeModal() { modal.remove(); }
|
|
3029
|
+
|
|
3030
|
+
modal.querySelector(".loop-status-backdrop").addEventListener("click", closeModal);
|
|
3031
|
+
modal.querySelector(".loop-status-dialog-close").addEventListener("click", closeModal);
|
|
3032
|
+
|
|
3033
|
+
modal.querySelector(".loop-status-dialog-stop").addEventListener("click", function(e) {
|
|
3034
|
+
e.stopPropagation();
|
|
3035
|
+
closeModal();
|
|
3036
|
+
showConfirm("Stop the running Ralph Loop?", function() {
|
|
3037
|
+
if (ws && ws.readyState === 1) {
|
|
3038
|
+
ws.send(JSON.stringify({ type: "loop_stop" }));
|
|
3039
|
+
}
|
|
3040
|
+
});
|
|
3041
|
+
});
|
|
3042
|
+
}
|
|
3043
|
+
|
|
3044
|
+
function showLoopBanner(show) {
|
|
3045
|
+
var stickyEl = document.getElementById("ralph-sticky");
|
|
3046
|
+
if (!stickyEl) { updateLoopButton(); return; }
|
|
3047
|
+
if (!show) {
|
|
3048
|
+
stickyEl.classList.add("hidden");
|
|
3049
|
+
stickyEl.classList.remove("ralph-running");
|
|
3050
|
+
stickyEl.innerHTML = "";
|
|
3051
|
+
updateLoopButton();
|
|
3052
|
+
return;
|
|
3053
|
+
}
|
|
3054
|
+
|
|
3055
|
+
stickyEl.innerHTML =
|
|
3056
|
+
'<div class="ralph-sticky-inner">' +
|
|
3057
|
+
'<div class="ralph-sticky-header">' +
|
|
3058
|
+
'<span class="ralph-sticky-icon">' + iconHtml("repeat") + '</span>' +
|
|
3059
|
+
'<span class="ralph-sticky-label">Ralph Loop</span>' +
|
|
3060
|
+
'<span class="ralph-sticky-status" id="loop-status">Starting\u2026</span>' +
|
|
3061
|
+
'<button class="ralph-sticky-action ralph-sticky-stop" title="Stop loop">' + iconHtml("square") + '</button>' +
|
|
3062
|
+
'</div>' +
|
|
3063
|
+
'</div>';
|
|
3064
|
+
stickyEl.classList.remove("hidden", "ralph-ready");
|
|
3065
|
+
stickyEl.classList.add("ralph-running");
|
|
3066
|
+
refreshIcons();
|
|
3067
|
+
|
|
3068
|
+
stickyEl.querySelector(".ralph-sticky-stop").addEventListener("click", function(e) {
|
|
3069
|
+
e.stopPropagation();
|
|
3070
|
+
if (ws && ws.readyState === 1) {
|
|
3071
|
+
ws.send(JSON.stringify({ type: "loop_stop" }));
|
|
3072
|
+
}
|
|
3073
|
+
});
|
|
3074
|
+
updateLoopButton();
|
|
3075
|
+
}
|
|
3076
|
+
|
|
3077
|
+
function updateLoopBanner(iteration, maxIterations, phase) {
|
|
3078
|
+
var statusEl = document.getElementById("loop-status");
|
|
3079
|
+
if (!statusEl) return;
|
|
3080
|
+
var text = "#" + iteration + "/" + maxIterations;
|
|
3081
|
+
if (phase === "judging") text += " judging\u2026";
|
|
3082
|
+
else if (phase === "stopping") text = "Stopping\u2026";
|
|
3083
|
+
else text += " running";
|
|
3084
|
+
statusEl.textContent = text;
|
|
3085
|
+
}
|
|
3086
|
+
|
|
3087
|
+
function updateRalphBars() {
|
|
3088
|
+
var onCraftingSession = ralphCraftingSessionId && activeSessionId === ralphCraftingSessionId;
|
|
3089
|
+
if (ralphPhase === "crafting" && onCraftingSession) {
|
|
3090
|
+
showRalphCraftingBar(true);
|
|
3091
|
+
} else {
|
|
3092
|
+
showRalphCraftingBar(false);
|
|
3093
|
+
}
|
|
3094
|
+
if (ralphPhase === "approval" && onCraftingSession) {
|
|
3095
|
+
showRalphApprovalBar(true);
|
|
3096
|
+
} else {
|
|
3097
|
+
showRalphApprovalBar(false);
|
|
3098
|
+
}
|
|
3099
|
+
}
|
|
3100
|
+
|
|
3101
|
+
// --- Ralph Wizard ---
|
|
3102
|
+
var ralphSkillInstalled = false;
|
|
3103
|
+
var ralphSkillInstalling = false;
|
|
3104
|
+
|
|
3105
|
+
function checkRalphSkillInstalled(cb) {
|
|
3106
|
+
fetch(basePath + "api/installed-skills")
|
|
3107
|
+
.then(function (res) { return res.json(); })
|
|
3108
|
+
.then(function (data) {
|
|
3109
|
+
var installed = data.installed || {};
|
|
3110
|
+
ralphSkillInstalled = !!installed["clay-ralph"];
|
|
3111
|
+
cb(ralphSkillInstalled);
|
|
3112
|
+
})
|
|
3113
|
+
.catch(function () { cb(false); });
|
|
3114
|
+
}
|
|
3115
|
+
|
|
3116
|
+
function openRalphWizard() {
|
|
3117
|
+
wizardData = { name: "", task: "", maxIterations: 25 };
|
|
3118
|
+
ralphSkillInstalling = false;
|
|
3119
|
+
var el = document.getElementById("ralph-wizard");
|
|
3120
|
+
if (!el) return;
|
|
3121
|
+
|
|
3122
|
+
var nameEl = document.getElementById("ralph-name");
|
|
3123
|
+
if (nameEl) nameEl.value = "";
|
|
3124
|
+
var taskEl = document.getElementById("ralph-task");
|
|
3125
|
+
if (taskEl) taskEl.value = "";
|
|
3126
|
+
var iterEl = document.getElementById("ralph-max-iterations");
|
|
3127
|
+
if (iterEl) iterEl.value = "25";
|
|
3128
|
+
|
|
3129
|
+
// Check if clay-ralph skill is installed — skip onboarding if so
|
|
3130
|
+
checkRalphSkillInstalled(function (installed) {
|
|
3131
|
+
wizardStep = installed ? 2 : 1;
|
|
3132
|
+
el.classList.remove("hidden");
|
|
3133
|
+
var statusEl = document.getElementById("ralph-install-status");
|
|
3134
|
+
if (statusEl) { statusEl.classList.add("hidden"); statusEl.innerHTML = ""; }
|
|
3135
|
+
updateWizardStep();
|
|
3136
|
+
});
|
|
3137
|
+
}
|
|
3138
|
+
|
|
3139
|
+
function closeRalphWizard() {
|
|
3140
|
+
var el = document.getElementById("ralph-wizard");
|
|
3141
|
+
if (el) el.classList.add("hidden");
|
|
3142
|
+
}
|
|
3143
|
+
|
|
3144
|
+
function updateWizardStep() {
|
|
3145
|
+
var steps = document.querySelectorAll(".ralph-step");
|
|
3146
|
+
for (var i = 0; i < steps.length; i++) {
|
|
3147
|
+
var stepNum = parseInt(steps[i].getAttribute("data-step"), 10);
|
|
3148
|
+
if (stepNum === wizardStep) {
|
|
3149
|
+
steps[i].classList.add("active");
|
|
3150
|
+
} else {
|
|
3151
|
+
steps[i].classList.remove("active");
|
|
3152
|
+
}
|
|
3153
|
+
}
|
|
3154
|
+
var dots = document.querySelectorAll(".ralph-dot");
|
|
3155
|
+
for (var j = 0; j < dots.length; j++) {
|
|
3156
|
+
var dotStep = parseInt(dots[j].getAttribute("data-step"), 10);
|
|
3157
|
+
dots[j].classList.remove("active", "done");
|
|
3158
|
+
if (dotStep === wizardStep) dots[j].classList.add("active");
|
|
3159
|
+
else if (dotStep < wizardStep) dots[j].classList.add("done");
|
|
3160
|
+
}
|
|
3161
|
+
|
|
3162
|
+
var backBtn = document.getElementById("ralph-wizard-back");
|
|
3163
|
+
var skipBtn = document.getElementById("ralph-wizard-skip");
|
|
3164
|
+
var nextBtn = document.getElementById("ralph-wizard-next");
|
|
3165
|
+
if (backBtn) backBtn.style.visibility = wizardStep === 1 ? "hidden" : "visible";
|
|
3166
|
+
if (skipBtn) skipBtn.style.display = "none";
|
|
3167
|
+
if (nextBtn) nextBtn.textContent = wizardStep === 2 ? "Launch" : "Get Started";
|
|
3168
|
+
}
|
|
3169
|
+
|
|
3170
|
+
function collectWizardData() {
|
|
3171
|
+
var nameEl = document.getElementById("ralph-name");
|
|
3172
|
+
var taskEl = document.getElementById("ralph-task");
|
|
3173
|
+
var iterEl = document.getElementById("ralph-max-iterations");
|
|
3174
|
+
wizardData.name = nameEl ? nameEl.value.replace(/[^a-zA-Z0-9_-]/g, "").trim() : "";
|
|
3175
|
+
wizardData.task = taskEl ? taskEl.value.trim() : "";
|
|
3176
|
+
wizardData.maxIterations = iterEl ? parseInt(iterEl.value, 10) || 25 : 25;
|
|
3177
|
+
wizardData.cron = null;
|
|
3178
|
+
}
|
|
3179
|
+
|
|
3180
|
+
function buildWizardCron() {
|
|
3181
|
+
var repeatEl = document.getElementById("ralph-repeat");
|
|
3182
|
+
if (!repeatEl) return null;
|
|
3183
|
+
var preset = repeatEl.value;
|
|
3184
|
+
if (preset === "none") return null;
|
|
3185
|
+
|
|
3186
|
+
var timeEl = document.getElementById("ralph-time");
|
|
3187
|
+
var timeVal = timeEl ? timeEl.value : "09:00";
|
|
3188
|
+
var timeParts = timeVal.split(":");
|
|
3189
|
+
var hour = parseInt(timeParts[0], 10) || 9;
|
|
3190
|
+
var minute = parseInt(timeParts[1], 10) || 0;
|
|
3191
|
+
|
|
3192
|
+
if (preset === "daily") return minute + " " + hour + " * * *";
|
|
3193
|
+
if (preset === "weekdays") return minute + " " + hour + " * * 1-5";
|
|
3194
|
+
if (preset === "weekly") return minute + " " + hour + " * * " + new Date().getDay();
|
|
3195
|
+
if (preset === "monthly") return minute + " " + hour + " " + new Date().getDate() + " * *";
|
|
3196
|
+
|
|
3197
|
+
if (preset === "custom") {
|
|
3198
|
+
var unitEl = document.getElementById("ralph-repeat-unit");
|
|
3199
|
+
var unit = unitEl ? unitEl.value : "day";
|
|
3200
|
+
if (unit === "day") return minute + " " + hour + " * * *";
|
|
3201
|
+
if (unit === "month") return minute + " " + hour + " " + new Date().getDate() + " * *";
|
|
3202
|
+
// week: collect selected days
|
|
3203
|
+
var dowBtns = document.querySelectorAll("#ralph-custom-repeat .sched-dow-btn.active");
|
|
3204
|
+
var days = [];
|
|
3205
|
+
for (var i = 0; i < dowBtns.length; i++) {
|
|
3206
|
+
days.push(dowBtns[i].dataset.dow);
|
|
3207
|
+
}
|
|
3208
|
+
if (days.length === 0) days.push(String(new Date().getDay()));
|
|
3209
|
+
return minute + " " + hour + " * * " + days.join(",");
|
|
3210
|
+
}
|
|
3211
|
+
return null;
|
|
3212
|
+
}
|
|
3213
|
+
|
|
3214
|
+
function cronToHumanText(cron) {
|
|
3215
|
+
if (!cron) return "";
|
|
3216
|
+
var parts = cron.trim().split(/\s+/);
|
|
3217
|
+
if (parts.length !== 5) return cron;
|
|
3218
|
+
var m = parts[0], h = parts[1], dom = parts[2], dow = parts[4];
|
|
3219
|
+
var pad = function(n) { return (parseInt(n,10) < 10 ? "0" : "") + parseInt(n,10); };
|
|
3220
|
+
var t = pad(h) + ":" + pad(m);
|
|
3221
|
+
var dayNames = ["Sun","Mon","Tue","Wed","Thu","Fri","Sat"];
|
|
3222
|
+
if (dow === "*" && dom === "*") return "Every day at " + t;
|
|
3223
|
+
if (dow === "1-5" && dom === "*") return "Weekdays at " + t;
|
|
3224
|
+
if (dom !== "*" && dow === "*") return "Monthly on day " + dom + " at " + t;
|
|
3225
|
+
if (dow !== "*" && dom === "*") {
|
|
3226
|
+
var ds = dow.split(",").map(function(d) { return dayNames[parseInt(d,10)] || d; });
|
|
3227
|
+
return "Every " + ds.join(", ") + " at " + t;
|
|
3228
|
+
}
|
|
3229
|
+
return cron;
|
|
3230
|
+
}
|
|
3231
|
+
|
|
3232
|
+
function wizardNext() {
|
|
3233
|
+
collectWizardData();
|
|
3234
|
+
|
|
3235
|
+
// Step 1: install clay-ralph skill if needed, otherwise just advance
|
|
3236
|
+
if (wizardStep === 1) {
|
|
3237
|
+
if (ralphSkillInstalled) {
|
|
3238
|
+
wizardStep++;
|
|
3239
|
+
updateWizardStep();
|
|
3240
|
+
return;
|
|
3241
|
+
}
|
|
3242
|
+
if (ralphSkillInstalling) return;
|
|
3243
|
+
ralphSkillInstalling = true;
|
|
3244
|
+
var nextBtn = document.getElementById("ralph-wizard-next");
|
|
3245
|
+
if (nextBtn) {
|
|
3246
|
+
nextBtn.disabled = true;
|
|
3247
|
+
nextBtn.textContent = "Installing...";
|
|
3248
|
+
}
|
|
3249
|
+
var statusEl = document.getElementById("ralph-install-status");
|
|
3250
|
+
if (statusEl) {
|
|
3251
|
+
statusEl.classList.remove("hidden");
|
|
3252
|
+
statusEl.innerHTML = '<div class="skills-spinner small"></div> Installing clay-ralph skill...';
|
|
3253
|
+
}
|
|
3254
|
+
fetch(basePath + "api/install-skill", {
|
|
3255
|
+
method: "POST",
|
|
3256
|
+
headers: { "Content-Type": "application/json" },
|
|
3257
|
+
body: JSON.stringify({ url: "https://github.com/chadbyte/clay-ralph", skill: "clay-ralph", scope: "global" }),
|
|
3258
|
+
})
|
|
3259
|
+
.then(function () {
|
|
3260
|
+
// Wait for skill_installed websocket message to advance
|
|
3261
|
+
})
|
|
3262
|
+
.catch(function () {
|
|
3263
|
+
ralphSkillInstalling = false;
|
|
3264
|
+
if (nextBtn) { nextBtn.disabled = false; nextBtn.textContent = "Get Started"; }
|
|
3265
|
+
if (statusEl) { statusEl.innerHTML = "Failed to install skill. Try again."; }
|
|
3266
|
+
});
|
|
3267
|
+
return;
|
|
3268
|
+
}
|
|
3269
|
+
|
|
3270
|
+
if (wizardStep === 2) {
|
|
3271
|
+
var nameEl = document.getElementById("ralph-name");
|
|
3272
|
+
var taskEl = document.getElementById("ralph-task");
|
|
3273
|
+
if (!wizardData.name) {
|
|
3274
|
+
if (nameEl) { nameEl.focus(); nameEl.style.borderColor = "#e74c3c"; setTimeout(function() { nameEl.style.borderColor = ""; }, 2000); }
|
|
3275
|
+
return;
|
|
3276
|
+
}
|
|
3277
|
+
if (!wizardData.task) {
|
|
3278
|
+
if (taskEl) { taskEl.focus(); taskEl.style.borderColor = "#e74c3c"; setTimeout(function() { taskEl.style.borderColor = ""; }, 2000); }
|
|
3279
|
+
return;
|
|
3280
|
+
}
|
|
3281
|
+
wizardSubmit();
|
|
3282
|
+
return;
|
|
3283
|
+
}
|
|
3284
|
+
wizardStep++;
|
|
3285
|
+
updateWizardStep();
|
|
3286
|
+
}
|
|
3287
|
+
|
|
3288
|
+
function wizardBack() {
|
|
3289
|
+
if (wizardStep > 1) {
|
|
3290
|
+
collectWizardData();
|
|
3291
|
+
wizardStep--;
|
|
3292
|
+
updateWizardStep();
|
|
3293
|
+
}
|
|
3294
|
+
}
|
|
3295
|
+
|
|
3296
|
+
function wizardSkip() {
|
|
3297
|
+
if (wizardStep < 2) {
|
|
3298
|
+
wizardStep++;
|
|
3299
|
+
updateWizardStep();
|
|
3300
|
+
}
|
|
3301
|
+
}
|
|
3302
|
+
|
|
3303
|
+
function wizardSubmit() {
|
|
3304
|
+
collectWizardData();
|
|
3305
|
+
closeRalphWizard();
|
|
3306
|
+
if (ws && ws.readyState === 1) {
|
|
3307
|
+
ws.send(JSON.stringify({ type: "ralph_wizard_complete", data: wizardData }));
|
|
3308
|
+
}
|
|
3309
|
+
}
|
|
3310
|
+
|
|
3311
|
+
// Wizard button listeners
|
|
3312
|
+
var wizardCloseBtn = document.getElementById("ralph-wizard-close");
|
|
3313
|
+
var wizardBackdrop = document.querySelector(".ralph-wizard-backdrop");
|
|
3314
|
+
var wizardBackBtn = document.getElementById("ralph-wizard-back");
|
|
3315
|
+
var wizardSkipBtn = document.getElementById("ralph-wizard-skip");
|
|
3316
|
+
var wizardNextBtn = document.getElementById("ralph-wizard-next");
|
|
3317
|
+
|
|
3318
|
+
if (wizardCloseBtn) wizardCloseBtn.addEventListener("click", closeRalphWizard);
|
|
3319
|
+
if (wizardBackdrop) wizardBackdrop.addEventListener("click", closeRalphWizard);
|
|
3320
|
+
if (wizardBackBtn) wizardBackBtn.addEventListener("click", wizardBack);
|
|
3321
|
+
if (wizardSkipBtn) wizardSkipBtn.addEventListener("click", wizardSkip);
|
|
3322
|
+
if (wizardNextBtn) wizardNextBtn.addEventListener("click", wizardNext);
|
|
3323
|
+
|
|
3324
|
+
// Enforce alphanumeric + hyphens + underscores on name input
|
|
3325
|
+
var wizardNameEl = document.getElementById("ralph-name");
|
|
3326
|
+
if (wizardNameEl) {
|
|
3327
|
+
wizardNameEl.addEventListener("input", function() {
|
|
3328
|
+
this.value = this.value.replace(/[^a-zA-Z0-9_-]/g, "");
|
|
3329
|
+
});
|
|
3330
|
+
}
|
|
3331
|
+
|
|
3332
|
+
// --- Repeat picker handlers ---
|
|
3333
|
+
var repeatSelect = document.getElementById("ralph-repeat");
|
|
3334
|
+
var repeatTimeRow = document.getElementById("ralph-time-row");
|
|
3335
|
+
var repeatCustom = document.getElementById("ralph-custom-repeat");
|
|
3336
|
+
var repeatUnitSelect = document.getElementById("ralph-repeat-unit");
|
|
3337
|
+
var repeatDowRow = document.getElementById("ralph-custom-dow-row");
|
|
3338
|
+
var cronPreview = document.getElementById("ralph-cron-preview");
|
|
3339
|
+
|
|
3340
|
+
function updateRepeatUI() {
|
|
3341
|
+
if (!repeatSelect) return;
|
|
3342
|
+
var val = repeatSelect.value;
|
|
3343
|
+
var isScheduled = val !== "none";
|
|
3344
|
+
if (repeatTimeRow) repeatTimeRow.style.display = isScheduled ? "" : "none";
|
|
3345
|
+
if (repeatCustom) repeatCustom.style.display = val === "custom" ? "" : "none";
|
|
3346
|
+
if (cronPreview) cronPreview.style.display = isScheduled ? "" : "none";
|
|
3347
|
+
if (isScheduled) {
|
|
3348
|
+
var cron = buildWizardCron();
|
|
3349
|
+
var humanEl = document.getElementById("ralph-cron-human");
|
|
3350
|
+
var cronEl = document.getElementById("ralph-cron-expr");
|
|
3351
|
+
if (humanEl) humanEl.textContent = cronToHumanText(cron);
|
|
3352
|
+
if (cronEl) cronEl.textContent = cron || "";
|
|
3353
|
+
}
|
|
3354
|
+
}
|
|
3355
|
+
|
|
3356
|
+
if (repeatSelect) {
|
|
3357
|
+
repeatSelect.addEventListener("change", updateRepeatUI);
|
|
3358
|
+
}
|
|
3359
|
+
if (repeatUnitSelect) {
|
|
3360
|
+
repeatUnitSelect.addEventListener("change", function () {
|
|
3361
|
+
if (repeatDowRow) repeatDowRow.style.display = this.value === "week" ? "" : "none";
|
|
3362
|
+
updateRepeatUI();
|
|
3363
|
+
});
|
|
3364
|
+
}
|
|
3365
|
+
|
|
3366
|
+
var timeInput = document.getElementById("ralph-time");
|
|
3367
|
+
if (timeInput) timeInput.addEventListener("change", updateRepeatUI);
|
|
3368
|
+
|
|
3369
|
+
// DOW buttons in custom repeat
|
|
3370
|
+
var customDowBtns = document.querySelectorAll("#ralph-custom-repeat .sched-dow-btn");
|
|
3371
|
+
for (var di = 0; di < customDowBtns.length; di++) {
|
|
3372
|
+
customDowBtns[di].addEventListener("click", function () {
|
|
3373
|
+
this.classList.toggle("active");
|
|
3374
|
+
updateRepeatUI();
|
|
3375
|
+
});
|
|
3376
|
+
}
|
|
3377
|
+
|
|
3378
|
+
// --- Ralph Sticky (title-bar island) ---
|
|
3379
|
+
function showRalphCraftingBar(show) {
|
|
3380
|
+
var stickyEl = document.getElementById("ralph-sticky");
|
|
3381
|
+
if (!stickyEl) return;
|
|
3382
|
+
if (!show) {
|
|
3383
|
+
stickyEl.classList.add("hidden");
|
|
3384
|
+
stickyEl.innerHTML = "";
|
|
3385
|
+
return;
|
|
3386
|
+
}
|
|
3387
|
+
stickyEl.innerHTML =
|
|
3388
|
+
'<div class="ralph-sticky-inner">' +
|
|
3389
|
+
'<div class="ralph-sticky-header">' +
|
|
3390
|
+
'<span class="ralph-sticky-icon">' + iconHtml("repeat") + '</span>' +
|
|
3391
|
+
'<span class="ralph-sticky-label">Ralph</span>' +
|
|
3392
|
+
'<span class="ralph-sticky-status">' + iconHtml("loader", "icon-spin") + ' Preparing\u2026</span>' +
|
|
3393
|
+
'<button class="ralph-sticky-cancel" title="Cancel">' + iconHtml("x") + '</button>' +
|
|
3394
|
+
'</div>' +
|
|
3395
|
+
'</div>';
|
|
3396
|
+
stickyEl.classList.remove("hidden");
|
|
3397
|
+
refreshIcons();
|
|
3398
|
+
|
|
3399
|
+
var cancelBtn = stickyEl.querySelector(".ralph-sticky-cancel");
|
|
3400
|
+
if (cancelBtn) {
|
|
3401
|
+
cancelBtn.addEventListener("click", function(e) {
|
|
3402
|
+
e.stopPropagation();
|
|
3403
|
+
if (ws && ws.readyState === 1) {
|
|
3404
|
+
ws.send(JSON.stringify({ type: "ralph_cancel_crafting" }));
|
|
3405
|
+
}
|
|
3406
|
+
showRalphCraftingBar(false);
|
|
3407
|
+
showRalphApprovalBar(false);
|
|
3408
|
+
});
|
|
3409
|
+
}
|
|
3410
|
+
}
|
|
3411
|
+
|
|
3412
|
+
// --- Ralph Approval Bar (also uses sticky island) ---
|
|
3413
|
+
function showRalphApprovalBar(show) {
|
|
3414
|
+
var stickyEl = document.getElementById("ralph-sticky");
|
|
3415
|
+
if (!stickyEl) return;
|
|
3416
|
+
if (!show) {
|
|
3417
|
+
// Only clear if we're in approval mode (don't clobber crafting)
|
|
3418
|
+
if (ralphPhase !== "crafting") {
|
|
3419
|
+
stickyEl.classList.add("hidden");
|
|
3420
|
+
stickyEl.innerHTML = "";
|
|
3421
|
+
}
|
|
3422
|
+
return;
|
|
3423
|
+
}
|
|
3424
|
+
|
|
3425
|
+
stickyEl.innerHTML =
|
|
3426
|
+
'<div class="ralph-sticky-inner">' +
|
|
3427
|
+
'<div class="ralph-sticky-header" id="ralph-sticky-header">' +
|
|
3428
|
+
'<span class="ralph-sticky-icon">' + iconHtml("repeat") + '</span>' +
|
|
3429
|
+
'<span class="ralph-sticky-label">Ralph</span>' +
|
|
3430
|
+
'<span class="ralph-sticky-status" id="ralph-sticky-status">Ready</span>' +
|
|
3431
|
+
'<button class="ralph-sticky-action ralph-sticky-preview" title="Preview files">' + iconHtml("eye") + '</button>' +
|
|
3432
|
+
'<button class="ralph-sticky-action ralph-sticky-start" title="' + (wizardData.cron ? 'Schedule' : 'Start loop') + '">' + iconHtml(wizardData.cron ? "calendar-clock" : "play") + '</button>' +
|
|
3433
|
+
'<button class="ralph-sticky-action ralph-sticky-dismiss" title="Cancel and discard">' + iconHtml("x") + '</button>' +
|
|
3434
|
+
'</div>' +
|
|
3435
|
+
'</div>';
|
|
3436
|
+
stickyEl.classList.remove("hidden");
|
|
3437
|
+
refreshIcons();
|
|
3438
|
+
|
|
3439
|
+
stickyEl.querySelector(".ralph-sticky-preview").addEventListener("click", function(e) {
|
|
3440
|
+
e.stopPropagation();
|
|
3441
|
+
if (ws && ws.readyState === 1) {
|
|
3442
|
+
ws.send(JSON.stringify({ type: "ralph_preview_files" }));
|
|
3443
|
+
}
|
|
3444
|
+
});
|
|
3445
|
+
|
|
3446
|
+
stickyEl.querySelector(".ralph-sticky-start").addEventListener("click", function(e) {
|
|
3447
|
+
e.stopPropagation();
|
|
3448
|
+
// Check for uncommitted changes before starting
|
|
3449
|
+
fetch(basePath + "api/git-dirty")
|
|
3450
|
+
.then(function (res) { return res.json(); })
|
|
3451
|
+
.then(function (data) {
|
|
3452
|
+
if (data.dirty) {
|
|
3453
|
+
showConfirm("You have uncommitted changes. Ralph Loop uses git diff to track progress \u2014 uncommitted files may cause unexpected results.\n\nStart anyway?", function () {
|
|
3454
|
+
if (ws && ws.readyState === 1) {
|
|
3455
|
+
ws.send(JSON.stringify({ type: "loop_start" }));
|
|
3456
|
+
}
|
|
3457
|
+
stickyEl.classList.add("hidden");
|
|
3458
|
+
stickyEl.innerHTML = "";
|
|
3459
|
+
});
|
|
3460
|
+
} else {
|
|
3461
|
+
if (ws && ws.readyState === 1) {
|
|
3462
|
+
ws.send(JSON.stringify({ type: "loop_start" }));
|
|
3463
|
+
}
|
|
3464
|
+
stickyEl.classList.add("hidden");
|
|
3465
|
+
stickyEl.innerHTML = "";
|
|
3466
|
+
}
|
|
3467
|
+
})
|
|
3468
|
+
.catch(function () {
|
|
3469
|
+
// If check fails, just start
|
|
3470
|
+
if (ws && ws.readyState === 1) {
|
|
3471
|
+
ws.send(JSON.stringify({ type: "loop_start" }));
|
|
3472
|
+
}
|
|
3473
|
+
stickyEl.classList.add("hidden");
|
|
3474
|
+
stickyEl.innerHTML = "";
|
|
3475
|
+
});
|
|
3476
|
+
});
|
|
3477
|
+
|
|
3478
|
+
stickyEl.querySelector(".ralph-sticky-dismiss").addEventListener("click", function(e) {
|
|
3479
|
+
e.stopPropagation();
|
|
3480
|
+
showConfirm("Discard this Ralph Loop setup?", function() {
|
|
3481
|
+
if (ws && ws.readyState === 1) {
|
|
3482
|
+
ws.send(JSON.stringify({ type: "ralph_wizard_cancel" }));
|
|
3483
|
+
}
|
|
3484
|
+
stickyEl.classList.add("hidden");
|
|
3485
|
+
stickyEl.classList.remove("ralph-ready");
|
|
3486
|
+
stickyEl.innerHTML = "";
|
|
3487
|
+
});
|
|
3488
|
+
});
|
|
3489
|
+
|
|
3490
|
+
updateRalphApprovalStatus();
|
|
3491
|
+
}
|
|
3492
|
+
|
|
3493
|
+
function updateRalphApprovalStatus() {
|
|
3494
|
+
var stickyEl = document.getElementById("ralph-sticky");
|
|
3495
|
+
var statusEl = document.getElementById("ralph-sticky-status");
|
|
3496
|
+
var startBtn = document.querySelector(".ralph-sticky-start");
|
|
3497
|
+
if (!statusEl) return;
|
|
3498
|
+
|
|
3499
|
+
if (ralphFilesReady.bothReady) {
|
|
3500
|
+
statusEl.textContent = "Ready";
|
|
3501
|
+
if (startBtn) startBtn.disabled = false;
|
|
3502
|
+
if (stickyEl) stickyEl.classList.add("ralph-ready");
|
|
3503
|
+
} else if (ralphFilesReady.promptReady || ralphFilesReady.judgeReady) {
|
|
3504
|
+
statusEl.textContent = "Partial\u2026";
|
|
3505
|
+
if (startBtn) startBtn.disabled = true;
|
|
3506
|
+
if (stickyEl) stickyEl.classList.remove("ralph-ready");
|
|
3507
|
+
} else {
|
|
3508
|
+
statusEl.textContent = "Waiting\u2026";
|
|
3509
|
+
if (startBtn) startBtn.disabled = true;
|
|
3510
|
+
if (stickyEl) stickyEl.classList.remove("ralph-ready");
|
|
3511
|
+
}
|
|
3512
|
+
}
|
|
3513
|
+
|
|
3514
|
+
// --- Ralph Preview Modal ---
|
|
3515
|
+
function openRalphPreviewModal() {
|
|
3516
|
+
var modal = document.getElementById("ralph-preview-modal");
|
|
3517
|
+
if (!modal) return;
|
|
3518
|
+
modal.classList.remove("hidden");
|
|
3519
|
+
showRalphPreviewTab("prompt");
|
|
3520
|
+
}
|
|
3521
|
+
|
|
3522
|
+
function closeRalphPreviewModal() {
|
|
3523
|
+
var modal = document.getElementById("ralph-preview-modal");
|
|
3524
|
+
if (modal) modal.classList.add("hidden");
|
|
3525
|
+
}
|
|
3526
|
+
|
|
3527
|
+
function showRalphPreviewTab(tab) {
|
|
3528
|
+
var tabs = document.querySelectorAll(".ralph-tab");
|
|
3529
|
+
for (var i = 0; i < tabs.length; i++) {
|
|
3530
|
+
if (tabs[i].getAttribute("data-tab") === tab) {
|
|
3531
|
+
tabs[i].classList.add("active");
|
|
3532
|
+
} else {
|
|
3533
|
+
tabs[i].classList.remove("active");
|
|
3534
|
+
}
|
|
3535
|
+
}
|
|
3536
|
+
var body = document.getElementById("ralph-preview-body");
|
|
3537
|
+
if (!body) return;
|
|
3538
|
+
var content = tab === "prompt" ? ralphPreviewContent.prompt : ralphPreviewContent.judge;
|
|
3539
|
+
if (typeof marked !== "undefined" && marked.parse) {
|
|
3540
|
+
body.innerHTML = DOMPurify.sanitize(marked.parse(content));
|
|
3541
|
+
} else {
|
|
3542
|
+
body.textContent = content;
|
|
3543
|
+
}
|
|
3544
|
+
}
|
|
3545
|
+
|
|
3546
|
+
// Preview modal listeners
|
|
3547
|
+
var previewCloseBtn = document.getElementById("ralph-preview-close");
|
|
3548
|
+
if (previewCloseBtn) previewCloseBtn.addEventListener("click", closeRalphPreviewModal);
|
|
3549
|
+
|
|
3550
|
+
var previewBackdrop = document.querySelector("#ralph-preview-modal .confirm-backdrop");
|
|
3551
|
+
if (previewBackdrop) previewBackdrop.addEventListener("click", closeRalphPreviewModal);
|
|
3552
|
+
|
|
3553
|
+
var previewTabs = document.querySelectorAll(".ralph-tab");
|
|
3554
|
+
for (var ti = 0; ti < previewTabs.length; ti++) {
|
|
3555
|
+
previewTabs[ti].addEventListener("click", function() {
|
|
3556
|
+
showRalphPreviewTab(this.getAttribute("data-tab"));
|
|
3557
|
+
});
|
|
3558
|
+
}
|
|
3559
|
+
|
|
2721
3560
|
// --- Skills ---
|
|
2722
3561
|
initSkills({
|
|
2723
3562
|
get ws() { return ws; },
|
|
@@ -2727,6 +3566,15 @@ import { initSkills, handleSkillInstalled, handleSkillUninstalled } from './modu
|
|
|
2727
3566
|
sendTerminalCommand: function (cmd) { sendTerminalCommand(cmd); },
|
|
2728
3567
|
});
|
|
2729
3568
|
|
|
3569
|
+
// --- Scheduler ---
|
|
3570
|
+
initScheduler({
|
|
3571
|
+
get ws() { return ws; },
|
|
3572
|
+
get connected() { return connected; },
|
|
3573
|
+
get activeSessionId() { return activeSessionId; },
|
|
3574
|
+
basePath: basePath,
|
|
3575
|
+
openRalphWizard: function () { openRalphWizard(); },
|
|
3576
|
+
});
|
|
3577
|
+
|
|
2730
3578
|
// --- Remove project ---
|
|
2731
3579
|
function confirmRemoveProject(slug, name) {
|
|
2732
3580
|
showConfirm("Remove project \"" + name + "\"?", function () {
|