clay-server 2.47.0-beta.6 → 3.0.0-beta.2
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/daemon.js +6 -27
- package/lib/project-connection.js +21 -8
- package/lib/project-session-pair.js +312 -0
- package/lib/project-session-spawn.js +8 -4
- package/lib/project-sessions.js +108 -10
- package/lib/project-user-message.js +18 -8
- package/lib/project.js +33 -0
- package/lib/public/app.js +20 -0
- package/lib/public/css/menus.css +83 -1
- package/lib/public/css/messages.css +14 -0
- package/lib/public/css/pane.css +420 -0
- package/lib/public/css/sidebar.css +40 -0
- package/lib/public/css/sticky-notes.css +2 -0
- package/lib/public/index.html +4 -0
- package/lib/public/modules/app-connection.js +6 -0
- package/lib/public/modules/app-favicon.js +5 -0
- package/lib/public/modules/app-header.js +101 -6
- package/lib/public/modules/app-messages.js +59 -6
- package/lib/public/modules/app-panels.js +49 -27
- package/lib/public/modules/app-rendering.js +11 -4
- package/lib/public/modules/pane-bridge.js +41 -0
- package/lib/public/modules/pane-session.js +18 -0
- package/lib/public/modules/sidebar-sessions.js +175 -1
- package/lib/public/modules/sidebar.js +2 -0
- package/lib/public/modules/split-group-helpers.js +18 -0
- package/lib/public/modules/split-pair-ui.js +324 -0
- package/lib/public/modules/split-view.js +499 -0
- package/lib/public/modules/sticky-notes.js +32 -91
- package/lib/public/style.css +1 -0
- package/lib/sdk-bridge.js +111 -20
- package/lib/sdk-message-processor.js +2 -2
- package/lib/server.js +24 -4
- package/lib/session-hygiene.js +100 -0
- package/lib/session-pair-mcp-server.js +28 -0
- package/lib/session-split-groups.js +298 -0
- package/lib/sessions.js +163 -34
- package/lib/ws-request.js +14 -0
- package/lib/ws-schema.js +13 -0
- package/lib/yoke/adapters/claude.js +32 -0
- package/lib/yoke/adapters/codex.js +100 -77
- package/lib/yoke/adapters/kiro.js +34 -35
- package/lib/yoke/codex-app-server.js +56 -6
- package/lib/yoke/index.js +1 -0
- package/lib/yoke/skill-discovery.js +196 -0
- package/lib/yoke/vendor-registry.js +34 -0
- package/package.json +2 -2
|
@@ -110,7 +110,10 @@ function CodexAppServer(executablePath, opts) {
|
|
|
110
110
|
this.rl = null;
|
|
111
111
|
this.nextId = 1;
|
|
112
112
|
this.pendingRequests = {}; // id -> { resolve, reject, timer }
|
|
113
|
-
|
|
113
|
+
// One app-server process is shared by every Codex session in a project.
|
|
114
|
+
// Route server-initiated events by threadId so a temporary query (such as
|
|
115
|
+
// title generation) cannot replace the handler for a long-lived session.
|
|
116
|
+
this.handlers = []; // [{ threadId, fn }]
|
|
114
117
|
this.executablePath = executablePath || findCodexPath();
|
|
115
118
|
this.opts = opts || {};
|
|
116
119
|
this.started = false;
|
|
@@ -215,20 +218,67 @@ CodexAppServer.prototype._handleMessage = function(msg) {
|
|
|
215
218
|
|
|
216
219
|
// Server-initiated request (has id + method) or notification (has method, no id)
|
|
217
220
|
if (msg.method) {
|
|
218
|
-
|
|
219
|
-
|
|
221
|
+
var params = msg.params || {};
|
|
222
|
+
var threadId = params.threadId
|
|
223
|
+
|| (params.thread && params.thread.id)
|
|
224
|
+
|| (params.turn && params.turn.threadId)
|
|
225
|
+
|| null;
|
|
226
|
+
var targets;
|
|
227
|
+
if (threadId) {
|
|
228
|
+
targets = this.handlers.filter(function(handler) {
|
|
229
|
+
return handler.threadId === threadId;
|
|
230
|
+
});
|
|
220
231
|
} else {
|
|
221
|
-
|
|
232
|
+
// Process-wide events such as auth failures belong to every live query.
|
|
233
|
+
targets = this.handlers.slice();
|
|
222
234
|
}
|
|
235
|
+
|
|
236
|
+
if (!targets.length) {
|
|
237
|
+
if (msg.id !== undefined && msg.id !== null) {
|
|
238
|
+
console.warn("[codex-app-server] No handler for request " + msg.method + " (thread=" + (threadId || "none") + "), rejecting");
|
|
239
|
+
this.respondError(msg.id, -32001, "No active handler for thread " + (threadId || "none"));
|
|
240
|
+
} else {
|
|
241
|
+
console.log("[codex-app-server] Unhandled event:", msg.method);
|
|
242
|
+
}
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// A JSON-RPC request must be answered exactly once. Notifications may fan
|
|
247
|
+
// out when they are process-wide and intentionally have no thread id.
|
|
248
|
+
if (msg.id !== undefined && msg.id !== null) {
|
|
249
|
+
try {
|
|
250
|
+
targets[0].fn(msg);
|
|
251
|
+
} catch (e) {
|
|
252
|
+
console.error("[codex-app-server] Handler threw for " + msg.method + ":", e && e.message ? e.message : e);
|
|
253
|
+
this.respondError(msg.id, -32000, "Handler error");
|
|
254
|
+
}
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
targets.forEach(function(handler) {
|
|
258
|
+
try { handler.fn(msg); } catch (e) {
|
|
259
|
+
console.error("[codex-app-server] Handler threw for " + msg.method + ":", e && e.message ? e.message : e);
|
|
260
|
+
}
|
|
261
|
+
});
|
|
223
262
|
}
|
|
224
263
|
};
|
|
225
264
|
|
|
265
|
+
CodexAppServer.prototype.addHandler = function(fn) {
|
|
266
|
+
var entry = { threadId: null, fn: fn };
|
|
267
|
+
this.handlers.push(entry);
|
|
268
|
+
return entry;
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
CodexAppServer.prototype.removeHandler = function(entry) {
|
|
272
|
+
var index = this.handlers.indexOf(entry);
|
|
273
|
+
if (index !== -1) this.handlers.splice(index, 1);
|
|
274
|
+
};
|
|
275
|
+
|
|
226
276
|
// The definitive "not logged in" signal from Codex is a 401 on its own
|
|
227
277
|
// responses endpoint, which only appears on stderr (not as a JSON-RPC error).
|
|
228
278
|
// When we see it, synthesize an error event so the adapter maps it to the
|
|
229
279
|
// neutral auth_required yokeType. Deduped so a burst of 401 retries collapses.
|
|
230
280
|
CodexAppServer.prototype._maybeSignalAuthError = function(line) {
|
|
231
|
-
if (!this.
|
|
281
|
+
if (!this.handlers.length || !line || this._authSignalSent) return;
|
|
232
282
|
var isAuth = /missing bearer|token[_ ]?revoked|invalidated oauth|please sign in again/i.test(line)
|
|
233
283
|
|| (/\b401\b/.test(line) && /unauthorized|responses|openai\.com|chatgpt\.com/i.test(line));
|
|
234
284
|
if (!isAuth) return;
|
|
@@ -236,7 +286,7 @@ CodexAppServer.prototype._maybeSignalAuthError = function(line) {
|
|
|
236
286
|
var self = this;
|
|
237
287
|
setTimeout(function () { self._authSignalSent = false; }, 15000);
|
|
238
288
|
try {
|
|
239
|
-
this.
|
|
289
|
+
this._handleMessage({ method: "error", params: { error: { codexErrorInfo: "unauthorized", message: line } } });
|
|
240
290
|
} catch (e) {}
|
|
241
291
|
};
|
|
242
292
|
|
package/lib/yoke/index.js
CHANGED
|
@@ -377,6 +377,7 @@ module.exports = {
|
|
|
377
377
|
invalidateAuthCache: invalidateAuthCache,
|
|
378
378
|
VENDOR_REGISTRY: vendorRegistry.VENDOR_REGISTRY,
|
|
379
379
|
getVendorInfo: vendorRegistry.getVendorInfo,
|
|
380
|
+
clampEffort: vendorRegistry.clampEffort,
|
|
380
381
|
TOOL_POLICIES: iface.TOOL_POLICIES,
|
|
381
382
|
validateAdapter: iface.validateAdapter,
|
|
382
383
|
validateQueryHandle: iface.validateQueryHandle,
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
// Cross-vendor skill discovery
|
|
2
|
+
// ----------------------------
|
|
3
|
+
// Skills remain in their vendor-owned directories. This module only builds a
|
|
4
|
+
// merged view so every YOKE adapter can expose the same installed skills.
|
|
5
|
+
|
|
6
|
+
var fs = require("fs");
|
|
7
|
+
var os = require("os");
|
|
8
|
+
var path = require("path");
|
|
9
|
+
|
|
10
|
+
function defaultHomeDir() {
|
|
11
|
+
try { return require("../config").REAL_HOME; } catch (e) { return os.homedir(); }
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function unquote(value) {
|
|
15
|
+
var text = String(value || "").trim();
|
|
16
|
+
if ((text[0] === '"' && text[text.length - 1] === '"') ||
|
|
17
|
+
(text[0] === "'" && text[text.length - 1] === "'")) {
|
|
18
|
+
return text.slice(1, -1);
|
|
19
|
+
}
|
|
20
|
+
return text;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function readMetadata(skillPath, fallbackName) {
|
|
24
|
+
var content;
|
|
25
|
+
try { content = fs.readFileSync(skillPath, "utf8"); } catch (e) { return null; }
|
|
26
|
+
var name = fallbackName;
|
|
27
|
+
var description = "";
|
|
28
|
+
var frontmatter = content.match(/^---\s*\r?\n([\s\S]*?)\r?\n---(?:\s*\r?\n|$)/);
|
|
29
|
+
if (frontmatter) {
|
|
30
|
+
var nameMatch = frontmatter[1].match(/^name:\s*(.+)$/m);
|
|
31
|
+
var descriptionMatch = frontmatter[1].match(/^description:\s*(.+)$/m);
|
|
32
|
+
if (nameMatch) name = unquote(nameMatch[1]) || fallbackName;
|
|
33
|
+
if (descriptionMatch) description = unquote(descriptionMatch[1]);
|
|
34
|
+
}
|
|
35
|
+
if (!description) {
|
|
36
|
+
var heading = content.match(/^#\s+(.+)$/m);
|
|
37
|
+
if (heading) description = heading[1].trim();
|
|
38
|
+
}
|
|
39
|
+
return { name: name, description: description };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function scanRoot(root, source, merged) {
|
|
43
|
+
var entries;
|
|
44
|
+
try { entries = fs.readdirSync(root, { withFileTypes: true }); } catch (e) { return; }
|
|
45
|
+
entries.sort(function(a, b) { return a.name.localeCompare(b.name); });
|
|
46
|
+
for (var i = 0; i < entries.length; i++) {
|
|
47
|
+
var entry = entries[i];
|
|
48
|
+
if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
|
|
49
|
+
var skillPath = path.join(root, entry.name, "SKILL.md");
|
|
50
|
+
var metadata = readMetadata(skillPath, entry.name);
|
|
51
|
+
if (!metadata) continue;
|
|
52
|
+
merged[metadata.name] = {
|
|
53
|
+
name: metadata.name,
|
|
54
|
+
path: skillPath,
|
|
55
|
+
description: metadata.description,
|
|
56
|
+
source: source,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function getSkillRoots(cwd, opts) {
|
|
62
|
+
var homeDir = (opts && opts.homeDir) || defaultHomeDir();
|
|
63
|
+
var roots = [
|
|
64
|
+
{ path: path.join(homeDir, ".codex", "skills"), source: "codex-user" },
|
|
65
|
+
{ path: path.join(homeDir, ".claude", "skills"), source: "claude-user" },
|
|
66
|
+
];
|
|
67
|
+
if (cwd) {
|
|
68
|
+
roots.push({ path: path.join(cwd, "skills"), source: "project" });
|
|
69
|
+
roots.push({ path: path.join(cwd, ".claude", "skills"), source: "claude-project" });
|
|
70
|
+
}
|
|
71
|
+
return roots;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function discoverSkills(cwd, opts) {
|
|
75
|
+
var merged = {};
|
|
76
|
+
var roots = getSkillRoots(cwd, opts);
|
|
77
|
+
for (var i = 0; i < roots.length; i++) scanRoot(roots[i].path, roots[i].source, merged);
|
|
78
|
+
return Object.keys(merged).sort().map(function(name) { return merged[name]; });
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function indexSkills(skills) {
|
|
82
|
+
var indexed = {};
|
|
83
|
+
for (var i = 0; i < skills.length; i++) indexed[skills[i].name] = skills[i];
|
|
84
|
+
return indexed;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function findSkillReferences(text, skills) {
|
|
88
|
+
if (typeof text !== "string") return [];
|
|
89
|
+
var indexed = Array.isArray(skills) ? indexSkills(skills) : skills;
|
|
90
|
+
var found = [];
|
|
91
|
+
var seen = {};
|
|
92
|
+
var match;
|
|
93
|
+
var pattern = /\$([a-zA-Z0-9_-]+)/g;
|
|
94
|
+
while ((match = pattern.exec(text)) !== null) {
|
|
95
|
+
var skill = indexed[match[1]];
|
|
96
|
+
if (!skill || seen[skill.name]) continue;
|
|
97
|
+
seen[skill.name] = true;
|
|
98
|
+
found.push(skill);
|
|
99
|
+
}
|
|
100
|
+
return found;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function buildSkillIndex(skills) {
|
|
104
|
+
if (!skills.length) return "";
|
|
105
|
+
var lines = [
|
|
106
|
+
"Available shared skills. When a task matches a description, read that SKILL.md with your file tools before proceeding. A user may also invoke one with $skill-name:",
|
|
107
|
+
];
|
|
108
|
+
for (var i = 0; i < skills.length; i++) {
|
|
109
|
+
var suffix = skills[i].description ? " — " + skills[i].description.replace(/\s+/g, " ") : "";
|
|
110
|
+
lines.push("- $" + skills[i].name + suffix + " — " + skills[i].path);
|
|
111
|
+
}
|
|
112
|
+
return lines.join("\n");
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function readReferencedSkills(text, skills, maxChars) {
|
|
116
|
+
var references = findSkillReferences(text, skills);
|
|
117
|
+
var remaining = maxChars || 32768;
|
|
118
|
+
var blocks = [];
|
|
119
|
+
for (var i = 0; i < references.length && remaining > 0; i++) {
|
|
120
|
+
var content;
|
|
121
|
+
try { content = fs.readFileSync(references[i].path, "utf8"); } catch (e) { continue; }
|
|
122
|
+
var header = "<shared-skill name=\"" + references[i].name + "\" source=\"" + references[i].source + "\">\n";
|
|
123
|
+
var footer = "\n</shared-skill>";
|
|
124
|
+
var available = Math.max(0, remaining - header.length - footer.length);
|
|
125
|
+
if (!available) break;
|
|
126
|
+
if (content.length > available) {
|
|
127
|
+
var marker = "\n[Skill truncated by Clay]";
|
|
128
|
+
content = content.slice(0, Math.max(0, available - marker.length)) + marker.slice(0, available);
|
|
129
|
+
}
|
|
130
|
+
var block = header + content + footer;
|
|
131
|
+
blocks.push(block);
|
|
132
|
+
remaining -= block.length;
|
|
133
|
+
}
|
|
134
|
+
return blocks.join("\n\n");
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function lstatOrNull(target) {
|
|
138
|
+
try { return fs.lstatSync(target); } catch (e) {
|
|
139
|
+
if (e.code === "ENOENT") return null;
|
|
140
|
+
throw e;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function ensureClaudeCodexPlugin(opts) {
|
|
145
|
+
var homeDir = (opts && opts.homeDir) || defaultHomeDir();
|
|
146
|
+
var source = path.join(homeDir, ".codex", "skills");
|
|
147
|
+
try {
|
|
148
|
+
if (!fs.statSync(source).isDirectory()) return null;
|
|
149
|
+
} catch (e) { return null; }
|
|
150
|
+
|
|
151
|
+
var pluginDir = path.join(homeDir, ".clay", "skill-bridge", "codex");
|
|
152
|
+
var bridgeRoot = path.dirname(pluginDir);
|
|
153
|
+
var manifestDir = path.join(pluginDir, ".claude-plugin");
|
|
154
|
+
var manifestPath = path.join(manifestDir, "plugin.json");
|
|
155
|
+
var skillsLink = path.join(pluginDir, "skills");
|
|
156
|
+
var manifest = JSON.stringify({ name: "codex", version: "1.0.0", description: "Codex skills shared by Clay" }, null, 2) + "\n";
|
|
157
|
+
|
|
158
|
+
try {
|
|
159
|
+
fs.mkdirSync(path.join(homeDir, ".clay"), { recursive: true });
|
|
160
|
+
var bridgeStat = lstatOrNull(bridgeRoot);
|
|
161
|
+
if (bridgeStat && (!bridgeStat.isDirectory() || bridgeStat.isSymbolicLink())) return null;
|
|
162
|
+
if (!bridgeStat) fs.mkdirSync(bridgeRoot);
|
|
163
|
+
var pluginStat = lstatOrNull(pluginDir);
|
|
164
|
+
if (pluginStat && (!pluginStat.isDirectory() || pluginStat.isSymbolicLink())) return null;
|
|
165
|
+
if (!pluginStat) fs.mkdirSync(pluginDir);
|
|
166
|
+
var manifestDirStat = lstatOrNull(manifestDir);
|
|
167
|
+
if (manifestDirStat && (!manifestDirStat.isDirectory() || manifestDirStat.isSymbolicLink())) return null;
|
|
168
|
+
if (!manifestDirStat) fs.mkdirSync(manifestDir);
|
|
169
|
+
var manifestStat = lstatOrNull(manifestPath);
|
|
170
|
+
if (manifestStat) {
|
|
171
|
+
if (!manifestStat.isFile() || fs.readFileSync(manifestPath, "utf8") !== manifest) return null;
|
|
172
|
+
} else {
|
|
173
|
+
fs.writeFileSync(manifestPath, manifest, { mode: 0o600, flag: "wx" });
|
|
174
|
+
}
|
|
175
|
+
var stat = lstatOrNull(skillsLink);
|
|
176
|
+
if (stat) {
|
|
177
|
+
if (!stat.isSymbolicLink() || path.resolve(pluginDir, fs.readlinkSync(skillsLink)) !== path.resolve(source)) return null;
|
|
178
|
+
} else {
|
|
179
|
+
fs.symlinkSync(source, skillsLink, "dir");
|
|
180
|
+
}
|
|
181
|
+
} catch (e) {
|
|
182
|
+
if (e.code !== "ENOENT") console.error("[yoke/skills] Failed to create Claude Codex skill bridge:", e.message);
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
return pluginDir;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
module.exports = {
|
|
189
|
+
buildSkillIndex: buildSkillIndex,
|
|
190
|
+
discoverSkills: discoverSkills,
|
|
191
|
+
ensureClaudeCodexPlugin: ensureClaudeCodexPlugin,
|
|
192
|
+
findSkillReferences: findSkillReferences,
|
|
193
|
+
getSkillRoots: getSkillRoots,
|
|
194
|
+
indexSkills: indexSkills,
|
|
195
|
+
readReferencedSkills: readReferencedSkills,
|
|
196
|
+
};
|
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
// needs metadata before adapter initialization should read it from here.
|
|
3
3
|
// Do not import adapters into this module.
|
|
4
4
|
|
|
5
|
+
// Canonical effort ordering used to clamp a level onto a vendor that does
|
|
6
|
+
// not support it (e.g. codex "minimal" -> claude "low", claude "max" ->
|
|
7
|
+
// codex "xhigh").
|
|
8
|
+
var EFFORT_ORDER = ["minimal", "low", "medium", "high", "xhigh", "max"];
|
|
9
|
+
|
|
5
10
|
var VENDOR_REGISTRY = {
|
|
6
11
|
claude: {
|
|
7
12
|
displayName: "Claude Code",
|
|
@@ -9,6 +14,7 @@ var VENDOR_REGISTRY = {
|
|
|
9
14
|
binaryName: "claude",
|
|
10
15
|
avatar: "/claude-code-avatar.png",
|
|
11
16
|
sessionModes: ["gui", "tui"],
|
|
17
|
+
effortLevels: ["low", "medium", "high", "xhigh", "max"],
|
|
12
18
|
osUserIsolation: true,
|
|
13
19
|
usageDashboard: {
|
|
14
20
|
icon: "/claude-code-avatar.png",
|
|
@@ -24,6 +30,7 @@ var VENDOR_REGISTRY = {
|
|
|
24
30
|
binaryName: "codex",
|
|
25
31
|
avatar: "/codex-avatar.png",
|
|
26
32
|
sessionModes: ["gui"],
|
|
33
|
+
effortLevels: ["minimal", "low", "medium", "high", "xhigh"],
|
|
27
34
|
osUserIsolation: true,
|
|
28
35
|
usageDashboard: {
|
|
29
36
|
icon: "/codex-avatar.png",
|
|
@@ -39,6 +46,7 @@ var VENDOR_REGISTRY = {
|
|
|
39
46
|
binaryName: "kiro-cli",
|
|
40
47
|
avatar: "/kiro-avatar.svg",
|
|
41
48
|
sessionModes: ["gui"],
|
|
49
|
+
effortLevels: [],
|
|
42
50
|
osUserIsolation: false,
|
|
43
51
|
usageDashboard: null,
|
|
44
52
|
rateLimitTracking: false,
|
|
@@ -49,7 +57,33 @@ function getVendorInfo(vendor) {
|
|
|
49
57
|
return VENDOR_REGISTRY[vendor] || null;
|
|
50
58
|
}
|
|
51
59
|
|
|
60
|
+
// Clamp an effort level onto what the vendor supports. Returns the level
|
|
61
|
+
// itself when supported, the nearest supported level by the canonical order
|
|
62
|
+
// otherwise, and undefined when the vendor has no effort concept (or the
|
|
63
|
+
// input is not a known level -- never forward junk to an adapter).
|
|
64
|
+
function clampEffort(vendor, effort) {
|
|
65
|
+
if (!effort) return undefined;
|
|
66
|
+
var info = VENDOR_REGISTRY[vendor];
|
|
67
|
+
var levels = (info && info.effortLevels) || [];
|
|
68
|
+
if (levels.length === 0) return undefined;
|
|
69
|
+
if (levels.indexOf(effort) !== -1) return effort;
|
|
70
|
+
var position = EFFORT_ORDER.indexOf(effort);
|
|
71
|
+
if (position === -1) return undefined;
|
|
72
|
+
var nearest = levels[0];
|
|
73
|
+
var nearestDistance = Infinity;
|
|
74
|
+
for (var i = 0; i < levels.length; i++) {
|
|
75
|
+
var distance = Math.abs(EFFORT_ORDER.indexOf(levels[i]) - position);
|
|
76
|
+
if (distance < nearestDistance) {
|
|
77
|
+
nearestDistance = distance;
|
|
78
|
+
nearest = levels[i];
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return nearest;
|
|
82
|
+
}
|
|
83
|
+
|
|
52
84
|
module.exports = {
|
|
53
85
|
VENDOR_REGISTRY: VENDOR_REGISTRY,
|
|
54
86
|
getVendorInfo: getVendorInfo,
|
|
87
|
+
clampEffort: clampEffort,
|
|
88
|
+
EFFORT_ORDER: EFFORT_ORDER,
|
|
55
89
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "clay-server",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0-beta.2",
|
|
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",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"@lydell/node-pty": "^1.2.0-beta.3",
|
|
54
54
|
"@openai/codex": "^0.147.0",
|
|
55
55
|
"imapflow": "^1.3.1",
|
|
56
|
-
"nodemailer": "^
|
|
56
|
+
"nodemailer": "^9.0.5",
|
|
57
57
|
"qrcode-terminal": "^0.12.0",
|
|
58
58
|
"web-push": "^3.6.7",
|
|
59
59
|
"ws": "^8.18.0"
|