clay-server 4.0.0-beta.1 → 4.0.0-beta.3
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/background-task-timing.js +72 -0
- package/lib/project-connection.js +4 -0
- package/lib/project-vendor-login.js +397 -0
- package/lib/project.js +20 -0
- package/lib/public/css/input.css +73 -25
- package/lib/public/modules/app-messages.js +24 -17
- package/lib/public/modules/app-notifications.js +4 -117
- package/lib/public/modules/background-tasks-ui.js +109 -15
- package/lib/public/modules/pane-bridge.js +9 -0
- package/lib/public/modules/split-view.js +7 -0
- package/lib/public/modules/tui-attention.js +10 -0
- package/lib/public/modules/vendor-login.js +287 -0
- package/lib/sdk-message-processor.js +10 -2
- package/lib/tools-registry.js +11 -1
- package/lib/ws-schema.js +7 -0
- package/lib/yoke/adapters/claude.js +6 -1
- package/lib/yoke/adapters/codex.js +3 -4
- package/lib/yoke/codex-app-server.js +20 -2
- package/lib/yoke/codex-background-tasks.js +8 -2
- package/package.json +2 -2
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
// vendor-login.js - Vendor login (auth_required) recovery flow.
|
|
2
|
+
//
|
|
3
|
+
// The server owns the login terminal (see lib/project-vendor-login.js): exactly
|
|
4
|
+
// one per vendor per project, tracked server-side, killed once the login lands.
|
|
5
|
+
// This module is the client half of that contract - it asks for the flow, opens
|
|
6
|
+
// the modal on whatever terminal the server hands back, and reflects the flow's
|
|
7
|
+
// state in a banner.
|
|
8
|
+
//
|
|
9
|
+
// Nothing here spawns a terminal speculatively. Every auth_required after the
|
|
10
|
+
// first is a no-op while a flow is live, which is what stops the login-terminal
|
|
11
|
+
// pile-up and the endless auth_required -> login -> 401 loop.
|
|
12
|
+
|
|
13
|
+
import { store } from './store.js';
|
|
14
|
+
import { getWs } from './ws-ref.js';
|
|
15
|
+
import { refreshIcons, iconHtml } from './icons.js';
|
|
16
|
+
import { openTuiModal, closeTuiModal, getTuiModalTerminalId } from './tui-attention.js';
|
|
17
|
+
import { forwardPaneAuthRequired } from './pane-bridge.js';
|
|
18
|
+
|
|
19
|
+
// vendor -> { terminalId, startedAt } as last broadcast by the server.
|
|
20
|
+
var activeFlows = Object.create(null);
|
|
21
|
+
// vendor -> true between sending vendor_login_start and hearing back.
|
|
22
|
+
var pendingStarts = Object.create(null);
|
|
23
|
+
// Vendor whose login terminal the modal is currently showing.
|
|
24
|
+
var modalVendor = null;
|
|
25
|
+
// Set while this module tears the modal down itself, so the modal's close hook
|
|
26
|
+
// does not read a programmatic close as "the user gave up".
|
|
27
|
+
var closingModalInternally = false;
|
|
28
|
+
|
|
29
|
+
function vendorDisplayName(vendor) {
|
|
30
|
+
var vendors = store.get('vendorInfo') || {};
|
|
31
|
+
var info = vendors[vendor];
|
|
32
|
+
if (info && info.displayName) return info.displayName;
|
|
33
|
+
return vendor === "codex" ? "Codex" : "Claude Code";
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function currentProjectSlug() {
|
|
37
|
+
var slug = store.get('currentSlug') || "";
|
|
38
|
+
if (slug) return slug;
|
|
39
|
+
// Fallback: derive from the URL (/p/<slug>/...).
|
|
40
|
+
try {
|
|
41
|
+
var m = (window.location.pathname || "").match(/^\/p\/([a-z0-9_-]+)/);
|
|
42
|
+
if (m) return m[1];
|
|
43
|
+
} catch (e) {}
|
|
44
|
+
return "";
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// The banner surface belongs to the notification center. Query it rather than
|
|
48
|
+
// importing app-notifications so the dependency stays one-directional
|
|
49
|
+
// (app-notifications -> vendor-login).
|
|
50
|
+
function bannerContainer() {
|
|
51
|
+
return document.querySelector(".notif-banner-container");
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function removeBannerEl(el) {
|
|
55
|
+
if (!el) return;
|
|
56
|
+
el.classList.remove("show");
|
|
57
|
+
setTimeout(function () {
|
|
58
|
+
if (el.parentNode) el.parentNode.removeChild(el);
|
|
59
|
+
}, 300);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function clearLoginBanners() {
|
|
63
|
+
var container = bannerContainer();
|
|
64
|
+
if (!container) return;
|
|
65
|
+
var existing = container.querySelectorAll('[data-vendor-login="true"]');
|
|
66
|
+
for (var i = 0; i < existing.length; i++) removeBannerEl(existing[i]);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function showLoginBanner(opts) {
|
|
70
|
+
var container = bannerContainer();
|
|
71
|
+
if (!container) return null;
|
|
72
|
+
clearLoginBanners();
|
|
73
|
+
|
|
74
|
+
var banner = document.createElement("div");
|
|
75
|
+
banner.className = "notif-banner notif-banner-update";
|
|
76
|
+
banner.setAttribute("data-notif-id", "_vendor_login");
|
|
77
|
+
banner.setAttribute("data-vendor-login", "true");
|
|
78
|
+
banner.innerHTML =
|
|
79
|
+
'<div class="notif-banner-icon">' + iconHtml(opts.icon || "check-circle") + '</div>' +
|
|
80
|
+
'<div class="notif-banner-body">' +
|
|
81
|
+
'<div class="notif-banner-project">CLAY</div>' +
|
|
82
|
+
'<div class="notif-banner-title">' + opts.title + '</div>' +
|
|
83
|
+
'<div class="notif-banner-text">' + opts.text + '</div>' +
|
|
84
|
+
'</div>' +
|
|
85
|
+
'<button class="notif-banner-close">' + iconHtml("x") + '</button>';
|
|
86
|
+
|
|
87
|
+
container.appendChild(banner);
|
|
88
|
+
refreshIcons();
|
|
89
|
+
requestAnimationFrame(function () { banner.classList.add("show"); });
|
|
90
|
+
|
|
91
|
+
var closeBtn = banner.querySelector(".notif-banner-close");
|
|
92
|
+
if (closeBtn) {
|
|
93
|
+
closeBtn.addEventListener("click", function (e) {
|
|
94
|
+
e.stopPropagation();
|
|
95
|
+
removeBannerEl(banner);
|
|
96
|
+
if (opts.onDismiss) opts.onDismiss();
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
if (typeof opts.autoDismissMs === "number") {
|
|
100
|
+
setTimeout(function () { removeBannerEl(banner); }, opts.autoDismissMs);
|
|
101
|
+
}
|
|
102
|
+
return banner;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function showLoginStartedBanner(vendor) {
|
|
106
|
+
showLoginBanner({
|
|
107
|
+
icon: "check-circle",
|
|
108
|
+
title: vendorDisplayName(vendor) + " login started",
|
|
109
|
+
text: "Finish the sign-in in the terminal. Clay reloads the credentials on its own, so your sessions keep working - no new session needed.",
|
|
110
|
+
onDismiss: function () { cancelVendorLogin(vendor); },
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function showLoginCompleteBanner(vendor) {
|
|
115
|
+
showLoginBanner({
|
|
116
|
+
icon: "check-circle",
|
|
117
|
+
title: "Signed in to " + vendorDisplayName(vendor),
|
|
118
|
+
text: "Credentials reloaded. Send your next message to continue.",
|
|
119
|
+
autoDismissMs: 6000,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function showLoginErrorBanner(vendor, error) {
|
|
124
|
+
showLoginBanner({
|
|
125
|
+
icon: "alert-triangle",
|
|
126
|
+
title: vendorDisplayName(vendor) + " login could not start",
|
|
127
|
+
text: error || "The login terminal could not be created.",
|
|
128
|
+
autoDismissMs: 8000,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function closeModalInternally() {
|
|
133
|
+
modalVendor = null;
|
|
134
|
+
closingModalInternally = true;
|
|
135
|
+
try {
|
|
136
|
+
closeTuiModal();
|
|
137
|
+
} finally {
|
|
138
|
+
closingModalInternally = false;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function closeModalForVendor(vendor, terminalId) {
|
|
143
|
+
if (modalVendor !== vendor) return;
|
|
144
|
+
var openId = getTuiModalTerminalId();
|
|
145
|
+
if (typeof terminalId === "number" && openId != null && openId !== terminalId) return;
|
|
146
|
+
closeModalInternally();
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// ========================================================
|
|
150
|
+
// Outgoing
|
|
151
|
+
// ========================================================
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Ask the server to start (or hand back) the login terminal for a vendor.
|
|
155
|
+
* opts.auto marks a request triggered by an auth_required event; those never
|
|
156
|
+
* open a second prompt while a flow is already running.
|
|
157
|
+
*/
|
|
158
|
+
export function requestVendorLogin(vendor, opts) {
|
|
159
|
+
var options = opts || {};
|
|
160
|
+
var target = vendor || "claude";
|
|
161
|
+
if (options.auto && (activeFlows[target] || pendingStarts[target])) return false;
|
|
162
|
+
|
|
163
|
+
var ws = getWs();
|
|
164
|
+
if (!ws || ws.readyState !== 1) return false;
|
|
165
|
+
|
|
166
|
+
pendingStarts[target] = true;
|
|
167
|
+
ws.send(JSON.stringify({
|
|
168
|
+
type: "vendor_login_start",
|
|
169
|
+
vendor: target,
|
|
170
|
+
auto: !!options.auto,
|
|
171
|
+
sessionId: options.sessionId || store.get('activeSessionId') || null,
|
|
172
|
+
}));
|
|
173
|
+
showLoginStartedBanner(target);
|
|
174
|
+
return true;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export function cancelVendorLogin(vendor) {
|
|
178
|
+
var target = vendor || modalVendor;
|
|
179
|
+
if (!target) return;
|
|
180
|
+
delete pendingStarts[target];
|
|
181
|
+
delete activeFlows[target];
|
|
182
|
+
clearLoginBanners();
|
|
183
|
+
if (modalVendor === target) closeModalInternally();
|
|
184
|
+
var ws = getWs();
|
|
185
|
+
if (ws && ws.readyState === 1) {
|
|
186
|
+
ws.send(JSON.stringify({ type: "vendor_login_cancel", vendor: target }));
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Auto-open the login flow when the server reports the vendor isn't logged in.
|
|
192
|
+
*
|
|
193
|
+
* Split panes have no banner surface of their own (the parent shell owns the
|
|
194
|
+
* single visible one), so a pane hands the event up instead of running two
|
|
195
|
+
* competing flows.
|
|
196
|
+
*/
|
|
197
|
+
export function autoStartLoginIfNeeded(msg) {
|
|
198
|
+
if (!msg) return false;
|
|
199
|
+
if (store.get('paneMode')) {
|
|
200
|
+
return forwardPaneAuthRequired({
|
|
201
|
+
vendor: msg.vendor || "claude",
|
|
202
|
+
sessionId: store.get('activeSessionId') || null,
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
var vendor = msg.vendor || "claude";
|
|
206
|
+
if (activeFlows[vendor] || pendingStarts[vendor]) return false;
|
|
207
|
+
return requestVendorLogin(vendor, { auto: true, sessionId: msg.sessionId || null });
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// ========================================================
|
|
211
|
+
// Incoming
|
|
212
|
+
// ========================================================
|
|
213
|
+
|
|
214
|
+
export function handleVendorLoginReady(msg) {
|
|
215
|
+
if (!msg || typeof msg.terminalId !== "number") return;
|
|
216
|
+
var vendor = msg.vendor || "claude";
|
|
217
|
+
delete pendingStarts[vendor];
|
|
218
|
+
activeFlows[vendor] = { terminalId: msg.terminalId, startedAt: Date.now() };
|
|
219
|
+
|
|
220
|
+
var slug = msg.slug || currentProjectSlug();
|
|
221
|
+
if (!slug) return;
|
|
222
|
+
modalVendor = vendor;
|
|
223
|
+
openTuiModal(msg.terminalId, slug, {
|
|
224
|
+
sessionTitle: vendorDisplayName(vendor) + " login",
|
|
225
|
+
projectName: slug,
|
|
226
|
+
compact: true,
|
|
227
|
+
// Dismissing the modal abandons the login: kill the terminal server-side
|
|
228
|
+
// so it never lingers in the sidebar terminal list.
|
|
229
|
+
onClose: function () {
|
|
230
|
+
if (closingModalInternally) return;
|
|
231
|
+
modalVendor = null;
|
|
232
|
+
cancelVendorLogin(vendor);
|
|
233
|
+
},
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export function handleVendorLoginState(msg) {
|
|
238
|
+
if (!msg) return;
|
|
239
|
+
var next = Object.create(null);
|
|
240
|
+
var flows = msg.flows || [];
|
|
241
|
+
for (var i = 0; i < flows.length; i++) {
|
|
242
|
+
next[flows[i].vendor] = { terminalId: flows[i].terminalId, startedAt: flows[i].startedAt };
|
|
243
|
+
delete pendingStarts[flows[i].vendor];
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// A flow the server dropped without an auth_refreshed (cancelled elsewhere,
|
|
247
|
+
// terminal killed from the sidebar) must not leave a stale modal behind.
|
|
248
|
+
var previous = Object.keys(activeFlows);
|
|
249
|
+
for (var p = 0; p < previous.length; p++) {
|
|
250
|
+
if (!next[previous[p]] && modalVendor === previous[p]) {
|
|
251
|
+
closeModalInternally();
|
|
252
|
+
clearLoginBanners();
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
activeFlows = next;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export function handleAuthRefreshed(msg) {
|
|
259
|
+
if (!msg) return;
|
|
260
|
+
var vendor = msg.vendor || "claude";
|
|
261
|
+
var flow = activeFlows[vendor];
|
|
262
|
+
delete pendingStarts[vendor];
|
|
263
|
+
delete activeFlows[vendor];
|
|
264
|
+
closeModalForVendor(vendor, flow ? flow.terminalId : undefined);
|
|
265
|
+
clearLoginBanners();
|
|
266
|
+
showLoginCompleteBanner(vendor);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
export function handleVendorLoginError(msg) {
|
|
270
|
+
if (!msg) return;
|
|
271
|
+
var vendor = msg.vendor || "claude";
|
|
272
|
+
delete pendingStarts[vendor];
|
|
273
|
+
delete activeFlows[vendor];
|
|
274
|
+
if (modalVendor === vendor) closeModalInternally();
|
|
275
|
+
clearLoginBanners();
|
|
276
|
+
showLoginErrorBanner(vendor, msg.error);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
export function requestVendorLoginState() {
|
|
280
|
+
var ws = getWs();
|
|
281
|
+
if (!ws || ws.readyState !== 1) return;
|
|
282
|
+
ws.send(JSON.stringify({ type: "vendor_login_state_request" }));
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
export function getActiveLoginFlow(vendor) {
|
|
286
|
+
return activeFlows[vendor] || null;
|
|
287
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
var usersModule = require("./users");
|
|
2
2
|
var yoke = require("./yoke");
|
|
3
|
+
var backgroundTaskTiming = require("./background-task-timing");
|
|
3
4
|
|
|
4
5
|
function attachMessageProcessor(ctx) {
|
|
5
6
|
var sm = ctx.sm;
|
|
@@ -686,8 +687,15 @@ function attachMessageProcessor(ctx) {
|
|
|
686
687
|
}
|
|
687
688
|
|
|
688
689
|
} else if (parsed.yokeType === "background_tasks_changed") {
|
|
689
|
-
var
|
|
690
|
-
var
|
|
690
|
+
var previousBackgroundTasks = session.activeBackgroundTasks || [];
|
|
691
|
+
var previousBackgroundTaskCount = previousBackgroundTasks.length;
|
|
692
|
+
// Vendors re-send the whole list on every change, so carry each task's
|
|
693
|
+
// first-seen time forward; otherwise the composer's elapsed timer would
|
|
694
|
+
// restart whenever any other task starts or finishes.
|
|
695
|
+
var activeBackgroundTasks = backgroundTaskTiming.mergeStartTimes(
|
|
696
|
+
previousBackgroundTasks,
|
|
697
|
+
Array.isArray(parsed.tasks) ? parsed.tasks : []
|
|
698
|
+
);
|
|
691
699
|
session.activeBackgroundTasks = activeBackgroundTasks;
|
|
692
700
|
sendToSession(session, { type: "active_background_tasks", tasks: activeBackgroundTasks });
|
|
693
701
|
if (previousBackgroundTaskCount !== activeBackgroundTasks.length) {
|
package/lib/tools-registry.js
CHANGED
|
@@ -137,7 +137,17 @@ function seedBuiltInCapsules(ctx) {
|
|
|
137
137
|
for (var ri = 0; ri < REMOVED_BUILTIN_IDS.length; ri++) {
|
|
138
138
|
fs.rmSync(path.join(root, REMOVED_BUILTIN_IDS[ri]), { recursive: true, force: true });
|
|
139
139
|
}
|
|
140
|
-
|
|
140
|
+
// Clay ships no built-in Capsules any more, so lib/capsules/ is absent from a
|
|
141
|
+
// fresh checkout and from the npm tarball (neither git nor npm carries an
|
|
142
|
+
// empty directory). Seeding still owns the removed-built-in migration above
|
|
143
|
+
// and the marker below, so an absent root means "nothing to copy" rather than
|
|
144
|
+
// a failure. Anything other than a missing root is still a real error.
|
|
145
|
+
var entries = [];
|
|
146
|
+
try {
|
|
147
|
+
entries = fs.readdirSync(CAPSULES_ROOT, { withFileTypes: true });
|
|
148
|
+
} catch (e) {
|
|
149
|
+
if (e.code !== "ENOENT") throw e;
|
|
150
|
+
}
|
|
141
151
|
var seeded = [];
|
|
142
152
|
for (var i = 0; i < entries.length; i++) {
|
|
143
153
|
if (!entries[i].isDirectory()) continue;
|
package/lib/ws-schema.js
CHANGED
|
@@ -171,6 +171,13 @@ var schema = {
|
|
|
171
171
|
// Auth
|
|
172
172
|
// -----------------------------------------------------------------------
|
|
173
173
|
"auth_required": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Authentication required (e.g. API key needed)" },
|
|
174
|
+
"vendor_login_start": { direction: "c2s", handler: "lib/project-vendor-login.js", description: "Start (or re-attach to) the per-vendor login terminal" },
|
|
175
|
+
"vendor_login_cancel": { direction: "c2s", handler: "lib/project-vendor-login.js", description: "Dismiss the login flow and kill its terminal" },
|
|
176
|
+
"vendor_login_state_request": { direction: "c2s", handler: "lib/project-vendor-login.js", description: "Ask for the project's active login flows" },
|
|
177
|
+
"vendor_login_ready": { direction: "s2c", handler: "lib/public/modules/vendor-login.js", description: "Login terminal is available; open/re-attach the modal to it" },
|
|
178
|
+
"vendor_login_state": { direction: "s2c", handler: "lib/public/modules/vendor-login.js", description: "Active login flows for this project (one per vendor)" },
|
|
179
|
+
"vendor_login_error": { direction: "s2c", handler: "lib/public/modules/vendor-login.js", description: "The login flow could not be started" },
|
|
180
|
+
"auth_refreshed": { direction: "s2c", handler: "lib/public/modules/vendor-login.js", description: "Login completed and the vendor adapter was restarted" },
|
|
174
181
|
|
|
175
182
|
// -----------------------------------------------------------------------
|
|
176
183
|
// Rate limiting / scheduling
|
|
@@ -400,11 +400,16 @@ function normalizeBackgroundTasks(tasks) {
|
|
|
400
400
|
var nativeType = task && task.task_type;
|
|
401
401
|
var taskType = nativeType === "local_bash" ? "shell"
|
|
402
402
|
: nativeType === "local_agent" ? "agent" : "other";
|
|
403
|
-
|
|
403
|
+
var normalized = {
|
|
404
404
|
task_id: task && task.task_id,
|
|
405
405
|
task_type: taskType,
|
|
406
406
|
description: (task && task.description) || "",
|
|
407
407
|
};
|
|
408
|
+
// The SDK does not report a start time today. Forward one if a future
|
|
409
|
+
// version adds it; otherwise the session stamps first-seen time.
|
|
410
|
+
var startedAt = task && (task.started_at || task.startedAt || task.created_at);
|
|
411
|
+
if (startedAt) normalized.started_at = startedAt;
|
|
412
|
+
return normalized;
|
|
408
413
|
});
|
|
409
414
|
}
|
|
410
415
|
|
|
@@ -1312,15 +1312,14 @@ function createCodexAdapter(opts) {
|
|
|
1312
1312
|
// model listing must not depend on a successful app-server init — otherwise a
|
|
1313
1313
|
// slow/failed `initialize` leaves the picker empty and the chip shows the
|
|
1314
1314
|
// previous vendor's model.
|
|
1315
|
+
// Kept in sync with what `model/list` reports for the bundled codex build
|
|
1316
|
+
// (0.152.1). Models the CLI no longer offers are rejected at turn/start, so
|
|
1317
|
+
// listing them would only surface a runtime failure in the model picker.
|
|
1315
1318
|
var CODEX_MODELS = [
|
|
1316
1319
|
"gpt-5.6-terra",
|
|
1317
1320
|
"gpt-5.6-sol",
|
|
1318
1321
|
"gpt-5.6-luna",
|
|
1319
1322
|
"gpt-5.5",
|
|
1320
|
-
"gpt-5.4",
|
|
1321
|
-
"gpt-5.4-mini",
|
|
1322
|
-
"gpt-5.3-codex",
|
|
1323
|
-
"gpt-5.3-codex-spark",
|
|
1324
1323
|
"gpt-5.2",
|
|
1325
1324
|
];
|
|
1326
1325
|
var _cachedModels = CODEX_MODELS.slice();
|
|
@@ -13,7 +13,13 @@ var { buildUserEnv } = require("../build-user-env");
|
|
|
13
13
|
var { wrapSpawnAsUser } = require("../os-users");
|
|
14
14
|
|
|
15
15
|
// --- Find the codex binary path ---
|
|
16
|
-
// Mirrors the logic from @openai/codex-sdk findCodexPath()
|
|
16
|
+
// Mirrors the logic from @openai/codex-sdk findCodexPath().
|
|
17
|
+
//
|
|
18
|
+
// The bundled @openai/codex platform package is the default. Set the
|
|
19
|
+
// CLAY_CODEX_PATH env var to an existing executable to run a different codex
|
|
20
|
+
// build (local dev build, system install, pinned older release); the chosen
|
|
21
|
+
// binary is logged on every resolution. An unset or non-existent
|
|
22
|
+
// CLAY_CODEX_PATH silently falls back to the bundled resolution.
|
|
17
23
|
|
|
18
24
|
var PLATFORM_PACKAGE_BY_TARGET = {
|
|
19
25
|
"aarch64-apple-darwin": "@openai/codex-darwin-arm64",
|
|
@@ -39,6 +45,15 @@ function getTargetTriple() {
|
|
|
39
45
|
}
|
|
40
46
|
|
|
41
47
|
function findCodexPath() {
|
|
48
|
+
var override = process.env.CLAY_CODEX_PATH;
|
|
49
|
+
if (override) {
|
|
50
|
+
if (fs.existsSync(override)) {
|
|
51
|
+
console.log("[codex-app-server] Using CLAY_CODEX_PATH binary:", override);
|
|
52
|
+
return override;
|
|
53
|
+
}
|
|
54
|
+
console.warn("[codex-app-server] CLAY_CODEX_PATH does not exist, falling back to bundled codex:", override);
|
|
55
|
+
}
|
|
56
|
+
|
|
42
57
|
var triple = getTargetTriple();
|
|
43
58
|
if (!triple) throw new Error("Unsupported platform: " + process.platform + "/" + process.arch);
|
|
44
59
|
|
|
@@ -59,7 +74,10 @@ function findCodexPath() {
|
|
|
59
74
|
path.join(vendorRoot, triple, "codex", binaryName),
|
|
60
75
|
];
|
|
61
76
|
for (var i = 0; i < candidates.length; i++) {
|
|
62
|
-
if (fs.existsSync(candidates[i]))
|
|
77
|
+
if (fs.existsSync(candidates[i])) {
|
|
78
|
+
console.log("[codex-app-server] Using bundled codex binary:", candidates[i]);
|
|
79
|
+
return candidates[i];
|
|
80
|
+
}
|
|
63
81
|
}
|
|
64
82
|
throw new Error("codex binary not found in any known layout under " + path.join(vendorRoot, triple));
|
|
65
83
|
} catch (e) {
|
|
@@ -19,11 +19,17 @@ function mapTerminals(result) {
|
|
|
19
19
|
var terminal = terminals[i] || {};
|
|
20
20
|
var taskId = terminal.id || terminal.terminalId || terminal.taskId || "";
|
|
21
21
|
if (!taskId || !isLiveTerminal(terminal)) continue;
|
|
22
|
-
|
|
22
|
+
var task = {
|
|
23
23
|
task_id: String(taskId),
|
|
24
24
|
task_type: "shell",
|
|
25
25
|
description: terminal.commandLine || terminal.command || terminal.name || "",
|
|
26
|
-
}
|
|
26
|
+
};
|
|
27
|
+
// Forward the terminal's own start time when the app-server reports one,
|
|
28
|
+
// so elapsed time survives a Clay restart. Falls back to first-seen
|
|
29
|
+
// stamping in background-task-timing.js when absent.
|
|
30
|
+
var startedAt = terminal.startedAt || terminal.started_at || terminal.createdAt || terminal.created_at;
|
|
31
|
+
if (startedAt) task.started_at = startedAt;
|
|
32
|
+
tasks.push(task);
|
|
27
33
|
}
|
|
28
34
|
return tasks;
|
|
29
35
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "clay-server",
|
|
3
|
-
"version": "4.0.0-beta.
|
|
3
|
+
"version": "4.0.0-beta.3",
|
|
4
4
|
"description": "Self-hosted team workspace for Claude Code and Codex. Multi-user, browser-based, with persistent AI mates.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"clay-server": "./bin/cli.js",
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"dependencies": {
|
|
52
52
|
"@anthropic-ai/claude-agent-sdk": "^0.3.241",
|
|
53
53
|
"@lydell/node-pty": "^1.2.0-beta.3",
|
|
54
|
-
"@openai/codex": "^0.
|
|
54
|
+
"@openai/codex": "^0.152.1",
|
|
55
55
|
"@seald-io/nedb": "^4.1.2",
|
|
56
56
|
"imapflow": "^1.3.1",
|
|
57
57
|
"nodemailer": "^9.0.5",
|