clay-server 2.18.0-beta.9 → 2.18.0
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/lib/project.js +1445 -32
- package/lib/public/app.js +309 -125
- package/lib/public/css/debate.css +1039 -0
- package/lib/public/css/mates.css +125 -0
- package/lib/public/css/scheduler-modal.css +110 -392
- package/lib/public/css/scheduler.css +10 -3
- package/lib/public/css/sidebar.css +80 -7
- package/lib/public/index.html +53 -106
- package/lib/public/modules/debate.js +633 -0
- package/lib/public/modules/input.js +14 -5
- package/lib/public/modules/mate-sidebar.js +169 -2
- package/lib/public/modules/mention.js +6 -3
- package/lib/public/modules/scheduler.js +373 -252
- package/lib/public/modules/sidebar.js +158 -28
- package/lib/public/modules/tools.js +13 -3
- package/lib/public/modules/tooltip.js +2 -0
- package/lib/public/style.css +1 -0
- package/lib/scheduler.js +59 -1
- package/lib/sessions.js +9 -2
- package/lib/user-presence.js +92 -0
- package/package.json +1 -1
|
@@ -19,6 +19,7 @@ var searchMatchIds = null; // null = no search, Set of matched session IDs
|
|
|
19
19
|
var searchDebounce = null;
|
|
20
20
|
var cachedSessions = [];
|
|
21
21
|
var expandedLoopGroups = new Set();
|
|
22
|
+
var expandedLoopRuns = new Set();
|
|
22
23
|
|
|
23
24
|
// --- Cached project data for mobile sheet ---
|
|
24
25
|
var cachedProjectList = [];
|
|
@@ -309,39 +310,52 @@ function renderLoopChild(s) {
|
|
|
309
310
|
|
|
310
311
|
function renderLoopGroup(loopId, children, groupKey) {
|
|
311
312
|
var gk = groupKey || loopId;
|
|
312
|
-
|
|
313
|
-
children
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
313
|
+
|
|
314
|
+
// Sub-group children by startedAt (each run)
|
|
315
|
+
var runMap = {};
|
|
316
|
+
for (var i = 0; i < children.length; i++) {
|
|
317
|
+
var runKey = String(children[i].loop && children[i].loop.startedAt || 0);
|
|
318
|
+
if (!runMap[runKey]) runMap[runKey] = [];
|
|
319
|
+
runMap[runKey].push(children[i]);
|
|
320
|
+
}
|
|
321
|
+
var runKeys = Object.keys(runMap);
|
|
322
|
+
|
|
323
|
+
// Sort each run's children by iteration then role
|
|
324
|
+
for (var ri = 0; ri < runKeys.length; ri++) {
|
|
325
|
+
runMap[runKeys[ri]].sort(function (a, b) {
|
|
326
|
+
var ai = (a.loop && a.loop.iteration) || 0;
|
|
327
|
+
var bi = (b.loop && b.loop.iteration) || 0;
|
|
328
|
+
if (ai !== bi) return ai - bi;
|
|
329
|
+
var ar = (a.loop && a.loop.role === "judge") ? 1 : 0;
|
|
330
|
+
var br = (b.loop && b.loop.role === "judge") ? 1 : 0;
|
|
331
|
+
return ar - br;
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// Sort runs by startedAt descending (newest first)
|
|
336
|
+
runKeys.sort(function (a, b) { return Number(b) - Number(a); });
|
|
322
337
|
|
|
323
338
|
var expanded = expandedLoopGroups.has(gk);
|
|
324
339
|
var hasActive = false;
|
|
325
340
|
var anyProcessing = false;
|
|
326
341
|
var latestSession = children[0];
|
|
327
|
-
for (var
|
|
328
|
-
if (children[
|
|
329
|
-
if (children[
|
|
330
|
-
if ((children[
|
|
331
|
-
latestSession = children[
|
|
342
|
+
for (var ci = 0; ci < children.length; ci++) {
|
|
343
|
+
if (children[ci].active) hasActive = true;
|
|
344
|
+
if (children[ci].isProcessing) anyProcessing = true;
|
|
345
|
+
if ((children[ci].lastActivity || 0) > (latestSession.lastActivity || 0)) {
|
|
346
|
+
latestSession = children[ci];
|
|
332
347
|
}
|
|
333
348
|
}
|
|
334
349
|
|
|
335
350
|
var loopName = (children[0].loop && children[0].loop.name) || "Ralph Loop";
|
|
336
351
|
var isRalph = children[0].loop && children[0].loop.source === "ralph";
|
|
337
352
|
var isCrafting = false;
|
|
338
|
-
var maxIter = 0;
|
|
339
353
|
for (var j = 0; j < children.length; j++) {
|
|
340
|
-
var iter = (children[j].loop && children[j].loop.iteration) || 0;
|
|
341
|
-
if (iter > maxIter) maxIter = iter;
|
|
342
354
|
if (children[j].loop && children[j].loop.role === "crafting") isCrafting = true;
|
|
343
355
|
}
|
|
344
356
|
|
|
357
|
+
var runCount = runKeys.length;
|
|
358
|
+
|
|
345
359
|
var wrapper = document.createElement("div");
|
|
346
360
|
wrapper.className = "session-loop-wrapper";
|
|
347
361
|
|
|
@@ -378,7 +392,8 @@ function renderLoopGroup(loopId, children, groupKey) {
|
|
|
378
392
|
if (isCrafting && children.length === 1) {
|
|
379
393
|
textHtml += '<span class="session-loop-badge crafting">Crafting</span>';
|
|
380
394
|
} else {
|
|
381
|
-
|
|
395
|
+
var countLabel = runCount === 1 ? children.length : runCount + (runCount === 1 ? " run" : " runs");
|
|
396
|
+
textHtml += '<span class="session-loop-count' + (isRalph ? "" : " scheduled") + '">' + countLabel + '</span>';
|
|
382
397
|
}
|
|
383
398
|
textSpan.innerHTML = textHtml;
|
|
384
399
|
el.appendChild(textSpan);
|
|
@@ -396,7 +411,7 @@ function renderLoopGroup(loopId, children, groupKey) {
|
|
|
396
411
|
})(loopId, loopName, children.length, moreBtn));
|
|
397
412
|
el.appendChild(moreBtn);
|
|
398
413
|
|
|
399
|
-
// Click row (not chevron/more)
|
|
414
|
+
// Click row (not chevron/more) -> switch to latest session
|
|
400
415
|
el.addEventListener("click", (function (id) {
|
|
401
416
|
return function () {
|
|
402
417
|
if (ctx.ws && ctx.connected) {
|
|
@@ -409,12 +424,98 @@ function renderLoopGroup(loopId, children, groupKey) {
|
|
|
409
424
|
|
|
410
425
|
wrapper.appendChild(el);
|
|
411
426
|
|
|
412
|
-
// Expanded
|
|
427
|
+
// Expanded: show runs as sub-groups
|
|
413
428
|
if (expanded) {
|
|
414
429
|
var childContainer = document.createElement("div");
|
|
415
430
|
childContainer.className = "session-loop-children";
|
|
416
|
-
|
|
417
|
-
|
|
431
|
+
|
|
432
|
+
if (runCount === 1) {
|
|
433
|
+
// Single run: show sessions directly (no extra nesting)
|
|
434
|
+
var singleRun = runMap[runKeys[0]];
|
|
435
|
+
for (var sk = 0; sk < singleRun.length; sk++) {
|
|
436
|
+
childContainer.appendChild(renderLoopChild(singleRun[sk]));
|
|
437
|
+
}
|
|
438
|
+
} else {
|
|
439
|
+
// Multiple runs: render each run as a collapsible sub-group
|
|
440
|
+
for (var rk = 0; rk < runKeys.length; rk++) {
|
|
441
|
+
childContainer.appendChild(renderLoopRun(gk, runKeys[rk], runMap[runKeys[rk]], isRalph));
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
wrapper.appendChild(childContainer);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
return wrapper;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
function renderLoopRun(parentGk, startedAtKey, sessions, isRalph) {
|
|
452
|
+
var runGk = parentGk + ":" + startedAtKey;
|
|
453
|
+
var expanded = expandedLoopRuns.has(runGk);
|
|
454
|
+
var startedAt = Number(startedAtKey);
|
|
455
|
+
var timeLabel = startedAt ? new Date(startedAt).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }) : "Unknown";
|
|
456
|
+
|
|
457
|
+
var hasActive = false;
|
|
458
|
+
var anyProcessing = false;
|
|
459
|
+
var latestSession = sessions[0];
|
|
460
|
+
for (var i = 0; i < sessions.length; i++) {
|
|
461
|
+
if (sessions[i].active) hasActive = true;
|
|
462
|
+
if (sessions[i].isProcessing) anyProcessing = true;
|
|
463
|
+
if ((sessions[i].lastActivity || 0) > (latestSession.lastActivity || 0)) {
|
|
464
|
+
latestSession = sessions[i];
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
var wrapper = document.createElement("div");
|
|
469
|
+
wrapper.className = "session-loop-run-wrapper";
|
|
470
|
+
|
|
471
|
+
var el = document.createElement("div");
|
|
472
|
+
el.className = "session-loop-run" + (hasActive ? " active" : "") + (expanded ? " expanded" : "") + (isRalph ? "" : " scheduled");
|
|
473
|
+
|
|
474
|
+
var chevron = document.createElement("button");
|
|
475
|
+
chevron.className = "session-loop-chevron";
|
|
476
|
+
chevron.innerHTML = iconHtml("chevron-right");
|
|
477
|
+
chevron.addEventListener("click", (function (rk) {
|
|
478
|
+
return function (e) {
|
|
479
|
+
e.stopPropagation();
|
|
480
|
+
if (expandedLoopRuns.has(rk)) {
|
|
481
|
+
expandedLoopRuns.delete(rk);
|
|
482
|
+
} else {
|
|
483
|
+
expandedLoopRuns.add(rk);
|
|
484
|
+
}
|
|
485
|
+
renderSessionList(null);
|
|
486
|
+
};
|
|
487
|
+
})(runGk));
|
|
488
|
+
el.appendChild(chevron);
|
|
489
|
+
|
|
490
|
+
var textSpan = document.createElement("span");
|
|
491
|
+
textSpan.className = "session-item-text";
|
|
492
|
+
var textHtml = "";
|
|
493
|
+
if (anyProcessing) {
|
|
494
|
+
textHtml += '<span class="session-processing"></span>';
|
|
495
|
+
}
|
|
496
|
+
textHtml += '<span class="session-loop-run-time">' + escapeHtml(timeLabel) + '</span>';
|
|
497
|
+
textHtml += '<span class="session-loop-count' + (isRalph ? "" : " scheduled") + '">' + sessions.length + '</span>';
|
|
498
|
+
textSpan.innerHTML = textHtml;
|
|
499
|
+
el.appendChild(textSpan);
|
|
500
|
+
|
|
501
|
+
// Click row -> switch to latest session of this run
|
|
502
|
+
el.addEventListener("click", (function (id) {
|
|
503
|
+
return function () {
|
|
504
|
+
if (ctx.ws && ctx.connected) {
|
|
505
|
+
ctx.ws.send(JSON.stringify({ type: "switch_session", id: id }));
|
|
506
|
+
dismissOverlayPanels();
|
|
507
|
+
closeSidebar();
|
|
508
|
+
}
|
|
509
|
+
};
|
|
510
|
+
})(latestSession.id));
|
|
511
|
+
|
|
512
|
+
wrapper.appendChild(el);
|
|
513
|
+
|
|
514
|
+
if (expanded) {
|
|
515
|
+
var childContainer = document.createElement("div");
|
|
516
|
+
childContainer.className = "session-loop-children";
|
|
517
|
+
for (var k = 0; k < sessions.length; k++) {
|
|
518
|
+
childContainer.appendChild(renderLoopChild(sessions[k]));
|
|
418
519
|
}
|
|
419
520
|
wrapper.appendChild(childContainer);
|
|
420
521
|
}
|
|
@@ -493,7 +594,7 @@ export function renderSessionList(sessions) {
|
|
|
493
594
|
ctx.sessionListEl.innerHTML = "";
|
|
494
595
|
|
|
495
596
|
// Partition: loop sessions vs normal sessions
|
|
496
|
-
// Group by loopId +
|
|
597
|
+
// Group by loopId + date so all runs of the same task on the same day are merged
|
|
497
598
|
var loopGroups = {}; // groupKey -> [sessions]
|
|
498
599
|
var normalSessions = [];
|
|
499
600
|
for (var i = 0; i < cachedSessions.length; i++) {
|
|
@@ -502,7 +603,9 @@ export function renderSessionList(sessions) {
|
|
|
502
603
|
// Task crafting sessions live in the scheduler calendar, not the main list
|
|
503
604
|
continue;
|
|
504
605
|
} else if (s.loop && s.loop.loopId) {
|
|
505
|
-
var
|
|
606
|
+
var startedAt = s.loop.startedAt || 0;
|
|
607
|
+
var dateStr = startedAt ? new Date(startedAt).toISOString().slice(0, 10) : "unknown";
|
|
608
|
+
var groupKey = s.loop.loopId + ":" + dateStr;
|
|
506
609
|
if (!loopGroups[groupKey]) loopGroups[groupKey] = [];
|
|
507
610
|
loopGroups[groupKey].push(s);
|
|
508
611
|
} else {
|
|
@@ -1665,8 +1768,12 @@ export function initSidebar(_ctx) {
|
|
|
1665
1768
|
var sidebarColumn = document.getElementById("sidebar-column");
|
|
1666
1769
|
|
|
1667
1770
|
function syncUserIslandWidth() {
|
|
1668
|
-
if (!userIsland
|
|
1669
|
-
var
|
|
1771
|
+
if (!userIsland) return;
|
|
1772
|
+
var mateSidebarColumn = document.getElementById("mate-sidebar-column");
|
|
1773
|
+
var isMateDM = document.body.classList.contains("mate-dm-active");
|
|
1774
|
+
var col = (isMateDM && mateSidebarColumn && !mateSidebarColumn.classList.contains("hidden")) ? mateSidebarColumn : sidebarColumn;
|
|
1775
|
+
if (!col) return;
|
|
1776
|
+
var rect = col.getBoundingClientRect();
|
|
1670
1777
|
userIsland.style.width = (rect.right - 8 - 8) + "px";
|
|
1671
1778
|
}
|
|
1672
1779
|
|
|
@@ -1919,6 +2026,23 @@ function showIconTooltip(el, text) {
|
|
|
1919
2026
|
});
|
|
1920
2027
|
}
|
|
1921
2028
|
|
|
2029
|
+
function showIconTooltipHtml(el, html) {
|
|
2030
|
+
hideIconTooltip();
|
|
2031
|
+
var tip = document.createElement("div");
|
|
2032
|
+
tip.className = "icon-strip-tooltip";
|
|
2033
|
+
tip.style.whiteSpace = "normal";
|
|
2034
|
+
tip.style.maxWidth = "260px";
|
|
2035
|
+
tip.innerHTML = html;
|
|
2036
|
+
document.body.appendChild(tip);
|
|
2037
|
+
iconStripTooltip = tip;
|
|
2038
|
+
|
|
2039
|
+
requestAnimationFrame(function () {
|
|
2040
|
+
var rect = el.getBoundingClientRect();
|
|
2041
|
+
tip.style.top = (rect.top + rect.height / 2 - tip.offsetHeight / 2) + "px";
|
|
2042
|
+
tip.classList.add("visible");
|
|
2043
|
+
});
|
|
2044
|
+
}
|
|
2045
|
+
|
|
1922
2046
|
function hideIconTooltip() {
|
|
1923
2047
|
if (iconStripTooltip) {
|
|
1924
2048
|
iconStripTooltip.remove();
|
|
@@ -3452,7 +3576,13 @@ export function renderUserStrip(allUsers, onlineUserIds, myUserId, dmFavorites,
|
|
|
3452
3576
|
|
|
3453
3577
|
// Tooltip
|
|
3454
3578
|
var displayName = mp.displayName || mate.name || "New Mate";
|
|
3455
|
-
el.addEventListener("mouseenter", function () {
|
|
3579
|
+
el.addEventListener("mouseenter", function () {
|
|
3580
|
+
if (mate.bio) {
|
|
3581
|
+
showIconTooltipHtml(el, '<div style="font-weight:600">' + escapeHtml(displayName) + '</div><div style="font-weight:400;font-size:12px;color:var(--text-secondary);margin-top:2px">' + escapeHtml(mate.bio) + '</div>');
|
|
3582
|
+
} else {
|
|
3583
|
+
showIconTooltip(el, displayName);
|
|
3584
|
+
}
|
|
3585
|
+
});
|
|
3456
3586
|
el.addEventListener("mouseleave", hideIconTooltip);
|
|
3457
3587
|
|
|
3458
3588
|
// Click: open DM with mate
|
|
@@ -3,6 +3,7 @@ import { iconHtml, refreshIcons, randomThinkingVerb } from './icons.js';
|
|
|
3
3
|
import { renderMarkdown, highlightCodeBlocks, renderMermaidBlocks } from './markdown.js';
|
|
4
4
|
import { renderUnifiedDiff, renderSplitDiff, renderPatchDiff } from './diff.js';
|
|
5
5
|
import { openFile } from './filebrowser.js';
|
|
6
|
+
import { mateAvatarUrl } from './avatar.js';
|
|
6
7
|
|
|
7
8
|
var ctx;
|
|
8
9
|
|
|
@@ -419,7 +420,7 @@ function permissionInputSummary(toolName, input) {
|
|
|
419
420
|
}
|
|
420
421
|
}
|
|
421
422
|
|
|
422
|
-
export function renderPermissionRequest(requestId, toolName, toolInput, decisionReason) {
|
|
423
|
+
export function renderPermissionRequest(requestId, toolName, toolInput, decisionReason, mateId) {
|
|
423
424
|
if (pendingPermissions[requestId]) return;
|
|
424
425
|
ctx.finalizeAssistantBlock();
|
|
425
426
|
stopThinking();
|
|
@@ -433,7 +434,7 @@ export function renderPermissionRequest(requestId, toolName, toolInput, decision
|
|
|
433
434
|
|
|
434
435
|
// Mate DM: render as conversational chat bubble instead of formal dialog
|
|
435
436
|
if (ctx.isMateDm && ctx.isMateDm()) {
|
|
436
|
-
renderMatePermission(requestId, toolName, toolInput);
|
|
437
|
+
renderMatePermission(requestId, toolName, toolInput, mateId);
|
|
437
438
|
return;
|
|
438
439
|
}
|
|
439
440
|
|
|
@@ -690,9 +691,18 @@ function matePermissionInfo(toolName, toolInput) {
|
|
|
690
691
|
return { verb: verb, target: target };
|
|
691
692
|
}
|
|
692
693
|
|
|
693
|
-
function renderMatePermission(requestId, toolName, toolInput) {
|
|
694
|
+
function renderMatePermission(requestId, toolName, toolInput, mateId) {
|
|
694
695
|
var mateName = ctx.getMateName();
|
|
695
696
|
var mateAvatar = ctx.getMateAvatarUrl();
|
|
697
|
+
|
|
698
|
+
// If mateId provided (e.g. @mention in DM), use that mate's info instead of DM target
|
|
699
|
+
if (mateId && ctx.getMateById) {
|
|
700
|
+
var mentionMate = ctx.getMateById(mateId);
|
|
701
|
+
if (mentionMate) {
|
|
702
|
+
mateName = (mentionMate.profile && mentionMate.profile.displayName) || mentionMate.displayName || mentionMate.name || mateName;
|
|
703
|
+
mateAvatar = mateAvatarUrl(mentionMate, 36);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
696
706
|
var info = matePermissionInfo(toolName, toolInput);
|
|
697
707
|
var askMsg = "Can I " + info.verb + (info.target ? " " + info.target : "") + "?";
|
|
698
708
|
|
|
@@ -46,6 +46,8 @@ function convertTitles() {
|
|
|
46
46
|
"#top-bar [title]",
|
|
47
47
|
".title-bar-content [title]",
|
|
48
48
|
"#input-area [title]",
|
|
49
|
+
".mate-sidebar-actions [title]",
|
|
50
|
+
"#mate-sidebar-header [title]",
|
|
49
51
|
];
|
|
50
52
|
var els = document.querySelectorAll(selectors.join(", "));
|
|
51
53
|
for (var i = 0; i < els.length; i++) {
|
package/lib/public/style.css
CHANGED
package/lib/scheduler.js
CHANGED
|
@@ -217,6 +217,62 @@ function createLoopRegistry(opts) {
|
|
|
217
217
|
}
|
|
218
218
|
}
|
|
219
219
|
|
|
220
|
+
// Check recurrenceEnd conditions before triggering
|
|
221
|
+
if (rec.recurrenceEnd) {
|
|
222
|
+
var shouldDisable = false;
|
|
223
|
+
if (rec.recurrenceEnd.type === "until" && rec.recurrenceEnd.date) {
|
|
224
|
+
var reParts = rec.recurrenceEnd.date.split("-");
|
|
225
|
+
var endDate = new Date(parseInt(reParts[0], 10), parseInt(reParts[1], 10) - 1, parseInt(reParts[2], 10), 23, 59, 59, 999);
|
|
226
|
+
if (now > endDate.getTime()) {
|
|
227
|
+
shouldDisable = true;
|
|
228
|
+
}
|
|
229
|
+
} else if (rec.recurrenceEnd.type === "after" && rec.recurrenceEnd.count > 0) {
|
|
230
|
+
if ((rec.runs || []).length >= rec.recurrenceEnd.count) {
|
|
231
|
+
shouldDisable = true;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
if (shouldDisable) {
|
|
235
|
+
rec.enabled = false;
|
|
236
|
+
rec.nextRunAt = null;
|
|
237
|
+
save();
|
|
238
|
+
if (onChange) onChange(records);
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Check intervalEnd conditions before triggering
|
|
244
|
+
if (rec.intervalEnd) {
|
|
245
|
+
var skipTrigger = false;
|
|
246
|
+
if (rec.intervalEnd.type === "until" && rec.intervalEnd.time) {
|
|
247
|
+
// Stop at a specific time of day
|
|
248
|
+
var nowDate = new Date(now);
|
|
249
|
+
var ieParts = rec.intervalEnd.time.split(":");
|
|
250
|
+
var stopH = parseInt(ieParts[0], 10);
|
|
251
|
+
var stopM = parseInt(ieParts[1], 10) || 0;
|
|
252
|
+
var nowMinOfDay = nowDate.getHours() * 60 + nowDate.getMinutes();
|
|
253
|
+
if (nowMinOfDay >= stopH * 60 + stopM) {
|
|
254
|
+
skipTrigger = true;
|
|
255
|
+
}
|
|
256
|
+
} else if (rec.intervalEnd.type === "after" && rec.intervalEnd.count > 0) {
|
|
257
|
+
// Stop after N runs today
|
|
258
|
+
var todayStart = new Date(now);
|
|
259
|
+
todayStart.setHours(0, 0, 0, 0);
|
|
260
|
+
var todayStartMs = todayStart.getTime();
|
|
261
|
+
var todayRuns = (rec.runs || []).filter(function (r) { return r.startedAt >= todayStartMs; });
|
|
262
|
+
if (todayRuns.length >= rec.intervalEnd.count) {
|
|
263
|
+
skipTrigger = true;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
if (skipTrigger) {
|
|
267
|
+
// Advance nextRunAt without triggering
|
|
268
|
+
if (rec.cron) {
|
|
269
|
+
rec.nextRunAt = nextRunTime(rec.cron, now);
|
|
270
|
+
}
|
|
271
|
+
save();
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
220
276
|
// Update nextRunAt
|
|
221
277
|
rec.lastRunAt = now;
|
|
222
278
|
if (rec.cron) {
|
|
@@ -247,7 +303,7 @@ function createLoopRegistry(opts) {
|
|
|
247
303
|
task: data.task || "",
|
|
248
304
|
cron: data.cron || null,
|
|
249
305
|
enabled: data.cron ? (data.enabled !== false) : false,
|
|
250
|
-
maxIterations: data.maxIterations
|
|
306
|
+
maxIterations: (data.maxIterations >= 1) ? data.maxIterations : 20,
|
|
251
307
|
createdAt: Date.now(),
|
|
252
308
|
updatedAt: Date.now(),
|
|
253
309
|
lastRunAt: null,
|
|
@@ -265,6 +321,7 @@ function createLoopRegistry(opts) {
|
|
|
265
321
|
mode: data.mode || "loop",
|
|
266
322
|
prompt: data.prompt || null,
|
|
267
323
|
skipIfRunning: data.skipIfRunning !== undefined ? data.skipIfRunning : true,
|
|
324
|
+
intervalEnd: data.intervalEnd || null,
|
|
268
325
|
runs: [],
|
|
269
326
|
};
|
|
270
327
|
if (rec.cron && rec.enabled) {
|
|
@@ -301,6 +358,7 @@ function createLoopRegistry(opts) {
|
|
|
301
358
|
if (data.allDay !== undefined) rec.allDay = data.allDay;
|
|
302
359
|
if (data.linkedTaskId !== undefined) rec.linkedTaskId = data.linkedTaskId;
|
|
303
360
|
if (data.skipIfRunning !== undefined) rec.skipIfRunning = data.skipIfRunning;
|
|
361
|
+
if (data.intervalEnd !== undefined) rec.intervalEnd = data.intervalEnd;
|
|
304
362
|
rec.updatedAt = Date.now();
|
|
305
363
|
if (rec.cron && rec.enabled) {
|
|
306
364
|
rec.nextRunAt = nextRunTime(rec.cron);
|
package/lib/sessions.js
CHANGED
|
@@ -76,7 +76,6 @@ function createSessionManager(opts) {
|
|
|
76
76
|
|
|
77
77
|
function saveSessionFile(session) {
|
|
78
78
|
if (!session.cliSessionId) return;
|
|
79
|
-
session.lastActivity = Date.now();
|
|
80
79
|
try {
|
|
81
80
|
var metaObj = {
|
|
82
81
|
type: "meta",
|
|
@@ -89,6 +88,8 @@ function createSessionManager(opts) {
|
|
|
89
88
|
if (session.sessionVisibility) metaObj.sessionVisibility = session.sessionVisibility;
|
|
90
89
|
if (session.lastRewindUuid) metaObj.lastRewindUuid = session.lastRewindUuid;
|
|
91
90
|
if (session.loop) metaObj.loop = session.loop;
|
|
91
|
+
if (session.debateState) metaObj.debateState = session.debateState;
|
|
92
|
+
if (session.debateSetupMode) metaObj.debateSetupMode = true;
|
|
92
93
|
var meta = JSON.stringify(metaObj);
|
|
93
94
|
var lines = [meta];
|
|
94
95
|
for (var i = 0; i < session.history.length; i++) {
|
|
@@ -174,6 +175,8 @@ function createSessionManager(opts) {
|
|
|
174
175
|
lastRewindUuid: m.lastRewindUuid || null,
|
|
175
176
|
};
|
|
176
177
|
if (m.loop) session.loop = m.loop;
|
|
178
|
+
if (m.debateState) session.debateState = m.debateState;
|
|
179
|
+
if (m.debateSetupMode) session.debateSetupMode = true;
|
|
177
180
|
if (m.ownerId) session.ownerId = m.ownerId;
|
|
178
181
|
session.sessionVisibility = m.sessionVisibility || "shared";
|
|
179
182
|
sessions.set(localId, session);
|
|
@@ -329,7 +332,11 @@ function createSessionManager(opts) {
|
|
|
329
332
|
_send({ type: "history_meta", total: total, from: fromIndex });
|
|
330
333
|
|
|
331
334
|
for (var i = fromIndex; i < total; i++) {
|
|
332
|
-
|
|
335
|
+
var _item = session.history[i];
|
|
336
|
+
if (_item && (_item.type === "mention_user" || _item.type === "mention_response")) {
|
|
337
|
+
console.log("[DEBUG replayHistory] sending mention at index=" + i + " from=" + fromIndex + " total=" + total + " type=" + _item.type + " mate=" + (_item.mateName || ""));
|
|
338
|
+
}
|
|
339
|
+
_send(transform ? transform(_item) : _item);
|
|
333
340
|
}
|
|
334
341
|
|
|
335
342
|
// Find the last result message in the full history for accurate context data
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
var fs = require("fs");
|
|
2
|
+
var path = require("path");
|
|
3
|
+
var config = require("./config");
|
|
4
|
+
|
|
5
|
+
// In-memory store: { slug: { userId: { sessionId, mateDm } } }
|
|
6
|
+
var store = {};
|
|
7
|
+
var presencePath = path.join(config.CONFIG_DIR, "user-presence.json");
|
|
8
|
+
var saveTimer = null;
|
|
9
|
+
|
|
10
|
+
// Load from disk on startup
|
|
11
|
+
function load() {
|
|
12
|
+
try {
|
|
13
|
+
if (fs.existsSync(presencePath)) {
|
|
14
|
+
var raw = fs.readFileSync(presencePath, "utf8");
|
|
15
|
+
store = JSON.parse(raw);
|
|
16
|
+
}
|
|
17
|
+
} catch (e) {
|
|
18
|
+
store = {};
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Debounced save to disk (200ms)
|
|
23
|
+
function scheduleSave() {
|
|
24
|
+
if (saveTimer) return;
|
|
25
|
+
saveTimer = setTimeout(function () {
|
|
26
|
+
saveTimer = null;
|
|
27
|
+
try {
|
|
28
|
+
config.ensureConfigDir();
|
|
29
|
+
fs.writeFileSync(presencePath, JSON.stringify(store, null, 2), "utf8");
|
|
30
|
+
} catch (e) {
|
|
31
|
+
// Silently ignore write errors
|
|
32
|
+
}
|
|
33
|
+
}, 200);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function setPresence(slug, userId, sessionId, mateDm) {
|
|
37
|
+
if (!slug || !userId) return;
|
|
38
|
+
if (!store[slug]) store[slug] = {};
|
|
39
|
+
store[slug][userId] = {
|
|
40
|
+
sessionId: sessionId || null,
|
|
41
|
+
mateDm: mateDm !== undefined ? mateDm : null,
|
|
42
|
+
};
|
|
43
|
+
scheduleSave();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function getPresence(slug, userId) {
|
|
47
|
+
if (!slug || !userId) return null;
|
|
48
|
+
if (!store[slug]) return null;
|
|
49
|
+
return store[slug][userId] || null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function setMateDm(slug, userId, mateDm) {
|
|
53
|
+
if (!slug || !userId) return;
|
|
54
|
+
if (!store[slug]) store[slug] = {};
|
|
55
|
+
var existing = store[slug][userId] || {};
|
|
56
|
+
store[slug][userId] = {
|
|
57
|
+
sessionId: existing.sessionId || null,
|
|
58
|
+
mateDm: mateDm !== undefined ? mateDm : null,
|
|
59
|
+
};
|
|
60
|
+
scheduleSave();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function clearPresence(slug, userId) {
|
|
64
|
+
if (!slug || !userId) return;
|
|
65
|
+
if (store[slug]) {
|
|
66
|
+
delete store[slug][userId];
|
|
67
|
+
if (Object.keys(store[slug]).length === 0) delete store[slug];
|
|
68
|
+
scheduleSave();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Remove all presence entries referencing a deleted session
|
|
73
|
+
function clearSession(slug, sessionId) {
|
|
74
|
+
if (!slug || !store[slug]) return;
|
|
75
|
+
var keys = Object.keys(store[slug]);
|
|
76
|
+
for (var i = 0; i < keys.length; i++) {
|
|
77
|
+
if (store[slug][keys[i]].sessionId === sessionId) {
|
|
78
|
+
store[slug][keys[i]].sessionId = null;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
scheduleSave();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
load();
|
|
85
|
+
|
|
86
|
+
module.exports = {
|
|
87
|
+
setPresence: setPresence,
|
|
88
|
+
getPresence: getPresence,
|
|
89
|
+
setMateDm: setMateDm,
|
|
90
|
+
clearPresence: clearPresence,
|
|
91
|
+
clearSession: clearSession,
|
|
92
|
+
};
|