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
package/lib/project-sessions.js
CHANGED
|
@@ -118,14 +118,20 @@ function attachSessions(ctx) {
|
|
|
118
118
|
var ensureProjectAccessForSession = ctx.ensureProjectAccessForSession;
|
|
119
119
|
var getOsUserInfoForWs = ctx.getOsUserInfoForWs;
|
|
120
120
|
var hydrateSkillCatalog = ctx.hydrateSkillCatalog || function () { return []; };
|
|
121
|
+
var sessionActions = require("./project-session-actions").attachSessionActions(ctx);
|
|
122
|
+
var sessionVendor = require("./project-session-vendor").attachSessionVendor(ctx);
|
|
123
|
+
var sessionRuntime = require("./project-session-runtime-actions").attachSessionRuntimeActions({ sm: sm, tm: tm, prepareGui: prepareTuiSessionForGuiView, isPendingQueuePaused: ctx.isPendingQueuePaused,
|
|
124
|
+
publishGui: function (session) {
|
|
125
|
+
clients.forEach(function (ws) {
|
|
126
|
+
if (ws.readyState !== 1 || ws._clayActiveSession !== session.localId) return;
|
|
127
|
+
if (usersModule.isMultiUser() && (!ws._clayUser || !usersModule.findUserById(ws._clayUser.id) || !usersModule.canAccessProject(ws._clayUser.id, getProjectAccess()) || !usersModule.canAccessSession(ws._clayUser.id, session, getProjectAccess()))) return;
|
|
128
|
+
sm.switchSession(session.localId, ws, ctx.hydrateImageRefs);
|
|
129
|
+
});
|
|
130
|
+
},
|
|
131
|
+
});
|
|
121
132
|
function stopSession(session, actor) {
|
|
122
133
|
if (!session) return false;
|
|
123
|
-
session
|
|
124
|
-
if (typeof ctx.onPendingQueuePause === "function") ctx.onPendingQueuePause(session, actor || null);
|
|
125
|
-
if (!session.isProcessing && !session._queryStarting) return true;
|
|
126
|
-
if (typeof ctx.onHumanPairStop === "function") ctx.onHumanPairStop(session);
|
|
127
|
-
session.taskStopRequested = true;
|
|
128
|
-
if (session.abortController) session.abortController.abort();
|
|
134
|
+
sessionActions.stop(session, actor);
|
|
129
135
|
return true;
|
|
130
136
|
}
|
|
131
137
|
var hydrateImageRefs = ctx.hydrateImageRefs;
|
|
@@ -149,9 +155,9 @@ function attachSessions(ctx) {
|
|
|
149
155
|
var answers = msg.answers || {};
|
|
150
156
|
var pending = session.pendingAskUser && session.pendingAskUser[toolId];
|
|
151
157
|
if (!pending) return false;
|
|
152
|
-
if (pending.request && pending.request.source
|
|
158
|
+
if (pending.request && ["mcp-import", "product-control"].indexOf(pending.request.source) >= 0 &&
|
|
153
159
|
(typeof pending.request.authorizeResponse !== "function" || !pending.request.authorizeResponse(ws))) return false;
|
|
154
|
-
if (pending.request && pending.request.source
|
|
160
|
+
if (pending.request && ["work-primary", "mate-schedule"].indexOf(pending.request.source) >= 0 && usersModule.isMultiUser() &&
|
|
155
161
|
(!ws || !ws._clayUser || ws._clayUser.id !== session.ownerId)) return false;
|
|
156
162
|
if (pending.mode === "yoke") {
|
|
157
163
|
try { yoke.userInput.normalizeAnswers(pending.request.questions, answers); }
|
|
@@ -213,6 +219,10 @@ function attachSessions(ctx) {
|
|
|
213
219
|
var setLatestVersion = ctx.setLatestVersion;
|
|
214
220
|
var loadContextSources = ctx.loadContextSources;
|
|
215
221
|
|
|
222
|
+
var sessionHistory = require("./project-session-history-actions").attachSessionHistoryActions(Object.assign({}, ctx, {
|
|
223
|
+
resolveSessionHome: resolveSessionHome, cancelPendingAskUser: cancelPendingAskUser,
|
|
224
|
+
}));
|
|
225
|
+
|
|
216
226
|
function canAccessProjectSlug(ws, targetSlug) {
|
|
217
227
|
if (!targetSlug) return false;
|
|
218
228
|
if (!usersModule || typeof usersModule.isMultiUser !== "function" || !usersModule.isMultiUser()) return true;
|
|
@@ -440,7 +450,7 @@ function attachSessions(ctx) {
|
|
|
440
450
|
// Idempotent: if the PTY is already gone and history is populated, this
|
|
441
451
|
// is a no-op (avoids re-reading jsonl and re-writing the session file on
|
|
442
452
|
// every click while the user is reading the session in GUI mode).
|
|
443
|
-
function prepareTuiSessionForGuiView(session) {
|
|
453
|
+
function prepareTuiSessionForGuiView(session, options) {
|
|
444
454
|
if (!session || session.cliSessionId == null) return;
|
|
445
455
|
var cliSess;
|
|
446
456
|
try { cliSess = require("./cli-sessions"); } catch (e) { return; }
|
|
@@ -467,7 +477,7 @@ function attachSessions(ctx) {
|
|
|
467
477
|
try { tm.close(session.terminalId); } catch (e) {}
|
|
468
478
|
}
|
|
469
479
|
session.terminalId = null;
|
|
470
|
-
try { sm.saveSessionFile(session); } catch (e) {}
|
|
480
|
+
if (!options || !options.deferSave) { try { sm.saveSessionFile(session); } catch (e) {} }
|
|
471
481
|
}
|
|
472
482
|
|
|
473
483
|
// Resolve how a session should be presented to a viewer WITHOUT spawning a
|
|
@@ -768,11 +778,10 @@ function attachSessions(ctx) {
|
|
|
768
778
|
sendTo(ws, { type: "error", text: "Cannot transfer ownership of home directory projects." });
|
|
769
779
|
return true;
|
|
770
780
|
}
|
|
771
|
-
var projectOwnerId =
|
|
772
|
-
var isAdmin = ws._clayUser && ws._clayUser.role === "admin";
|
|
781
|
+
var projectOwnerId = getProjectAccess().ownerId;
|
|
773
782
|
var isProjectOwner = ws._clayUser && projectOwnerId && ws._clayUser.id === projectOwnerId;
|
|
774
|
-
if (!ws._clayUser ||
|
|
775
|
-
sendTo(ws, { type: "error", text: "Only project owners
|
|
783
|
+
if (!ws._clayUser || !isProjectOwner) {
|
|
784
|
+
sendTo(ws, { type: "error", text: "Only project owners can transfer ownership." });
|
|
776
785
|
return true;
|
|
777
786
|
}
|
|
778
787
|
var targetUser = msg.userId ? usersModule.findUserById(msg.userId) : null;
|
|
@@ -780,11 +789,11 @@ function attachSessions(ctx) {
|
|
|
780
789
|
sendTo(ws, { type: "error", text: "User not found." });
|
|
781
790
|
return true;
|
|
782
791
|
}
|
|
792
|
+
// Persist before publishing runtime ownership.
|
|
793
|
+
if (!opts.onProjectOwnerChanged) return true;
|
|
794
|
+
var savedOwner = opts.onProjectOwnerChanged(slug, targetUser.id);
|
|
795
|
+
if (savedOwner && savedOwner.error) { sendTo(ws, { type: "error", text: savedOwner.error }); return true; }
|
|
783
796
|
setProjectOwnerId(targetUser.id);
|
|
784
|
-
// Persist via daemon callback
|
|
785
|
-
if (opts.onProjectOwnerChanged) {
|
|
786
|
-
opts.onProjectOwnerChanged(slug, targetUser.id);
|
|
787
|
-
}
|
|
788
797
|
send({ type: "project_owner_changed", ownerId: targetUser.id, ownerName: targetUser.displayName || targetUser.username });
|
|
789
798
|
return true;
|
|
790
799
|
}
|
|
@@ -801,6 +810,7 @@ function attachSessions(ctx) {
|
|
|
801
810
|
// broadcasts session_switched. A live runtime keeps its actual mode
|
|
802
811
|
// for every viewer; idle sessions open in chat. Nothing is spawned here.
|
|
803
812
|
var xmTarget = sm.sessions.get(msg.id);
|
|
813
|
+
if (usersModule.isMultiUser() && (!ws._clayUser || !usersModule.canAccessSession(ws._clayUser.id, xmTarget, getProjectAccess()))) return true;
|
|
804
814
|
if (xmTarget && (!xmTarget.vendor || vendorSupportsTui(xmTarget.vendor))) {
|
|
805
815
|
// Single source of truth: live runtime wins (tui stays tui, gui stays
|
|
806
816
|
// gui); idle legacy sessions hydrate their transcript into chat.
|
|
@@ -939,19 +949,7 @@ function attachSessions(ctx) {
|
|
|
939
949
|
var s = sm.sessions.get(msg.id);
|
|
940
950
|
if (usersModule.isMultiUser() && (!ws._clayUser ||
|
|
941
951
|
!usersModule.canAccessSession(ws._clayUser.id, s, getProjectAccess()))) return true;
|
|
942
|
-
s
|
|
943
|
-
s.titleProvisional = false;
|
|
944
|
-
s.titleAutoGenerated = false;
|
|
945
|
-
s.titleManuallySet = true;
|
|
946
|
-
sm.saveSessionFile(s);
|
|
947
|
-
sm.notifySessionRenamed(s.localId);
|
|
948
|
-
sm.broadcastSessionList();
|
|
949
|
-
// Sync title to SDK session
|
|
950
|
-
if (s.cliSessionId) {
|
|
951
|
-
adapter.renameSession(s.cliSessionId, s.title, { dir: cwd }).catch(function(e) {
|
|
952
|
-
console.error("[project] SDK renameSession failed:", e.message);
|
|
953
|
-
});
|
|
954
|
-
}
|
|
952
|
+
sessionActions.rename(s, String(msg.title).substring(0, 100));
|
|
955
953
|
}
|
|
956
954
|
return true;
|
|
957
955
|
}
|
|
@@ -1086,7 +1084,6 @@ function attachSessions(ctx) {
|
|
|
1086
1084
|
|
|
1087
1085
|
if (msg.type === "stop") {
|
|
1088
1086
|
var session = getSessionForWs(ws);
|
|
1089
|
-
if (autonomousRun && session && session.autonomousRun) autonomousRun.finish(session, "stopped", "Stopped by the user.");
|
|
1090
1087
|
stopSession(session, ws && ws._clayUser);
|
|
1091
1088
|
return true;
|
|
1092
1089
|
}
|
|
@@ -1187,38 +1184,14 @@ function attachSessions(ctx) {
|
|
|
1187
1184
|
|
|
1188
1185
|
if (msg.type === "set_vendor" && msg.vendor) {
|
|
1189
1186
|
var vendorSession = getSessionForWs(ws);
|
|
1190
|
-
if (vendorSession)
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
if (alreadyBound) {
|
|
1197
|
-
console.warn("[project] set_vendor ignored: session " + vendorSession.localId +
|
|
1198
|
-
" is bound to '" + vendorSession.vendor + "', refused rebind to '" + msg.vendor + "'");
|
|
1199
|
-
} else {
|
|
1200
|
-
vendorSession.vendor = msg.vendor;
|
|
1201
|
-
if (vendorSession.permissionMode === "auto" && msg.vendor !== "claude") {
|
|
1202
|
-
vendorSession.permissionMode = "default";
|
|
1203
|
-
vendorSession.effectivePermissionMode = null;
|
|
1204
|
-
}
|
|
1205
|
-
if (vendorSession.vendor !== msg.vendor) vendorSession.model = "";
|
|
1206
|
-
sm.saveSessionFile(vendorSession);
|
|
1207
|
-
sm.broadcastSessionList();
|
|
1208
|
-
}
|
|
1209
|
-
}
|
|
1210
|
-
if (msg.vendor) {
|
|
1211
|
-
var vendorModels = (sm.modelsByVendor && sm.modelsByVendor[msg.vendor]) || [];
|
|
1212
|
-
sendTo(ws, {
|
|
1213
|
-
type: "model_info",
|
|
1214
|
-
model: "",
|
|
1215
|
-
models: vendorModels,
|
|
1216
|
-
vendor: msg.vendor, sessionId: vendorSession ? vendorSession.localId : null,
|
|
1217
|
-
availableVendors: sm.availableVendors || [],
|
|
1218
|
-
installedVendors: sm.installedVendors || [],
|
|
1219
|
-
});
|
|
1220
|
-
send({ type: "config_state", vendor: null, sessionId: null, mode: sm.currentPermissionMode || "default", effort: sm.currentEffort || "medium", betas: sm.currentBetas || [], thinking: sm.currentThinking || "adaptive", thinkingBudget: sm.currentThinkingBudget || 10000 });
|
|
1187
|
+
if (!vendorSession) return true;
|
|
1188
|
+
// Re-selecting the provider of an established thread does not rebind it.
|
|
1189
|
+
if (vendorSession.cliSessionId && vendorSession.vendor === msg.vendor) return true;
|
|
1190
|
+
try {
|
|
1191
|
+
var vendorResult = sessionVendor.setVendor(vendorSession, msg.vendor);
|
|
1192
|
+
if (vendorResult.status === "failed") sendTo(ws, { type: "error", text: vendorResult.error });
|
|
1221
1193
|
}
|
|
1194
|
+
catch (error) { sendTo(ws, { type: "error", text: error.message }); }
|
|
1222
1195
|
return true;
|
|
1223
1196
|
}
|
|
1224
1197
|
|
|
@@ -1286,15 +1259,7 @@ function attachSessions(ctx) {
|
|
|
1286
1259
|
if (!valid.ok) { permissionResult(false, valid.error, true); return true; }
|
|
1287
1260
|
(async function() {
|
|
1288
1261
|
try {
|
|
1289
|
-
|
|
1290
|
-
await autonomousRun.stopForPermissionChange(session);
|
|
1291
|
-
}
|
|
1292
|
-
await fullAccessService.setMode(session, valid.mode);
|
|
1293
|
-
var state = permissionModes.clientState(session, sm.currentPermissionMode, sm.defaultVendor);
|
|
1294
|
-
sm.sendToSession(session, { type: "config_state", sessionId: session.localId, vendor: session.vendor || sm.defaultVendor || "claude", mode: state.requestedPermissionMode,
|
|
1295
|
-
effectivePermissionMode: state.effectivePermissionMode, permissionRequestId: requestId,
|
|
1296
|
-
permissionCapabilities: state.permissionCapabilities, mcpPermissionModeOverrides: state.mcpPermissionModeOverrides,
|
|
1297
|
-
effort: session.effort || sm.currentEffort || "medium", betas: sm.currentBetas || [] });
|
|
1262
|
+
await sessionActions.permission(session, valid.mode, requestId);
|
|
1298
1263
|
permissionResult(true, null, true);
|
|
1299
1264
|
} catch (error) {
|
|
1300
1265
|
permissionResult(false, "Failed to change permission mode: " + (error.message || error), true);
|
|
@@ -1364,10 +1329,9 @@ function attachSessions(ctx) {
|
|
|
1364
1329
|
sendTo(ws, { type: "error", text: "This effort level is not supported by the selected vendor." });
|
|
1365
1330
|
return true;
|
|
1366
1331
|
}
|
|
1367
|
-
|
|
1332
|
+
sessionActions.effort(session, sessionEffort).catch(function (e) {
|
|
1368
1333
|
sendTo(ws, { type: "error", text: "Failed to change reasoning effort: " + (e.message || e) });
|
|
1369
1334
|
});
|
|
1370
|
-
sm.sendToSession(session, { type: "config_state", model: session.model || ((sm.defaultModelByVendor || {})[effortVendor]) || "", vendor: effortVendor, sessionId: session.localId, mode: session.permissionMode || sm.currentPermissionMode || "default", effort: sessionEffort, betas: sm.currentBetas || [], thinking: sm.currentThinking || "adaptive", thinkingBudget: sm.currentThinkingBudget || 10000 });
|
|
1371
1335
|
}
|
|
1372
1336
|
return true;
|
|
1373
1337
|
}
|
|
@@ -1444,92 +1408,9 @@ function attachSessions(ctx) {
|
|
|
1444
1408
|
sendTo(ws, { type: "rewind_error", text: "Rewind already in progress." });
|
|
1445
1409
|
return true;
|
|
1446
1410
|
}
|
|
1447
|
-
session.
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
(async function () {
|
|
1451
|
-
try {
|
|
1452
|
-
// File restoration (delegated to adapter via sdk-bridge)
|
|
1453
|
-
if (mode !== "chat") {
|
|
1454
|
-
await sdk.rewindExecuteFiles(session, msg.uuid);
|
|
1455
|
-
}
|
|
1456
|
-
|
|
1457
|
-
// Conversation rollback (skip for files-only mode)
|
|
1458
|
-
if (mode !== "files") {
|
|
1459
|
-
var targetIdx = -1;
|
|
1460
|
-
for (var i = 0; i < session.messageUUIDs.length; i++) {
|
|
1461
|
-
if (session.messageUUIDs[i].uuid === msg.uuid) {
|
|
1462
|
-
targetIdx = i;
|
|
1463
|
-
break;
|
|
1464
|
-
}
|
|
1465
|
-
}
|
|
1466
|
-
|
|
1467
|
-
// Codex's thread/revert cuts at a turn id, not a count. Read the
|
|
1468
|
-
// target entry's turnId BEFORE trimming local history below.
|
|
1469
|
-
var hasTurnsToRollBack = false;
|
|
1470
|
-
var targetTurnId = null;
|
|
1471
|
-
if (targetIdx >= 0) {
|
|
1472
|
-
targetTurnId = session.messageUUIDs[targetIdx].turnId || null;
|
|
1473
|
-
for (var ri = targetIdx; ri < session.messageUUIDs.length; ri++) {
|
|
1474
|
-
if (session.messageUUIDs[ri].type === "user") { hasTurnsToRollBack = true; break; }
|
|
1475
|
-
}
|
|
1476
|
-
}
|
|
1477
|
-
|
|
1478
|
-
// Notify adapter of conversation rollback (e.g. Codex thread/revert)
|
|
1479
|
-
// BEFORE trimming local history below. Deliberately not caught
|
|
1480
|
-
// here: a failed or impossible (missing turnId) provider-side
|
|
1481
|
-
// rollback must abort the whole rewind via the outer catch, never
|
|
1482
|
-
// leave local history trimmed ahead of the real provider thread.
|
|
1483
|
-
if (hasTurnsToRollBack) {
|
|
1484
|
-
await sdk.rollbackConversation(session, targetTurnId);
|
|
1485
|
-
}
|
|
1486
|
-
|
|
1487
|
-
if (targetIdx >= 0) {
|
|
1488
|
-
var trimTo = session.messageUUIDs[targetIdx].historyIndex;
|
|
1489
|
-
for (var k = trimTo - 1; k >= 0; k--) {
|
|
1490
|
-
if (session.history[k].type === "user_message") {
|
|
1491
|
-
trimTo = k;
|
|
1492
|
-
break;
|
|
1493
|
-
}
|
|
1494
|
-
}
|
|
1495
|
-
session.history = session.history.slice(0, trimTo);
|
|
1496
|
-
session.messageUUIDs = session.messageUUIDs.slice(0, targetIdx);
|
|
1497
|
-
// Reset digest checkpoint if it points past the trimmed history
|
|
1498
|
-
if (typeof session._dmLastDigestedIndex === "number" && session._dmLastDigestedIndex > trimTo) {
|
|
1499
|
-
session._dmLastDigestedIndex = trimTo;
|
|
1500
|
-
}
|
|
1501
|
-
}
|
|
1502
|
-
|
|
1503
|
-
var kept = session.messageUUIDs;
|
|
1504
|
-
session.lastRewindUuid = kept.length > 0 ? kept[kept.length - 1].uuid : null;
|
|
1505
|
-
}
|
|
1506
|
-
|
|
1507
|
-
if (session.abortController) {
|
|
1508
|
-
try { session.abortController.abort(); } catch (e) {}
|
|
1509
|
-
}
|
|
1510
|
-
if (session.messageQueue) {
|
|
1511
|
-
try { session.messageQueue.end(); } catch (e) {}
|
|
1512
|
-
}
|
|
1513
|
-
session.queryInstance = null;
|
|
1514
|
-
session.messageQueue = null;
|
|
1515
|
-
session.abortController = null;
|
|
1516
|
-
session.blocks = {};
|
|
1517
|
-
session.sentToolResults = {};
|
|
1518
|
-
session.pendingPermissions = {};
|
|
1519
|
-
cancelPendingAskUser(session, "Conversation rewound");
|
|
1520
|
-
session.isProcessing = false;
|
|
1521
|
-
onProcessingChanged();
|
|
1522
|
-
|
|
1523
|
-
sm.saveSessionFile(session);
|
|
1524
|
-
sm.switchSession(session.localId, ws, hydrateImageRefs);
|
|
1525
|
-
sm.sendAndRecord(session, { type: "rewind_complete", mode: mode });
|
|
1526
|
-
sm.broadcastSessionList();
|
|
1527
|
-
} catch (err) {
|
|
1528
|
-
sendTo(ws, { type: "rewind_error", text: "Rewind failed: " + err.message });
|
|
1529
|
-
} finally {
|
|
1530
|
-
session._rewindInProgress = false;
|
|
1531
|
-
}
|
|
1532
|
-
})();
|
|
1411
|
+
sessionHistory.rewind(session, msg.uuid, msg.mode || "both", ws, function () {}).catch(function (error) {
|
|
1412
|
+
sendTo(ws, { type: "rewind_error", text: "Rewind failed: " + error.message });
|
|
1413
|
+
});
|
|
1533
1414
|
return true;
|
|
1534
1415
|
}
|
|
1535
1416
|
|
|
@@ -1539,62 +1420,10 @@ function attachSessions(ctx) {
|
|
|
1539
1420
|
sendTo(ws, { type: "error", text: "Cannot fork: no CLI session" });
|
|
1540
1421
|
return true;
|
|
1541
1422
|
}
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
// Copy local history up to the target UUID
|
|
1547
|
-
var targetIdx = -1;
|
|
1548
|
-
for (var fi = 0; fi < session.messageUUIDs.length; fi++) {
|
|
1549
|
-
if (session.messageUUIDs[fi].uuid === msg.uuid) { targetIdx = fi; break; }
|
|
1550
|
-
}
|
|
1551
|
-
var forkHistory = [];
|
|
1552
|
-
if (targetIdx >= 0) {
|
|
1553
|
-
var trimTo = session.messageUUIDs[targetIdx].historyIndex;
|
|
1554
|
-
forkHistory = session.history.slice(0, trimTo);
|
|
1555
|
-
} else {
|
|
1556
|
-
forkHistory = session.history.slice();
|
|
1557
|
-
}
|
|
1558
|
-
var forked = sm.createSession({
|
|
1559
|
-
vendor: session.vendor,
|
|
1560
|
-
ownerId: session.ownerId || null,
|
|
1561
|
-
userKnowledgeOwnerId: session.userKnowledgeOwnerId || null,
|
|
1562
|
-
sessionVisibility: sessionVisibility.normalize(session.sessionVisibility),
|
|
1563
|
-
}, ws);
|
|
1564
|
-
forked.cliSessionId = result.sessionId;
|
|
1565
|
-
forked.title = forkTitle;
|
|
1566
|
-
forked.history = forkHistory;
|
|
1567
|
-
forked.messageUUIDs = [];
|
|
1568
|
-
for (var hi = 0; hi < forkHistory.length; hi++) {
|
|
1569
|
-
if (forkHistory[hi].type === "message_uuid") {
|
|
1570
|
-
forked.messageUUIDs.push({ uuid: forkHistory[hi].uuid, type: forkHistory[hi].messageType, historyIndex: hi, turnId: forkHistory[hi].turnId || null });
|
|
1571
|
-
}
|
|
1572
|
-
}
|
|
1573
|
-
sm.saveSessionFile(forked);
|
|
1574
|
-
sm.switchSession(forked.localId, ws, hydrateImageRefs);
|
|
1575
|
-
sendTo(ws, { type: "fork_complete", sessionId: forked.localId });
|
|
1576
|
-
sm.broadcastSessionList();
|
|
1577
|
-
} else {
|
|
1578
|
-
// Read history from CLI session files
|
|
1579
|
-
var cliSess = require("./cli-sessions");
|
|
1580
|
-
return cliSess.readCliSessionHistory(resolveSessionHome(session), cwd, result.sessionId).then(function(history) {
|
|
1581
|
-
var forked = sm.resumeSession(result.sessionId, {
|
|
1582
|
-
history: history,
|
|
1583
|
-
title: forkTitle,
|
|
1584
|
-
vendor: session.vendor,
|
|
1585
|
-
ownerId: session.ownerId || null,
|
|
1586
|
-
userKnowledgeOwnerId: session.userKnowledgeOwnerId || null,
|
|
1587
|
-
sessionVisibility: sessionVisibility.normalize(session.sessionVisibility),
|
|
1588
|
-
sessionVisibilityExplicit: session.sessionVisibilityExplicit === true,
|
|
1589
|
-
}, ws);
|
|
1590
|
-
if (forked) {
|
|
1591
|
-
ws._clayActiveSession = forked.localId;
|
|
1592
|
-
sendTo(ws, { type: "fork_complete", sessionId: forked.localId });
|
|
1593
|
-
}
|
|
1594
|
-
});
|
|
1595
|
-
}
|
|
1596
|
-
}).catch(function(e) {
|
|
1597
|
-
sendTo(ws, { type: "error", text: "Fork failed: " + (e.message || e) });
|
|
1423
|
+
sessionHistory.fork(session, msg.uuid, ws, function () {}).then(function (forked) {
|
|
1424
|
+
if (forked) sendTo(ws, { type: "fork_complete", sessionId: forked.localId });
|
|
1425
|
+
}).catch(function (error) {
|
|
1426
|
+
sendTo(ws, { type: "error", text: "Fork failed: " + error.message });
|
|
1598
1427
|
});
|
|
1599
1428
|
return true;
|
|
1600
1429
|
}
|
|
@@ -1809,14 +1638,6 @@ function attachSessions(ctx) {
|
|
|
1809
1638
|
if (msg.type === "browse_dir") {
|
|
1810
1639
|
var rawPath = (msg.path || "").replace(/^~/, require("./config").REAL_HOME);
|
|
1811
1640
|
var absTarget = path.resolve(rawPath);
|
|
1812
|
-
// Multi-user mode: non-admins can only browse their home directory
|
|
1813
|
-
if (osUsers && osUsers.length > 0 && ws._clayUser && ws._clayUser.role !== "admin") {
|
|
1814
|
-
var browseHome = ws._clayUser.linuxUser ? "/home/" + ws._clayUser.linuxUser : null;
|
|
1815
|
-
if (!browseHome || (absTarget !== browseHome && (absTarget + "/").indexOf(browseHome + "/") !== 0)) {
|
|
1816
|
-
sendTo(ws, { type: "browse_dir_result", path: msg.path, entries: [], error: "Access restricted to your home directory" });
|
|
1817
|
-
return true;
|
|
1818
|
-
}
|
|
1819
|
-
}
|
|
1820
1641
|
var parentDir, prefix;
|
|
1821
1642
|
try {
|
|
1822
1643
|
var stat = fs.statSync(absTarget);
|
|
@@ -1833,12 +1654,17 @@ function attachSessions(ctx) {
|
|
|
1833
1654
|
parentDir = path.dirname(absTarget);
|
|
1834
1655
|
prefix = path.basename(absTarget).toLowerCase();
|
|
1835
1656
|
}
|
|
1657
|
+
if (opts.canUsePath && !opts.canUsePath(ws._clayUser && ws._clayUser.id, parentDir)) {
|
|
1658
|
+
sendTo(ws, { type: "browse_dir_result", entries: [], error: "Directory access is not permitted" });
|
|
1659
|
+
return true;
|
|
1660
|
+
}
|
|
1836
1661
|
try {
|
|
1837
1662
|
var dirItems = fs.readdirSync(parentDir, { withFileTypes: true });
|
|
1838
1663
|
var dirEntries = [];
|
|
1839
1664
|
for (var di = 0; di < dirItems.length; di++) {
|
|
1840
1665
|
var d = dirItems[di];
|
|
1841
1666
|
if (!d.isDirectory()) continue;
|
|
1667
|
+
if (opts.canUsePath && !opts.canUsePath(ws._clayUser && ws._clayUser.id, path.join(parentDir, d.name))) continue;
|
|
1842
1668
|
if (d.name.charAt(0) === ".") continue;
|
|
1843
1669
|
if (IGNORED_DIRS.has(d.name)) continue;
|
|
1844
1670
|
if (prefix && !d.name.toLowerCase().startsWith(prefix)) continue;
|
|
@@ -1856,17 +1682,9 @@ function attachSessions(ctx) {
|
|
|
1856
1682
|
if (msg.type === "add_project") {
|
|
1857
1683
|
var addPath = (msg.path || "").replace(/^~/, require("./config").REAL_HOME);
|
|
1858
1684
|
var addAbs = path.resolve(addPath);
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
sendTo(ws, { type: "add_project_result", ok: false, error: "No Linux user assigned" });
|
|
1863
|
-
return true;
|
|
1864
|
-
}
|
|
1865
|
-
var userHome = "/home/" + ws._clayUser.linuxUser;
|
|
1866
|
-
if (addAbs !== userHome && (addAbs + "/").indexOf(userHome + "/") !== 0) {
|
|
1867
|
-
sendTo(ws, { type: "add_project_result", ok: false, error: "Path not allowed. You can only add directories under " + userHome });
|
|
1868
|
-
return true;
|
|
1869
|
-
}
|
|
1685
|
+
if (opts.canUsePath && !opts.canUsePath(ws._clayUser && ws._clayUser.id, addAbs)) {
|
|
1686
|
+
sendTo(ws, { type: "add_project_result", ok: false, error: "Directory access is not permitted" });
|
|
1687
|
+
return true;
|
|
1870
1688
|
}
|
|
1871
1689
|
try {
|
|
1872
1690
|
var addStat = fs.statSync(addAbs);
|
|
@@ -1880,7 +1698,7 @@ function attachSessions(ctx) {
|
|
|
1880
1698
|
}
|
|
1881
1699
|
if (typeof opts.onAddProject === "function") {
|
|
1882
1700
|
var result = opts.onAddProject(addAbs, ws._clayUser);
|
|
1883
|
-
sendTo(ws, {
|
|
1701
|
+
sendTo(ws, Object.assign({}, result, { type: "add_project_result" }));
|
|
1884
1702
|
} else {
|
|
1885
1703
|
sendTo(ws, { type: "add_project_result", ok: false, error: "Not supported" });
|
|
1886
1704
|
}
|
|
@@ -1905,7 +1723,7 @@ function attachSessions(ctx) {
|
|
|
1905
1723
|
}
|
|
1906
1724
|
if (typeof opts.onCreateProject === "function") {
|
|
1907
1725
|
var createResult = opts.onCreateProject(createName, ws._clayUser);
|
|
1908
|
-
sendTo(ws, {
|
|
1726
|
+
sendTo(ws, Object.assign({}, createResult, { type: "add_project_result" }));
|
|
1909
1727
|
} else {
|
|
1910
1728
|
sendTo(ws, { type: "add_project_result", ok: false, error: "Not supported" });
|
|
1911
1729
|
}
|
|
@@ -1995,10 +1813,10 @@ function attachSessions(ctx) {
|
|
|
1995
1813
|
moveAllSchedulesToProject(removeSlug, msg.moveTasksTo);
|
|
1996
1814
|
}
|
|
1997
1815
|
if (typeof opts.onRemoveProject === "function") {
|
|
1998
|
-
// Send result before removing so the WS is still open
|
|
1999
|
-
sendTo(ws, { type: "remove_project_result", ok: true, slug: removeSlug });
|
|
2000
1816
|
var removeUserId = ws._clayUser ? ws._clayUser.id : null;
|
|
2001
|
-
opts.onRemoveProject(removeSlug, removeUserId);
|
|
1817
|
+
Promise.resolve().then(function () { return opts.onRemoveProject(removeSlug, removeUserId); }).then(function (result) {
|
|
1818
|
+
sendTo(ws, { type: "remove_project_result", ok: !!result && result.ok === true, slug: removeSlug, error: result && result.error });
|
|
1819
|
+
}, function (error) { sendTo(ws, { type: "remove_project_result", ok: false, slug: removeSlug, error: error.message }); });
|
|
2002
1820
|
} else {
|
|
2003
1821
|
sendTo(ws, { type: "remove_project_result", ok: false, error: "Not supported" });
|
|
2004
1822
|
}
|
|
@@ -2044,7 +1862,7 @@ function attachSessions(ctx) {
|
|
|
2044
1862
|
}
|
|
2045
1863
|
if (typeof opts.onSetProjectTitle === "function") {
|
|
2046
1864
|
var titleResult = opts.onSetProjectTitle(msg.slug, msg.title || null);
|
|
2047
|
-
sendTo(ws, { type: "set_project_title_result",
|
|
1865
|
+
sendTo(ws, Object.assign({}, titleResult, { type: "set_project_title_result", slug: msg.slug }));
|
|
2048
1866
|
} else {
|
|
2049
1867
|
sendTo(ws, { type: "set_project_title_result", ok: false, error: "Not supported" });
|
|
2050
1868
|
}
|
|
@@ -2198,6 +2016,11 @@ function attachSessions(ctx) {
|
|
|
2198
2016
|
return handleSessionsMessage(ws, { type: "permission_response", requestId: msg.requestId, decision: msg.decision });
|
|
2199
2017
|
},
|
|
2200
2018
|
stopHomeSession: stopSession,
|
|
2019
|
+
sessionActions: sessionActions,
|
|
2020
|
+
sessionHistoryActions: sessionHistory,
|
|
2021
|
+
sessionVendorActions: sessionVendor,
|
|
2022
|
+
sessionRuntimeActions: sessionRuntime,
|
|
2023
|
+
deleteProductControlPlan: function (plan) { sessionDelete.executeOwnedDeletion(plan); },
|
|
2201
2024
|
};
|
|
2202
2025
|
}
|
|
2203
2026
|
|
|
@@ -118,6 +118,12 @@ function attachUserMessage(ctx) {
|
|
|
118
118
|
requestId: msg.requestId || null, ok: !!ok, error: error || null });
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
+
function noteUpdateResult(ws, msg, ok, note, error) {
|
|
122
|
+
if (!msg.requestId) return;
|
|
123
|
+
sendTo(ws, { type: "note_update_result", projectSlug: slug, noteId: msg.id || null,
|
|
124
|
+
requestId: msg.requestId, ok: !!ok, note: note || null, error: error || null });
|
|
125
|
+
}
|
|
126
|
+
|
|
121
127
|
// --------------- Main handler ---------------
|
|
122
128
|
|
|
123
129
|
function handleUserMessage(ws, msg, forcedSession, internal) {
|
|
@@ -155,7 +161,10 @@ function attachUserMessage(ctx) {
|
|
|
155
161
|
}
|
|
156
162
|
|
|
157
163
|
if (msg.type === "note_update") {
|
|
158
|
-
if (!msg.id)
|
|
164
|
+
if (!msg.id) {
|
|
165
|
+
noteUpdateResult(ws, msg, false, null, "Note id is required");
|
|
166
|
+
return true;
|
|
167
|
+
}
|
|
159
168
|
// A legacy `hidden` update is a lifecycle transition, so the actor is
|
|
160
169
|
// derived from the bound socket rather than taken from the message.
|
|
161
170
|
var noteChanges = msg;
|
|
@@ -166,10 +175,24 @@ function attachUserMessage(ctx) {
|
|
|
166
175
|
if (updated) {
|
|
167
176
|
send({ type: "note_updated", note: updated });
|
|
168
177
|
if (msg.text !== undefined || msg.hidden !== undefined) syncNotesKnowledge();
|
|
178
|
+
noteUpdateResult(ws, msg, true, updated, null);
|
|
179
|
+
} else {
|
|
180
|
+
noteUpdateResult(ws, msg, false, null, "Note update could not be saved");
|
|
169
181
|
}
|
|
170
182
|
return true;
|
|
171
183
|
}
|
|
172
184
|
|
|
185
|
+
if (msg.type === "note_close" && msg.humanClose === true) {
|
|
186
|
+
if (!canHumanManageNotes(ws)) { noteUpdateResult(ws, msg, false, null, "You are not authorized to close notes in this project."); return true; }
|
|
187
|
+
var result = nm.closeHuman(msg.id, msg, notesLifecycle.userActor(ws && ws._clayUser));
|
|
188
|
+
if (!result) { noteUpdateResult(ws, msg, false, null, "The note changed or could not be saved. Review it and retry."); return true; }
|
|
189
|
+
if (result.removed) send({ type: "note_deleted", id: result.removed.id });
|
|
190
|
+
else send({ type: "note_updated", note: result.note });
|
|
191
|
+
noteUpdateResult(ws, msg, true, result.note || null, null);
|
|
192
|
+
syncNotesKnowledge();
|
|
193
|
+
return true;
|
|
194
|
+
}
|
|
195
|
+
|
|
173
196
|
// Completing a note closes it. `note_delete` is the retired spelling and is
|
|
174
197
|
// deliberately routed to the same non-destructive close, so an older client
|
|
175
198
|
// still on the wire cannot erase a record.
|
|
@@ -443,6 +466,10 @@ function attachUserMessage(ctx) {
|
|
|
443
466
|
finishPendingEarly(false, "Pending messages cannot be delivered to a Driver-operated Split Worker");
|
|
444
467
|
return true;
|
|
445
468
|
}
|
|
469
|
+
if (!pendingInternal && ctx.resumeLifecycleProject) {
|
|
470
|
+
try { ctx.resumeLifecycleProject(ws); }
|
|
471
|
+
catch (error) { sendTo(ws, { type: "error", text: error.message }); return true; }
|
|
472
|
+
}
|
|
446
473
|
if (!pendingInternal && session._driverContinuationLease && pendingMessageQueue) {
|
|
447
474
|
var continuationQueued = pendingMessageQueue.admit(session, msg, ws && ws._clayUser);
|
|
448
475
|
if (continuationQueued.ok) {
|
|
@@ -719,6 +746,7 @@ function attachUserMessage(ctx) {
|
|
|
719
746
|
|
|
720
747
|
function pendingBlockReason() {
|
|
721
748
|
if (!pendingInternal) return null;
|
|
749
|
+
if (ctx.isLifecycleExecutionPaused && ctx.isLifecycleExecutionPaused()) return "Pending message dispatch paused";
|
|
722
750
|
if (!pendingMessageQueue.isClaimed(session, pendingInternal.item.id)) return "Pending message was cancelled";
|
|
723
751
|
if (pendingMessageQueue.isPaused(session) || session._pendingMessageDrainPaused || session.taskStopRequested) return "Pending message dispatch paused";
|
|
724
752
|
if (typeof ctx.authorizePendingDispatch !== "function" || !ctx.authorizePendingDispatch(session, ws && ws._clayUser, pendingInternal.item)) return "Pending message authorization changed";
|
|
@@ -941,6 +969,7 @@ function attachUserMessage(ctx) {
|
|
|
941
969
|
}
|
|
942
970
|
|
|
943
971
|
function consumePendingMessage(session) {
|
|
972
|
+
if (ctx.isLifecycleExecutionPaused && ctx.isLifecycleExecutionPaused()) return false;
|
|
944
973
|
if (!pendingMessageQueue) return false;
|
|
945
974
|
var consumed = pendingMessageQueue.consumeOne(session, function (item, complete) {
|
|
946
975
|
var actor = item.actorId && usersModule.findUserById(item.actorId);
|