clay-server 2.7.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 +2 -1
- package/lib/config.js +7 -4
- package/lib/notes.js +1 -1
- package/lib/project.js +178 -6
- package/lib/public/app.js +219 -56
- package/lib/public/css/loop.css +62 -1
- package/lib/public/css/menus.css +5 -0
- package/lib/public/css/mobile-nav.css +15 -15
- package/lib/public/css/scheduler-modal.css +546 -0
- package/lib/public/css/scheduler.css +944 -0
- package/lib/public/css/title-bar.css +6 -6
- package/lib/public/index.html +85 -11
- package/lib/public/modules/input.js +13 -3
- package/lib/public/modules/markdown.js +10 -0
- package/lib/public/modules/scheduler.js +1240 -0
- package/lib/public/style.css +2 -0
- package/lib/scheduler.js +362 -0
- package/lib/sdk-bridge.js +8 -0
- package/lib/sessions.js +7 -4
- package/lib/utils.js +49 -3
- package/package.json +1 -1
package/lib/public/app.js
CHANGED
|
@@ -14,6 +14,7 @@ import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUs
|
|
|
14
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_-]+)/);
|
|
@@ -179,7 +180,7 @@ import { initSkills, handleSkillInstalled, handleSkillUninstalled } from './modu
|
|
|
179
180
|
var ralphPhase = "idle"; // idle | wizard | crafting | approval | executing | done
|
|
180
181
|
var ralphCraftingSessionId = null;
|
|
181
182
|
var wizardStep = 1;
|
|
182
|
-
var wizardData = { name: "", task: "", maxIterations: 25 };
|
|
183
|
+
var wizardData = { name: "", task: "", maxIterations: 25, cron: null };
|
|
183
184
|
var ralphFilesReady = { promptReady: false, judgeReady: false, bothReady: false };
|
|
184
185
|
var ralphPreviewContent = { prompt: "", judge: "" };
|
|
185
186
|
var slashCommands = [];
|
|
@@ -560,7 +561,7 @@ import { initSkills, handleSkillInstalled, handleSkillUninstalled } from './modu
|
|
|
560
561
|
} else if (status === "processing") {
|
|
561
562
|
if (dot) { dot.classList.add("connected"); dot.classList.add("processing"); }
|
|
562
563
|
processing = true;
|
|
563
|
-
setSendBtnMode("stop");
|
|
564
|
+
setSendBtnMode(inputEl.value.trim() ? "send" : "stop");
|
|
564
565
|
} else {
|
|
565
566
|
connected = false;
|
|
566
567
|
sendBtn.disabled = true;
|
|
@@ -1052,13 +1053,21 @@ import { initSkills, handleSkillInstalled, handleSkillUninstalled } from './modu
|
|
|
1052
1053
|
contextTurnsEl.textContent = String(contextData.turns);
|
|
1053
1054
|
}
|
|
1054
1055
|
|
|
1055
|
-
function accumulateContext(cost, usage, modelUsage) {
|
|
1056
|
+
function accumulateContext(cost, usage, modelUsage, lastStreamInputTokens) {
|
|
1056
1057
|
if (cost != null) contextData.cost += cost;
|
|
1057
1058
|
// Use latest turn values (not cumulative) since each turn's input_tokens
|
|
1058
1059
|
// already includes the full conversation context up to that point
|
|
1059
1060
|
if (usage) {
|
|
1060
|
-
|
|
1061
|
-
|
|
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
|
+
}
|
|
1062
1071
|
contextData.output = usage.output_tokens || usage.outputTokens || 0;
|
|
1063
1072
|
contextData.cacheRead = usage.cache_read_input_tokens || usage.cacheReadInputTokens || 0;
|
|
1064
1073
|
contextData.cacheWrite = usage.cache_creation_input_tokens || usage.cacheCreationInputTokens || 0;
|
|
@@ -1736,6 +1745,7 @@ import { initSkills, handleSkillInstalled, handleSkillUninstalled } from './modu
|
|
|
1736
1745
|
if (!slug || slug === currentSlug) return;
|
|
1737
1746
|
resetFileBrowser();
|
|
1738
1747
|
closeArchive();
|
|
1748
|
+
if (isSchedulerOpen()) closeScheduler();
|
|
1739
1749
|
currentSlug = slug;
|
|
1740
1750
|
basePath = "/p/" + slug + "/";
|
|
1741
1751
|
wsPath = "/p/" + slug + "/ws";
|
|
@@ -1750,6 +1760,7 @@ import { initSkills, handleSkillInstalled, handleSkillUninstalled } from './modu
|
|
|
1750
1760
|
if (newSlug && newSlug !== currentSlug) {
|
|
1751
1761
|
resetFileBrowser();
|
|
1752
1762
|
closeArchive();
|
|
1763
|
+
if (isSchedulerOpen()) closeScheduler();
|
|
1753
1764
|
currentSlug = newSlug;
|
|
1754
1765
|
basePath = "/p/" + newSlug + "/";
|
|
1755
1766
|
wsPath = "/p/" + newSlug + "/ws";
|
|
@@ -1880,7 +1891,7 @@ import { initSkills, handleSkillInstalled, handleSkillUninstalled } from './modu
|
|
|
1880
1891
|
replayingHistory = false;
|
|
1881
1892
|
// Restore accurate context data from the last result in full history
|
|
1882
1893
|
if (msg.lastUsage || msg.lastModelUsage) {
|
|
1883
|
-
accumulateContext(msg.lastCost, msg.lastUsage, msg.lastModelUsage);
|
|
1894
|
+
accumulateContext(msg.lastCost, msg.lastUsage, msg.lastModelUsage, msg.lastStreamInputTokens);
|
|
1884
1895
|
}
|
|
1885
1896
|
updateContextPanel();
|
|
1886
1897
|
updateUsagePanel();
|
|
@@ -2055,6 +2066,22 @@ import { initSkills, handleSkillInstalled, handleSkillUninstalled } from './modu
|
|
|
2055
2066
|
handleSkillUninstalled(msg);
|
|
2056
2067
|
break;
|
|
2057
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
|
+
|
|
2058
2085
|
case "input_sync":
|
|
2059
2086
|
handleInputSync(msg.text);
|
|
2060
2087
|
break;
|
|
@@ -2302,7 +2329,7 @@ import { initSkills, handleSkillInstalled, handleSkillUninstalled } from './modu
|
|
|
2302
2329
|
finalizeAssistantBlock();
|
|
2303
2330
|
addTurnMeta(msg.cost, msg.duration);
|
|
2304
2331
|
accumulateUsage(msg.cost, msg.usage);
|
|
2305
|
-
accumulateContext(msg.cost, msg.usage, msg.modelUsage);
|
|
2332
|
+
accumulateContext(msg.cost, msg.usage, msg.modelUsage, msg.lastStreamInputTokens);
|
|
2306
2333
|
break;
|
|
2307
2334
|
|
|
2308
2335
|
case "done":
|
|
@@ -2566,7 +2593,9 @@ import { initSkills, handleSkillInstalled, handleSkillUninstalled } from './modu
|
|
|
2566
2593
|
|
|
2567
2594
|
case "loop_iteration":
|
|
2568
2595
|
loopIteration = msg.iteration;
|
|
2596
|
+
loopMaxIterations = msg.maxIterations;
|
|
2569
2597
|
updateLoopBanner(msg.iteration, msg.maxIterations, "running");
|
|
2598
|
+
updateLoopButton();
|
|
2570
2599
|
addSystemMessage("Ralph Loop iteration #" + msg.iteration + " started", false);
|
|
2571
2600
|
break;
|
|
2572
2601
|
|
|
@@ -2615,6 +2644,7 @@ import { initSkills, handleSkillInstalled, handleSkillUninstalled } from './modu
|
|
|
2615
2644
|
ralphCraftingSessionId = msg.sessionId || activeSessionId;
|
|
2616
2645
|
updateLoopButton();
|
|
2617
2646
|
updateRalphBars();
|
|
2647
|
+
enterCraftingMode(ralphCraftingSessionId, msg.taskId || null);
|
|
2618
2648
|
break;
|
|
2619
2649
|
|
|
2620
2650
|
case "ralph_files_status":
|
|
@@ -2626,10 +2656,15 @@ import { initSkills, handleSkillInstalled, handleSkillUninstalled } from './modu
|
|
|
2626
2656
|
if (msg.bothReady && (ralphPhase === "crafting" || ralphPhase === "approval")) {
|
|
2627
2657
|
ralphPhase = "approval";
|
|
2628
2658
|
showRalphApprovalBar(true);
|
|
2659
|
+
if (isSchedulerOpen()) exitCraftingMode(msg.taskId || null);
|
|
2629
2660
|
}
|
|
2630
2661
|
updateRalphApprovalStatus();
|
|
2631
2662
|
break;
|
|
2632
2663
|
|
|
2664
|
+
case "loop_registry_files_content":
|
|
2665
|
+
handleLoopRegistryFiles(msg);
|
|
2666
|
+
break;
|
|
2667
|
+
|
|
2633
2668
|
case "ralph_files_content":
|
|
2634
2669
|
ralphPreviewContent = { prompt: msg.prompt || "", judge: msg.judge || "" };
|
|
2635
2670
|
openRalphPreviewModal();
|
|
@@ -2779,6 +2814,7 @@ import { initSkills, handleSkillInstalled, handleSkillUninstalled } from './modu
|
|
|
2779
2814
|
resetContextData: resetContextData,
|
|
2780
2815
|
showImageModal: showImageModal,
|
|
2781
2816
|
hideSuggestionChips: hideSuggestionChips,
|
|
2817
|
+
setSendBtnMode: setSendBtnMode,
|
|
2782
2818
|
});
|
|
2783
2819
|
|
|
2784
2820
|
// --- Notifications module (viewport, banners, notifications, debug, service worker) ---
|
|
@@ -2853,6 +2889,7 @@ import { initSkills, handleSkillInstalled, handleSkillUninstalled } from './modu
|
|
|
2853
2889
|
var stickyNotesSidebarBtn = $("sticky-notes-sidebar-btn");
|
|
2854
2890
|
if (stickyNotesSidebarBtn) {
|
|
2855
2891
|
stickyNotesSidebarBtn.addEventListener("click", function () {
|
|
2892
|
+
if (isSchedulerOpen()) closeScheduler();
|
|
2856
2893
|
if (isArchiveOpen()) {
|
|
2857
2894
|
closeArchive();
|
|
2858
2895
|
} else {
|
|
@@ -2861,11 +2898,11 @@ import { initSkills, handleSkillInstalled, handleSkillUninstalled } from './modu
|
|
|
2861
2898
|
});
|
|
2862
2899
|
}
|
|
2863
2900
|
|
|
2864
|
-
// Close archive when switching to other sidebar panels
|
|
2901
|
+
// Close archive / scheduler panel when switching to other sidebar panels
|
|
2865
2902
|
var fileBrowserBtn = $("file-browser-btn");
|
|
2866
2903
|
var terminalSidebarBtn = $("terminal-sidebar-btn");
|
|
2867
|
-
if (fileBrowserBtn) fileBrowserBtn.addEventListener("click", function () { if (isArchiveOpen()) closeArchive(); });
|
|
2868
|
-
if (terminalSidebarBtn) terminalSidebarBtn.addEventListener("click", function () { if (isArchiveOpen()) closeArchive(); });
|
|
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(); });
|
|
2869
2906
|
|
|
2870
2907
|
// --- Ralph Loop UI ---
|
|
2871
2908
|
function updateLoopInputVisibility(loop) {
|
|
@@ -2879,39 +2916,72 @@ import { initSkills, handleSkillInstalled, handleSkillUninstalled } from './modu
|
|
|
2879
2916
|
}
|
|
2880
2917
|
|
|
2881
2918
|
function updateLoopButton() {
|
|
2882
|
-
var
|
|
2883
|
-
if (!
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
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") {
|
|
2891
2970
|
toggleLoopPopover();
|
|
2892
|
-
} else {
|
|
2971
|
+
} else if (clickAction === "wizard") {
|
|
2893
2972
|
openRalphWizard();
|
|
2894
2973
|
}
|
|
2895
2974
|
});
|
|
2896
|
-
var sessionActions = document.getElementById("session-actions");
|
|
2897
|
-
if (sessionActions) sessionActions.appendChild(btn);
|
|
2898
|
-
if (typeof lucide !== "undefined") lucide.createIcons();
|
|
2899
|
-
existing = btn;
|
|
2900
2975
|
}
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
existing.appendChild(hint);
|
|
2911
|
-
refreshIcons();
|
|
2912
|
-
}
|
|
2913
|
-
} else {
|
|
2914
|
-
if (hint) hint.remove();
|
|
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
|
+
});
|
|
2915
2985
|
}
|
|
2916
2986
|
}
|
|
2917
2987
|
|
|
@@ -3094,20 +3164,7 @@ import { initSkills, handleSkillInstalled, handleSkillUninstalled } from './modu
|
|
|
3094
3164
|
var nextBtn = document.getElementById("ralph-wizard-next");
|
|
3095
3165
|
if (backBtn) backBtn.style.visibility = wizardStep === 1 ? "hidden" : "visible";
|
|
3096
3166
|
if (skipBtn) skipBtn.style.display = "none";
|
|
3097
|
-
if (nextBtn) nextBtn.textContent = wizardStep ===
|
|
3098
|
-
|
|
3099
|
-
// Build review on step 3
|
|
3100
|
-
if (wizardStep === 3) {
|
|
3101
|
-
collectWizardData();
|
|
3102
|
-
var summary = document.getElementById("ralph-review-summary");
|
|
3103
|
-
if (summary) {
|
|
3104
|
-
summary.innerHTML =
|
|
3105
|
-
'<div class="ralph-review-label">Name</div>' +
|
|
3106
|
-
'<div class="ralph-review-value">' + escapeHtml(wizardData.name || "(empty)") + '</div>' +
|
|
3107
|
-
'<div class="ralph-review-label">Task</div>' +
|
|
3108
|
-
'<div class="ralph-review-value">' + escapeHtml(wizardData.task || "(empty)") + '</div>';
|
|
3109
|
-
}
|
|
3110
|
-
}
|
|
3167
|
+
if (nextBtn) nextBtn.textContent = wizardStep === 2 ? "Launch" : "Get Started";
|
|
3111
3168
|
}
|
|
3112
3169
|
|
|
3113
3170
|
function collectWizardData() {
|
|
@@ -3117,6 +3174,59 @@ import { initSkills, handleSkillInstalled, handleSkillUninstalled } from './modu
|
|
|
3117
3174
|
wizardData.name = nameEl ? nameEl.value.replace(/[^a-zA-Z0-9_-]/g, "").trim() : "";
|
|
3118
3175
|
wizardData.task = taskEl ? taskEl.value.trim() : "";
|
|
3119
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;
|
|
3120
3230
|
}
|
|
3121
3231
|
|
|
3122
3232
|
function wizardNext() {
|
|
@@ -3168,8 +3278,6 @@ import { initSkills, handleSkillInstalled, handleSkillUninstalled } from './modu
|
|
|
3168
3278
|
if (taskEl) { taskEl.focus(); taskEl.style.borderColor = "#e74c3c"; setTimeout(function() { taskEl.style.borderColor = ""; }, 2000); }
|
|
3169
3279
|
return;
|
|
3170
3280
|
}
|
|
3171
|
-
}
|
|
3172
|
-
if (wizardStep === 3) {
|
|
3173
3281
|
wizardSubmit();
|
|
3174
3282
|
return;
|
|
3175
3283
|
}
|
|
@@ -3186,7 +3294,7 @@ import { initSkills, handleSkillInstalled, handleSkillUninstalled } from './modu
|
|
|
3186
3294
|
}
|
|
3187
3295
|
|
|
3188
3296
|
function wizardSkip() {
|
|
3189
|
-
if (wizardStep <
|
|
3297
|
+
if (wizardStep < 2) {
|
|
3190
3298
|
wizardStep++;
|
|
3191
3299
|
updateWizardStep();
|
|
3192
3300
|
}
|
|
@@ -3221,6 +3329,52 @@ import { initSkills, handleSkillInstalled, handleSkillUninstalled } from './modu
|
|
|
3221
3329
|
});
|
|
3222
3330
|
}
|
|
3223
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
|
+
|
|
3224
3378
|
// --- Ralph Sticky (title-bar island) ---
|
|
3225
3379
|
function showRalphCraftingBar(show) {
|
|
3226
3380
|
var stickyEl = document.getElementById("ralph-sticky");
|
|
@@ -3275,7 +3429,7 @@ import { initSkills, handleSkillInstalled, handleSkillUninstalled } from './modu
|
|
|
3275
3429
|
'<span class="ralph-sticky-label">Ralph</span>' +
|
|
3276
3430
|
'<span class="ralph-sticky-status" id="ralph-sticky-status">Ready</span>' +
|
|
3277
3431
|
'<button class="ralph-sticky-action ralph-sticky-preview" title="Preview files">' + iconHtml("eye") + '</button>' +
|
|
3278
|
-
'<button class="ralph-sticky-action ralph-sticky-start" title="Start loop">' + iconHtml("play") + '</button>' +
|
|
3432
|
+
'<button class="ralph-sticky-action ralph-sticky-start" title="' + (wizardData.cron ? 'Schedule' : 'Start loop') + '">' + iconHtml(wizardData.cron ? "calendar-clock" : "play") + '</button>' +
|
|
3279
3433
|
'<button class="ralph-sticky-action ralph-sticky-dismiss" title="Cancel and discard">' + iconHtml("x") + '</button>' +
|
|
3280
3434
|
'</div>' +
|
|
3281
3435
|
'</div>';
|
|
@@ -3412,6 +3566,15 @@ import { initSkills, handleSkillInstalled, handleSkillUninstalled } from './modu
|
|
|
3412
3566
|
sendTerminalCommand: function (cmd) { sendTerminalCommand(cmd); },
|
|
3413
3567
|
});
|
|
3414
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
|
+
|
|
3415
3578
|
// --- Remove project ---
|
|
3416
3579
|
function confirmRemoveProject(slug, name) {
|
|
3417
3580
|
showConfirm("Remove project \"" + name + "\"?", function () {
|
package/lib/public/css/loop.css
CHANGED
|
@@ -38,7 +38,68 @@
|
|
|
38
38
|
.loop-stop-btn:hover {
|
|
39
39
|
opacity: 0.85;
|
|
40
40
|
}
|
|
41
|
-
/*
|
|
41
|
+
/* Ralph Loop sidebar section */
|
|
42
|
+
#ralph-loop-section {
|
|
43
|
+
border-left: 3px solid var(--accent);
|
|
44
|
+
background: rgba(var(--overlay-rgb), 0.03);
|
|
45
|
+
padding: 8px 12px;
|
|
46
|
+
margin: 0 8px;
|
|
47
|
+
border-radius: 6px;
|
|
48
|
+
border-bottom: 1px solid var(--border);
|
|
49
|
+
margin-bottom: 4px;
|
|
50
|
+
}
|
|
51
|
+
.ralph-section-inner {
|
|
52
|
+
display: flex;
|
|
53
|
+
flex-direction: column;
|
|
54
|
+
gap: 4px;
|
|
55
|
+
}
|
|
56
|
+
.ralph-section-header {
|
|
57
|
+
display: flex;
|
|
58
|
+
align-items: center;
|
|
59
|
+
gap: 6px;
|
|
60
|
+
font-size: 12px;
|
|
61
|
+
font-weight: 600;
|
|
62
|
+
color: var(--text);
|
|
63
|
+
}
|
|
64
|
+
.ralph-section-header .ralph-section-icon { display: inline-flex; color: var(--accent); }
|
|
65
|
+
.ralph-section-header .ralph-section-icon .lucide { width: 13px; height: 13px; }
|
|
66
|
+
.ralph-section-header .ralph-section-label { flex: 1; }
|
|
67
|
+
.ralph-section-header .loop-experimental { margin-left: 0; font-size: 9.5px; }
|
|
68
|
+
.ralph-section-body {
|
|
69
|
+
font-size: 11.5px;
|
|
70
|
+
color: var(--text-muted);
|
|
71
|
+
padding-left: 2px;
|
|
72
|
+
margin-top: 2px;
|
|
73
|
+
}
|
|
74
|
+
.ralph-section-hint {
|
|
75
|
+
color: var(--text-muted);
|
|
76
|
+
}
|
|
77
|
+
.ralph-section-status {
|
|
78
|
+
display: inline-flex;
|
|
79
|
+
align-items: center;
|
|
80
|
+
gap: 4px;
|
|
81
|
+
}
|
|
82
|
+
.ralph-section-status .lucide { width: 11px; height: 11px; }
|
|
83
|
+
.ralph-section-status.crafting { color: var(--accent); }
|
|
84
|
+
.ralph-section-status.ready { color: var(--success, #27ae60); font-weight: 600; }
|
|
85
|
+
.ralph-section-status.running { color: var(--accent); }
|
|
86
|
+
.ralph-section-status.done { color: var(--success, #27ae60); }
|
|
87
|
+
.ralph-section-tasks-link {
|
|
88
|
+
display: block;
|
|
89
|
+
margin-top: 2px;
|
|
90
|
+
font-size: 11px;
|
|
91
|
+
color: var(--accent);
|
|
92
|
+
text-decoration: none;
|
|
93
|
+
cursor: pointer;
|
|
94
|
+
}
|
|
95
|
+
.ralph-section-tasks-link:hover { text-decoration: underline; }
|
|
96
|
+
|
|
97
|
+
/* Section accent states */
|
|
98
|
+
.ralph-section-ready { border-left-color: var(--success, #27ae60); }
|
|
99
|
+
.ralph-section-running { border-left-color: var(--accent); }
|
|
100
|
+
.ralph-section-done { border-left-color: var(--success, #27ae60); }
|
|
101
|
+
|
|
102
|
+
/* .loop-start-btn is now inside #ralph-loop-section, inherits sidebar button styles */
|
|
42
103
|
.loop-experimental {
|
|
43
104
|
display: inline-flex;
|
|
44
105
|
align-items: center;
|
package/lib/public/css/menus.css
CHANGED
|
@@ -507,6 +507,7 @@
|
|
|
507
507
|
}
|
|
508
508
|
|
|
509
509
|
#config-chip .lucide { width: 10px; height: 10px; }
|
|
510
|
+
#config-chip .config-chip-icon { display: none; }
|
|
510
511
|
#config-chip:hover { color: var(--text-secondary); background: rgba(var(--overlay-rgb),0.06); }
|
|
511
512
|
#config-chip.active { color: var(--text-secondary); background: rgba(var(--overlay-rgb),0.06); }
|
|
512
513
|
|
|
@@ -851,6 +852,10 @@
|
|
|
851
852
|
width: auto;
|
|
852
853
|
max-height: 60vh;
|
|
853
854
|
}
|
|
855
|
+
|
|
856
|
+
/* Config chip: icon-only on mobile */
|
|
857
|
+
#config-chip .config-chip-icon { display: inline; width: 14px; height: 14px; }
|
|
858
|
+
#config-chip-label { display: none; }
|
|
854
859
|
}
|
|
855
860
|
|
|
856
861
|
|
|
@@ -17,11 +17,12 @@
|
|
|
17
17
|
left: 0;
|
|
18
18
|
right: 0;
|
|
19
19
|
height: calc(56px + var(--safe-bottom));
|
|
20
|
+
padding-top: 1px;
|
|
20
21
|
padding-bottom: var(--safe-bottom);
|
|
21
22
|
background: var(--bg);
|
|
22
23
|
border-top: 1px solid var(--border);
|
|
23
24
|
display: flex;
|
|
24
|
-
align-items:
|
|
25
|
+
align-items: center;
|
|
25
26
|
justify-content: space-around;
|
|
26
27
|
z-index: 200;
|
|
27
28
|
}
|
|
@@ -88,33 +89,32 @@
|
|
|
88
89
|
|
|
89
90
|
.mobile-tab { position: relative; }
|
|
90
91
|
|
|
91
|
-
/* --- Center "+" button
|
|
92
|
+
/* --- Center "+" button --- */
|
|
92
93
|
.mobile-tab-new {
|
|
93
|
-
flex:
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
border-radius: 50%;
|
|
97
|
-
background: var(--accent);
|
|
98
|
-
color: #fff;
|
|
94
|
+
flex: 1;
|
|
95
|
+
height: 100%;
|
|
96
|
+
background: none;
|
|
99
97
|
border: none;
|
|
100
98
|
display: flex;
|
|
101
99
|
align-items: center;
|
|
102
100
|
justify-content: center;
|
|
103
101
|
cursor: pointer;
|
|
104
|
-
margin-top: -12px;
|
|
105
|
-
box-shadow: 0 2px 12px rgba(var(--shadow-rgb), 0.25);
|
|
106
102
|
-webkit-tap-highlight-color: transparent;
|
|
107
|
-
transition: transform 0.1s, box-shadow 0.15s;
|
|
108
103
|
}
|
|
109
104
|
|
|
110
105
|
.mobile-tab-new .lucide {
|
|
111
|
-
width:
|
|
112
|
-
height:
|
|
106
|
+
width: 20px;
|
|
107
|
+
height: 20px;
|
|
108
|
+
padding: 8px;
|
|
109
|
+
box-sizing: content-box;
|
|
110
|
+
border-radius: 50%;
|
|
111
|
+
background: var(--border);
|
|
112
|
+
color: #fff;
|
|
113
|
+
transition: transform 0.1s;
|
|
113
114
|
}
|
|
114
115
|
|
|
115
|
-
.mobile-tab-new:active {
|
|
116
|
+
.mobile-tab-new:active .lucide {
|
|
116
117
|
transform: scale(0.92);
|
|
117
|
-
box-shadow: 0 1px 6px rgba(var(--shadow-rgb), 0.2);
|
|
118
118
|
}
|
|
119
119
|
|
|
120
120
|
/* --- Mobile project list items (inside sidebar) --- */
|