clay-server 2.33.1 → 2.34.0-beta.10

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.
Files changed (47) hide show
  1. package/lib/ask-user-mcp-server.js +120 -0
  2. package/lib/config.js +9 -13
  3. package/lib/daemon.js +116 -55
  4. package/lib/mate-datastore.js +359 -0
  5. package/lib/mates.js +2 -2
  6. package/lib/os-users.js +70 -37
  7. package/lib/project-connection.js +16 -9
  8. package/lib/project-http.js +3 -4
  9. package/lib/project-image.js +3 -2
  10. package/lib/project-mate-datastore.js +232 -0
  11. package/lib/project-sessions.js +110 -7
  12. package/lib/project-user-message.js +4 -3
  13. package/lib/project.js +126 -10
  14. package/lib/public/app.js +2 -0
  15. package/lib/public/css/mates.css +228 -11
  16. package/lib/public/css/messages.css +23 -0
  17. package/lib/public/css/mobile-nav.css +0 -14
  18. package/lib/public/css/notifications-center.css +80 -0
  19. package/lib/public/css/sidebar.css +326 -101
  20. package/lib/public/index.html +24 -29
  21. package/lib/public/modules/app-dm.js +0 -2
  22. package/lib/public/modules/app-messages.js +23 -0
  23. package/lib/public/modules/app-rendering.js +0 -2
  24. package/lib/public/modules/diff.js +21 -7
  25. package/lib/public/modules/mate-datastore-ui.js +280 -0
  26. package/lib/public/modules/mate-sidebar.js +3 -9
  27. package/lib/public/modules/mate-wizard.js +15 -15
  28. package/lib/public/modules/sidebar-mobile.js +10 -20
  29. package/lib/public/modules/sidebar-sessions.js +490 -113
  30. package/lib/public/modules/sidebar.js +8 -6
  31. package/lib/public/modules/tools.js +115 -18
  32. package/lib/public/sw.js +1 -1
  33. package/lib/sdk-bridge.js +56 -41
  34. package/lib/sdk-message-processor.js +21 -4
  35. package/lib/server.js +28 -72
  36. package/lib/sessions.js +157 -20
  37. package/lib/updater.js +2 -2
  38. package/lib/users.js +2 -2
  39. package/lib/ws-schema.js +16 -0
  40. package/lib/yoke/adapters/claude-worker.js +114 -2
  41. package/lib/yoke/adapters/claude.js +56 -5
  42. package/lib/yoke/adapters/codex.js +350 -58
  43. package/lib/yoke/index.js +93 -48
  44. package/lib/yoke/instructions.js +0 -1
  45. package/lib/yoke/mcp-bridge-server.js +14 -6
  46. package/package.json +1 -2
  47. package/lib/yoke/adapters/gemini.js +0 -709
package/lib/yoke/index.js CHANGED
@@ -4,7 +4,6 @@
4
4
  var iface = require("./interface");
5
5
  var instructions = require("./instructions");
6
6
  var createClaudeAdapter = require("./adapters/claude").createClaudeAdapter;
7
- var createGeminiAdapter = require("./adapters/gemini").createGeminiAdapter;
8
7
  var createCodexAdapter = require("./adapters/codex").createCodexAdapter;
9
8
 
10
9
  /**
@@ -20,6 +19,7 @@ function wrapCreateQuery(adapter, defaultCwd) {
20
19
  var originalCreateQuery = adapter.createQuery.bind(adapter);
21
20
 
22
21
  adapter.createQuery = function(queryOpts) {
22
+ queryOpts = queryOpts || {};
23
23
  var projectDir = (queryOpts && queryOpts.cwd) || defaultCwd;
24
24
  var merged = instructions.scanAndMerge(projectDir, adapter.vendor);
25
25
 
@@ -48,15 +48,13 @@ function createAdapter(opts) {
48
48
  var adapter;
49
49
  if (vendor === "claude") {
50
50
  adapter = createClaudeAdapter(opts);
51
- } else if (vendor === "gemini") {
52
- adapter = createGeminiAdapter(opts);
53
51
  } else if (vendor === "codex") {
54
52
  adapter = createCodexAdapter(opts);
55
53
  } else {
56
54
  throw new Error("[YOKE] Unknown adapter vendor: " + vendor);
57
55
  }
58
56
  iface.validateAdapter(adapter);
59
- wrapCreateQuery(adapter, opts.cwd);
57
+ wrapCreateQuery(adapter, opts && opts.cwd);
60
58
  return adapter;
61
59
  }
62
60
 
@@ -82,6 +80,18 @@ function checkAuth() {
82
80
  if (_authCache) return _authCache;
83
81
 
84
82
  var execSync = require("child_process").execSync;
83
+ var execFileSync = require("child_process").execFileSync;
84
+
85
+ function lookupBinary(name) {
86
+ try {
87
+ if (process.platform === "win32") {
88
+ return execFileSync("where", [name], { timeout: 3000, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] }).trim().split(/\r?\n/)[0] || null;
89
+ }
90
+ return execFileSync("which", [name], { timeout: 3000, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] }).trim().split(/\r?\n/)[0] || null;
91
+ } catch (e) {
92
+ return null;
93
+ }
94
+ }
85
95
 
86
96
  function parseClaudeAuthStatusJson(out) {
87
97
  if (!out) return null;
@@ -101,7 +111,31 @@ function checkAuth() {
101
111
  return false;
102
112
  }
103
113
 
114
+ function hasThirdPartyProviderAuth() {
115
+ // Claude Code supports third-party providers via env vars. When these are set,
116
+ // `claude auth status` reports "not logged in" because there is no OAuth session,
117
+ // but Claude Code itself authenticates directly through the provider.
118
+ var env = process.env;
119
+ if (env.CLAUDE_CODE_USE_BEDROCK === "1"
120
+ && (env.AWS_BEARER_TOKEN_BEDROCK
121
+ || env.AWS_ACCESS_KEY_ID
122
+ || env.AWS_PROFILE
123
+ || env.AWS_SESSION_TOKEN)) {
124
+ return "bedrock";
125
+ }
126
+ if (env.CLAUDE_CODE_USE_VERTEX === "1") return "vertex";
127
+ if (env.ANTHROPIC_API_KEY) return "api_key";
128
+ if (env.ANTHROPIC_AUTH_TOKEN) return "auth_token";
129
+ return null;
130
+ }
131
+
104
132
  function checkClaude() {
133
+ var provider = hasThirdPartyProviderAuth();
134
+ if (provider) {
135
+ console.log("[yoke] Claude auth via third-party provider: " + provider);
136
+ return true;
137
+ }
138
+
105
139
  try {
106
140
  var out = execSync("claude auth status --json", { timeout: 5000, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] });
107
141
  var parsed = parseClaudeAuthStatusJson(out);
@@ -127,47 +161,52 @@ function checkAuth() {
127
161
  function resolveCodexBinary() {
128
162
  var fs = require("fs");
129
163
  var findCodexPath = require("./codex-app-server").findCodexPath;
130
- var pathProbeCmd = process.platform === "win32" ? "where codex" : "which codex";
131
164
 
132
165
  try {
133
166
  var codexBin = findCodexPath();
134
167
  if (codexBin && fs.existsSync(codexBin)) return codexBin;
135
168
  } catch (e) {}
136
169
 
137
- try {
138
- var whichOut = execSync(pathProbeCmd, { timeout: 3000, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] });
139
- var codexPath = whichOut.trim().split(/\r?\n/)[0];
140
- if (codexPath) return codexPath;
141
- } catch (e) {}
142
-
143
- return null;
170
+ return lookupBinary("codex");
144
171
  }
145
172
 
146
173
  function checkCodex() {
147
174
  try {
148
175
  var codexBin = resolveCodexBinary();
149
176
  if (!codexBin) return false;
150
- execSync('"' + codexBin + '" login status', { timeout: 5000, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] });
177
+ execFileSync(codexBin, ["login", "status"], { timeout: 5000, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] });
151
178
  return true;
152
179
  } catch (e) {
153
180
  return false;
154
181
  }
155
182
  }
156
183
 
157
- _authCache = { claude: checkClaude(), codex: checkCodex(), gemini: false };
184
+ _authCache = { claude: checkClaude(), codex: checkCodex() };
158
185
  logAuthCheck(_authCache);
159
186
  return _authCache;
160
187
  }
161
188
 
162
189
  /**
163
190
  * Check which vendor binaries are installed (regardless of auth status).
191
+ *
192
+ * Result is cached at module scope because the check runs two execFileSync
193
+ * calls per invocation and is triggered once per project context on daemon
194
+ * startup. With N projects this used to cost ~2N synchronous subprocesses;
195
+ * caching collapses it to two total. The cache is invalidated alongside
196
+ * the auth cache (via invalidateAuthCache) since "just installed" is the
197
+ * same situation as "just logged in" from the daemon's perspective.
164
198
  */
199
+ var _installedCache = null;
200
+
165
201
  function checkInstalled() {
202
+ if (_installedCache) return _installedCache;
203
+
166
204
  var fs = require("fs");
167
- var execSync = require("child_process").execSync;
205
+ var execFileSync = require("child_process").execFileSync;
168
206
  var result = { claude: false, codex: false };
169
207
  try {
170
- execSync("which claude", { timeout: 3000, stdio: ["pipe", "pipe", "pipe"] });
208
+ if (process.platform === "win32") execFileSync("where", ["claude"], { timeout: 3000, stdio: ["pipe", "pipe", "pipe"] });
209
+ else execFileSync("which", ["claude"], { timeout: 3000, stdio: ["pipe", "pipe", "pipe"] });
171
210
  result.claude = true;
172
211
  } catch (e) {}
173
212
  try {
@@ -177,53 +216,66 @@ function checkInstalled() {
177
216
  codexBin = findCodexPath();
178
217
  if (codexBin && fs.existsSync(codexBin)) {
179
218
  result.codex = true;
219
+ _installedCache = result;
180
220
  return result;
181
221
  }
182
222
  } catch (e) {}
183
223
 
184
- var pathProbeCmd = process.platform === "win32" ? "where codex" : "which codex";
185
- var whichOut = execSync(pathProbeCmd, { timeout: 3000, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] });
224
+ var whichOut = process.platform === "win32"
225
+ ? execFileSync("where", ["codex"], { timeout: 3000, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] })
226
+ : execFileSync("which", ["codex"], { timeout: 3000, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] });
186
227
  if (whichOut.trim()) result.codex = true;
187
228
  } catch (e) {}
229
+ _installedCache = result;
188
230
  return result;
189
231
  }
190
232
 
191
233
  function invalidateAuthCache() {
192
234
  _authCache = null;
235
+ _installedCache = null;
193
236
  }
194
237
 
195
238
  /**
196
239
  * Create adapters for all authenticated vendors.
197
- * Adapters are singletons shared across all projects (they are stateless factories).
198
- * The cwd is passed per-query, not per-adapter.
240
+ * Claude may be shared across projects, but Codex is instantiated per project
241
+ * so its app-server and bridge stay scoped to a single project slug.
199
242
  * Returns { adapters: { vendor: Adapter }, auth: { vendor: boolean } }
200
243
  */
201
- var _sharedAdapters = null;
202
- var _sharedAuth = null;
244
+ var _sharedClaudeAdapter = null;
203
245
 
204
246
  function createAdapters(opts) {
205
- if (_sharedAdapters) {
206
- return { adapters: _sharedAdapters, auth: _sharedAuth };
207
- }
208
-
209
- var auth = checkAuth();
247
+ opts = opts || {};
248
+ // Gate adapter creation on binary installation, not OAuth auth status.
249
+ // Claude Code supports multiple auth modes (OAuth, Bedrock, Vertex, API key)
250
+ // that `claude auth status` does not always detect. Runtime auth failures are
251
+ // handled downstream via query-level error detection.
252
+ var installed = checkInstalled();
253
+ var auth = { claude: false, codex: false };
210
254
  var adapters = {};
211
- var vendors = Object.keys(auth);
212
255
 
213
- for (var i = 0; i < vendors.length; i++) {
214
- var vendor = vendors[i];
215
- if (!auth[vendor]) continue;
256
+ if (installed.claude) {
216
257
  try {
217
- adapters[vendor] = createAdapter({ vendor: vendor, cwd: opts.cwd });
218
- console.log("[yoke] Adapter created: " + vendor);
258
+ if (!_sharedClaudeAdapter) {
259
+ _sharedClaudeAdapter = createAdapter({ vendor: "claude", cwd: opts.cwd });
260
+ }
261
+ adapters.claude = _sharedClaudeAdapter;
262
+ auth.claude = true;
263
+ console.log("[yoke] Adapter created: claude");
219
264
  } catch (e) {
220
- console.error("[yoke] Failed to create adapter for " + vendor + ":", e.message);
221
- auth[vendor] = false;
265
+ console.error("[yoke] Failed to create adapter for claude:", e.message);
266
+ }
267
+ }
268
+
269
+ if (installed.codex) {
270
+ try {
271
+ adapters.codex = createAdapter({ vendor: "codex", cwd: opts.cwd, slug: opts.slug });
272
+ auth.codex = true;
273
+ console.log("[yoke] Adapter created: codex");
274
+ } catch (e) {
275
+ console.error("[yoke] Failed to create adapter for codex:", e.message);
222
276
  }
223
277
  }
224
278
 
225
- _sharedAdapters = adapters;
226
- _sharedAuth = auth;
227
279
  return { adapters: adapters, auth: auth };
228
280
  }
229
281
 
@@ -233,26 +285,19 @@ function createAdapters(opts) {
233
285
  * Returns the adapter or null.
234
286
  */
235
287
  async function lazyCreateAdapter(adapters, vendor, opts) {
236
- if (_sharedAdapters && _sharedAdapters[vendor]) {
237
- adapters[vendor] = _sharedAdapters[vendor];
238
- return adapters[vendor];
239
- }
288
+ opts = opts || {};
240
289
 
241
290
  // Force re-check since user may have logged in after server start
242
291
  invalidateAuthCache();
243
- _sharedAdapters = null;
244
- _sharedAuth = null;
245
- var auth = checkAuth();
246
- if (!auth[vendor]) return null;
292
+ var installed = checkInstalled();
293
+ if (!installed[vendor]) return null;
247
294
 
248
295
  try {
249
- var ad = createAdapter({ vendor: vendor, cwd: opts.cwd });
296
+ var ad = createAdapter({ vendor: vendor, cwd: opts.cwd, slug: opts.slug });
250
297
  if (typeof ad.init === "function") {
251
298
  await ad.init(opts || {});
252
299
  }
253
300
  console.log("[yoke] Lazy adapter created: " + vendor);
254
- if (_sharedAdapters) _sharedAdapters[vendor] = ad;
255
- if (_sharedAuth) _sharedAuth[vendor] = true;
256
301
  adapters[vendor] = ad;
257
302
  return ad;
258
303
  } catch (e) {
@@ -24,7 +24,6 @@ var KNOWN_FILES = [
24
24
  var NATIVE_FILES = {
25
25
  claude: ["CLAUDE.md"],
26
26
  codex: ["AGENTS.md"],
27
- gemini: [],
28
27
  };
29
28
 
30
29
  // Scan projectDir for instruction files and return merged text.
@@ -3,7 +3,8 @@
3
3
  // --------------------------------------------------
4
4
  // Codex spawns this as a native MCP server via config.mcp_servers["clay-tools"].
5
5
  // It implements the MCP protocol on stdio (JSON-RPC) and proxies tool
6
- // list/call requests to Clay's HTTP endpoint (/api/mcp-bridge).
6
+ // list/call requests to Clay's project-scoped HTTP endpoint
7
+ // (/p/{slug}/api/mcp-bridge) when a slug is provided.
7
8
  //
8
9
  // Usage: node mcp-bridge-server.js --port 2633 --slug my-project
9
10
  //
@@ -37,8 +38,10 @@ for (var i = 0; i < args.length; i++) {
37
38
  }
38
39
 
39
40
  var CLAY_PROTOCOL = clayTls ? "https" : "http";
40
- // Use global endpoint (not project-scoped) so bridge works regardless of which project was active at init
41
41
  var CLAY_BASE_URL = CLAY_PROTOCOL + "://127.0.0.1:" + clayPort;
42
+ var CLAY_MCP_PATH = claySlug
43
+ ? ("/p/" + claySlug + "/api/mcp-bridge")
44
+ : "/api/mcp-bridge";
42
45
 
43
46
  // --- Auth ---
44
47
  var clayAuthToken = process.env.CLAY_AUTH_TOKEN || "";
@@ -91,8 +94,13 @@ function postJson(urlPath, body) {
91
94
  reject(err);
92
95
  });
93
96
 
94
- // 30s timeout for tool calls
95
- req.setTimeout(30000, function () {
97
+ // 10 min safety ceiling. ask_user_questions is stateless (returns
98
+ // immediately; the user's answer arrives on the next turn as a new
99
+ // user message), so no tool call should legitimately block long.
100
+ // The generous ceiling is just a belt-and-suspenders against slow
101
+ // browser automation or external MCP servers, not the primary
102
+ // mechanism for human-in-the-loop tools.
103
+ req.setTimeout(600000, function () {
96
104
  req.destroy(new Error("Request timed out"));
97
105
  });
98
106
 
@@ -103,7 +111,7 @@ function postJson(urlPath, body) {
103
111
 
104
112
  // --- Fetch tools from Clay ---
105
113
  function fetchTools() {
106
- return postJson("/api/mcp-bridge", { action: "list_tools" }).then(function (resp) {
114
+ return postJson(CLAY_MCP_PATH, { action: "list_tools" }).then(function (resp) {
107
115
  if (resp.error) {
108
116
  log("Failed to fetch tools: " + resp.error);
109
117
  return [];
@@ -124,7 +132,7 @@ function fetchTools() {
124
132
 
125
133
  // --- Call a tool via Clay ---
126
134
  function callTool(serverName, toolName, args) {
127
- return postJson("/api/mcp-bridge", {
135
+ return postJson(CLAY_MCP_PATH, {
128
136
  action: "call_tool",
129
137
  server: serverName,
130
138
  tool: toolName,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clay-server",
3
- "version": "2.33.1",
3
+ "version": "2.34.0-beta.10",
4
4
  "description": "Self-hosted Claude Code in your browser. Multi-session, multi-user, push notifications.",
5
5
  "bin": {
6
6
  "clay-server": "./bin/cli.js",
@@ -37,7 +37,6 @@
37
37
  "author": "Chad",
38
38
  "dependencies": {
39
39
  "@anthropic-ai/claude-agent-sdk": "^0.2.112",
40
- "@google/genai": "^1.49.0",
41
40
  "@lydell/node-pty": "^1.2.0-beta.3",
42
41
  "@openai/codex": "^0.121.0",
43
42
  "imapflow": "^1.3.1",