clay-server 5.2.0-beta.4 → 5.2.0-beta.5
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/account-auth-socket.js +1 -0
- package/lib/builtin-mates.js +17 -14
- package/lib/canonical-clay-record-authority.js +15 -0
- package/lib/canonical-clay-scheduling-authority.js +19 -0
- package/lib/claude-hook-installer.js +28 -0
- package/lib/clay-mate-chat-receipts.js +26 -0
- package/lib/clay-mate-chat.js +87 -0
- package/lib/clay-mate-knowledge-input.js +26 -0
- package/lib/clay-mate-knowledge.js +95 -0
- package/lib/clay-mate-lifecycle.js +95 -0
- package/lib/clay-product-control-input.js +33 -0
- package/lib/clay-product-control-tools.js +51 -0
- package/lib/clay-product-control.js +237 -0
- package/lib/clay-project-lifecycle.js +154 -0
- package/lib/clay-project-record-input.js +44 -0
- package/lib/clay-project-records.js +83 -0
- package/lib/clay-scheduling-input.js +21 -0
- package/lib/clay-scheduling-receipts.js +26 -0
- package/lib/clay-scheduling.js +119 -0
- package/lib/clay-screen-controls.js +96 -0
- package/lib/clay-workspace-controls.js +83 -0
- package/lib/clay-workspace-input.js +17 -0
- package/lib/clay-workspace-receipts.js +26 -0
- package/lib/daemon-project-access.js +11 -0
- package/lib/daemon-project-lifecycle-callbacks.js +15 -0
- package/lib/daemon-project-lifecycle.js +262 -0
- package/lib/daemon-projects.js +1 -1
- package/lib/daemon.js +73 -221
- package/lib/issues-service.js +29 -15
- package/lib/knowledge-db-import.js +5 -4
- package/lib/knowledge-db-service.js +9 -6
- package/lib/knowledge-document-index.js +2 -1
- package/lib/knowledge-product-operations.js +46 -0
- package/lib/mate-creation-mcp-server.js +2 -0
- package/lib/mate-knowledge-canonical-access.js +10 -0
- package/lib/mate-knowledge-service.js +7 -4
- package/lib/mate-lifecycle-service.js +94 -0
- package/lib/mate-message-delivery.js +43 -0
- package/lib/mate-ready-creation.js +9 -1
- package/lib/mate-schedule-input.js +29 -0
- package/lib/mate-schedule-lifecycle.js +151 -0
- package/lib/mate-scheduling.js +103 -0
- package/lib/mates.js +4 -3
- package/lib/notes-checklist.js +50 -0
- package/lib/notes.js +49 -3
- package/lib/os-users.js +20 -5
- package/lib/project-admin-overview.js +51 -0
- package/lib/project-ask-user.js +4 -2
- package/lib/project-delegated-follow-up.js +2 -1
- package/lib/project-directory-claim.js +65 -0
- package/lib/project-file-operations.js +61 -0
- package/lib/project-filesystem.js +13 -87
- package/lib/project-full-access.js +23 -2
- package/lib/project-knowledge.js +32 -18
- package/lib/project-lifecycle-operations.js +98 -0
- package/lib/project-lifecycle-process-witness.js +32 -0
- package/lib/project-lifecycle-runtime.js +50 -0
- package/lib/project-linux-access.js +22 -0
- package/lib/project-logs-canonical-clay.js +27 -0
- package/lib/project-logs-clay-query.js +16 -0
- package/lib/project-logs-schema.js +4 -2
- package/lib/project-logs-service.js +8 -7
- package/lib/project-logs.js +1 -0
- package/lib/project-loop.js +1 -0
- package/lib/project-mate-chat-control.js +55 -0
- package/lib/project-mate-creation-proposal.js +18 -3
- package/lib/project-models.js +14 -31
- package/lib/project-notifications.js +74 -16
- package/lib/project-ownership.js +77 -0
- package/lib/project-pair-result-delivery.js +14 -3
- package/lib/project-pending-message-queue.js +1 -0
- package/lib/project-product-control.js +87 -0
- package/lib/project-product-session-queue.js +46 -0
- package/lib/project-push-access.js +28 -0
- package/lib/project-scheduled-message-queue.js +30 -0
- package/lib/project-scheduled-messages.js +63 -64
- package/lib/project-scheduled-task-control.js +83 -0
- package/lib/project-scheduled-tasks.js +13 -2
- package/lib/project-scheduling-access.js +10 -0
- package/lib/project-session-actions.js +65 -0
- package/lib/project-session-delete.js +8 -1
- package/lib/project-session-github.js +47 -12
- package/lib/project-session-history-actions.js +165 -0
- package/lib/project-session-runtime-actions.js +63 -0
- package/lib/project-session-vendor.js +54 -0
- package/lib/project-sessions.js +64 -241
- package/lib/project-user-message.js +30 -1
- package/lib/project-workspace-operations.js +126 -0
- package/lib/project-workspace-query.js +9 -2
- package/lib/project.js +95 -3
- package/lib/public/css/clay-tool-activity.css +51 -0
- package/lib/public/css/home-mate-creation.css +5 -0
- package/lib/public/css/overlays.css +4 -2
- package/lib/public/css/project-admin-overview.css +13 -0
- package/lib/public/css/session-folders.css +11 -0
- package/lib/public/css/session-github.css +3 -0
- package/lib/public/css/sticky-notes.css +61 -1
- package/lib/public/index.html +1 -1
- package/lib/public/modules/admin-settings-projects.js +6 -2
- package/lib/public/modules/app-connection.js +8 -1
- package/lib/public/modules/app-message-router.js +1 -1
- package/lib/public/modules/app-messages.js +18 -3
- package/lib/public/modules/app-projects.js +5 -0
- package/lib/public/modules/clay-owner-navigation.js +27 -0
- package/lib/public/modules/clay-tool-activity.js +140 -0
- package/lib/public/modules/clay-tool-presentation.js +338 -0
- package/lib/public/modules/command-palette.js +1 -1
- package/lib/public/modules/default-ai.js +11 -3
- package/lib/public/modules/home-debate-planning.js +5 -5
- package/lib/public/modules/home-mate-chat.js +8 -6
- package/lib/public/modules/home-mate-creation.js +73 -1
- package/lib/public/modules/personal-settings-dialog.js +7 -1
- package/lib/public/modules/project-access-recovery.js +17 -0
- package/lib/public/modules/project-admin-overview.js +21 -0
- package/lib/public/modules/session-folder-layout.js +1 -1
- package/lib/public/modules/session-folder-view-menu.js +9 -0
- package/lib/public/modules/session-github.js +65 -8
- package/lib/public/modules/sidebar-session-folders-ui.js +1 -1
- package/lib/public/modules/sticky-note-markdown.js +72 -6
- package/lib/public/modules/sticky-notes-actions.js +30 -0
- package/lib/public/modules/sticky-notes-browser.js +9 -2
- package/lib/public/modules/sticky-notes-card.js +23 -7
- package/lib/public/modules/sticky-notes-checklist.js +71 -0
- package/lib/public/modules/sticky-notes-editor.js +108 -44
- package/lib/public/modules/sticky-notes-shared.js +50 -2
- package/lib/public/modules/sticky-notes-state.js +66 -0
- package/lib/public/modules/sticky-notes.js +34 -51
- package/lib/public/modules/thinking-lifecycle.js +33 -3
- package/lib/public/modules/thinking-view.js +23 -1
- package/lib/public/modules/tools.js +34 -6
- package/lib/public/modules/user-settings-appearance.js +10 -3
- package/lib/public/modules/user-settings-editors.js +1 -1
- package/lib/public/modules/user-settings-layout.js +11 -2
- package/lib/public/modules/user-settings-preferences.js +6 -2
- package/lib/public/modules/user-settings-profile.js +12 -2
- package/lib/public/modules/user-settings.js +3 -2
- package/lib/public/style.css +2 -0
- package/lib/scheduled-task-records.js +3 -1
- package/lib/scheduler.js +2 -0
- package/lib/sdk-bridge.js +220 -21
- package/lib/sdk-message-processor.js +2 -0
- package/lib/sdk-response-recovery.js +155 -0
- package/lib/server-admin.js +63 -16
- package/lib/server-dm.js +1 -1
- package/lib/server-global-ws.js +10 -20
- package/lib/server-home-chat-events.js +7 -3
- package/lib/server-home-chat.js +4 -16
- package/lib/server-home-mate-creation.js +1 -0
- package/lib/server-home-mcp-consent.js +3 -3
- package/lib/server-mates.js +25 -67
- package/lib/server-owner-ui.js +77 -0
- package/lib/server-path-access.js +38 -0
- package/lib/server-project-removal.js +22 -0
- package/lib/server.js +99 -29
- package/lib/session-config-transition.js +45 -0
- package/lib/session-folders.js +7 -2
- package/lib/session-github.js +9 -2
- package/lib/session-visibility.js +5 -2
- package/lib/sessions.js +10 -0
- package/lib/terminal-manager.js +52 -0
- package/lib/users-permissions.js +2 -8
- package/lib/users.js +4 -0
- package/lib/workspace-query-access.js +4 -1
- package/lib/ws-schema.js +8 -2
- package/lib/yoke/adapters/claude.js +47 -8
- package/lib/yoke/adapters/codex.js +52 -111
- package/lib/yoke/claude-worker-startup.js +28 -0
- package/lib/yoke/codex-idle-recovery.js +81 -1
- package/lib/yoke/codex-recovery-admission.js +164 -0
- package/lib/yoke/session-tool-transport.js +3 -0
- package/package.json +1 -1
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
function attachScheduledMessageQueue(ctx) {
|
|
2
|
+
var users = ctx.users;
|
|
3
|
+
var pendingMessageQueue = ctx.pendingMessageQueue;
|
|
4
|
+
function queueActor(actorId) {
|
|
5
|
+
return users.isMultiUser() ? users.findUserById(actorId) : { id: "default" };
|
|
6
|
+
}
|
|
7
|
+
function queueItemForJob(session, job, actorId) {
|
|
8
|
+
if (!pendingMessageQueue || !session || !job) return null;
|
|
9
|
+
var actor = queueActor(actorId || (job.payload && job.payload.actorId));
|
|
10
|
+
var items = pendingMessageQueue.list(session, actor);
|
|
11
|
+
for (var i = 0; i < items.length; i++) {
|
|
12
|
+
if (items[i].schedule && items[i].schedule.jobId === job.id && items[i].schedule.revision === job.payload.revision) return items[i];
|
|
13
|
+
}
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
function validateQueueControl(session, job, actorId, correlation, requireQueueRevision) {
|
|
17
|
+
if (!pendingMessageQueue) return null;
|
|
18
|
+
if (requireQueueRevision && (!correlation || correlation.queueRevision == null || Number(correlation.queueRevision) !== pendingMessageQueue.getRevision())) {
|
|
19
|
+
throw new Error("The scheduled-message queue is stale.");
|
|
20
|
+
}
|
|
21
|
+
var actor = queueActor(actorId || (job.payload && job.payload.actorId));
|
|
22
|
+
var item = queueItemForJob(session, job, actorId);
|
|
23
|
+
if (!item || item.actorId !== actor.id || ["cancelled", "consumed", "failed"].indexOf(item.state) >= 0) {
|
|
24
|
+
throw new Error("The scheduled queue item is no longer available.");
|
|
25
|
+
}
|
|
26
|
+
return item;
|
|
27
|
+
}
|
|
28
|
+
return { actor: queueActor, item: queueItemForJob, validate: validateQueueControl };
|
|
29
|
+
}
|
|
30
|
+
module.exports = { attachScheduledMessageQueue: attachScheduledMessageQueue };
|
|
@@ -15,31 +15,37 @@ function attachProjectScheduledMessages(ctx) {
|
|
|
15
15
|
var now = ctx.now || Date.now;
|
|
16
16
|
var unregister = null;
|
|
17
17
|
var stopped = false;
|
|
18
|
-
function
|
|
18
|
+
function sessionIdentity(session) {
|
|
19
|
+
if (!session) return null;
|
|
20
|
+
if (ctx.isMate) return session.sessionOriginId || session._sessionRecordId || session.cliSessionId || null;
|
|
21
|
+
return session.cliSessionId || null;
|
|
22
|
+
}
|
|
23
|
+
function findSession(targetId) {
|
|
19
24
|
var found = null;
|
|
20
25
|
sm.sessions.forEach(function (session) {
|
|
21
|
-
if (!found && session
|
|
26
|
+
if (!found && (sessionIdentity(session) === targetId || (ctx.isMate &&
|
|
27
|
+
(session.cliSessionId === targetId || session._sessionRecordId === targetId)))) found = session;
|
|
22
28
|
});
|
|
23
29
|
return found;
|
|
24
30
|
}
|
|
25
31
|
function latestJob(session) {
|
|
26
|
-
if (!scheduler || !session || !session
|
|
32
|
+
if (!scheduler || !session || !sessionIdentity(session)) return null;
|
|
27
33
|
var jobs = scheduler.listJobs();
|
|
28
34
|
for (var i = jobs.length - 1; i >= 0; i--) {
|
|
29
|
-
if (jobs[i].type === type && jobs[i].projectId === slug && jobs[i].target && jobs[i].target.id === session
|
|
35
|
+
if (jobs[i].type === type && jobs[i].projectId === slug && jobs[i].target && findSession(jobs[i].target.id) === session) return jobs[i];
|
|
30
36
|
}
|
|
31
37
|
return null;
|
|
32
38
|
}
|
|
33
39
|
function pendingJob(session) {
|
|
34
|
-
if (!scheduler || !session || !session
|
|
40
|
+
if (!scheduler || !session || !sessionIdentity(session)) return null;
|
|
35
41
|
if (session.scheduledMessage && session.scheduledMessage.jobId) {
|
|
36
42
|
var direct = scheduler.getJob(session.scheduledMessage.jobId);
|
|
37
|
-
if (direct && direct.state === "queued") return direct;
|
|
43
|
+
if (direct && direct.state === "queued" && !direct.payload.mateScheduleId) return direct;
|
|
38
44
|
}
|
|
39
45
|
var jobs = scheduler.listJobs();
|
|
40
46
|
for (var i = jobs.length - 1; i >= 0; i--) {
|
|
41
|
-
if (jobs[i].type === type && jobs[i].projectId === slug && jobs[i].state === "queued" &&
|
|
42
|
-
jobs[i].target && jobs[i].target.id === session
|
|
47
|
+
if (jobs[i].type === type && jobs[i].projectId === slug && jobs[i].state === "queued" && !jobs[i].payload.mateScheduleId &&
|
|
48
|
+
jobs[i].target && findSession(jobs[i].target.id) === session) return jobs[i];
|
|
43
49
|
}
|
|
44
50
|
return null;
|
|
45
51
|
}
|
|
@@ -53,30 +59,10 @@ function attachProjectScheduledMessages(ctx) {
|
|
|
53
59
|
scheduledAt: job.payload.scheduledAt,
|
|
54
60
|
};
|
|
55
61
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
if (!pendingMessageQueue || !session || !job) return null;
|
|
61
|
-
var actor = queueActor(actorId || (job.payload && job.payload.actorId));
|
|
62
|
-
var items = pendingMessageQueue.list(session, actor);
|
|
63
|
-
for (var i = 0; i < items.length; i++) {
|
|
64
|
-
if (items[i].schedule && items[i].schedule.jobId === job.id && items[i].schedule.revision === job.payload.revision) return items[i];
|
|
65
|
-
}
|
|
66
|
-
return null;
|
|
67
|
-
}
|
|
68
|
-
function validateQueueControl(session, job, actorId, correlation, requireQueueRevision) {
|
|
69
|
-
if (!pendingMessageQueue) return null;
|
|
70
|
-
if (requireQueueRevision && (!correlation || correlation.queueRevision == null || Number(correlation.queueRevision) !== pendingMessageQueue.getRevision())) {
|
|
71
|
-
throw new Error("The scheduled-message queue is stale.");
|
|
72
|
-
}
|
|
73
|
-
var actor = queueActor(actorId || (job.payload && job.payload.actorId));
|
|
74
|
-
var item = queueItemForJob(session, job, actorId);
|
|
75
|
-
if (!item || item.actorId !== actor.id || ["cancelled", "consumed", "failed"].indexOf(item.state) >= 0) {
|
|
76
|
-
throw new Error("The scheduled queue item is no longer available.");
|
|
77
|
-
}
|
|
78
|
-
return item;
|
|
79
|
-
}
|
|
62
|
+
var queue = require('./project-scheduled-message-queue').attachScheduledMessageQueue({ users: users, pendingMessageQueue: pendingMessageQueue });
|
|
63
|
+
var queueActor = queue.actor;
|
|
64
|
+
var queueItemForJob = queue.item;
|
|
65
|
+
var validateQueueControl = queue.validate;
|
|
80
66
|
function sendState(session, targetWs, sendFn) {
|
|
81
67
|
var job = pendingJob(session);
|
|
82
68
|
session.scheduledMessage = projection(job, queueItemForJob(session, job, job && job.payload && job.payload.actorId));
|
|
@@ -106,11 +92,13 @@ function attachProjectScheduledMessages(ctx) {
|
|
|
106
92
|
function authorize(session, actorId) {
|
|
107
93
|
if (!session || session.destroying) throw new Error("The session is no longer available.");
|
|
108
94
|
if (sm.sessions.get(session.localId) !== session) throw new Error("The session is no longer live.");
|
|
109
|
-
if (!session
|
|
95
|
+
if (!sessionIdentity(session)) throw new Error(ctx.isMate ? "This Mate has no persistent server identity yet." : "This session has no persistent provider identity yet.");
|
|
110
96
|
if (session.mode === "tui" || session.runtimeMode === "tui" || sessionProvenance.isWorker(session) ||
|
|
111
97
|
(ctx.isDriverOperatedSession && ctx.isDriverOperatedSession(session))) {
|
|
112
98
|
throw new Error("Scheduled messages are available only for ordinary project chat sessions.");
|
|
113
99
|
}
|
|
100
|
+
if (ctx.isMate && (session._mcpForeignInput || session.sessionVisibility === "shared" || session.mateCreationMode ||
|
|
101
|
+
(session.history || []).some(function (entry) { return entry.type === "user_message" && entry.from && entry.from !== (session.ownerId || "default"); }))) throw new Error("Foreign input cannot authorize private Mate schedules.");
|
|
114
102
|
if (!users.isMultiUser()) return LOCAL_OWNER;
|
|
115
103
|
if (!actorId || !users.findUserById(actorId)) throw new Error("The scheduling user no longer exists.");
|
|
116
104
|
var access = ctx.getProjectAccess();
|
|
@@ -123,26 +111,33 @@ function attachProjectScheduledMessages(ctx) {
|
|
|
123
111
|
}
|
|
124
112
|
return ownerId;
|
|
125
113
|
}
|
|
126
|
-
function buildSpec(session, text, resetsAt, actorId, revision) {
|
|
114
|
+
function buildSpec(session, text, resetsAt, actorId, revision, options) {
|
|
115
|
+
options = options || {};
|
|
127
116
|
var timestamp = now();
|
|
128
|
-
var runAt = resetsAt <= timestamp ? timestamp + 5000 : resetsAt + 60000;
|
|
117
|
+
var runAt = options.exact === true ? resetsAt : (resetsAt <= timestamp ? timestamp + 5000 : resetsAt + 60000);
|
|
129
118
|
var ownerId = authorize(session, actorId);
|
|
130
119
|
return {
|
|
131
120
|
type: type,
|
|
132
|
-
idempotencyKey: "project-message:" + slug + ":" + session
|
|
121
|
+
idempotencyKey: "project-message:" + slug + ":" + sessionIdentity(session) + ":" + revision,
|
|
133
122
|
ownerId: ownerId,
|
|
134
123
|
projectId: slug,
|
|
135
|
-
target: { kind: "project-session", id: session
|
|
124
|
+
target: { kind: "project-session", id: sessionIdentity(session) },
|
|
136
125
|
payload: {
|
|
137
126
|
text: text,
|
|
138
127
|
hasAttachments: false,
|
|
139
128
|
queueAdmissionRequired: true,
|
|
140
|
-
cliSessionId: session.cliSessionId,
|
|
129
|
+
cliSessionId: session.cliSessionId || null,
|
|
130
|
+
sessionIdentity: sessionIdentity(session),
|
|
141
131
|
actorId: actorId || LOCAL_OWNER,
|
|
142
132
|
sessionOwnerId: ownerId,
|
|
143
133
|
revision: revision,
|
|
144
134
|
requestedResetsAt: resetsAt,
|
|
145
135
|
scheduledAt: timestamp,
|
|
136
|
+
scheduleRule: options.scheduleRule || null,
|
|
137
|
+
mateScheduleId: options.mateScheduleId || null,
|
|
138
|
+
logicalScheduleId: options.logicalScheduleId || options.mateScheduleId || null,
|
|
139
|
+
logicalRevision: options.logicalRevision || null,
|
|
140
|
+
occurrence: Number(options.occurrence || 0),
|
|
146
141
|
},
|
|
147
142
|
runAt: runAt,
|
|
148
143
|
};
|
|
@@ -151,12 +146,12 @@ function attachProjectScheduledMessages(ctx) {
|
|
|
151
146
|
if (!scheduler || !session || !requestId) return [];
|
|
152
147
|
var jobs = scheduler.listJobs();
|
|
153
148
|
return jobs.filter(function (job) {
|
|
154
|
-
return job.type === type && job.projectId === slug && job.target && job.target.id === session
|
|
149
|
+
return job.type === type && job.projectId === slug && job.target && findSession(job.target.id) === session &&
|
|
155
150
|
job.payload && job.payload.admissionRequestId === requestId && job.payload.actorId === actorId;
|
|
156
151
|
});
|
|
157
152
|
}
|
|
158
153
|
function requestIdempotencyKey(session, actorId, requestId) {
|
|
159
|
-
var base = "project-message:" + slug + ":" + session
|
|
154
|
+
var base = "project-message:" + slug + ":" + sessionIdentity(session) + ":actor:" + actorId + ":request:" + requestId;
|
|
160
155
|
var jobs = requestJobs(session, actorId, requestId);
|
|
161
156
|
var retryCount = 0;
|
|
162
157
|
for (var i = 0; i < jobs.length; i++) if (jobs[i].state === "cancelled" && jobs[i].cancelledReason === "queue_admission_failed") retryCount++;
|
|
@@ -176,8 +171,7 @@ function attachProjectScheduledMessages(ctx) {
|
|
|
176
171
|
scheduledMessage: job ? projection(job, queueItemForJob(session, job, actorId)) : null,
|
|
177
172
|
});
|
|
178
173
|
}
|
|
179
|
-
|
|
180
|
-
function schedule(session, text, resetsAt, actorUser, targetWs, attachments, requestId, transportAccountId) {
|
|
174
|
+
function schedule(session, text, resetsAt, actorUser, targetWs, attachments, requestId, transportAccountId, options) {
|
|
181
175
|
var actorId = users.isMultiUser() ? (actorUser && actorUser.id) : LOCAL_OWNER;
|
|
182
176
|
if (!scheduler || stopped) {
|
|
183
177
|
sendAdmissionResult(targetWs, session, actorId, requestId, null, "Scheduled messages are temporarily unavailable.", transportAccountId);
|
|
@@ -198,7 +192,7 @@ function attachProjectScheduledMessages(ctx) {
|
|
|
198
192
|
sendAdmissionResult(targetWs, session, actorId, requestId, null, "Invalid schedule request identifier.", transportAccountId);
|
|
199
193
|
return null;
|
|
200
194
|
}
|
|
201
|
-
if (typeof text !== "string" || (!text.trim() && !images.length && !pastes.length) || !isFinite(Number(resetsAt))) {
|
|
195
|
+
if (typeof text !== "string" || (!text.trim() && !images.length && !pastes.length) || !isFinite(Number(resetsAt)) || (options && options.exact === true && Number(resetsAt) <= now())) {
|
|
202
196
|
sendAdmissionResult(targetWs, session, actorId, requestId, null, "A valid scheduled message and reset time are required.", transportAccountId);
|
|
203
197
|
visibleError(session, targetWs, "A valid scheduled message and reset time are required.");
|
|
204
198
|
return null;
|
|
@@ -219,7 +213,7 @@ function attachProjectScheduledMessages(ctx) {
|
|
|
219
213
|
return prior;
|
|
220
214
|
}
|
|
221
215
|
var revision = makeRevision();
|
|
222
|
-
var spec = buildSpec(session, text, Number(resetsAt), actorId, revision);
|
|
216
|
+
var spec = buildSpec(session, text, Number(resetsAt), actorId, revision, options);
|
|
223
217
|
if (requestId) {
|
|
224
218
|
spec.idempotencyKey = requestIdempotencyKey(session, actorId, requestId);
|
|
225
219
|
spec.payload.admissionRequestId = requestId;
|
|
@@ -227,6 +221,7 @@ function attachProjectScheduledMessages(ctx) {
|
|
|
227
221
|
}
|
|
228
222
|
spec.payload.hasAttachments = images.length > 0 || pastes.length > 0;
|
|
229
223
|
var current = pendingJob(session);
|
|
224
|
+
if (options && options.mateScheduleId) current = null;
|
|
230
225
|
if (current && requestId && (!current.payload || current.payload.admissionRequestId !== requestId)) {
|
|
231
226
|
throw new Error("Another scheduled message is already pending.");
|
|
232
227
|
}
|
|
@@ -262,18 +257,18 @@ function attachProjectScheduledMessages(ctx) {
|
|
|
262
257
|
return null;
|
|
263
258
|
}
|
|
264
259
|
}
|
|
265
|
-
|
|
266
260
|
function requireCorrelation(job, correlation, targetWs) {
|
|
267
261
|
if (!targetWs) return;
|
|
268
262
|
if (!correlation || correlation.jobId !== job.id || correlation.revision !== job.payload.revision || correlation.queueRevision == null || !pendingMessageQueue || Number(correlation.queueRevision) !== pendingMessageQueue.getRevision()) {
|
|
269
263
|
throw new Error("The scheduled-message control is stale.");
|
|
270
264
|
}
|
|
271
265
|
}
|
|
272
|
-
|
|
273
266
|
function cancel(session, actorUser, targetWs, reason, correlation) {
|
|
274
267
|
try {
|
|
275
268
|
authorize(session, users.isMultiUser() ? (actorUser && actorUser.id) : LOCAL_OWNER);
|
|
276
|
-
var job = pendingJob(session) || latestJob(session);
|
|
269
|
+
var job = correlation && correlation.jobId ? scheduler.getJob(correlation.jobId) : (pendingJob(session) || latestJob(session));
|
|
270
|
+
if (job && (job.projectId !== slug || job.type !== type || !job.target || findSession(job.target.id) !== session || job.ownerId !== ownerFor(session) ||
|
|
271
|
+
(correlation && correlation.revision !== job.payload.revision))) throw new Error("The scheduled-message control is stale.");
|
|
277
272
|
if (!job) return false;
|
|
278
273
|
requireCorrelation(job, correlation, targetWs);
|
|
279
274
|
if (pendingMessageQueue) validateQueueControl(session, job, actorUser && actorUser.id, correlation, !!targetWs);
|
|
@@ -291,7 +286,6 @@ function attachProjectScheduledMessages(ctx) {
|
|
|
291
286
|
return false;
|
|
292
287
|
}
|
|
293
288
|
}
|
|
294
|
-
|
|
295
289
|
function sendNow(session, actorUser, targetWs, correlation) {
|
|
296
290
|
try {
|
|
297
291
|
authorize(session, users.isMultiUser() ? (actorUser && actorUser.id) : LOCAL_OWNER);
|
|
@@ -326,14 +320,16 @@ function attachProjectScheduledMessages(ctx) {
|
|
|
326
320
|
return false;
|
|
327
321
|
}
|
|
328
322
|
}
|
|
329
|
-
|
|
330
323
|
function canRun(job) {
|
|
331
324
|
var session = findSession(job.target && job.target.id);
|
|
332
325
|
if (!session) return true;
|
|
333
326
|
return !session.isProcessing && !session._queryStarting;
|
|
334
327
|
}
|
|
335
|
-
|
|
336
328
|
function failJob(session, job, error, processingOwner) {
|
|
329
|
+
if (session && job.payload.mateScheduleId && ctx.mateLifecycle) {
|
|
330
|
+
try { ctx.mateLifecycle().failed(session, job, error); }
|
|
331
|
+
catch (saveError) { console.error("[mate-schedules] Failed to persist dispatch failure:", saveError.message); }
|
|
332
|
+
}
|
|
337
333
|
var ownsProcessing = session && processingOwner && session._scheduledMessageProcessing === processingOwner &&
|
|
338
334
|
session._queryGeneration === processingOwner.queryGeneration &&
|
|
339
335
|
session.queryInstance === processingOwner.queryInstance && !session._queryStarting;
|
|
@@ -358,6 +354,7 @@ function attachProjectScheduledMessages(ctx) {
|
|
|
358
354
|
try {
|
|
359
355
|
if (!session) throw new Error("The scheduled session was deleted.");
|
|
360
356
|
var ownerId = authorize(session, job.payload.actorId);
|
|
357
|
+
if (job.payload.mateScheduleId && ctx.mateLifecycle && !ctx.mateLifecycle().current(session, job)) throw new Error("The Mate schedule is no longer active.");
|
|
361
358
|
if (ownerId !== job.ownerId || ownerId !== job.payload.sessionOwnerId) throw new Error("The session owner changed.");
|
|
362
359
|
if (ctx.osUsers) {
|
|
363
360
|
var owner = users.findUserById(job.ownerId);
|
|
@@ -390,7 +387,6 @@ function attachProjectScheduledMessages(ctx) {
|
|
|
390
387
|
throw error;
|
|
391
388
|
}
|
|
392
389
|
}
|
|
393
|
-
|
|
394
390
|
function onQueueConsumed(item) {
|
|
395
391
|
if (!item || !item.schedule || !scheduler) return false;
|
|
396
392
|
var job = scheduler.getJob(item.schedule.jobId);
|
|
@@ -401,10 +397,10 @@ function attachProjectScheduledMessages(ctx) {
|
|
|
401
397
|
session.rateLimitAutoContinuePending = false;
|
|
402
398
|
}
|
|
403
399
|
sm.sendAndRecord(session, { type: "scheduled_message_sent", jobId: job.id, revision: item.schedule.revision });
|
|
400
|
+
if (job.payload && job.payload.mateScheduleId && ctx.mateLifecycle) ctx.mateLifecycle().consumed(session, job);
|
|
404
401
|
sm.broadcastSessionList();
|
|
405
402
|
return true;
|
|
406
403
|
}
|
|
407
|
-
|
|
408
404
|
function onQueueCancelled(item) {
|
|
409
405
|
if (!item || !item.schedule || !scheduler) return false;
|
|
410
406
|
var job = scheduler.getJob(item.schedule.jobId);
|
|
@@ -412,6 +408,7 @@ function attachProjectScheduledMessages(ctx) {
|
|
|
412
408
|
try { scheduler.cancel(job.id, "cancelled_by_user"); } catch (error) {}
|
|
413
409
|
}
|
|
414
410
|
var session = job && job.target ? findSession(job.target.id) : null;
|
|
411
|
+
if (session && job.payload.mateScheduleId && ctx.mateLifecycle) ctx.mateLifecycle().cancelled(session, job);
|
|
415
412
|
if (session && session.scheduledMessage && session.scheduledMessage.jobId === item.schedule.jobId) {
|
|
416
413
|
session.scheduledMessage = null;
|
|
417
414
|
session.rateLimitAutoContinuePending = false;
|
|
@@ -419,17 +416,16 @@ function attachProjectScheduledMessages(ctx) {
|
|
|
419
416
|
}
|
|
420
417
|
return true;
|
|
421
418
|
}
|
|
422
|
-
|
|
423
419
|
function authorizePending(item, session) {
|
|
424
420
|
if (!item || !item.schedule || !scheduler || !session) return true;
|
|
425
421
|
var job = scheduler.getJob(item.schedule.jobId);
|
|
426
422
|
var expectedActorId = users.isMultiUser() ? job && job.payload && job.payload.actorId : "default";
|
|
427
|
-
if (!job || ["running", "completed", "interrupted"].indexOf(job.state) < 0 || !job.target || job.target.id !== session
|
|
423
|
+
if (!job || ["running", "completed", "interrupted"].indexOf(job.state) < 0 || !job.target || findSession(job.target.id) !== session ||
|
|
428
424
|
!job.payload || job.projectId !== slug || job.payload.revision !== item.schedule.revision || item.actorId !== expectedActorId ||
|
|
429
425
|
job.ownerId !== job.payload.sessionOwnerId) return false;
|
|
426
|
+
if (job.payload.mateScheduleId && ctx.mateLifecycle && !ctx.mateLifecycle().current(session, job)) return false;
|
|
430
427
|
try { return authorize(session, job.payload.actorId) === job.ownerId; } catch (error) { return false; }
|
|
431
428
|
}
|
|
432
|
-
|
|
433
429
|
function rehydrate() {
|
|
434
430
|
sm.sessions.forEach(function (session) {
|
|
435
431
|
var pending = pendingJob(session);
|
|
@@ -437,10 +433,10 @@ function attachProjectScheduledMessages(ctx) {
|
|
|
437
433
|
if (!pendingMessageQueue) return;
|
|
438
434
|
var jobs = scheduler.listJobs();
|
|
439
435
|
for (var i = 0; i < jobs.length; i++) {
|
|
440
|
-
if (jobs[i].type !== type || jobs[i].projectId !== slug || !jobs[i].target || jobs[i].target.id !== session
|
|
436
|
+
if (jobs[i].type !== type || jobs[i].projectId !== slug || !jobs[i].target || findSession(jobs[i].target.id) !== session) continue;
|
|
441
437
|
var actor = users.isMultiUser() ? users.findUserById(jobs[i].payload.actorId) : { id: "default" };
|
|
442
438
|
if (jobs[i].state === "queued" && !queueItemForJob(session, jobs[i], jobs[i].payload.actorId)) {
|
|
443
|
-
if (jobs[i].payload.queueAdmissionRequired !== true) pendingMessageQueue.upsertScheduled(session, {
|
|
439
|
+
if (jobs[i].payload.queueAdmissionRequired !== true || (jobs[i].payload.mateScheduleId && ctx.mateLifecycle && ctx.mateLifecycle().current(session, jobs[i]))) pendingMessageQueue.upsertScheduled(session, {
|
|
444
440
|
type: "message", text: jobs[i].payload.text, clientMessageId: "scheduled-" + jobs[i].id,
|
|
445
441
|
schedule: { jobId: jobs[i].id, revision: jobs[i].payload.revision, notBefore: jobs[i].runAt, ready: false },
|
|
446
442
|
}, actor);
|
|
@@ -455,19 +451,19 @@ function attachProjectScheduledMessages(ctx) {
|
|
|
455
451
|
}
|
|
456
452
|
});
|
|
457
453
|
}
|
|
458
|
-
|
|
459
454
|
function deleted(localId, session) {
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
455
|
+
if (!session) return;
|
|
456
|
+
scheduler.listJobs().forEach(function (job) {
|
|
457
|
+
if (job.type === type && job.state === "queued" && job.target && [sessionIdentity(session), session.cliSessionId, session._sessionRecordId].indexOf(job.target.id) >= 0) {
|
|
458
|
+
try { scheduler.cancel(job.id, "session_deleted"); } catch (error) {}
|
|
459
|
+
}
|
|
460
|
+
});
|
|
463
461
|
session.scheduledMessage = null;
|
|
464
462
|
}
|
|
465
|
-
|
|
466
463
|
function notify() {
|
|
467
464
|
if (!scheduler || stopped) return;
|
|
468
465
|
try { scheduler.tick(); } catch (error) {}
|
|
469
466
|
}
|
|
470
|
-
|
|
471
467
|
function shutdown() {
|
|
472
468
|
stopped = true;
|
|
473
469
|
if (unregister) unregister();
|
|
@@ -476,7 +472,7 @@ function attachProjectScheduledMessages(ctx) {
|
|
|
476
472
|
|
|
477
473
|
if (scheduler) {
|
|
478
474
|
unregister = scheduler.registerHandler(type, dispatch, { canRun: canRun });
|
|
479
|
-
rehydrate();
|
|
475
|
+
if (!ctx.deferRehydrate) rehydrate();
|
|
480
476
|
sm.addOnSessionDeleted(deleted);
|
|
481
477
|
sm.addOnSessionViewed(function (session, targetWs, sendFn) { sendState(session, targetWs, sendFn); });
|
|
482
478
|
}
|
|
@@ -491,6 +487,9 @@ function attachProjectScheduledMessages(ctx) {
|
|
|
491
487
|
shutdown: shutdown,
|
|
492
488
|
handlerType: type,
|
|
493
489
|
authorizePending: authorizePending,
|
|
490
|
+
authorize: authorize,
|
|
491
|
+
identity: sessionIdentity,
|
|
492
|
+
queueItem: queueItemForJob,
|
|
494
493
|
onQueueConsumed: onQueueConsumed,
|
|
495
494
|
onQueueCancelled: onQueueCancelled,
|
|
496
495
|
};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
var crypto = require('crypto');
|
|
2
|
+
var authority = require('./canonical-clay-scheduling-authority');
|
|
3
|
+
var validation = require('./schedule-validation');
|
|
4
|
+
function attachScheduledTaskControl(ctx) {
|
|
5
|
+
var drafts = new Map();
|
|
6
|
+
function current(session, token) {
|
|
7
|
+
var actor = authority.validate(token, ctx.slug, session);
|
|
8
|
+
if (!ctx.available(session)) throw new Error('Scheduled Tasks requires an eligible owned project Driver session.');
|
|
9
|
+
if (ctx.validateOwnerIdentity) ctx.validateOwnerIdentity(session);
|
|
10
|
+
return actor;
|
|
11
|
+
}
|
|
12
|
+
function read(session, op, token) {
|
|
13
|
+
var actor = current(session, token);
|
|
14
|
+
if (op.action === 'list') return { records: ctx.records.list(session).filter(function (item) { return !ctx.multiUser() || item.ownerId === actor.userId; }), timezone: ctx.timezone };
|
|
15
|
+
var result = ctx.records.read(session, op.id);
|
|
16
|
+
if (!result.ok) throw new Error(result.error);
|
|
17
|
+
if (ctx.multiUser() && result.record.ownerId !== actor.userId) throw new Error('This task belongs to another owner or requires workbench ownership setup.');
|
|
18
|
+
return result;
|
|
19
|
+
}
|
|
20
|
+
function snapshot(session, op, token) {
|
|
21
|
+
current(session, token);
|
|
22
|
+
return op.action === 'propose' ? { draft: session.scheduledTaskDraft || null, interviewId: ctx.interviewId(session) } : read(session, { id: op.id }, token).record;
|
|
23
|
+
}
|
|
24
|
+
function prepare(session, op, token, requestId) {
|
|
25
|
+
current(session, token);
|
|
26
|
+
if (ctx.interviewId(session) || session.scheduledTaskDraft) throw new Error('Finish or cancel this Driver schedule draft before proposing another.');
|
|
27
|
+
var checked = validation.validateScheduledTaskInput(op.data);
|
|
28
|
+
if (!checked.ok || !checked.value.execution) throw new Error(checked.error || 'Choose Driver and Split Worker runtimes.');
|
|
29
|
+
ctx.preflight(checked.value.execution.driver, null, true); ctx.preflight(checked.value.execution.worker, null, true);
|
|
30
|
+
current(session, token);
|
|
31
|
+
var begun = ctx.beginManual(session, requestId);
|
|
32
|
+
if (!begun.ok) throw new Error(begun.error);
|
|
33
|
+
var prior = session.scheduledTaskDraft;
|
|
34
|
+
var draft = Object.assign({}, prior, checked.value, { canonicalScheduling: true });
|
|
35
|
+
session.scheduledTaskDraft = draft;
|
|
36
|
+
if (ctx.sm.saveSessionFile(session) !== true) { session.scheduledTaskDraft = prior; throw new Error('The canonical schedule proposal could not be saved.'); }
|
|
37
|
+
if (drafts.size >= 100) drafts.delete(drafts.keys().next().value);
|
|
38
|
+
drafts.set(draft.id, { session: session, token: token });
|
|
39
|
+
ctx.publish(session, { open: true });
|
|
40
|
+
return { status: 'proposed', persisted: true, proposalId: draft.id, version: draft.version, interviewId: draft.interviewId, sessionId: session.localId, guidance: 'The task has not been created. Open this selected Driver Scheduled Tasks workbench and use its final Create task control. No execution has started.' };
|
|
41
|
+
}
|
|
42
|
+
function actorForDraft(session, draft) {
|
|
43
|
+
if (!draft.canonicalScheduling) return null;
|
|
44
|
+
var grant = drafts.get(draft.id);
|
|
45
|
+
if (!grant || grant.session !== session) throw new Error('This canonical schedule proposal needs a fresh live Clay query after reload.');
|
|
46
|
+
return current(session, grant.token);
|
|
47
|
+
}
|
|
48
|
+
function apply(session, op, token, requestId) {
|
|
49
|
+
var actor = current(session, token);
|
|
50
|
+
if (op.action === 'propose') return prepare(session, op, token, requestId);
|
|
51
|
+
var selected = read(session, op, token).record;
|
|
52
|
+
if (selected.revision !== op.revision) throw new Error('Scheduled task revision changed.');
|
|
53
|
+
if (!selected.ownerId && ctx.multiUser()) throw new Error('Claim legacy task ownership through the existing workbench first.');
|
|
54
|
+
var record = ctx.registry.getById(op.id), result;
|
|
55
|
+
current(session, token);
|
|
56
|
+
if (op.action === 'update') {
|
|
57
|
+
result = ctx.records.update(session, op.id, op.revision, op.data, actor);
|
|
58
|
+
if (!result.ok) throw new Error(result.error);
|
|
59
|
+
ctx.publishRecord(session, result.record);
|
|
60
|
+
return { status: 'completed', persisted: true, record: result.record };
|
|
61
|
+
}
|
|
62
|
+
if (op.action === 'run') {
|
|
63
|
+
result = ctx.execution.trigger(record, 'canonical-clay');
|
|
64
|
+
if (!result.ok) return { status: 'failed', persisted: 'unknown', error: result.error, result: result };
|
|
65
|
+
return { status: 'queued', persisted: true, result: result, guidance: 'The existing scheduled-task executor dispatched its Driver. Task completion requires its normal Worker and outcome reporting.' };
|
|
66
|
+
}
|
|
67
|
+
if (op.action === 'cancel') {
|
|
68
|
+
var running = !!record.activeRun;
|
|
69
|
+
if (running && !ctx.execution.stopRecord(record, 'The owner cancelled this scheduled task through Clay.')) throw new Error('The active scheduled run could not be stopped; task retained.');
|
|
70
|
+
current(session, token);
|
|
71
|
+
if (!ctx.registry.remove(record.id)) throw new Error('The scheduled task could not be cancelled.');
|
|
72
|
+
return { status: 'completed', persisted: true, removed: true, runStopRequested: running };
|
|
73
|
+
}
|
|
74
|
+
var enabled = op.action === 'resume';
|
|
75
|
+
result = ctx.registry.update(record.id, { enabled: enabled, updatedBy: actor });
|
|
76
|
+
if (!result) throw new Error('The scheduled task state could not be saved.');
|
|
77
|
+
ctx.publishRecord(session, ctx.records.read(session, op.id).record);
|
|
78
|
+
return { status: 'completed', persisted: true, enabled: enabled };
|
|
79
|
+
}
|
|
80
|
+
return { authorize: current, read: read, snapshot: snapshot, apply: apply, actorForDraft: actorForDraft,
|
|
81
|
+
creationId: function (draft) { return 'loop_clay_' + crypto.createHash('sha256').update(draft.id).digest('hex').slice(0, 32); } };
|
|
82
|
+
}
|
|
83
|
+
module.exports = { attachScheduledTaskControl: attachScheduledTaskControl };
|
|
@@ -313,13 +313,19 @@ function attachScheduledTasks(ctx) {
|
|
|
313
313
|
ctx.preflightRuntime(checked.value.execution.worker, null, true);
|
|
314
314
|
} catch (runtimeError) { return { ok: false, error: runtimeError.message || String(runtimeError) }; }
|
|
315
315
|
}
|
|
316
|
-
var
|
|
316
|
+
var canonicalActor;
|
|
317
|
+
try { canonicalActor = productControl.actorForDraft(session, draft); } catch (error) { return { ok: false, error: error.message }; }
|
|
318
|
+
var recordId = draft.canonicalScheduling ? productControl.creationId(draft) : "loop_" + Date.now() + "_" + crypto.randomBytes(3).toString("hex");
|
|
319
|
+
var creationKey = crypto.createHash('sha256').update(JSON.stringify(checked.value)).digest('hex');
|
|
320
|
+
var existing = draft.canonicalScheduling && registry.getById(recordId);
|
|
321
|
+
if (existing) return existing.creationFingerprint === creationKey ? { ok: true, duplicate: true, recordId: existing.id } : { ok: false, error: 'This schedule proposal already created a different reviewed task.' };
|
|
317
322
|
var saved;
|
|
318
323
|
try {
|
|
319
324
|
var actor = owner(ws);
|
|
320
325
|
if (session.ownerId && actor.id !== session.ownerId) return { ok: false, error: "The schedule owner does not match this Driver session." };
|
|
321
326
|
createTaskFiles(recordId, checked.value);
|
|
322
327
|
saved = registry.register(Object.assign({}, checked.value, {
|
|
328
|
+
createdBy: canonicalActor, updatedBy: canonicalActor, creationFingerprint: creationKey,
|
|
323
329
|
id: recordId, task: checked.value.instructions, prompt: checked.value.instructions, enabled: true,
|
|
324
330
|
mode: "simple", source: checked.value.cron ? null : "schedule", ownerId: actor.id, ownerName: actor.name, sessionId: session.localId, createdViaScheduledTasks: true,
|
|
325
331
|
}));
|
|
@@ -475,7 +481,12 @@ function attachScheduledTasks(ctx) {
|
|
|
475
481
|
return allowed(ws, session) && available(session);
|
|
476
482
|
}
|
|
477
483
|
|
|
478
|
-
|
|
484
|
+
var productControl = require('./project-scheduled-task-control').attachScheduledTaskControl({
|
|
485
|
+
slug: ctx.projectSlug, sm: sm, available: available, records: records, registry: registry, execution: ctx.scheduledExecution,
|
|
486
|
+
validateOwnerIdentity: ctx.validateOwnerIdentity, multiUser: function () { return !!(ctx.usersModule && ctx.usersModule.isMultiUser()); }, preflight: ctx.preflightRuntime, timezone: schedulerTimeZone,
|
|
487
|
+
beginManual: beginManual, interviewId: activeInterviewId, publish: publish, publishRecord: publishRecord,
|
|
488
|
+
});
|
|
489
|
+
return { productControl: productControl, getToolDefs: getToolDefs, getSystemPrompt: getSystemPrompt, handleMessage: handleMessage, activeInterviewId: activeInterviewId, canStartInterview: canStartInterview, canCreateInterview: canCreateInterview };
|
|
479
490
|
}
|
|
480
491
|
|
|
481
492
|
module.exports = { attachScheduledTasks: attachScheduledTasks, activeInterviewId: activeInterviewId, interviewPrompt: interviewPrompt, authorizeStoredOwner: authorizeStoredOwner };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
function attachSchedulingAccess(ctx) {
|
|
2
|
+
function ownerIdentity(session) {
|
|
3
|
+
if (!ctx.osUsers) return null;
|
|
4
|
+
var username = ctx.getLinuxUser(session), identity = username && ctx.resolveLinuxUser(username);
|
|
5
|
+
if (!identity) throw new Error('The schedule owner no longer has a valid mapped OS identity.');
|
|
6
|
+
return identity;
|
|
7
|
+
}
|
|
8
|
+
return { ownerIdentity: ownerIdentity };
|
|
9
|
+
}
|
|
10
|
+
module.exports = { attachSchedulingAccess: attachSchedulingAccess };
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
var yoke = require("./yoke");
|
|
2
|
+
var permissionModes = require("./session-permission-mode");
|
|
3
|
+
|
|
4
|
+
// Shared session effects. Callers authorize the exact live session before entry.
|
|
5
|
+
function attachSessionActions(ctx) {
|
|
6
|
+
var sm = ctx.sm;
|
|
7
|
+
function exact(session) {
|
|
8
|
+
if (!session || sm.sessions.get(session.localId) !== session) throw new Error("Session was deleted or replaced.");
|
|
9
|
+
}
|
|
10
|
+
function rename(session, title) {
|
|
11
|
+
exact(session);
|
|
12
|
+
var previous = { title: session.title, titleProvisional: session.titleProvisional, titleAutoGenerated: session.titleAutoGenerated, titleManuallySet: session.titleManuallySet };
|
|
13
|
+
session.title = title;
|
|
14
|
+
session.titleProvisional = false;
|
|
15
|
+
session.titleAutoGenerated = false;
|
|
16
|
+
session.titleManuallySet = true;
|
|
17
|
+
try { if (sm.saveSessionFile(session) === false) throw new Error("Could not save session title."); }
|
|
18
|
+
catch (error) { Object.assign(session, previous); throw error; }
|
|
19
|
+
sm.notifySessionRenamed(session.localId);
|
|
20
|
+
sm.broadcastSessionList();
|
|
21
|
+
var adapter = ctx.adapters && ctx.adapters[session.vendor] || ctx.adapter;
|
|
22
|
+
if (session.cliSessionId && adapter && adapter.renameSession) {
|
|
23
|
+
Promise.resolve(adapter.renameSession(session.cliSessionId, title, { dir: ctx.cwd, linuxUser: ctx.getLinuxUserForSession && ctx.getLinuxUserForSession(session) || undefined })).catch(function (error) {
|
|
24
|
+
console.error("[session-actions] Provider title sync failed:", error.message);
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
return { title: title };
|
|
28
|
+
}
|
|
29
|
+
function stop(session, actor) {
|
|
30
|
+
exact(session);
|
|
31
|
+
if (ctx.autonomousRun && session.autonomousRun) ctx.autonomousRun.finish(session, "stopped", "Stopped by the user.");
|
|
32
|
+
if (typeof ctx.sdk.cancelResponseRecovery === "function") ctx.sdk.cancelResponseRecovery(session);
|
|
33
|
+
session._pendingMessageDrainPaused = true;
|
|
34
|
+
if (ctx.onPendingQueuePause) ctx.onPendingQueuePause(session, actor || null);
|
|
35
|
+
if (!session.isProcessing && !session._queryStarting && !session._responseRecoveryStopPending) return { status: "completed", queuePaused: true };
|
|
36
|
+
if (ctx.onHumanPairStop) ctx.onHumanPairStop(session);
|
|
37
|
+
session.taskStopRequested = true;
|
|
38
|
+
if (session.abortController) session.abortController.abort();
|
|
39
|
+
return { status: "queued", queuePaused: true, detail: "Stop requested; runtime completion is still pending." };
|
|
40
|
+
}
|
|
41
|
+
async function effort(session, value, revalidate) {
|
|
42
|
+
exact(session);
|
|
43
|
+
var vendor = session.vendor || sm.defaultVendor || "claude";
|
|
44
|
+
if (yoke.clampEffort(vendor, value) !== value) throw new Error("This effort level is not supported by the selected vendor.");
|
|
45
|
+
var result = await ctx.sdk.setEffort(session, value, revalidate ? { revalidate: revalidate } : undefined);
|
|
46
|
+
if (revalidate) return result;
|
|
47
|
+
sm.sendToSession(session, { type: "config_state", sessionId: session.localId, vendor: vendor, effort: value, model: session.model || ((sm.defaultModelByVendor || {})[vendor]) || "", mode: session.permissionMode || sm.currentPermissionMode || "default", betas: sm.currentBetas || [], thinking: sm.currentThinking || "adaptive", thinkingBudget: sm.currentThinkingBudget || 10000 });
|
|
48
|
+
return { effort: value };
|
|
49
|
+
}
|
|
50
|
+
async function permission(session, value, requestId, revalidate) {
|
|
51
|
+
exact(session);
|
|
52
|
+
if ((session.runtimeMode || session.mode || "gui") !== "gui" || ctx.isDriverOperatedSession && ctx.isDriverOperatedSession(session)) throw new Error("Permissions require a GUI Driver session.");
|
|
53
|
+
if (ctx.dangerouslySkipPermissions) throw new Error("Skip permissions is forced by the server setting.");
|
|
54
|
+
var valid = permissionModes.validate(session, value, sm.defaultVendor);
|
|
55
|
+
if (!valid.ok) throw new Error(valid.error);
|
|
56
|
+
if (session.permissionMode === "bypassPermissions" && valid.mode !== "bypassPermissions" && ctx.autonomousRun) await ctx.autonomousRun.stopForPermissionChange(session);
|
|
57
|
+
var result = await ctx.fullAccessService.setMode(session, valid.mode, revalidate ? { revalidate: revalidate } : undefined);
|
|
58
|
+
if (revalidate) return result;
|
|
59
|
+
var state = permissionModes.clientState(session, sm.currentPermissionMode, sm.defaultVendor);
|
|
60
|
+
sm.sendToSession(session, Object.assign({ type: "config_state", sessionId: session.localId, vendor: session.vendor || sm.defaultVendor || "claude", mode: state.requestedPermissionMode, permissionRequestId: requestId || null, effort: session.effort || sm.currentEffort || "medium", betas: sm.currentBetas || [] }, state));
|
|
61
|
+
return state;
|
|
62
|
+
}
|
|
63
|
+
return { rename: rename, stop: stop, effort: effort, permission: permission };
|
|
64
|
+
}
|
|
65
|
+
module.exports = { attachSessionActions: attachSessionActions };
|
|
@@ -97,6 +97,13 @@ function attachSessionDelete(ctx) {
|
|
|
97
97
|
sm.deleteSessionsBulk(plan.ids, ws);
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
+
function executeOwnedDeletion(plan) {
|
|
101
|
+
if (!plan || !plan.ok || !plan.ids.length) throw new Error("No exact deletion plan.");
|
|
102
|
+
releaseRuntime(plan.ids);
|
|
103
|
+
plan.ids.forEach(function (id) { sm.deleteSessionQuiet(id); });
|
|
104
|
+
sm.broadcastSessionList();
|
|
105
|
+
}
|
|
106
|
+
|
|
100
107
|
function bulkDeleteSessions(ws, msg) {
|
|
101
108
|
if (!Array.isArray(msg.sessionIds) || msg.sessionIds.length === 0) return;
|
|
102
109
|
if (!hasDeletePermission(ws)) return refuse(ws, "You do not have permission to delete sessions");
|
|
@@ -113,7 +120,7 @@ function attachSessionDelete(ctx) {
|
|
|
113
120
|
executeDeletion(ws, scope);
|
|
114
121
|
}
|
|
115
122
|
|
|
116
|
-
return { deleteSession: deleteSession, bulkDeleteSessions: bulkDeleteSessions, planDeletion: planDeletion, executeDeletion: executeDeletion, hasDeletePermission: hasDeletePermission };
|
|
123
|
+
return { deleteSession: deleteSession, bulkDeleteSessions: bulkDeleteSessions, planDeletion: planDeletion, executeDeletion: executeDeletion, executeOwnedDeletion: executeOwnedDeletion, hasDeletePermission: hasDeletePermission };
|
|
117
124
|
}
|
|
118
125
|
|
|
119
126
|
module.exports = { attachSessionDelete: attachSessionDelete, resolveDeletionScope: resolveDeletionScope };
|