clay-server 4.1.0-beta.9 → 4.2.0-beta.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/lib/context-usage.js +47 -0
- package/lib/daemon-project-access.js +14 -0
- package/lib/daemon.js +7 -0
- package/lib/issues-mcp-server.js +1 -1
- package/lib/issues-service.js +3 -1
- package/lib/loop-registry-occurrence.js +1 -0
- package/lib/notes.js +20 -3
- package/lib/os-users.js +6 -0
- package/lib/project-connection.js +14 -3
- package/lib/project-file-path.js +13 -0
- package/lib/project-file-watch.js +19 -8
- package/lib/project-filesystem.js +10 -16
- package/lib/project-http.js +53 -48
- package/lib/project-issue-launch.js +3 -1
- package/lib/project-logs-mcp-server.js +8 -8
- package/lib/project-logs-schema.js +1 -1
- package/lib/project-logs-service.js +46 -13
- package/lib/project-logs-store.js +17 -15
- package/lib/project-logs.js +70 -0
- package/lib/project-loop.js +1 -0
- package/lib/project-notifications.js +1 -1
- package/lib/project-scheduled-result-store.js +131 -0
- package/lib/project-scheduled-task-execution.js +35 -2
- package/lib/project-session-notes.js +11 -4
- package/lib/project-sessions.js +39 -6
- package/lib/project-user-message.js +47 -0
- package/lib/project.js +57 -3
- package/lib/public/app.js +27 -9
- package/lib/public/css/avatar-imprints.css +1 -1
- package/lib/public/css/command-palette.css +57 -19
- package/lib/public/css/git-panel.css +13 -12
- package/lib/public/css/git-placard.css +4 -4
- package/lib/public/css/issues.css +79 -32
- package/lib/public/css/mobile-nav.css +7 -0
- package/lib/public/css/notifications-center.css +3 -3
- package/lib/public/css/project-logs.css +15 -0
- package/lib/public/css/sidebar.css +7 -0
- package/lib/public/css/sticky-notes.css +29 -0
- package/lib/public/index.html +10 -0
- package/lib/public/modules/app-connection.js +9 -1
- package/lib/public/modules/app-favicon.js +10 -0
- package/lib/public/modules/app-messages.js +50 -12
- package/lib/public/modules/app-notifications.js +2 -0
- package/lib/public/modules/app-panels.js +77 -23
- package/lib/public/modules/app-projects.js +2 -0
- package/lib/public/modules/app-rate-limit.js +148 -61
- package/lib/public/modules/avatar-imprint.js +25 -70
- package/lib/public/modules/avatar.js +12 -4
- package/lib/public/modules/chat-render-runtime.js +20 -0
- package/lib/public/modules/clay-issue-links.js +24 -2
- package/lib/public/modules/clay-log-links.js +12 -1
- package/lib/public/modules/context-view-preference.js +143 -0
- package/lib/public/modules/default-vendor.js +116 -0
- package/lib/public/modules/filebrowser-tabs.js +1 -0
- package/lib/public/modules/filebrowser.js +45 -18
- package/lib/public/modules/issues-render.js +36 -10
- package/lib/public/modules/issues.js +95 -13
- package/lib/public/modules/markdown.js +3 -1
- package/lib/public/modules/pane-bridge.js +14 -8
- package/lib/public/modules/pane-issue-bridge.js +27 -0
- package/lib/public/modules/permission-hints.js +13 -0
- package/lib/public/modules/profile.js +9 -9
- package/lib/public/modules/project-activation.js +49 -0
- package/lib/public/modules/project-logs-render.js +13 -3
- package/lib/public/modules/project-logs.js +45 -15
- package/lib/public/modules/project-session-visibility-settings.js +138 -0
- package/lib/public/modules/project-settings.js +5 -0
- package/lib/public/modules/right-workbench.js +44 -0
- package/lib/public/modules/scheduled-result-client.js +33 -0
- package/lib/public/modules/scheduled-result-display.js +50 -0
- package/lib/public/modules/scheduled-result-notification.js +31 -0
- package/lib/public/modules/scheduled-task-presentation.js +15 -0
- package/lib/public/modules/scheduled-tasks.js +9 -21
- package/lib/public/modules/sidebar-mobile.js +51 -3
- package/lib/public/modules/sidebar-projects.js +14 -3
- package/lib/public/modules/sidebar-sessions.js +65 -5
- package/lib/public/modules/split-session-boundary.js +28 -0
- package/lib/public/modules/split-view.js +42 -7
- package/lib/public/modules/sticky-notes-browser.js +130 -18
- package/lib/public/modules/sticky-notes.js +2 -3
- package/lib/public/modules/terminal.js +4 -4
- package/lib/public/modules/thinking-lifecycle.js +4 -1
- package/lib/public/modules/tools.js +5 -1
- package/lib/scheduled-run-state.js +10 -2
- package/lib/scheduler.js +1 -0
- package/lib/sdk-bridge.js +97 -61
- package/lib/sdk-message-processor.js +44 -13
- package/lib/sdk-skill-discovery.js +6 -28
- package/lib/sdk-turn-queue.js +14 -0
- package/lib/server-context-view.js +53 -0
- package/lib/server-default-vendor.js +60 -0
- package/lib/server.js +31 -7
- package/lib/session-notes-mcp-server.js +3 -5
- package/lib/session-pair-factory.js +2 -2
- package/lib/session-title-generator.js +89 -0
- package/lib/session-title-policy.js +8 -2
- package/lib/session-visibility.js +62 -1
- package/lib/sessions.js +28 -1
- package/lib/user-presence.js +6 -0
- package/lib/users-context-view-preferences.js +65 -0
- package/lib/users-default-vendor-preferences.js +80 -0
- package/lib/users.js +12 -0
- package/lib/ws-schema.js +13 -0
- package/lib/yoke/acp-agent-profiles.js +15 -2
- package/lib/yoke/adapters/acp.js +3 -2
- package/lib/yoke/adapters/antigravity.js +2 -2
- package/lib/yoke/adapters/claude-worker.js +11 -12
- package/lib/yoke/adapters/claude.js +75 -22
- package/lib/yoke/adapters/codex.js +216 -29
- package/lib/yoke/adapters/kiro.js +6 -5
- package/lib/yoke/claude-context-usage.js +57 -0
- package/lib/yoke/claude-message-correlation.js +31 -0
- package/lib/yoke/claude-model-switch.js +15 -0
- package/lib/yoke/index.js +5 -1
- package/lib/yoke/mcp-bridge-server.js +18 -5
- package/lib/yoke/session-skill-catalog.js +45 -0
- package/lib/yoke/skill-discovery.js +60 -20
- package/lib/yoke/vendor-registry.js +1 -1
- package/package.json +5 -7
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { store } from './store.js';
|
|
2
|
+
import { getWs } from './ws-ref.js';
|
|
3
|
+
import { showToast } from './utils.js';
|
|
4
|
+
|
|
5
|
+
var requestSequence = 0;
|
|
6
|
+
var initialized = false;
|
|
7
|
+
|
|
8
|
+
function selectElement() {
|
|
9
|
+
return document.getElementById("ps-session-visibility-default");
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function pendingState() {
|
|
13
|
+
return (store.get('projectSessionVisibilityState') || {}).pending || null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function activeState() {
|
|
17
|
+
return (store.get('projectSessionVisibilityState') || {}).active || null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function setVisibilityState(active, pending) {
|
|
21
|
+
store.set({ projectSessionVisibilityState: { active: active || null, pending: pending || null } });
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function setSelectState(select, value, disabled) {
|
|
25
|
+
if (!select) return;
|
|
26
|
+
select.value = value;
|
|
27
|
+
select.disabled = !!disabled;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function isCurrentProject(active) {
|
|
31
|
+
return !!(active && active.slug && active.slug === store.get('currentSlug'));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function isReadOnly(active) {
|
|
35
|
+
return !active || !active.authorized || active.isWorktree || !isCurrentProject(active);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function renderActiveState() {
|
|
39
|
+
var active = activeState();
|
|
40
|
+
var pending = pendingState();
|
|
41
|
+
var select = selectElement();
|
|
42
|
+
var inheritedHint = document.getElementById("ps-session-visibility-default-inherited-hint");
|
|
43
|
+
var nonCurrentHint = document.getElementById("ps-session-visibility-default-non-current-hint");
|
|
44
|
+
if (inheritedHint) inheritedHint.classList.toggle("hidden", !(active && active.isWorktree));
|
|
45
|
+
if (nonCurrentHint) nonCurrentHint.classList.toggle("hidden", !(active && active.authorized && !active.isWorktree && !isCurrentProject(active)));
|
|
46
|
+
if (!select || !active) return;
|
|
47
|
+
setSelectState(select, active.effectiveValue, isReadOnly(active) || !!(pending && pending.slug === active.slug));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function isAuthorized(project) {
|
|
51
|
+
var users = store.get('cachedAllUsers') || [];
|
|
52
|
+
var userId = store.get('myUserId');
|
|
53
|
+
var currentUser = null;
|
|
54
|
+
for (var i = 0; i < users.length; i++) {
|
|
55
|
+
if (users[i].id === userId) { currentUser = users[i]; break; }
|
|
56
|
+
}
|
|
57
|
+
return !!(store.get('isMultiUserMode') && project &&
|
|
58
|
+
((project.projectOwnerId && project.projectOwnerId === userId) || (currentUser && currentUser.role === "admin")) &&
|
|
59
|
+
(!store.get('permissions') || store.get('permissions').projectSettings !== false));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function initProjectSessionVisibilitySettings() {
|
|
63
|
+
var select = selectElement();
|
|
64
|
+
if (!select || initialized) return;
|
|
65
|
+
initialized = true;
|
|
66
|
+
select.addEventListener("change", function () {
|
|
67
|
+
var ws = getWs();
|
|
68
|
+
var active = activeState();
|
|
69
|
+
var slug = active && active.slug || "";
|
|
70
|
+
var prior = active && active.effectiveValue || "private";
|
|
71
|
+
if (!slug || !ws || ws.readyState !== 1 || pendingState() || isReadOnly(active)) {
|
|
72
|
+
setSelectState(select, prior, isReadOnly(active) || !!pendingState());
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
requestSequence += 1;
|
|
76
|
+
var requestId = "project-session-visibility-" + Date.now() + "-" + requestSequence;
|
|
77
|
+
setVisibilityState(active, { requestId: requestId, slug: slug, requested: select.value, previous: prior });
|
|
78
|
+
setSelectState(select, select.value, true);
|
|
79
|
+
ws.send(JSON.stringify({ type: "set_project_session_visibility_default", slug: slug, visibility: select.value, requestId: requestId }));
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
store.subscribe(function (state, previous) {
|
|
83
|
+
if (state.currentSlug !== previous.currentSlug) renderActiveState();
|
|
84
|
+
if (state.connected || previous.connected !== true) return;
|
|
85
|
+
var pending = pendingState();
|
|
86
|
+
if (!pending) return;
|
|
87
|
+
var current = selectElement();
|
|
88
|
+
var active = activeState();
|
|
89
|
+
if (current && active && active.slug === pending.slug) setSelectState(current, pending.previous, isReadOnly(active));
|
|
90
|
+
setVisibilityState(active, null);
|
|
91
|
+
renderActiveState();
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function populateProjectSessionVisibilitySettings(slug, project) {
|
|
96
|
+
var field = document.getElementById("ps-session-visibility-default-field");
|
|
97
|
+
var select = selectElement();
|
|
98
|
+
if (field) field.style.display = isAuthorized(project) ? "" : "none";
|
|
99
|
+
if (!select) return;
|
|
100
|
+
var value = project && project.sessionVisibilityDefault === "shared" ? "shared" : "private";
|
|
101
|
+
var active = { slug: slug || "", effectiveValue: value, isWorktree: !!(project && project.isWorktree), authorized: isAuthorized(project) };
|
|
102
|
+
var pending = pendingState();
|
|
103
|
+
setVisibilityState(active, pending);
|
|
104
|
+
renderActiveState();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function refreshProjectSessionVisibilitySettings(projects) {
|
|
108
|
+
var select = selectElement();
|
|
109
|
+
if (!select) return;
|
|
110
|
+
var active = activeState();
|
|
111
|
+
var slug = active && active.slug || "";
|
|
112
|
+
var source = Array.isArray(projects) ? projects : [];
|
|
113
|
+
for (var i = 0; i < source.length; i++) {
|
|
114
|
+
if (source[i] && source[i].slug === slug) {
|
|
115
|
+
populateProjectSessionVisibilitySettings(slug, source[i]);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function handleProjectSessionVisibilityDefault(msg) {
|
|
122
|
+
var pending = pendingState();
|
|
123
|
+
if (!pending || msg.requestId !== pending.requestId || msg.slug !== pending.slug) return;
|
|
124
|
+
var active = activeState();
|
|
125
|
+
setVisibilityState(active, null);
|
|
126
|
+
var select = selectElement();
|
|
127
|
+
if (!select || !active || active.slug !== msg.slug) return;
|
|
128
|
+
if (!msg.ok) {
|
|
129
|
+
active = Object.assign({}, active, { effectiveValue: pending.previous });
|
|
130
|
+
setVisibilityState(active, null);
|
|
131
|
+
setSelectState(select, pending.previous, isReadOnly(active));
|
|
132
|
+
showToast(msg.error || "Failed to update new session visibility", "error");
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
active = Object.assign({}, active, { effectiveValue: msg.visibility === "shared" ? "shared" : "private" });
|
|
136
|
+
setVisibilityState(active, null);
|
|
137
|
+
setSelectState(select, active.effectiveValue, isReadOnly(active));
|
|
138
|
+
}
|
|
@@ -4,6 +4,7 @@ import { showToast } from './utils.js';
|
|
|
4
4
|
import { parseEmojis } from './markdown.js';
|
|
5
5
|
import { closeFileViewer } from './filebrowser.js';
|
|
6
6
|
import { renderModelList, renderModeList, renderEffortBar, renderThinkingBar, renderBetaCard } from './settings-defaults.js';
|
|
7
|
+
import { initProjectSessionVisibilitySettings, populateProjectSessionVisibilitySettings } from './project-session-visibility-settings.js';
|
|
7
8
|
|
|
8
9
|
var ctx = null;
|
|
9
10
|
var panelEl = null;
|
|
@@ -162,6 +163,8 @@ export function initProjectSettings(appCtx, emojiCategories) {
|
|
|
162
163
|
hideTransferForm();
|
|
163
164
|
});
|
|
164
165
|
}
|
|
166
|
+
|
|
167
|
+
initProjectSessionVisibilitySettings();
|
|
165
168
|
}
|
|
166
169
|
|
|
167
170
|
// ===== Open / Close =====
|
|
@@ -246,6 +249,8 @@ function populateProfile() {
|
|
|
246
249
|
// Icon
|
|
247
250
|
updateIconPreview(currentProject ? currentProject.icon : null);
|
|
248
251
|
|
|
252
|
+
populateProjectSessionVisibilitySettings(currentSlug, currentProject);
|
|
253
|
+
|
|
249
254
|
// Owner (only in multi-user mode)
|
|
250
255
|
var ownerField = document.getElementById("ps-owner-field");
|
|
251
256
|
if (ownerField) {
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// Single arbitration point for the mutually exclusive right-side workbench.
|
|
2
|
+
|
|
3
|
+
import { store } from './store.js';
|
|
4
|
+
|
|
5
|
+
var closers = {};
|
|
6
|
+
|
|
7
|
+
export function registerRightWorkbench(name, close) {
|
|
8
|
+
if (!name || typeof close !== "function") return;
|
|
9
|
+
closers[name] = close;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function claimRightWorkbench(name) {
|
|
13
|
+
var previousRevision = store.get("rightWorkbenchRevision") || 0;
|
|
14
|
+
var nextRevision = previousRevision + 1;
|
|
15
|
+
storeSet(name, nextRevision);
|
|
16
|
+
var keys = Object.keys(closers);
|
|
17
|
+
for (var i = 0; i < keys.length; i++) {
|
|
18
|
+
if (keys[i] === name) continue;
|
|
19
|
+
try { closers[keys[i]](); } catch (error) { /* one panel must not block another */ }
|
|
20
|
+
}
|
|
21
|
+
return nextRevision;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function storeSet(name, value) {
|
|
25
|
+
store.set({ rightWorkbenchOwner: name, rightWorkbenchRevision: value });
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function releaseRightWorkbench(name) {
|
|
29
|
+
if (store.get("rightWorkbenchOwner") !== name) return;
|
|
30
|
+
storeSet(null, (store.get("rightWorkbenchRevision") || 0) + 1);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function isRightWorkbenchCurrent(name, expectedRevision) {
|
|
34
|
+
return store.get("rightWorkbenchOwner") === name && store.get("rightWorkbenchRevision") === expectedRevision;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function resetRightWorkbench() {
|
|
38
|
+
storeSet(null, (store.get("rightWorkbenchRevision") || 0) + 1);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
store.subscribe(function (state, previous) {
|
|
42
|
+
if (state.currentSlug === previous.currentSlug && state.myUserId === previous.myUserId) return;
|
|
43
|
+
resetRightWorkbench();
|
|
44
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// Pure Scheduled Tasks result-news decisions. The production controller owns
|
|
2
|
+
// store, WebSocket, DOM, and peer-module calls directly.
|
|
3
|
+
|
|
4
|
+
export function scheduledResultStateTransition(state, msg) {
|
|
5
|
+
var patch = { scheduledResultUnread: Number(msg.unreadCount) || 0 };
|
|
6
|
+
var openRef = null;
|
|
7
|
+
if (msg.autoFocus && msg.newestUnreadRef && !state.scheduledResultAutoFocusHandled) {
|
|
8
|
+
patch.scheduledResultAutoFocusHandled = true;
|
|
9
|
+
patch.scheduledResultFocusRef = msg.newestUnreadRef;
|
|
10
|
+
openRef = msg.newestUnreadRef;
|
|
11
|
+
}
|
|
12
|
+
return { patch: patch, openRef: openRef };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function scheduledResultCreatedTransition(state, msg) {
|
|
16
|
+
if (!msg || !msg.ref || (state.scheduledResultSeenRefs || {})[msg.ref]) return null;
|
|
17
|
+
var seen = Object.assign({}, state.scheduledResultSeenRefs || {});
|
|
18
|
+
seen[msg.ref] = true;
|
|
19
|
+
return { scheduledResultSeenRefs: seen, scheduledResultUnread: (state.scheduledResultUnread || 0) + 1 };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function shouldAcknowledgeScheduledResult(entry, state, visibilityState) {
|
|
23
|
+
return !!(entry && entry.scheduledResult && visibilityState !== "hidden" && state.projectLogsOpen && state.projectLogsView === "detail" && state.projectLogsSelectedRef === entry.ref);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function shouldFocusScheduledResult(entry, state) {
|
|
27
|
+
return !!(entry && state.scheduledResultFocusRef === entry.ref);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function scheduledResultSessionId(state, msg) {
|
|
31
|
+
if (msg.requestId && msg.requestId !== state.scheduledResultSessionRequestId) return null;
|
|
32
|
+
return typeof msg.sessionId === "number" ? msg.sessionId : null;
|
|
33
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { store } from './store.js';
|
|
2
|
+
import { getWs } from './ws-ref.js';
|
|
3
|
+
import { showToast } from './utils.js';
|
|
4
|
+
import { shouldAcknowledgeScheduledResult, shouldFocusScheduledResult } from './scheduled-result-client.js';
|
|
5
|
+
|
|
6
|
+
function sendRead(ref) {
|
|
7
|
+
var ws = getWs();
|
|
8
|
+
if (!ws || ws.readyState !== 1) return false;
|
|
9
|
+
ws.send(JSON.stringify({ type: "scheduled_task_result_mark_read", ref: ref }));
|
|
10
|
+
return true;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
var openRequestCounter = 0;
|
|
14
|
+
export function openScheduledResultSession(entry) {
|
|
15
|
+
if (!entry || !entry.scheduledResult) return false;
|
|
16
|
+
openRequestCounter += 1;
|
|
17
|
+
var requestId = "open-scheduled-session-" + Date.now() + "-" + openRequestCounter;
|
|
18
|
+
store.set({ scheduledResultSessionRequestId: requestId });
|
|
19
|
+
var ws = getWs();
|
|
20
|
+
if (!ws || ws.readyState !== 1) { showToast("Reconnect to view this scheduled session.", "warn"); return false; }
|
|
21
|
+
ws.send(JSON.stringify({ type: "scheduled_task_result_open_session", requestId: requestId, ref: entry.ref })); return true;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function acknowledgeVisible() {
|
|
25
|
+
var ref = store.get('scheduledResultPendingReadRef');
|
|
26
|
+
if (!ref || document.visibilityState === "hidden") return;
|
|
27
|
+
store.set({ scheduledResultPendingReadRef: null });
|
|
28
|
+
var state = store.snap();
|
|
29
|
+
if (state.projectLogsOpen && state.projectLogsView === "detail" && state.projectLogsSelectedRef === ref) sendRead(ref);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function clearPendingScheduledResultRead() {
|
|
33
|
+
store.set({ scheduledResultPendingReadRef: null });
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function handleScheduledResultDisplayed(entry) {
|
|
37
|
+
var state = store.snap();
|
|
38
|
+
if (shouldFocusScheduledResult(entry, state)) {
|
|
39
|
+
store.set({ scheduledResultFocusRef: null });
|
|
40
|
+
var heading = document.querySelector("#project-logs-detail .project-log-document h1");
|
|
41
|
+
if (heading) { heading.setAttribute("tabindex", "-1"); heading.focus(); }
|
|
42
|
+
}
|
|
43
|
+
if (shouldAcknowledgeScheduledResult(entry, store.snap(), document.visibilityState)) {
|
|
44
|
+
clearPendingScheduledResultRead(); sendRead(entry.ref);
|
|
45
|
+
} else if (entry && entry.scheduledResult && document.visibilityState === "hidden") store.set({ scheduledResultPendingReadRef: entry.ref });
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function initScheduledResultDisplay() {
|
|
49
|
+
document.addEventListener("visibilitychange", acknowledgeVisible);
|
|
50
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { refreshIcons, iconHtml } from './icons.js';
|
|
2
|
+
import { escapeHtml } from './utils.js';
|
|
3
|
+
|
|
4
|
+
function removeBanner(banner) {
|
|
5
|
+
if (!banner || !banner.parentNode) return;
|
|
6
|
+
banner.classList.remove("show");
|
|
7
|
+
setTimeout(function () { if (banner.parentNode) banner.remove(); }, 220);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function showScheduledResultNotification(title, body, onOpen) {
|
|
11
|
+
var container = document.querySelector(".notif-banner-container");
|
|
12
|
+
if (!container) return false;
|
|
13
|
+
var banner = document.createElement("div");
|
|
14
|
+
banner.className = "notif-banner";
|
|
15
|
+
banner.innerHTML =
|
|
16
|
+
'<div class="notif-banner-icon">' + iconHtml("calendar-check") + '</div>' +
|
|
17
|
+
'<div class="notif-banner-body"><div class="notif-banner-project">SCHEDULED TASKS</div>' +
|
|
18
|
+
'<div class="notif-banner-title">' + escapeHtml(title || "Scheduled task finished") + '</div>' +
|
|
19
|
+
(body ? '<div class="notif-banner-text">' + escapeHtml(body) + '</div>' : '') +
|
|
20
|
+
'<div class="notif-banner-actions"><button class="notif-banner-result-open">View result</button></div></div>' +
|
|
21
|
+
'<button class="notif-banner-close" aria-label="Dismiss notification">' + iconHtml("x") + '</button>';
|
|
22
|
+
container.appendChild(banner);
|
|
23
|
+
refreshIcons(banner);
|
|
24
|
+
requestAnimationFrame(function () { banner.classList.add("show"); });
|
|
25
|
+
banner.querySelector(".notif-banner-result-open").addEventListener("click", function (event) {
|
|
26
|
+
event.stopPropagation(); removeBanner(banner); if (typeof onOpen === "function") onOpen();
|
|
27
|
+
});
|
|
28
|
+
banner.querySelector(".notif-banner-close").addEventListener("click", function (event) { event.stopPropagation(); removeBanner(banner); });
|
|
29
|
+
setTimeout(function () { removeBanner(banner); }, 8000);
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export function scheduledTaskExecutionLabel(record) {
|
|
2
|
+
if (record.activeRun) {
|
|
3
|
+
if (record.activeRun.status === "needs-input") return "Needs input";
|
|
4
|
+
if (record.activeRun.status === "waiting-worker") return "Waiting for Worker";
|
|
5
|
+
if (record.activeRun.status === "reviewing") return "Driver reviewing";
|
|
6
|
+
return "Running";
|
|
7
|
+
}
|
|
8
|
+
if (!record.execution) return "Needs setup";
|
|
9
|
+
if (record.readiness && record.readiness.ok === false) return "Unavailable";
|
|
10
|
+
return record.enabled === false ? "Paused" : "Active";
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function scheduledTaskOpenRunLabel(record) {
|
|
14
|
+
return record.activeRun && record.activeRun.status === "needs-input" ? "Open blocked run" : "Open run";
|
|
15
|
+
}
|
|
@@ -2,16 +2,14 @@ import { store } from './store.js';
|
|
|
2
2
|
import { getWs } from './ws-ref.js';
|
|
3
3
|
import { refreshIcons, iconHtml } from './icons.js';
|
|
4
4
|
import { showToast } from './utils.js';
|
|
5
|
-
import {
|
|
6
|
-
import { closeNotesBrowser } from './sticky-notes-browser.js';
|
|
7
|
-
import { closeFileViewer } from './filebrowser.js';
|
|
8
|
-
import { closeTerminal } from './terminal.js';
|
|
5
|
+
import { claimRightWorkbench, registerRightWorkbench, releaseRightWorkbench } from './right-workbench.js';
|
|
9
6
|
import { humanRecurrence } from './scheduled-task-recurrence.js';
|
|
10
7
|
import { humanScheduleRule } from './scheduled-task-schedule-rule.js';
|
|
11
8
|
import { readExecutionRuntime, renderExecutionRuntime } from './scheduled-task-engine.js';
|
|
12
9
|
import { openDefaultAi } from './default-ai.js';
|
|
13
10
|
import { reconcileScheduledTaskEdit } from './scheduled-task-edit-state.js';
|
|
14
11
|
import { editorFields, fillEditor, readEditor, serverEditorData, validateEditor, handleEditorScheduleChange } from './scheduled-task-form.js';
|
|
12
|
+
import { scheduledTaskExecutionLabel, scheduledTaskOpenRunLabel } from './scheduled-task-presentation.js';
|
|
15
13
|
|
|
16
14
|
export { isStrictCron, humanRecurrence } from './scheduled-task-recurrence.js';
|
|
17
15
|
|
|
@@ -56,18 +54,6 @@ function scheduleLabel(record) {
|
|
|
56
54
|
return "No schedule";
|
|
57
55
|
}
|
|
58
56
|
|
|
59
|
-
function executionLabel(record) {
|
|
60
|
-
if (record.activeRun) {
|
|
61
|
-
if (record.activeRun.status === "needs-input") return "Needs input";
|
|
62
|
-
if (record.activeRun.status === "waiting-worker") return "Waiting for Worker";
|
|
63
|
-
if (record.activeRun.status === "reviewing") return "Driver reviewing";
|
|
64
|
-
return "Running";
|
|
65
|
-
}
|
|
66
|
-
if (!record.execution) return "Needs setup";
|
|
67
|
-
if (record.readiness && record.readiness.ok === false) return "Unavailable";
|
|
68
|
-
return record.enabled === false ? "Paused" : "Active";
|
|
69
|
-
}
|
|
70
|
-
|
|
71
57
|
function ensurePanel() {
|
|
72
58
|
if (panel) return;
|
|
73
59
|
var host = document.getElementById("main-panels");
|
|
@@ -185,7 +171,7 @@ function buildRow(record) {
|
|
|
185
171
|
row.className = "scheduled-task-row" + (record.enabled === false ? " paused" : "");
|
|
186
172
|
row.dataset.taskId = record.id;
|
|
187
173
|
row.setAttribute("role", "listitem");
|
|
188
|
-
var stateLabel =
|
|
174
|
+
var stateLabel = scheduledTaskExecutionLabel(record);
|
|
189
175
|
row.innerHTML =
|
|
190
176
|
'<button class="scheduled-task-summary" type="button" data-action="expand" aria-expanded="' + (expandedId === record.id ? "true" : "false") + '">' +
|
|
191
177
|
'<span class="scheduled-task-state-dot" aria-hidden="true"></span>' +
|
|
@@ -212,12 +198,12 @@ function buildRow(record) {
|
|
|
212
198
|
detail.appendChild(instructions);
|
|
213
199
|
var context = document.createElement("div");
|
|
214
200
|
context.className = "scheduled-task-context";
|
|
215
|
-
var executionState =
|
|
201
|
+
var executionState = scheduledTaskExecutionLabel(record);
|
|
216
202
|
context.textContent = (record.projectTitle || store.get('currentSlug') || "Project") + " · " + executionState + (record.execution ? " · Driver and Split Worker" : " · Choose execution pair in Edit");
|
|
217
203
|
detail.appendChild(context);
|
|
218
204
|
var actions = document.createElement("div");
|
|
219
205
|
actions.className = "scheduled-task-actions";
|
|
220
|
-
actions.innerHTML = '<button type="button" data-action="edit">' + iconHtml("pencil") + 'Edit</button><button type="button" data-action="toggle">' + iconHtml(record.enabled === false ? "play" : "pause") + (record.enabled === false ? "Resume" : "Pause") + '</button><button type="button" data-action="run">' + iconHtml("zap") + 'Run now</button>' + (record.activeRun || record.latestRun ? '<button type="button" data-action="open-run">' + iconHtml("columns-2") + '
|
|
206
|
+
actions.innerHTML = '<button type="button" data-action="edit">' + iconHtml("pencil") + 'Edit</button><button type="button" data-action="toggle">' + iconHtml(record.enabled === false ? "play" : "pause") + (record.enabled === false ? "Resume" : "Pause") + '</button><button type="button" data-action="run">' + iconHtml("zap") + 'Run now</button>' + (record.activeRun || record.latestRun ? '<button type="button" data-action="open-run">' + iconHtml("columns-2") + scheduledTaskOpenRunLabel(record) + '</button>' : '') + '<button type="button" data-action="delete" class="danger">' + iconHtml("trash-2") + 'Delete</button>';
|
|
221
207
|
detail.appendChild(actions);
|
|
222
208
|
}
|
|
223
209
|
row.appendChild(detail);
|
|
@@ -271,7 +257,7 @@ function handleClick(event) {
|
|
|
271
257
|
var button = event.target.closest("button[data-action]");
|
|
272
258
|
if (!button) return;
|
|
273
259
|
var action = button.dataset.action;
|
|
274
|
-
if (action === "default-ai") { openDefaultAi(); return; }
|
|
260
|
+
if (action === "default-ai") { event.stopPropagation(); openDefaultAi(); return; }
|
|
275
261
|
if (action === "close") { closeScheduledTasks(); return; }
|
|
276
262
|
if (action === "wide") { applyWindowState(!store.get('scheduledTasksWide'), store.get('scheduledTasksFullscreen')); return; }
|
|
277
263
|
if (action === "fullscreen") { applyWindowState(store.get('scheduledTasksWide'), !store.get('scheduledTasksFullscreen')); return; }
|
|
@@ -331,7 +317,7 @@ export function openScheduledTasks() {
|
|
|
331
317
|
if (!canOpen()) { closeScheduledTasks(); return; }
|
|
332
318
|
ensurePanel();
|
|
333
319
|
if (!panel) return;
|
|
334
|
-
|
|
320
|
+
claimRightWorkbench("scheduled-tasks");
|
|
335
321
|
panel.classList.remove("hidden");
|
|
336
322
|
applyWindowState(store.get('scheduledTasksWide'), false);
|
|
337
323
|
store.set({ scheduledTasksOpen: true });
|
|
@@ -341,6 +327,7 @@ export function openScheduledTasks() {
|
|
|
341
327
|
}
|
|
342
328
|
|
|
343
329
|
export function closeScheduledTasks() {
|
|
330
|
+
releaseRightWorkbench("scheduled-tasks");
|
|
344
331
|
if (panel) panel.classList.add("hidden");
|
|
345
332
|
setCreateMenu(false);
|
|
346
333
|
applyWindowState(store.get('scheduledTasksWide'), false);
|
|
@@ -441,6 +428,7 @@ export function handleScheduledTaskSessionSwitched(msg) {
|
|
|
441
428
|
}
|
|
442
429
|
|
|
443
430
|
export function initScheduledTasks() {
|
|
431
|
+
registerRightWorkbench("scheduled-tasks", closeScheduledTasks);
|
|
444
432
|
var button = document.getElementById("scheduler-btn");
|
|
445
433
|
if (button) button.addEventListener("click", function () { if (isScheduledTasksOpen()) closeScheduledTasks(); else openScheduledTasks(); });
|
|
446
434
|
document.addEventListener("click", handleDocumentClick);
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
startNewSession
|
|
16
16
|
} from './sidebar-sessions.js';
|
|
17
17
|
import { VENDOR_AVATARS, VENDOR_NAMES, VENDOR_ORDER, VENDOR_HOMEPAGES, isExperimentalVendor } from './app-rendering.js';
|
|
18
|
+
import { defaultVendorUnavailable, resolvePreferredVendor, saveDefaultVendor } from './default-vendor.js';
|
|
18
19
|
import {
|
|
19
20
|
getCachedProjectList,
|
|
20
21
|
getCachedCurrentSlug,
|
|
@@ -36,7 +37,7 @@ import { showHomeHub } from './app-home-hub.js';
|
|
|
36
37
|
import { openHomeChat } from './home-mate-chat.js';
|
|
37
38
|
import { openTerminal } from './terminal.js';
|
|
38
39
|
import { requestKnowledgeList } from './mate-knowledge.js';
|
|
39
|
-
import { loadRootDirectory } from './filebrowser.js';
|
|
40
|
+
import { loadRootDirectory, reopenFileViewer } from './filebrowser.js';
|
|
40
41
|
import {
|
|
41
42
|
prepareSidebarHierarchy,
|
|
42
43
|
renderMobileDriverHierarchy,
|
|
@@ -91,6 +92,7 @@ export function openMobileSheet(type) {
|
|
|
91
92
|
listEl.appendChild(fileTree);
|
|
92
93
|
fileTree.classList.remove("hidden");
|
|
93
94
|
}
|
|
95
|
+
reopenFileViewer();
|
|
94
96
|
loadRootDirectory();
|
|
95
97
|
} else if (type === "mate-knowledge") {
|
|
96
98
|
titleEl.textContent = "Knowledge";
|
|
@@ -674,14 +676,17 @@ function renderMobileSessionsInto(container) {
|
|
|
674
676
|
// the project's last-used vendor, plus a chevron that expands the full
|
|
675
677
|
// vendor list. The list expands inline rather than as a fixed-position
|
|
676
678
|
// popover because it lives inside a scrolling bottom sheet.
|
|
677
|
-
var mobileDefaultVendor = resolveDefaultVendor();
|
|
679
|
+
var mobileDefaultVendor = resolvePreferredVendor(store.get('installedVendors') || []) || resolveDefaultVendor();
|
|
678
680
|
var mobileInstalled = store.get('installedVendors') || [];
|
|
681
|
+
var mobileDefaultState = store.get('defaultVendorState') || {};
|
|
679
682
|
|
|
680
683
|
var newRow = document.createElement("div");
|
|
681
684
|
newRow.className = "mobile-session-new-row";
|
|
682
685
|
|
|
683
686
|
var mainBtn = document.createElement("button");
|
|
684
687
|
mainBtn.className = "mobile-session-new mobile-session-new-main";
|
|
688
|
+
mainBtn.title = "New " + (VENDOR_NAMES[mobileDefaultVendor] || mobileDefaultVendor) + " session";
|
|
689
|
+
if (defaultVendorUnavailable(mobileInstalled)) mainBtn.title += " (saved default is unavailable; using installed priority)";
|
|
685
690
|
mainBtn.innerHTML = '<img src="' + (VENDOR_AVATARS[mobileDefaultVendor] || VENDOR_AVATARS.claude) +
|
|
686
691
|
'" class="mobile-session-new-icon" alt=""><span>New ' + (VENDOR_NAMES[mobileDefaultVendor] || mobileDefaultVendor) + ' session</span>';
|
|
687
692
|
mainBtn.addEventListener("click", function () {
|
|
@@ -703,12 +708,20 @@ function renderMobileSessionsInto(container) {
|
|
|
703
708
|
});
|
|
704
709
|
newRow.appendChild(chevronBtn);
|
|
705
710
|
|
|
711
|
+
if (defaultVendorUnavailable(mobileInstalled) && mobileDefaultState.preference) {
|
|
712
|
+
var mobileFallbackNote = document.createElement("div");
|
|
713
|
+
mobileFallbackNote.className = "mobile-session-new-preference-status mobile-session-new-fallback-note";
|
|
714
|
+
mobileFallbackNote.textContent = "Saved default " + (VENDOR_NAMES[mobileDefaultState.preference] || mobileDefaultState.preference) + " is unavailable. Using " + (VENDOR_NAMES[mobileDefaultVendor] || mobileDefaultVendor) + ".";
|
|
715
|
+
}
|
|
716
|
+
|
|
706
717
|
// Every known vendor is listed. Uninstalled ones stay visible but disabled,
|
|
707
718
|
// and tapping one opens the vendor's homepage.
|
|
708
719
|
for (var vi = 0; vi < VENDOR_ORDER.length; vi++) {
|
|
709
720
|
(function (vendor) {
|
|
710
721
|
var isInstalled = mobileInstalled.indexOf(vendor) !== -1;
|
|
711
722
|
var name = VENDOR_NAMES[vendor] || vendor;
|
|
723
|
+
var vendorRow = document.createElement("div");
|
|
724
|
+
vendorRow.className = "mobile-vendor-row";
|
|
712
725
|
var vBtn = document.createElement("button");
|
|
713
726
|
vBtn.className = "mobile-session-new mobile-session-new-vendor";
|
|
714
727
|
if (!isInstalled) vBtn.classList.add("disabled");
|
|
@@ -725,11 +738,46 @@ function renderMobileSessionsInto(container) {
|
|
|
725
738
|
startNewSession(vendor);
|
|
726
739
|
closeMobileSheet();
|
|
727
740
|
});
|
|
728
|
-
|
|
741
|
+
vendorRow.appendChild(vBtn);
|
|
742
|
+
if (isInstalled) {
|
|
743
|
+
var defaultBtn = document.createElement("button");
|
|
744
|
+
defaultBtn.type = "button";
|
|
745
|
+
defaultBtn.className = "mobile-vendor-set-default";
|
|
746
|
+
defaultBtn.textContent = mobileDefaultState.saving ? "Saving…" : (vendor === mobileDefaultVendor && !defaultVendorUnavailable(mobileInstalled) ? "Default" : "Set default");
|
|
747
|
+
defaultBtn.disabled = mobileDefaultState.saving === true;
|
|
748
|
+
defaultBtn.setAttribute("aria-label", "Set " + name + " as default vendor");
|
|
749
|
+
defaultBtn.addEventListener("click", function (e) {
|
|
750
|
+
e.preventDefault();
|
|
751
|
+
e.stopPropagation();
|
|
752
|
+
saveDefaultVendor(vendor);
|
|
753
|
+
});
|
|
754
|
+
vendorRow.appendChild(defaultBtn);
|
|
755
|
+
}
|
|
756
|
+
vendorList.appendChild(vendorRow);
|
|
729
757
|
})(VENDOR_ORDER[vi]);
|
|
730
758
|
}
|
|
731
759
|
|
|
760
|
+
var automaticBtn = document.createElement("button");
|
|
761
|
+
automaticBtn.className = "mobile-session-new mobile-session-new-vendor mobile-session-new-automatic";
|
|
762
|
+
automaticBtn.type = "button";
|
|
763
|
+
automaticBtn.textContent = "Use automatic default";
|
|
764
|
+
automaticBtn.disabled = mobileDefaultState.saving === true;
|
|
765
|
+
automaticBtn.addEventListener("click", function (e) {
|
|
766
|
+
e.preventDefault();
|
|
767
|
+
e.stopPropagation();
|
|
768
|
+
saveDefaultVendor("");
|
|
769
|
+
});
|
|
770
|
+
vendorList.appendChild(automaticBtn);
|
|
771
|
+
if (mobileDefaultState.error) {
|
|
772
|
+
var mobileStatus = document.createElement("div");
|
|
773
|
+
mobileStatus.className = "mobile-session-new-preference-status";
|
|
774
|
+
mobileStatus.setAttribute("role", "alert");
|
|
775
|
+
mobileStatus.textContent = mobileDefaultState.error;
|
|
776
|
+
vendorList.appendChild(mobileStatus);
|
|
777
|
+
}
|
|
778
|
+
|
|
732
779
|
container.appendChild(newRow);
|
|
780
|
+
if (mobileFallbackNote) container.appendChild(mobileFallbackNote);
|
|
733
781
|
container.appendChild(vendorList);
|
|
734
782
|
|
|
735
783
|
// Partition: loop sessions vs normal sessions (same logic as desktop renderSessionList)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// sidebar-projects.js - Project icon strip, context menus, emoji picker, drag-and-drop, worktree modal
|
|
2
2
|
// Extracted from sidebar.js (PR-36)
|
|
3
3
|
|
|
4
|
-
import { escapeHtml } from './utils.js';
|
|
4
|
+
import { escapeHtml, showToast } from './utils.js';
|
|
5
5
|
import { iconHtml, refreshIcons } from './icons.js';
|
|
6
6
|
import { openProjectSettings } from './project-settings.js';
|
|
7
7
|
import { triggerShare } from './qrcode.js';
|
|
@@ -415,6 +415,11 @@ function showProjectCtxMenu(anchorEl, slug, name, icon, position) {
|
|
|
415
415
|
closeProjectCtxMenu();
|
|
416
416
|
if (closeUserCtxMenu) closeUserCtxMenu();
|
|
417
417
|
closeEmojiPicker();
|
|
418
|
+
var projects = getCachedProjects();
|
|
419
|
+
var targetProject = null;
|
|
420
|
+
for (var projectIndex = 0; projectIndex < projects.length; projectIndex++) {
|
|
421
|
+
if (projects[projectIndex].slug === slug) { targetProject = projects[projectIndex]; break; }
|
|
422
|
+
}
|
|
418
423
|
|
|
419
424
|
var menu = document.createElement("div");
|
|
420
425
|
menu.className = "project-ctx-menu";
|
|
@@ -438,7 +443,13 @@ function showProjectCtxMenu(anchorEl, slug, name, icon, position) {
|
|
|
438
443
|
settingsItem.addEventListener("click", function (e) {
|
|
439
444
|
e.stopPropagation();
|
|
440
445
|
closeProjectCtxMenu();
|
|
441
|
-
|
|
446
|
+
if (!targetProject) {
|
|
447
|
+
showToast("Project details are unavailable. Refresh and try again.", "error");
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
openProjectSettings(slug, Object.assign({}, targetProject, {
|
|
451
|
+
name: targetProject.title || targetProject.project || name,
|
|
452
|
+
}));
|
|
442
453
|
});
|
|
443
454
|
menu.appendChild(settingsItem);
|
|
444
455
|
}
|
|
@@ -460,7 +471,7 @@ function showProjectCtxMenu(anchorEl, slug, name, icon, position) {
|
|
|
460
471
|
|
|
461
472
|
// --- Manage Access ---
|
|
462
473
|
if (store.get('isMultiUserMode')) {
|
|
463
|
-
var isProjectOwner = store.get('myUserId') &&
|
|
474
|
+
var isProjectOwner = store.get('myUserId') && targetProject && targetProject.projectOwnerId && store.get('myUserId') === targetProject.projectOwnerId;
|
|
464
475
|
var isAdmin = store.get('permissions') && store.get('permissions').projectSettings !== false;
|
|
465
476
|
if (isProjectOwner || isAdmin) {
|
|
466
477
|
var accessItem = document.createElement("button");
|