clay-server 5.1.0-beta.2 → 5.1.0
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/bin/cli.js +9 -91
- package/lib/builtin-mates.js +7 -7
- package/lib/daemon-settings.js +11 -0
- package/lib/daemon.js +8 -37
- package/lib/linear-store.js +35 -8
- package/lib/mate-identity-file.js +39 -0
- package/lib/mate-knowledge-sync.js +1 -1
- package/lib/mate-ready-creation.js +2 -1
- package/lib/mates-identity.js +4 -4
- package/lib/mates-prompts.js +4 -3
- package/lib/mates.js +12 -11
- package/lib/os-users.js +5 -16
- package/lib/project-connection.js +0 -7
- package/lib/project-debate.js +3 -2
- package/lib/project-filesystem.js +21 -39
- package/lib/project-mate-interaction.js +7 -6
- package/lib/project-server-settings.js +31 -0
- package/lib/project-sessions.js +57 -184
- package/lib/project.js +3 -3
- package/lib/public/app.js +6 -37
- package/lib/public/css/input.css +3 -1
- package/lib/public/css/menus.css +3 -0
- package/lib/public/css/overlays.css +7 -0
- package/lib/public/css/project-settings-modal.css +56 -0
- package/lib/public/css/server-settings-system.css +125 -0
- package/lib/public/css/session-github.css +25 -16
- package/lib/public/css/settings-ui.css +247 -22
- package/lib/public/css/shared-browser.css +7 -3
- package/lib/public/css/title-bar.css +5 -1
- package/lib/public/index.html +173 -447
- package/lib/public/modules/account-island.js +9 -7
- package/lib/public/modules/admin-settings-email.js +197 -0
- package/lib/public/modules/admin-settings-invitations.js +183 -0
- package/lib/public/modules/admin-settings-projects.js +198 -0
- package/lib/public/modules/admin-settings-ui.js +100 -0
- package/lib/public/modules/admin-settings-users.js +331 -0
- package/lib/public/modules/admin.js +19 -949
- package/lib/public/modules/app-dm.js +0 -2
- package/lib/public/modules/app-messages.js +13 -52
- package/lib/public/modules/app-rate-limit.js +70 -12
- package/lib/public/modules/avatar.js +6 -1
- package/lib/public/modules/context-sources.js +27 -18
- package/lib/public/modules/default-ai.js +14 -7
- package/lib/public/modules/header-tui-font.js +10 -20
- package/lib/public/modules/home-mate-avatar-editor.js +2 -2
- package/lib/public/modules/linear-settings.js +3 -2
- package/lib/public/modules/markdown.js +5 -6
- package/lib/public/modules/notification-settings.js +81 -0
- package/lib/public/modules/notifications.js +44 -28
- package/lib/public/modules/personal-settings-dialog.js +51 -0
- package/lib/public/modules/profile.js +15 -6
- package/lib/public/modules/project-settings-environment.js +106 -0
- package/lib/public/modules/project-settings.js +72 -628
- package/lib/public/modules/server-settings-controls.js +316 -0
- package/lib/public/modules/server-settings-environment.js +194 -0
- package/lib/public/modules/server-settings-navigation.js +39 -0
- package/lib/public/modules/server-settings-state.js +54 -0
- package/lib/public/modules/server-settings.js +116 -788
- package/lib/public/modules/server-storage-settings.js +84 -0
- package/lib/public/modules/session-github.js +2 -1
- package/lib/public/modules/session-presence-ui.js +1 -1
- package/lib/public/modules/settings-env-values.js +34 -0
- package/lib/public/modules/settings-select.js +121 -0
- package/lib/public/modules/shared-browser.js +22 -12
- package/lib/public/modules/stt-picker.js +14 -5
- package/lib/public/modules/terminal-prefs.js +22 -0
- package/lib/public/modules/theme.js +88 -52
- package/lib/public/modules/user-settings-account.js +318 -0
- package/lib/public/modules/user-settings-appearance.js +55 -0
- package/lib/public/modules/user-settings-editors.js +24 -0
- package/lib/public/modules/user-settings-integrations.js +104 -0
- package/lib/public/modules/user-settings-layout.js +80 -0
- package/lib/public/modules/user-settings-preferences.js +48 -0
- package/lib/public/modules/user-settings-profile.js +53 -0
- package/lib/public/modules/user-settings-themes.js +38 -0
- package/lib/public/modules/user-settings-voice.js +75 -0
- package/lib/public/modules/user-settings.js +53 -480
- package/lib/public/style.css +4 -0
- package/lib/sdk-message-processor.js +16 -4
- package/lib/sdk-quota-snapshot.js +37 -0
- package/lib/server-appearance-settings.js +71 -0
- package/lib/server-linear.js +14 -8
- package/lib/server-mate-instructions.js +2 -2
- package/lib/server-mates.js +2 -1
- package/lib/server-notification-settings.js +67 -0
- package/lib/server-settings.js +4 -0
- package/lib/server-storage.js +57 -0
- package/lib/server.js +10 -2
- package/lib/sessions.js +3 -4
- package/lib/startup-projects.js +40 -0
- package/lib/storage-native-walk.js +85 -0
- package/lib/storage-usage.js +144 -0
- package/lib/users-preferences.js +0 -74
- package/lib/users.js +0 -4
- package/lib/ws-schema.js +2 -4
- package/lib/yoke/acp-process-manager.js +2 -2
- package/lib/yoke/adapters/codex.js +12 -52
- package/lib/yoke/codex-app-server.js +2 -2
- package/lib/yoke/codex-model-catalog.js +52 -0
- package/lib/yoke/codex-rate-limits.js +26 -0
- package/package.json +3 -3
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
var fs = require('node:fs/promises');
|
|
2
|
+
var path = require('node:path');
|
|
3
|
+
var execFile = require('node:child_process').execFile;
|
|
4
|
+
var nativeWalk = require('./storage-native-walk');
|
|
5
|
+
|
|
6
|
+
function resolveIdentity(username) {
|
|
7
|
+
return new Promise(function (resolve) {
|
|
8
|
+
if (!/^[a-z_][a-z0-9_-]*[$]?$/.test(username || '')) return resolve(null);
|
|
9
|
+
execFile('getent', ['passwd', username], { timeout: 3000, maxBuffer: 8192 }, function (error, output) {
|
|
10
|
+
if (error) return resolve(null);
|
|
11
|
+
var parts = output.trim().split(':');
|
|
12
|
+
var uid = Number(parts[2]);
|
|
13
|
+
resolve(parts.length >= 7 && /^\d+$/.test(parts[2]) && Number.isInteger(uid) && uid >= 0 && path.isAbsolute(parts[5]) ? { uid: uid, home: parts[5] } : null);
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Read metadata only. Shared paths and hard links count once across all roots.
|
|
19
|
+
async function scanStorage(options) {
|
|
20
|
+
var started = Date.now();
|
|
21
|
+
var deadline = options.maxMs ? started + options.maxMs : Infinity;
|
|
22
|
+
var maxEntries = options.maxEntries || Infinity;
|
|
23
|
+
var users = options.users || [];
|
|
24
|
+
var projectMode = options.attribution === "project";
|
|
25
|
+
var projects = options.projects || [];
|
|
26
|
+
var identities = await Promise.all(users.map(function (user) {
|
|
27
|
+
return !projectMode && options.platform === 'linux' && user.linuxUser ? (options.resolveIdentity || resolveIdentity)(user.linuxUser) : null;
|
|
28
|
+
}));
|
|
29
|
+
var rows = users.map(function (user, index) {
|
|
30
|
+
return { id: user.id, name: user.displayName || user.username, linuxUser: user.linuxUser || null, mapped: projectMode || !!identities[index], projects: 0, bytes: 0, files: 0 };
|
|
31
|
+
});
|
|
32
|
+
var owners = new Map();
|
|
33
|
+
identities.forEach(function (identity, index) {
|
|
34
|
+
if (!identity) return;
|
|
35
|
+
// A reused OS identity cannot be split accurately between Clay accounts.
|
|
36
|
+
owners.set(identity.uid, owners.has(identity.uid) ? null : rows[index]);
|
|
37
|
+
});
|
|
38
|
+
identities.forEach(function (identity, index) {
|
|
39
|
+
if (identity && !owners.get(identity.uid)) rows[index].mapped = false;
|
|
40
|
+
});
|
|
41
|
+
var projectRoots = new Map();
|
|
42
|
+
var projectErrors = 0;
|
|
43
|
+
for (var project of projects) {
|
|
44
|
+
try {
|
|
45
|
+
var projectPath = await fs.realpath(project.path);
|
|
46
|
+
var row = rows.find(function (item) { return item.id === project.projectOwnerId; }) || null;
|
|
47
|
+
if (row) row.projects++;
|
|
48
|
+
if (projectRoots.has(projectPath) && projectRoots.get(projectPath) !== row) row = null;
|
|
49
|
+
projectRoots.set(projectPath, row);
|
|
50
|
+
} catch (error) { projectErrors++; }
|
|
51
|
+
}
|
|
52
|
+
var orderedProjects = Array.from(projectRoots.keys()).sort(function (a, b) { return b.length - a.length; });
|
|
53
|
+
function projectOwner(filePath) {
|
|
54
|
+
var root = orderedProjects.find(function (candidate) { return filePath === candidate || filePath.indexOf(candidate + path.sep) === 0; });
|
|
55
|
+
return root ? projectRoots.get(root) : null;
|
|
56
|
+
}
|
|
57
|
+
var roots = orderedProjects.concat(options.projectPaths || []);
|
|
58
|
+
identities.forEach(function (identity) { if (identity) roots.push(identity.home); });
|
|
59
|
+
roots.push(options.configDir);
|
|
60
|
+
var report = { attribution: projectMode ? "project" : "filesystem", users: rows, totalBytes: 0, totalFiles: 0, unattributedBytes: 0, unattributedFiles: 0,
|
|
61
|
+
partial: false, errors: projectErrors + users.filter(function (user, index) { return !projectMode && options.platform === 'linux' && user.linuxUser && !identities[index]; }).length, skippedLinks: 0, skippedMounts: 0, scannedEntries: 0, roots: 0, platform: options.platform };
|
|
62
|
+
var lastProgress = 0;
|
|
63
|
+
function progress() {
|
|
64
|
+
if (!options.onProgress || Date.now() - lastProgress < 500) return;
|
|
65
|
+
lastProgress = Date.now();
|
|
66
|
+
options.onProgress(Object.assign({}, report, { scanning: true, checkedAt: new Date().toISOString(),
|
|
67
|
+
users: rows.map(function (row) { return Object.assign({}, row); }) }));
|
|
68
|
+
}
|
|
69
|
+
progress();
|
|
70
|
+
var seen = new Map();
|
|
71
|
+
var rootPaths = new Set();
|
|
72
|
+
var completedRoots = [];
|
|
73
|
+
var useNative = options.native !== false && await nativeWalk.available();
|
|
74
|
+
for (var root of roots) {
|
|
75
|
+
if (typeof root !== 'string' || !path.isAbsolute(root)) { report.errors++; continue; }
|
|
76
|
+
try {
|
|
77
|
+
root = await fs.realpath(root);
|
|
78
|
+
// Never turn a misconfigured home/project into a whole-host scan.
|
|
79
|
+
if (root === path.parse(root).root) { report.errors++; continue; }
|
|
80
|
+
if (rootPaths.has(root)) continue;
|
|
81
|
+
rootPaths.add(root);
|
|
82
|
+
var rootStat = await fs.lstat(root);
|
|
83
|
+
report.roots++;
|
|
84
|
+
if (useNative) {
|
|
85
|
+
if (seen.has(rootStat.dev + ':' + rootStat.ino)) continue;
|
|
86
|
+
var result = await nativeWalk.walk(root, completedRoots, function (filePath, stat) {
|
|
87
|
+
if (Date.now() >= deadline || report.scannedEntries >= maxEntries) { report.partial = true; return false; }
|
|
88
|
+
account(filePath, stat, rootStat.dev);
|
|
89
|
+
}, deadline - Date.now());
|
|
90
|
+
report.errors += result.errors;
|
|
91
|
+
if (result.stopped) report.partial = true;
|
|
92
|
+
completedRoots.push(root);
|
|
93
|
+
} else await walk(root, rootStat.dev);
|
|
94
|
+
} catch (error) { report.errors++; }
|
|
95
|
+
if (report.partial) break;
|
|
96
|
+
}
|
|
97
|
+
report.partial = report.partial || report.errors > 0 || report.skippedMounts > 0;
|
|
98
|
+
report.scanning = false;
|
|
99
|
+
report.checkedAt = new Date().toISOString();
|
|
100
|
+
return report;
|
|
101
|
+
|
|
102
|
+
async function walk(filePath, device) {
|
|
103
|
+
if (Date.now() >= deadline || report.scannedEntries >= maxEntries) { report.partial = true; return; }
|
|
104
|
+
var stat;
|
|
105
|
+
try { stat = await fs.lstat(filePath); } catch (error) { report.errors++; return; }
|
|
106
|
+
if (!account(filePath, stat, device)) return;
|
|
107
|
+
if (!stat.isDirectory()) return;
|
|
108
|
+
try {
|
|
109
|
+
var directory = await fs.opendir(filePath);
|
|
110
|
+
for await (var entry of directory) {
|
|
111
|
+
await walk(path.join(filePath, entry.name), device);
|
|
112
|
+
if (report.partial) break;
|
|
113
|
+
}
|
|
114
|
+
} catch (error) { report.errors++; }
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function account(filePath, stat, device) {
|
|
118
|
+
if (stat.isSymbolicLink()) { report.skippedLinks++; return; }
|
|
119
|
+
if (stat.dev !== device) { report.skippedMounts++; return; }
|
|
120
|
+
var key = stat.dev + ':' + stat.ino;
|
|
121
|
+
var owner = projectMode ? projectOwner(filePath) : owners.get(stat.uid);
|
|
122
|
+
if (seen.has(key)) {
|
|
123
|
+
var previous = seen.get(key);
|
|
124
|
+
if (projectMode && stat.isFile() && previous.owner && previous.owner !== owner) {
|
|
125
|
+
previous.owner.bytes -= previous.bytes; previous.owner.files--;
|
|
126
|
+
report.unattributedBytes += previous.bytes; report.unattributedFiles++;
|
|
127
|
+
previous.owner = null;
|
|
128
|
+
}
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
report.scannedEntries++;
|
|
132
|
+
if (!stat.isDirectory() && !stat.isFile()) return;
|
|
133
|
+
var bytes = typeof stat.blocks === 'number' ? stat.blocks * 512 : stat.size;
|
|
134
|
+
// Only directories and multiply-linked files need identity deduplication.
|
|
135
|
+
if (stat.isDirectory() || stat.nlink > 1) seen.set(key, { owner: owner, bytes: bytes });
|
|
136
|
+
report.totalBytes += bytes;
|
|
137
|
+
if (stat.isFile()) report.totalFiles++;
|
|
138
|
+
if (owner) { owner.bytes += bytes; if (stat.isFile()) owner.files++; }
|
|
139
|
+
else { report.unattributedBytes += bytes; if (stat.isFile()) report.unattributedFiles++; }
|
|
140
|
+
progress();
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
module.exports = { scanStorage: scanStorage };
|
package/lib/users-preferences.js
CHANGED
|
@@ -218,78 +218,6 @@ function attachPreferences(deps) {
|
|
|
218
218
|
return { error: "User not found" };
|
|
219
219
|
}
|
|
220
220
|
|
|
221
|
-
// --- Per-user Claude open mode ---
|
|
222
|
-
//
|
|
223
|
-
// Decides how Claude sessions are rendered when the user clicks into one:
|
|
224
|
-
// 'gui' - Clay's custom chat UI driven by the Claude Agent SDK.
|
|
225
|
-
// 'tui' - Embedded xterm running the real `claude` CLI. Keeps usage in
|
|
226
|
-
// the Interactive billing bucket post Agent SDK split.
|
|
227
|
-
//
|
|
228
|
-
// Cutover moment: midnight 2026-06-15 in UTC+14 (Line Islands - the
|
|
229
|
-
// earliest timezone on Earth in June, 14h ahead of UTC). On that instant
|
|
230
|
-
// two things happen at once:
|
|
231
|
-
// 1. The default flips from 'gui' to 'tui' (so usage lands in the
|
|
232
|
-
// Interactive billing bucket).
|
|
233
|
-
// 2. Any previously-stored per-user preference is treated as cleared
|
|
234
|
-
// so the new default applies to everyone. Toggles made on or after
|
|
235
|
-
// the cutover persist normally.
|
|
236
|
-
//
|
|
237
|
-
// The preference applies on the next session open. Currently displayed
|
|
238
|
-
// sessions are not re-rendered retroactively. The cross-mode click
|
|
239
|
-
// logic in project-sessions.js handles both directions in-place
|
|
240
|
-
// (prepareTuiSessionForGuiView for tui->gui rendering, claude --resume
|
|
241
|
-
// PTY respawn for gui->tui rendering on a born-tui session).
|
|
242
|
-
|
|
243
|
-
// Single cutover instant for both default flip and one-time reset:
|
|
244
|
-
// 2026-06-15 00:00 in UTC+14 = 2026-06-14 10:00 UTC.
|
|
245
|
-
var CLAUDE_OPEN_MODE_CUTOVER_MS = Date.UTC(2026, 5, 14, 10, 0, 0);
|
|
246
|
-
var CLAUDE_OPEN_MODE_RESET_MS = CLAUDE_OPEN_MODE_CUTOVER_MS;
|
|
247
|
-
|
|
248
|
-
function defaultClaudeOpenMode() {
|
|
249
|
-
// Reverted to GUI: the TUI default was a hedge against the (since-cancelled)
|
|
250
|
-
// Agent SDK separate-credit billing. Users who explicitly picked TUI after
|
|
251
|
-
// the cutover keep it (isStoredPreferenceLive); everyone else gets GUI.
|
|
252
|
-
return "gui";
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
// A stored preference counts only if it was set on or after the reset
|
|
256
|
-
// moment (or we haven't reached the reset moment yet). Older preferences
|
|
257
|
-
// are ignored so the new default applies once.
|
|
258
|
-
function isStoredPreferenceLive(user) {
|
|
259
|
-
if (Date.now() < CLAUDE_OPEN_MODE_RESET_MS) return true;
|
|
260
|
-
var setAt = user && user.claudeOpenModeSetAt;
|
|
261
|
-
return typeof setAt === "number" && setAt >= CLAUDE_OPEN_MODE_RESET_MS;
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
function getClaudeOpenMode(userId) {
|
|
265
|
-
var data = loadUsers();
|
|
266
|
-
var fallback = defaultClaudeOpenMode();
|
|
267
|
-
for (var i = 0; i < data.users.length; i++) {
|
|
268
|
-
if (data.users[i].id === userId) {
|
|
269
|
-
var m = data.users[i].claudeOpenMode;
|
|
270
|
-
if ((m === "gui" || m === "tui") && isStoredPreferenceLive(data.users[i])) return m;
|
|
271
|
-
return fallback;
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
return fallback;
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
function setClaudeOpenMode(userId, mode) {
|
|
278
|
-
// GUI is the default in every other resolution path, so an unrecognized
|
|
279
|
-
// value must land on GUI too. Only an explicit "tui" opts into terminal.
|
|
280
|
-
var normalized = (mode === "tui") ? "tui" : "gui";
|
|
281
|
-
var data = loadUsers();
|
|
282
|
-
for (var i = 0; i < data.users.length; i++) {
|
|
283
|
-
if (data.users[i].id === userId) {
|
|
284
|
-
data.users[i].claudeOpenMode = normalized;
|
|
285
|
-
data.users[i].claudeOpenModeSetAt = Date.now();
|
|
286
|
-
saveUsers(data);
|
|
287
|
-
return { ok: true, claudeOpenMode: normalized };
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
return { error: "User not found" };
|
|
291
|
-
}
|
|
292
|
-
|
|
293
221
|
// --- Per-user cursor sharing preference ---
|
|
294
222
|
|
|
295
223
|
function getCursorSharing(userId) {
|
|
@@ -512,8 +440,6 @@ function attachPreferences(deps) {
|
|
|
512
440
|
setChatLayout: setChatLayout,
|
|
513
441
|
getAutoContinue: getAutoContinue,
|
|
514
442
|
setAutoContinue: setAutoContinue,
|
|
515
|
-
getClaudeOpenMode: getClaudeOpenMode,
|
|
516
|
-
setClaudeOpenMode: setClaudeOpenMode,
|
|
517
443
|
getCursorSharing: getCursorSharing,
|
|
518
444
|
setCursorSharing: setCursorSharing,
|
|
519
445
|
getClaudeUserAllowList: getClaudeUserAllowList,
|
package/lib/users.js
CHANGED
|
@@ -449,8 +449,6 @@ var getAutoContinue = preferences.getAutoContinue;
|
|
|
449
449
|
var setAutoContinue = preferences.setAutoContinue;
|
|
450
450
|
var getProjectMateFilterMode = projectMateFilterPreferences.get;
|
|
451
451
|
var setProjectMateFilterMode = projectMateFilterPreferences.set;
|
|
452
|
-
var getClaudeOpenMode = preferences.getClaudeOpenMode;
|
|
453
|
-
var setClaudeOpenMode = preferences.setClaudeOpenMode;
|
|
454
452
|
var getCursorSharing = preferences.getCursorSharing;
|
|
455
453
|
var setCursorSharing = preferences.setCursorSharing;
|
|
456
454
|
var getClaudeUserAllowList = preferences.getClaudeUserAllowList;
|
|
@@ -537,8 +535,6 @@ module.exports = {
|
|
|
537
535
|
setAutoContinue: setAutoContinue,
|
|
538
536
|
getProjectMateFilterMode: getProjectMateFilterMode,
|
|
539
537
|
setProjectMateFilterMode: setProjectMateFilterMode,
|
|
540
|
-
getClaudeOpenMode: getClaudeOpenMode,
|
|
541
|
-
setClaudeOpenMode: setClaudeOpenMode,
|
|
542
538
|
getCursorSharing: getCursorSharing,
|
|
543
539
|
setCursorSharing: setCursorSharing,
|
|
544
540
|
getClaudeUserAllowList: getClaudeUserAllowList,
|
package/lib/ws-schema.js
CHANGED
|
@@ -346,6 +346,8 @@ var schema = {
|
|
|
346
346
|
// -----------------------------------------------------------------------
|
|
347
347
|
// Updates
|
|
348
348
|
// -----------------------------------------------------------------------
|
|
349
|
+
"update_check_failed": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Update check failure" },
|
|
350
|
+
"set_update_channel_result": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Release channel save acknowledgement" },
|
|
349
351
|
"set_update_channel": { direction: "c2s", handler: "lib/project-sessions.js", description: "Set the update channel (stable/beta)" },
|
|
350
352
|
"check_update": { direction: "c2s", handler: "lib/project-sessions.js", description: "Check if an update is available" },
|
|
351
353
|
"update_now": { direction: "c2s", handler: "lib/project-sessions.js", description: "Start the update process" },
|
|
@@ -454,10 +456,6 @@ var schema = {
|
|
|
454
456
|
"project_env_result": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Project .envrc content" },
|
|
455
457
|
"set_project_env": { direction: "c2s", handler: "lib/project-filesystem.js", description: "Write the project .envrc" },
|
|
456
458
|
"set_project_env_result": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Project .envrc write result" },
|
|
457
|
-
"read_global_claude_md": { direction: "c2s", handler: "lib/project-filesystem.js", description: "Read the global CLAUDE.md" },
|
|
458
|
-
"global_claude_md_result": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Global CLAUDE.md content" },
|
|
459
|
-
"write_global_claude_md": { direction: "c2s", handler: "lib/project-filesystem.js", description: "Write the global CLAUDE.md" },
|
|
460
|
-
"write_global_claude_md_result": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Global CLAUDE.md write result" },
|
|
461
459
|
"get_shared_env": { direction: "c2s", handler: "lib/project-filesystem.js", description: "Read the shared .envrc" },
|
|
462
460
|
"shared_env_result": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Shared .envrc content" },
|
|
463
461
|
"set_shared_env": { direction: "c2s", handler: "lib/project-filesystem.js", description: "Write the shared .envrc" },
|
|
@@ -46,7 +46,7 @@ AcpProcessManager.prototype.start = function() {
|
|
|
46
46
|
var args = self.opts.args ? self.opts.args.slice() : [];
|
|
47
47
|
var env = Object.assign({}, process.env, self.opts.env || {});
|
|
48
48
|
|
|
49
|
-
self._log("Spawning:", self.executablePath, args.
|
|
49
|
+
self._log("Spawning:", self.executablePath, "(" + args.length + " arguments; values omitted)");
|
|
50
50
|
self.proc = spawn(self.executablePath, args, {
|
|
51
51
|
stdio: ["pipe", "pipe", "pipe"],
|
|
52
52
|
env: env,
|
|
@@ -82,7 +82,7 @@ AcpProcessManager.prototype.start = function() {
|
|
|
82
82
|
try {
|
|
83
83
|
self._handleMessage(JSON.parse(line));
|
|
84
84
|
} catch (e) {
|
|
85
|
-
self._error("Failed to parse
|
|
85
|
+
self._error("Failed to parse protocol message (content omitted)");
|
|
86
86
|
}
|
|
87
87
|
});
|
|
88
88
|
self.rl.on("close", function() {
|
|
@@ -15,6 +15,8 @@ var backgroundTasks = require("../codex-background-tasks");
|
|
|
15
15
|
var codexUserInput = require("../codex-user-input");
|
|
16
16
|
var createTurnStop = require("../codex-turn-stop").createTurnStop;
|
|
17
17
|
var idleRecovery = require("../codex-idle-recovery");
|
|
18
|
+
var modelCatalog = require("../codex-model-catalog");
|
|
19
|
+
var normalizeQuotaSnapshot = require("../codex-rate-limits").normalizeSnapshot;
|
|
18
20
|
|
|
19
21
|
// --- Reasoning defaults ---
|
|
20
22
|
// Reasoning summaries are the only readable thinking channel Codex offers:
|
|
@@ -30,7 +32,6 @@ var idleRecovery = require("../codex-idle-recovery");
|
|
|
30
32
|
// Everything here is overridable via adapterOptions.CODEX.config.
|
|
31
33
|
var CODEX_REASONING_DEFAULTS = {
|
|
32
34
|
model_reasoning_summary: "detailed",
|
|
33
|
-
model_supports_reasoning_summaries: true,
|
|
34
35
|
};
|
|
35
36
|
var DEFAULT_CODEX_MODEL = "gpt-6-astra";
|
|
36
37
|
var CODEX_QUERY_BOUND_TOOL_CAPABILITY = { mode: "dynamic", resume: "session-mcp-recovery" };
|
|
@@ -295,30 +296,7 @@ function flattenEvent(notification, state) {
|
|
|
295
296
|
// Rate limits from Codex account
|
|
296
297
|
if (method === "account/rateLimits/updated") {
|
|
297
298
|
var rl = params.rateLimits;
|
|
298
|
-
if (rl)
|
|
299
|
-
var windows = [
|
|
300
|
-
{ key: "primary", type: "five_hour" },
|
|
301
|
-
{ key: "secondary", type: "seven_day" },
|
|
302
|
-
];
|
|
303
|
-
for (var wi = 0; wi < windows.length; wi++) {
|
|
304
|
-
var w = rl[windows[wi].key];
|
|
305
|
-
if (!w) continue;
|
|
306
|
-
var utilization = (w.usedPercent || 0) / 100;
|
|
307
|
-
var status = "allowed";
|
|
308
|
-
if (w.usedPercent >= 100) status = "rejected";
|
|
309
|
-
else if (w.usedPercent >= 80) status = "allowed_warning";
|
|
310
|
-
events.push({
|
|
311
|
-
yokeType: "rate_limit",
|
|
312
|
-
rateLimitInfo: {
|
|
313
|
-
status: status,
|
|
314
|
-
resetsAt: w.resetsAt || null,
|
|
315
|
-
rateLimitType: windows[wi].type,
|
|
316
|
-
utilization: utilization,
|
|
317
|
-
isUsingOverage: false,
|
|
318
|
-
},
|
|
319
|
-
});
|
|
320
|
-
}
|
|
321
|
-
}
|
|
299
|
+
if (rl) events.push(normalizeQuotaSnapshot(rl));
|
|
322
300
|
return events;
|
|
323
301
|
}
|
|
324
302
|
|
|
@@ -777,7 +755,7 @@ function flattenEvent(notification, state) {
|
|
|
777
755
|
}
|
|
778
756
|
|
|
779
757
|
// Unknown event type - pass through
|
|
780
|
-
console.log("[yoke/codex]
|
|
758
|
+
console.log("[yoke/codex] Unhandled event:", method);
|
|
781
759
|
events.push({
|
|
782
760
|
yokeType: "runtime_specific",
|
|
783
761
|
vendor: "codex",
|
|
@@ -1459,22 +1437,8 @@ function createCodexAdapter(opts) {
|
|
|
1459
1437
|
var _createAppServer = (opts && opts.createAppServer) || function(serverOpts) {
|
|
1460
1438
|
return new CodexAppServer(null, serverOpts);
|
|
1461
1439
|
};
|
|
1462
|
-
//
|
|
1463
|
-
|
|
1464
|
-
// slow/failed `initialize` leaves the picker empty and the chip shows the
|
|
1465
|
-
// previous vendor's model.
|
|
1466
|
-
// Kept in sync with what `model/list` reports for the bundled codex build
|
|
1467
|
-
// (0.153.4). Models the CLI no longer offers are rejected at turn/start, so
|
|
1468
|
-
// listing them would only surface a runtime failure in the model picker.
|
|
1469
|
-
var CODEX_MODELS = [
|
|
1470
|
-
"gpt-6-astra",
|
|
1471
|
-
"gpt-5.6-sol",
|
|
1472
|
-
"gpt-5.6-terra",
|
|
1473
|
-
"gpt-5.6-luna",
|
|
1474
|
-
"gpt-5.5",
|
|
1475
|
-
"gpt-5.2",
|
|
1476
|
-
];
|
|
1477
|
-
var _cachedModels = CODEX_MODELS.slice();
|
|
1440
|
+
// Keep the picker available before initialization and during offline recovery.
|
|
1441
|
+
var _cachedModels = modelCatalog.fallback();
|
|
1478
1442
|
var _appServer = null;
|
|
1479
1443
|
var _backgroundTaskResetListeners = [];
|
|
1480
1444
|
|
|
@@ -1921,9 +1885,7 @@ function createCodexAdapter(opts) {
|
|
|
1921
1885
|
throw createShutdownError();
|
|
1922
1886
|
}
|
|
1923
1887
|
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
_cachedModels = CODEX_MODELS.slice();
|
|
1888
|
+
_cachedModels = await modelCatalog.load(_appServer);
|
|
1927
1889
|
|
|
1928
1890
|
// Register foreign vendor roots with Codex so automatic skill
|
|
1929
1891
|
// triggering and skills/list use the same merged inventory.
|
|
@@ -1990,8 +1952,8 @@ function createCodexAdapter(opts) {
|
|
|
1990
1952
|
},
|
|
1991
1953
|
|
|
1992
1954
|
supportedModels: function() {
|
|
1993
|
-
//
|
|
1994
|
-
return Promise.resolve(
|
|
1955
|
+
// Never start a process merely to populate a picker.
|
|
1956
|
+
return Promise.resolve(_cachedModels.length ? _cachedModels.slice() : modelCatalog.fallback());
|
|
1995
1957
|
},
|
|
1996
1958
|
|
|
1997
1959
|
createToolServer: function(def) {
|
|
@@ -2150,12 +2112,10 @@ function createCodexAdapter(opts) {
|
|
|
2150
2112
|
throw new Error("Codex requires a mapped Linux user while OS-user isolation is enabled");
|
|
2151
2113
|
}
|
|
2152
2114
|
var models = await adapter.supportedModels();
|
|
2153
|
-
var
|
|
2154
|
-
: (opts && models.indexOf(opts.model) !== -1 ? opts.model : models[0]);
|
|
2155
|
-
if (!model) throw new Error("No supported Codex title model is available");
|
|
2115
|
+
var selection = modelCatalog.selectTitleModel(models, opts && opts.model);
|
|
2156
2116
|
return require("../title-query").generateTitle(adapter, messages, opts, {
|
|
2157
|
-
model: model,
|
|
2158
|
-
effort:
|
|
2117
|
+
model: selection.model,
|
|
2118
|
+
effort: selection.effort,
|
|
2159
2119
|
ephemeral: true,
|
|
2160
2120
|
adapterOptions: { CODEX: { sandboxMode: "read-only", approvalPolicy: "never" } },
|
|
2161
2121
|
});
|
|
@@ -174,7 +174,7 @@ CodexAppServer.prototype.start = function() {
|
|
|
174
174
|
|
|
175
175
|
var spawnSpec = buildSpawnSpec(self.executablePath, args, self.opts);
|
|
176
176
|
var userSuffix = self.opts.osUserInfo ? " as " + self.opts.osUserInfo.user : "";
|
|
177
|
-
console.log("[codex-app-server] Spawning" + userSuffix + ":", spawnSpec.command, spawnSpec.args.
|
|
177
|
+
console.log("[codex-app-server] Spawning" + userSuffix + ":", spawnSpec.command, "(" + spawnSpec.args.length + " arguments; values omitted)");
|
|
178
178
|
|
|
179
179
|
self.proc = spawn(spawnSpec.command, spawnSpec.args, spawnSpec.options);
|
|
180
180
|
|
|
@@ -218,7 +218,7 @@ CodexAppServer.prototype.start = function() {
|
|
|
218
218
|
var msg = JSON.parse(line);
|
|
219
219
|
self._handleMessage(msg);
|
|
220
220
|
} catch (e) {
|
|
221
|
-
console.error("[codex-app-server] Failed to parse
|
|
221
|
+
console.error("[codex-app-server] Failed to parse protocol message (content omitted)");
|
|
222
222
|
}
|
|
223
223
|
});
|
|
224
224
|
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// Runtime catalogs are scoped to the adapter's OS user and provider configuration.
|
|
2
|
+
var FALLBACK = ["gpt-6-astra", "gpt-6.1-sol", "gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna", "gpt-5.5", "gpt-5.2"];
|
|
3
|
+
|
|
4
|
+
function fallback() {
|
|
5
|
+
return FALLBACK.slice();
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
async function load(server) {
|
|
9
|
+
var models = [];
|
|
10
|
+
var seen = new Set();
|
|
11
|
+
var cursors = new Set();
|
|
12
|
+
var cursor = null;
|
|
13
|
+
var deadline = Date.now() + 3000;
|
|
14
|
+
try {
|
|
15
|
+
for (var page = 0; page < 4; page++) {
|
|
16
|
+
var remaining = deadline - Date.now();
|
|
17
|
+
if (remaining <= 0) return fallback();
|
|
18
|
+
var result = await server.send("model/list", { limit: 100, includeHidden: false, cursor: cursor }, remaining);
|
|
19
|
+
if (!result || !Array.isArray(result.data)) return fallback();
|
|
20
|
+
result.data.forEach(function (entry) {
|
|
21
|
+
if (!entry || entry.hidden || typeof entry.model !== "string" || !entry.model || seen.has(entry.model)) return;
|
|
22
|
+
seen.add(entry.model);
|
|
23
|
+
var model = { value: entry.model, displayName: entry.displayName || entry.model, description: entry.description || "" };
|
|
24
|
+
if (Array.isArray(entry.supportedReasoningEfforts)) {
|
|
25
|
+
model.supportedEffortLevels = entry.supportedReasoningEfforts.map(function (option) { return option.reasoningEffort; }).filter(function (effort) { return typeof effort === "string" && effort.length > 0; });
|
|
26
|
+
}
|
|
27
|
+
models.push(model);
|
|
28
|
+
});
|
|
29
|
+
cursor = result.nextCursor;
|
|
30
|
+
if (!cursor) return models.length ? models : fallback();
|
|
31
|
+
if (cursors.has(cursor)) return fallback();
|
|
32
|
+
cursors.add(cursor);
|
|
33
|
+
}
|
|
34
|
+
} catch (error) {
|
|
35
|
+
// Catalog failure must not prevent existing conversations from starting.
|
|
36
|
+
}
|
|
37
|
+
return fallback();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function selectTitleModel(models, sessionModel) {
|
|
41
|
+
var entries = models.map(function (entry) {
|
|
42
|
+
return typeof entry === "string" ? { value: entry } : entry;
|
|
43
|
+
}).filter(function (entry) { return entry && typeof entry.value === "string" && entry.value.trim(); });
|
|
44
|
+
var selected = entries.find(function (entry) { return entry.value === "gpt-5.6-luna"; })
|
|
45
|
+
|| entries.find(function (entry) { return entry.value === sessionModel; }) || entries[0];
|
|
46
|
+
if (!selected) throw new Error("No supported Codex title model is available");
|
|
47
|
+
var efforts = selected.supportedEffortLevels;
|
|
48
|
+
var effort = Array.isArray(efforts) ? (efforts.indexOf("low") !== -1 ? "low" : efforts[0]) : "low";
|
|
49
|
+
return { model: selected.value, effort: effort };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
module.exports = { fallback: fallback, load: load, selectTitleModel: selectTitleModel };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// Account quota snapshots describe usage, never whether a turn was rejected.
|
|
2
|
+
function normalizeSnapshot(rateLimits) {
|
|
3
|
+
var windows = [];
|
|
4
|
+
["primary", "secondary"].forEach(function (key) {
|
|
5
|
+
var window = rateLimits[key];
|
|
6
|
+
if (!window || window.windowDurationMins === 0) return;
|
|
7
|
+
var duration = window.windowDurationMins;
|
|
8
|
+
var used = window.usedPercent;
|
|
9
|
+
windows.push({
|
|
10
|
+
key: key,
|
|
11
|
+
windowDurationMins: Number.isFinite(duration) && duration > 0 ? duration : null,
|
|
12
|
+
usedPercent: Number.isFinite(used) && used >= 0 ? used : null,
|
|
13
|
+
resetsAt: Number.isFinite(window.resetsAt) && window.resetsAt > 0 ? window.resetsAt : null,
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
return {
|
|
17
|
+
yokeType: "rate_limit",
|
|
18
|
+
rateLimitInfo: {
|
|
19
|
+
source: "account_snapshot",
|
|
20
|
+
limitId: rateLimits.limitId || "codex",
|
|
21
|
+
windows: windows,
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
module.exports = { normalizeSnapshot: normalizeSnapshot };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "clay-server",
|
|
3
|
-
"version": "5.1.0
|
|
3
|
+
"version": "5.1.0",
|
|
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"
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"homepage": "https://github.com/chadbyte/clay#readme",
|
|
48
48
|
"author": "Chad",
|
|
49
49
|
"dependencies": {
|
|
50
|
-
"@anthropic-ai/claude-agent-sdk": "^0.3.
|
|
50
|
+
"@anthropic-ai/claude-agent-sdk": "^0.3.296",
|
|
51
51
|
"@dicebear/adventurer": "9.4.3",
|
|
52
52
|
"@dicebear/bottts": "9.4.3",
|
|
53
53
|
"@dicebear/core": "9.4.3",
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
"@dicebear/thumbs": "9.4.3",
|
|
59
59
|
"@lydell/node-pty": "^1.2.0-beta.3",
|
|
60
60
|
"@modelcontextprotocol/sdk": "^1.32.1",
|
|
61
|
-
"@openai/codex": "^0.
|
|
61
|
+
"@openai/codex": "^0.162.1",
|
|
62
62
|
"@seald-io/nedb": "^4.1.2",
|
|
63
63
|
"codemirror": "^5.65.21",
|
|
64
64
|
"imapflow": "^1.7.8",
|