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
|
@@ -8,11 +8,24 @@ function attachSessionGithub(ctx) {
|
|
|
8
8
|
var attempts = new Map();
|
|
9
9
|
var statusCache = new Map();
|
|
10
10
|
var statusQueue = Promise.resolve();
|
|
11
|
-
|
|
11
|
+
var viewerCache = new Map();
|
|
12
|
+
async function readViewerLogin(identity, viewerKey) {
|
|
13
|
+
var key = JSON.stringify([viewerKey || null, identity || null]);
|
|
14
|
+
var cached = viewerCache.get(key);
|
|
15
|
+
if (cached && cached.until > Date.now()) return cached.value;
|
|
16
|
+
var pending = (ctx.run || github.runGh)(ctx.cwd, ["api", "user"], identity).then(function (value) {
|
|
17
|
+
var login = String(value && value.login || "").trim().toLowerCase();
|
|
18
|
+
viewerCache.set(key, { value: login, until: Date.now() + 300000 });
|
|
19
|
+
return login;
|
|
20
|
+
});
|
|
21
|
+
viewerCache.set(key, { value: pending, until: Date.now() + 30000 });
|
|
22
|
+
try { return await pending; } catch (error) { viewerCache.delete(key); throw error; }
|
|
23
|
+
}
|
|
24
|
+
function readStatus(session, link, stillAllowed, viewerKey, allowPersonalReview) {
|
|
12
25
|
var identity = authorize(session);
|
|
13
|
-
var key = JSON.stringify([session.ownerId || null, identity && identity.uid, identity && identity.home, link.url.toLowerCase()]);
|
|
26
|
+
var key = JSON.stringify([session.ownerId || null, viewerKey || null, !!allowPersonalReview, identity && identity.uid, identity && identity.home, link.url.toLowerCase()]);
|
|
14
27
|
var cached = statusCache.get(key);
|
|
15
|
-
if (cached && (cached.pending || cached.until > Date.now())) return cached.pending || (cached.error ? Promise.reject(cached.error) : Promise.resolve(cached.value));
|
|
28
|
+
if (cached && (cached.pending || cached.until > Date.now())) return cached.pending || (cached.error ? Promise.reject(cached.error) : Promise.resolve({ value: cached.value, personalReviewState: cached.personalReviewState || "unknown" }));
|
|
16
29
|
var entry = { failures: cached ? cached.failures : 0 };
|
|
17
30
|
statusCache.set(key, entry);
|
|
18
31
|
entry.pending = statusQueue.then(async function () {
|
|
@@ -20,9 +33,17 @@ function attachSessionGithub(ctx) {
|
|
|
20
33
|
var currentIdentity = authorize(session);
|
|
21
34
|
if (JSON.stringify(currentIdentity) !== JSON.stringify(identity) || (stillAllowed && !stillAllowed())) throw new Error("Session access changed.");
|
|
22
35
|
entry.value = await github.readReference(ctx.cwd, link.url, identity, ctx.run, true);
|
|
36
|
+
entry.personalReviewState = "clear";
|
|
37
|
+
if (allowPersonalReview && entry.value.reviewRequests && entry.value.reviewRequests.length) {
|
|
38
|
+
try {
|
|
39
|
+
entry.personalReviewState = entry.value.reviewRequests.indexOf(await readViewerLogin(identity, viewerKey)) >= 0 ? "requested" : "clear";
|
|
40
|
+
} catch (viewerError) { entry.personalReviewState = "unknown"; }
|
|
41
|
+
}
|
|
42
|
+
if (!allowPersonalReview) entry.personalReviewState = "unknown";
|
|
43
|
+
delete entry.value.reviewRequests;
|
|
23
44
|
entry.failures = 0;
|
|
24
45
|
entry.until = Date.now() + (entry.value.state === "closed" || entry.value.state === "merged" ? 300000 : 45000);
|
|
25
|
-
return entry.value;
|
|
46
|
+
return { value: entry.value, personalReviewState: entry.personalReviewState };
|
|
26
47
|
} catch (error) {
|
|
27
48
|
entry.error = error;
|
|
28
49
|
entry.failures++;
|
|
@@ -51,21 +72,29 @@ function attachSessionGithub(ctx) {
|
|
|
51
72
|
if (ctx.sm.saveSessionFile(session) === false) throw new Error("Could not save GitHub links.");
|
|
52
73
|
ctx.sm.broadcastSessionList();
|
|
53
74
|
}
|
|
54
|
-
async function refresh(session, force, statusOnly, stillAllowed) {
|
|
75
|
+
async function refresh(session, force, statusOnly, stillAllowed, viewerKey, allowPersonalReview) {
|
|
55
76
|
var identity = authorize(session);
|
|
56
|
-
var id = session.localId + (statusOnly ? ":status" : ":full");
|
|
77
|
+
var id = session.localId + (statusOnly ? ":status:" + (viewerKey || "anonymous") : ":full");
|
|
57
78
|
if (inflight.has(id)) return inflight.get(id);
|
|
58
|
-
if (!force && Date.now() - (attempts.get(id) || 0) < (statusOnly ? 30000 : 60000)) return session.githubLinks || [];
|
|
79
|
+
if (!force && Date.now() - (attempts.get(id) || 0) < (statusOnly ? 30000 : 60000)) return { links: session.githubLinks || [], viewerReviewRequests: {} };
|
|
59
80
|
attempts.set(id, Date.now());
|
|
60
81
|
var pending = (async function () {
|
|
61
82
|
var links = (session.githubLinks || []).slice();
|
|
62
83
|
var updated = [];
|
|
84
|
+
var viewerReviewRequests = {};
|
|
63
85
|
for (var link of links) {
|
|
64
86
|
try {
|
|
65
87
|
if (stillAllowed && !stillAllowed()) throw new Error("Session access changed.");
|
|
66
|
-
|
|
88
|
+
if (statusOnly) {
|
|
89
|
+
var status = await readStatus(session, link, stillAllowed, viewerKey, allowPersonalReview);
|
|
90
|
+
updated.push(Object.assign({}, link, status.value, { stale: false }));
|
|
91
|
+
if (status.personalReviewState !== "unknown") viewerReviewRequests[link.url.toLowerCase()] = status.personalReviewState;
|
|
92
|
+
} else updated.push(await github.readReference(ctx.cwd, link.url, identity, ctx.run));
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
updated.push(Object.assign({}, link, { stale: true }));
|
|
96
|
+
if (statusOnly) viewerReviewRequests[link.url.toLowerCase()] = "unknown";
|
|
67
97
|
}
|
|
68
|
-
catch (error) { updated.push(Object.assign({}, link, { stale: true })); }
|
|
69
98
|
}
|
|
70
99
|
authorize(session);
|
|
71
100
|
if (stillAllowed && !stillAllowed()) throw new Error("Session access changed.");
|
|
@@ -81,7 +110,7 @@ function attachSessionGithub(ctx) {
|
|
|
81
110
|
return next;
|
|
82
111
|
});
|
|
83
112
|
if (changed) persist(session);
|
|
84
|
-
return session.githubLinks || [];
|
|
113
|
+
return { links: session.githubLinks || [], viewerReviewRequests: viewerReviewRequests };
|
|
85
114
|
})();
|
|
86
115
|
inflight.set(id, pending);
|
|
87
116
|
try { return await pending; } finally { inflight.delete(id); }
|
|
@@ -89,7 +118,8 @@ function attachSessionGithub(ctx) {
|
|
|
89
118
|
async function invoke(session, name, args) {
|
|
90
119
|
var identity = authorize(session);
|
|
91
120
|
if (name === "get_session_github") {
|
|
92
|
-
var
|
|
121
|
+
var refreshed = await refresh(session, false);
|
|
122
|
+
var links = refreshed.links;
|
|
93
123
|
var matches = [];
|
|
94
124
|
if (args.url) {
|
|
95
125
|
var url = github.parseReference(args.url).url.toLowerCase();
|
|
@@ -140,7 +170,12 @@ function attachSessionGithub(ctx) {
|
|
|
140
170
|
Array.from(new Set(ids)).forEach(function (id) {
|
|
141
171
|
var session = ctx.sm.sessions.get(id);
|
|
142
172
|
if (!session || !ctx.canRefresh(ws, session) || !(session.githubLinks || []).length) return;
|
|
143
|
-
|
|
173
|
+
var viewerId = ws._clayUser && ws._clayUser.id;
|
|
174
|
+
var allowPersonalReview = !session.ownerId || !viewerId || viewerId === session.ownerId;
|
|
175
|
+
refresh(session, false, true, function () { return ctx.canRefresh(ws, session); }, viewerId, allowPersonalReview).then(function (result) {
|
|
176
|
+
if (!allowPersonalReview || typeof ctx.sendTo !== "function" || !result.viewerReviewRequests || !ctx.canRefresh(ws, session)) return;
|
|
177
|
+
ctx.sendTo(ws, { type: "session_github_status", projectSlug: ctx.projectSlug || null, accountId: viewerId || null, requestId: msg.requestId || null, sessionId: session.localId, personalReview: result.viewerReviewRequests });
|
|
178
|
+
}).catch(function () {});
|
|
144
179
|
});
|
|
145
180
|
return true;
|
|
146
181
|
},
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
var sessionVisibility = require("./session-visibility");
|
|
2
|
+
function attachSessionHistoryActions(ctx) {
|
|
3
|
+
var sm = ctx.sm, sdk = ctx.sdk, cwd = ctx.cwd, hydrateImageRefs = ctx.hydrateImageRefs;
|
|
4
|
+
var resolveSessionHome = ctx.resolveSessionHome, cancelPendingAskUser = ctx.cancelPendingAskUser;
|
|
5
|
+
var onProcessingChanged = ctx.onProcessingChanged;
|
|
6
|
+
async function rewind(session, uuid, mode, ws, revalidate) {
|
|
7
|
+
if (session._rewindInProgress) throw new Error("Rewind already in progress.");
|
|
8
|
+
session._rewindInProgress = true;
|
|
9
|
+
try {
|
|
10
|
+
// File restoration (delegated to adapter via sdk-bridge)
|
|
11
|
+
if (mode !== "chat") {
|
|
12
|
+
await sdk.rewindExecuteFiles(session, uuid);
|
|
13
|
+
revalidate();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Conversation rollback (skip for files-only mode)
|
|
17
|
+
if (mode !== "files") {
|
|
18
|
+
var targetIdx = -1;
|
|
19
|
+
for (var i = 0; i < session.messageUUIDs.length; i++) {
|
|
20
|
+
if (session.messageUUIDs[i].uuid === uuid) {
|
|
21
|
+
targetIdx = i;
|
|
22
|
+
break;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Codex's thread/revert cuts at a turn id, not a count. Read the
|
|
27
|
+
// target entry's turnId BEFORE trimming local history below.
|
|
28
|
+
var hasTurnsToRollBack = false;
|
|
29
|
+
var targetTurnId = null;
|
|
30
|
+
if (targetIdx >= 0) {
|
|
31
|
+
targetTurnId = session.messageUUIDs[targetIdx].turnId || null;
|
|
32
|
+
for (var ri = targetIdx; ri < session.messageUUIDs.length; ri++) {
|
|
33
|
+
if (session.messageUUIDs[ri].type === "user") { hasTurnsToRollBack = true; break; }
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Notify adapter of conversation rollback (e.g. Codex thread/revert)
|
|
38
|
+
// BEFORE trimming local history below. Deliberately not caught
|
|
39
|
+
// here: a failed or impossible (missing turnId) provider-side
|
|
40
|
+
// rollback must abort the whole rewind via the outer catch, never
|
|
41
|
+
// leave local history trimmed ahead of the real provider thread.
|
|
42
|
+
if (hasTurnsToRollBack) {
|
|
43
|
+
await sdk.rollbackConversation(session, targetTurnId);
|
|
44
|
+
revalidate();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (targetIdx >= 0) {
|
|
48
|
+
var trimTo = session.messageUUIDs[targetIdx].historyIndex;
|
|
49
|
+
for (var k = trimTo - 1; k >= 0; k--) {
|
|
50
|
+
if (session.history[k].type === "user_message") {
|
|
51
|
+
trimTo = k;
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
session.history = session.history.slice(0, trimTo);
|
|
56
|
+
session.messageUUIDs = session.messageUUIDs.slice(0, targetIdx);
|
|
57
|
+
// Reset digest checkpoint if it points past the trimmed history
|
|
58
|
+
if (typeof session._dmLastDigestedIndex === "number" && session._dmLastDigestedIndex > trimTo) {
|
|
59
|
+
session._dmLastDigestedIndex = trimTo;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
var kept = session.messageUUIDs;
|
|
64
|
+
session.lastRewindUuid = kept.length > 0 ? kept[kept.length - 1].uuid : null;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (session.abortController) {
|
|
68
|
+
try { session.abortController.abort(); } catch (e) {}
|
|
69
|
+
}
|
|
70
|
+
if (session.messageQueue) {
|
|
71
|
+
try { session.messageQueue.end(); } catch (e) {}
|
|
72
|
+
}
|
|
73
|
+
session.queryInstance = null;
|
|
74
|
+
session.messageQueue = null;
|
|
75
|
+
session.abortController = null;
|
|
76
|
+
session.blocks = {};
|
|
77
|
+
session.sentToolResults = {};
|
|
78
|
+
session.pendingPermissions = {};
|
|
79
|
+
cancelPendingAskUser(session, "Conversation rewound");
|
|
80
|
+
session.isProcessing = false;
|
|
81
|
+
onProcessingChanged();
|
|
82
|
+
|
|
83
|
+
if (sm.saveSessionFile(session) === false) throw new Error("The conversation was rewound but could not be saved.");
|
|
84
|
+
if (ws) sm.switchSession(session.localId, ws, hydrateImageRefs);
|
|
85
|
+
sm.sendAndRecord(session, { type: "rewind_complete", mode: mode });
|
|
86
|
+
sm.broadcastSessionList();
|
|
87
|
+
} finally { session._rewindInProgress = false; }
|
|
88
|
+
return { mode: mode };
|
|
89
|
+
}
|
|
90
|
+
function saveFork(forked, ws) {
|
|
91
|
+
if (sm.saveSessionFile(forked) !== false) return;
|
|
92
|
+
var error = new Error("The provider fork exists and its local session is retained in memory, but the fork could not be saved. Do not create another fork to retry this save.");
|
|
93
|
+
if (!ws) error.productControlResult = { status: "failed", outcome: "partial", persisted: false, runtimeEffect: "applied", retrySafe: false,
|
|
94
|
+
retainedSession: true, forkedSessionRef: require("./workspace-query-service").sessionRef(ctx.slug, forked), error: error.message };
|
|
95
|
+
throw error;
|
|
96
|
+
}
|
|
97
|
+
async function fork(session, uuid, ws, revalidate) {
|
|
98
|
+
var forkTitle = (session.title || "New Session") + " (fork)";
|
|
99
|
+
var result = await sdk.forkSession(session, uuid);
|
|
100
|
+
revalidate();
|
|
101
|
+
if (result.useLocalHistory) {
|
|
102
|
+
// Copy local history up to the target UUID
|
|
103
|
+
var targetIdx = -1;
|
|
104
|
+
for (var fi = 0; fi < session.messageUUIDs.length; fi++) {
|
|
105
|
+
if (session.messageUUIDs[fi].uuid === uuid) { targetIdx = fi; break; }
|
|
106
|
+
}
|
|
107
|
+
var forkHistory = [];
|
|
108
|
+
if (targetIdx >= 0) {
|
|
109
|
+
var trimTo = session.messageUUIDs[targetIdx].historyIndex;
|
|
110
|
+
forkHistory = session.history.slice(0, trimTo);
|
|
111
|
+
} else {
|
|
112
|
+
forkHistory = session.history.slice();
|
|
113
|
+
}
|
|
114
|
+
var create = ws ? sm.createSession : sm.createSessionRaw;
|
|
115
|
+
var forked = create({
|
|
116
|
+
vendor: session.vendor,
|
|
117
|
+
ownerId: session.ownerId || null,
|
|
118
|
+
userKnowledgeOwnerId: session.userKnowledgeOwnerId || null,
|
|
119
|
+
sessionVisibility: sessionVisibility.normalize(session.sessionVisibility),
|
|
120
|
+
}, ws);
|
|
121
|
+
forked.cliSessionId = result.sessionId;
|
|
122
|
+
forked.title = forkTitle;
|
|
123
|
+
forked.history = forkHistory;
|
|
124
|
+
forked.messageUUIDs = [];
|
|
125
|
+
for (var hi = 0; hi < forkHistory.length; hi++) {
|
|
126
|
+
if (forkHistory[hi].type === "message_uuid") {
|
|
127
|
+
forked.messageUUIDs.push({ uuid: forkHistory[hi].uuid, type: forkHistory[hi].messageType, historyIndex: hi, turnId: forkHistory[hi].turnId || null });
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
saveFork(forked, ws);
|
|
131
|
+
if (ws) sm.switchSession(forked.localId, ws, hydrateImageRefs);
|
|
132
|
+
sm.broadcastSessionList();
|
|
133
|
+
return forked;
|
|
134
|
+
} else {
|
|
135
|
+
// Read history from CLI session files
|
|
136
|
+
var cliSess = require("./cli-sessions");
|
|
137
|
+
var history = await cliSess.readCliSessionHistory(resolveSessionHome(session), cwd, result.sessionId);
|
|
138
|
+
revalidate();
|
|
139
|
+
var options = {
|
|
140
|
+
history: history,
|
|
141
|
+
title: forkTitle,
|
|
142
|
+
vendor: session.vendor,
|
|
143
|
+
ownerId: session.ownerId || null,
|
|
144
|
+
userKnowledgeOwnerId: session.userKnowledgeOwnerId || null,
|
|
145
|
+
sessionVisibility: sessionVisibility.normalize(session.sessionVisibility),
|
|
146
|
+
sessionVisibilityExplicit: session.sessionVisibilityExplicit === true,
|
|
147
|
+
};
|
|
148
|
+
var forked;
|
|
149
|
+
if (ws) forked = sm.resumeSession(result.sessionId, options, ws);
|
|
150
|
+
else {
|
|
151
|
+
forked = sm.createSessionRaw(Object.assign({}, options, { cliSessionId: result.sessionId }));
|
|
152
|
+
forked.title = forkTitle;
|
|
153
|
+
forked.history = history;
|
|
154
|
+
saveFork(forked, ws);
|
|
155
|
+
sm.broadcastSessionList();
|
|
156
|
+
}
|
|
157
|
+
if (forked) {
|
|
158
|
+
if (ws) ws._clayActiveSession = forked.localId;
|
|
159
|
+
}
|
|
160
|
+
return forked;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return { fork: fork, rewind: rewind };
|
|
164
|
+
}
|
|
165
|
+
module.exports = { attachSessionHistoryActions: attachSessionHistoryActions };
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// Legacy terminal recovery only. New terminal presentation has been retired.
|
|
2
|
+
function attachSessionRuntimeActions(ctx) {
|
|
3
|
+
function inspect(session) {
|
|
4
|
+
return JSON.stringify([session.terminalId, session.runtimeTerminalId, session.tuiSuspended, [session.terminalId, session.runtimeTerminalId].map(function (id) { return typeof id === "number" && ctx.tm ? ctx.tm.identity(id) : null; })]);
|
|
5
|
+
}
|
|
6
|
+
async function change(session, mode, revalidate) {
|
|
7
|
+
revalidate();
|
|
8
|
+
if (mode !== "gui") throw new Error("New terminal presentation has been removed. Only return to GUI chat is supported; provider threads cannot migrate across vendors.");
|
|
9
|
+
if (session.isProcessing || session._queryStarting || session.queryInstance || Object.keys(session.pendingPermissions || {}).length || Object.keys(session.pendingAskUser || {}).length) throw new Error("Runtime changes require an idle session without an active provider handle or pending approvals.");
|
|
10
|
+
if (session.taskStopRequested || session._pendingMessageDrainPaused || ctx.isPendingQueuePaused && ctx.isPendingQueuePaused(session)) throw new Error("A stopped session cannot be resumed by a runtime change.");
|
|
11
|
+
var vendor = session.vendor || ctx.sm.defaultVendor || "claude";
|
|
12
|
+
var ids = Array.from(new Set([session.terminalId, session.runtimeTerminalId].filter(function (id) { return typeof id === "number"; })));
|
|
13
|
+
if (!ids.length && (session.runtimeMode || session.mode || "gui") === "gui") return { status: "completed", runtimeMode: "gui", runtimeEffect: "none", threadPreserved: true };
|
|
14
|
+
if (vendor !== "claude" || !session.cliSessionId) throw new Error("Only a legacy Claude terminal thread can return to GUI chat.");
|
|
15
|
+
var closed = 0, persisted = false;
|
|
16
|
+
var fields = ['terminalId', 'runtimeTerminalId'];
|
|
17
|
+
var originalFields = fields.map(function (field) { return session[field]; });
|
|
18
|
+
var targets = ids.map(function (id) { return { id: id, identity: ctx.tm && ctx.tm.identity(id), closed: false }; });
|
|
19
|
+
var suspended = session.tuiSuspended;
|
|
20
|
+
function exactTerminals() {
|
|
21
|
+
if (session.tuiSuspended !== suspended) throw new Error("Terminal presentation changed during shutdown.");
|
|
22
|
+
targets.forEach(function (target) {
|
|
23
|
+
var current = ctx.tm && ctx.tm.identity(target.id);
|
|
24
|
+
if (!target.identity || (target.closed ? current !== null : current !== target.identity)) throw new Error("A terminal runtime was replaced during shutdown.");
|
|
25
|
+
});
|
|
26
|
+
fields.forEach(function (field, index) {
|
|
27
|
+
var original = originalFields[index], current = session[field];
|
|
28
|
+
if (current === original || current == null && original == null) return;
|
|
29
|
+
var target = targets.find(function (item) { return item.id === original; });
|
|
30
|
+
if (current == null && target && target.closed) return;
|
|
31
|
+
throw new Error("A new or replacement terminal was attached during shutdown.");
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
try {
|
|
35
|
+
for (var target of targets) {
|
|
36
|
+
revalidate();
|
|
37
|
+
exactTerminals();
|
|
38
|
+
if (!ctx.tm || typeof ctx.tm.closeAndWait !== "function") throw new Error("Completion-aware terminal shutdown is unavailable.");
|
|
39
|
+
ctx.tm.markReclaimed(target.id);
|
|
40
|
+
await ctx.tm.closeAndWait(target.id); target.closed = true; closed++;
|
|
41
|
+
revalidate();
|
|
42
|
+
exactTerminals();
|
|
43
|
+
}
|
|
44
|
+
revalidate();
|
|
45
|
+
exactTerminals();
|
|
46
|
+
session.terminalId = null; session.runtimeTerminalId = null;
|
|
47
|
+
ctx.prepareGui(session, { deferSave: true });
|
|
48
|
+
exactTerminals();
|
|
49
|
+
session.runtimeMode = "gui"; session.tuiSuspended = false;
|
|
50
|
+
if (ctx.sm.saveSessionFile(session) !== true) throw new Error("GUI presentation could not be saved after terminal shutdown.");
|
|
51
|
+
persisted = true;
|
|
52
|
+
ctx.sm.broadcastSessionList();
|
|
53
|
+
ctx.publishGui(session);
|
|
54
|
+
return { status: "completed", runtimeMode: "gui", runtimeEffect: closed ? "terminal_closed" : "none", persisted: true, threadPreserved: true, executionStarted: false };
|
|
55
|
+
} catch (error) {
|
|
56
|
+
error.productControlResult = { status: "failed", outcome: "partial", persisted: persisted, runtimeEffect: closed ? "terminal_closed" : "closure_unconfirmed", retrySafe: false, executionStarted: false };
|
|
57
|
+
throw error;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function inspectLifecycle() { return ctx.tm.list().map(function (terminal) { return Object.assign({}, terminal, { identity: ctx.tm.identity(terminal.id) }); }); }
|
|
61
|
+
return { change: change, inspect: inspect, inspectLifecycle: inspectLifecycle };
|
|
62
|
+
}
|
|
63
|
+
module.exports = { attachSessionRuntimeActions: attachSessionRuntimeActions };
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
var yoke = require('./yoke');
|
|
2
|
+
function attachSessionVendor(ctx) {
|
|
3
|
+
function verifyIdentity(session, vendor) {
|
|
4
|
+
if (!ctx.osUsers) return;
|
|
5
|
+
var info = yoke.getVendorInfo(vendor), linuxUser = ctx.getLinuxUserForSession(session);
|
|
6
|
+
if (!info || !info.osUserIsolation || !linuxUser) throw new Error('The provider requires an authorized mapped OS identity.');
|
|
7
|
+
var identity = require('./os-users').resolveOsUserInfo(linuxUser);
|
|
8
|
+
if (!Number.isInteger(identity.uid) || identity.uid < 0 || !Number.isInteger(identity.gid) || identity.gid < 0) throw new Error('The mapped OS identity is unavailable.');
|
|
9
|
+
}
|
|
10
|
+
function setVendor(session, vendor, revalidate) {
|
|
11
|
+
if (!session || ctx.sm.sessions.get(session.localId) !== session) throw new Error('The Driver was deleted or replaced.');
|
|
12
|
+
if (revalidate) revalidate();
|
|
13
|
+
if (!(ctx.adapters || {})[vendor] || (ctx.sm.availableVendors || []).indexOf(vendor) < 0) throw new Error('This provider is unavailable in the project runtime.');
|
|
14
|
+
verifyIdentity(session, vendor);
|
|
15
|
+
if (session.cliSessionId || session.queryInstance || session.isProcessing || session._queryStarting || (session.history || []).length || (session.runtimeMode || session.mode || 'gui') !== 'gui') throw new Error('Provider selection requires a fresh idle GUI Driver without a provider thread or transcript.');
|
|
16
|
+
if (session.taskStopRequested || session._pendingMessageDrainPaused || ctx.isPendingQueuePaused && ctx.isPendingQueuePaused(session)) throw new Error('A stopped Driver requires an explicit separate resume action.');
|
|
17
|
+
var previous = { vendor: session.vendor, model: session.model, effort: session.effort, permissionMode: session.permissionMode, effectivePermissionMode: session.effectivePermissionMode };
|
|
18
|
+
session.vendor = vendor;
|
|
19
|
+
session.model = null;
|
|
20
|
+
session.effort = yoke.clampEffort(vendor, session.effort) || null;
|
|
21
|
+
if (session.permissionMode === 'auto' && vendor !== 'claude') session.permissionMode = 'default';
|
|
22
|
+
session.effectivePermissionMode = null;
|
|
23
|
+
try { if (ctx.sm.saveSessionFile(session) !== true) throw new Error('Could not persist the provider selection.'); }
|
|
24
|
+
catch (error) { Object.assign(session, previous); throw error; }
|
|
25
|
+
try {
|
|
26
|
+
ctx.sm.broadcastSessionList();
|
|
27
|
+
ctx.sm.sendToSession(session, { type: 'model_info', vendor: vendor, sessionId: session.localId, model: '', models: (ctx.sm.modelsByVendor || {})[vendor] || [], availableVendors: ctx.sm.availableVendors || [], installedVendors: ctx.sm.installedVendors || [] });
|
|
28
|
+
ctx.sm.sendToSession(session, { type: 'config_state', vendor: vendor, sessionId: session.localId, model: '', effort: session.effort, mode: session.permissionMode || ctx.sm.currentPermissionMode || 'default' });
|
|
29
|
+
} catch (error) { return { status: 'failed', outcome: 'partial', persisted: true, runtimeEffect: 'none', vendor: vendor, error: 'Provider selection saved, but publication failed: ' + error.message }; }
|
|
30
|
+
return { status: 'completed', persisted: true, runtimeEffect: 'none', vendor: vendor };
|
|
31
|
+
}
|
|
32
|
+
function newSession(source, vendor, revalidate) {
|
|
33
|
+
revalidate();
|
|
34
|
+
if (!(ctx.adapters || {})[vendor] || (ctx.sm.availableVendors || []).indexOf(vendor) < 0) throw new Error('This provider is unavailable in the project runtime.');
|
|
35
|
+
if (source.isProcessing || source._queryStarting || source.taskStopRequested || source._pendingMessageDrainPaused || ctx.isPendingQueuePaused && ctx.isPendingQueuePaused(source)) throw new Error('Create a replacement provider session only from an idle, unstopped Driver.');
|
|
36
|
+
verifyIdentity(source, vendor);
|
|
37
|
+
revalidate();
|
|
38
|
+
var created = ctx.sm.createSessionRaw({ ownerId: source.ownerId || null, vendor: vendor, mode: 'gui', sessionVisibility: 'private' });
|
|
39
|
+
var ref = require('./workspace-query-service').sessionRef(ctx.slug, created), persisted = false;
|
|
40
|
+
try {
|
|
41
|
+
revalidate();
|
|
42
|
+
if (ctx.sm.saveSessionFile(created) !== true) throw new Error('Could not save the new provider session.');
|
|
43
|
+
persisted = true;
|
|
44
|
+
revalidate();
|
|
45
|
+
ctx.sm.broadcastSessionList();
|
|
46
|
+
return { status: 'completed', persisted: true, newSessionRef: ref, vendor: vendor, threadMigrated: false, originalRetained: true, executionStarted: false };
|
|
47
|
+
} catch (error) {
|
|
48
|
+
error.productControlResult = { status: 'failed', outcome: 'partial', persisted: persisted, retainedInMemory: true, newSessionRef: ref, threadMigrated: false, originalRetained: true, executionStarted: false, retrySafe: false };
|
|
49
|
+
throw error;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return { setVendor: setVendor, newSession: newSession };
|
|
53
|
+
}
|
|
54
|
+
module.exports = { attachSessionVendor: attachSessionVendor };
|