clay-server 4.3.0-beta.5 → 4.3.0-beta.7

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 (49) hide show
  1. package/lib/driver-continuation-pair.js +5 -1
  2. package/lib/multi-worker-feature.js +11 -0
  3. package/lib/project-pair-autonomous-stop.js +14 -8
  4. package/lib/project-pair-close-transaction.js +59 -0
  5. package/lib/project-pair-global-stop.js +18 -0
  6. package/lib/project-pair-lifecycle-status.js +34 -0
  7. package/lib/project-pair-lifecycle.js +58 -76
  8. package/lib/project-pair-message.js +1 -1
  9. package/lib/project-pair-owned-stop.js +7 -4
  10. package/lib/project-pair-result-capture.js +41 -11
  11. package/lib/project-pair-result-delivery.js +4 -3
  12. package/lib/project-pair-result-outbox.js +73 -4
  13. package/lib/project-pair-result-recovery.js +6 -2
  14. package/lib/project-pair-structural-close.js +20 -0
  15. package/lib/project-pair-task-control.js +39 -18
  16. package/lib/project-pair-worker-close.js +68 -0
  17. package/lib/project-session-pair-prompt.js +26 -0
  18. package/lib/project-session-pair.js +55 -78
  19. package/lib/project-worker-creation-proposal.js +176 -0
  20. package/lib/project-worker-permission.js +7 -8
  21. package/lib/project-worker-proposal-runner.js +45 -0
  22. package/lib/project-worker-proposal.js +79 -115
  23. package/lib/project.js +15 -0
  24. package/lib/public/css/pane.css +48 -2
  25. package/lib/public/modules/app-connection.js +49 -76
  26. package/lib/public/modules/app-messages.js +4 -0
  27. package/lib/public/modules/project-activation.js +10 -7
  28. package/lib/public/modules/sidebar-sessions.js +31 -11
  29. package/lib/public/modules/split-group-helpers.js +74 -2
  30. package/lib/public/modules/split-pair-ui.js +28 -6
  31. package/lib/public/modules/split-pane-reconciler.js +28 -0
  32. package/lib/public/modules/split-pane-renderer.js +107 -0
  33. package/lib/public/modules/split-view.js +58 -188
  34. package/lib/public/modules/websocket-watchdog.js +126 -0
  35. package/lib/public/modules/worker-pane-lock.js +13 -3
  36. package/lib/server.js +1 -0
  37. package/lib/session-pair-factory.js +66 -0
  38. package/lib/session-pair-mcp-server.js +8 -3
  39. package/lib/session-pair-prompts.js +34 -24
  40. package/lib/session-pair-target.js +42 -0
  41. package/lib/session-pair-turn-control.js +11 -6
  42. package/lib/session-split-group-anchors.js +89 -7
  43. package/lib/session-split-group-persistence.js +136 -0
  44. package/lib/session-split-group-roles.js +164 -0
  45. package/lib/session-split-group-v2-store.js +146 -0
  46. package/lib/session-split-groups.js +109 -99
  47. package/lib/sessions.js +40 -22
  48. package/lib/ws-schema.js +2 -0
  49. package/package.json +1 -1
package/lib/sessions.js CHANGED
@@ -144,17 +144,32 @@ function createSessionManager(opts) {
144
144
  }
145
145
  }
146
146
 
147
- function sessionFilePath(cliSessionId) {
148
- return path.join(sessionsDir, cliSessionId + ".jsonl");
147
+ function sessionFilePath(recordId) {
148
+ return path.join(sessionsDir, recordId + ".jsonl");
149
+ }
150
+
151
+ // A Clay record must exist before a provider assigns its resumable id.
152
+ // Existing records keep their filename; fresh provider-less sessions use
153
+ // the already durable origin id and later store cliSessionId as metadata.
154
+ function sessionRecordId(session) {
155
+ if (!session) return null;
156
+ return session._sessionRecordId || session.cliSessionId || session.sessionOriginId || null;
157
+ }
158
+
159
+ function existingSessionRecordId(session) {
160
+ if (!session) return null;
161
+ return session._sessionRecordId || session.cliSessionId || null;
149
162
  }
150
163
 
151
164
  function saveSessionFile(session) {
152
- if (!session.cliSessionId) return false;
153
165
  try {
166
+ var provenanceMeta = sessionProvenance.metadata(session);
167
+ var recordId = sessionRecordId(session);
168
+ if (!recordId) return false;
154
169
  var metaObj = {
155
170
  type: "meta",
156
171
  localId: session.localId,
157
- cliSessionId: session.cliSessionId,
172
+ cliSessionId: session.cliSessionId || null,
158
173
  title: session.title,
159
174
  titleProvisional: session.titleProvisional === true,
160
175
  titleAutoGenerated: session.titleAutoGenerated === true,
@@ -204,7 +219,6 @@ function createSessionManager(opts) {
204
219
  if (session.homeDebateInitialTopic) metaObj.homeDebateInitialTopic = session.homeDebateInitialTopic;
205
220
  if (session.homeClayEntryRequestId) metaObj.homeClayEntryRequestId = session.homeClayEntryRequestId;
206
221
  if (session.homeClayEntryMode) metaObj.homeClayEntryMode = session.homeClayEntryMode;
207
- var provenanceMeta = sessionProvenance.metadata(session);
208
222
  metaObj.sessionOriginId = provenanceMeta.sessionOriginId;
209
223
  if (provenanceMeta.sessionProvenance) metaObj.sessionProvenance = provenanceMeta.sessionProvenance;
210
224
  if (Array.isArray(session._workerGenerations)) metaObj.workerGenerations = session._workerGenerations;
@@ -213,7 +227,7 @@ function createSessionManager(opts) {
213
227
  for (var i = 0; i < session.history.length; i++) {
214
228
  lines.push(JSON.stringify(session.history[i]));
215
229
  }
216
- var sfPath = sessionFilePath(session.cliSessionId);
230
+ var sfPath = sessionFilePath(recordId);
217
231
  // Atomic write: write to temp file then rename, so a crash mid-write
218
232
  // cannot leave a truncated/corrupted session file.
219
233
  var tmpPath = sfPath + ".tmp." + process.pid;
@@ -222,6 +236,7 @@ function createSessionManager(opts) {
222
236
  try { fs.chmodSync(tmpPath, 0o600); } catch (chmodErr) {}
223
237
  }
224
238
  fs.renameSync(tmpPath, sfPath);
239
+ session._sessionRecordId = recordId;
225
240
  return true;
226
241
  } catch(e) {
227
242
  console.error("[session] Failed to save session file:", e.message);
@@ -230,10 +245,11 @@ function createSessionManager(opts) {
230
245
  }
231
246
 
232
247
  function appendToSessionFile(session, obj) {
233
- if (!session.cliSessionId) return true;
248
+ var recordId = existingSessionRecordId(session);
249
+ if (!recordId) return true;
234
250
  session.lastActivity = Date.now();
235
251
  try {
236
- var afPath = sessionFilePath(session.cliSessionId);
252
+ var afPath = sessionFilePath(recordId);
237
253
  fs.appendFileSync(afPath, JSON.stringify(obj) + "\n");
238
254
  if (process.platform !== "win32") {
239
255
  try { fs.chmodSync(afPath, 0o600); } catch (chmodErr) {}
@@ -276,7 +292,8 @@ function createSessionManager(opts) {
276
292
 
277
293
  var meta;
278
294
  try { meta = JSON.parse(lines[0]); } catch { continue; }
279
- if (meta.type !== "meta" || !meta.cliSessionId) continue;
295
+ var pendingOrigin = typeof meta.sessionOriginId === "string" && meta.sessionOriginId.trim() && meta.sessionOriginId.length <= 128;
296
+ if (meta.type !== "meta" || (!meta.cliSessionId && !pendingOrigin)) continue;
280
297
 
281
298
  var history = [];
282
299
  for (var j = 1; j < lines.length; j++) {
@@ -285,7 +302,7 @@ function createSessionManager(opts) {
285
302
 
286
303
  var fileMtime = 0;
287
304
  try { fileMtime = fs.statSync(path.join(sessionsDir, files[i])).mtimeMs; } catch {}
288
- loaded.push({ meta: meta, history: history, mtime: fileMtime });
305
+ loaded.push({ meta: meta, history: history, mtime: fileMtime, recordId: files[i].slice(0, -".jsonl".length) });
289
306
  }
290
307
 
291
308
  loaded.sort(function(a, b) { return a.meta.createdAt - b.meta.createdAt; });
@@ -310,6 +327,7 @@ function createSessionManager(opts) {
310
327
  queryInstance: null,
311
328
  messageQueue: null,
312
329
  cliSessionId: m.cliSessionId,
330
+ _sessionRecordId: loaded[i].recordId,
313
331
  blocks: {},
314
332
  sentToolResults: {},
315
333
  pendingPermissions: {},
@@ -388,7 +406,7 @@ function createSessionManager(opts) {
388
406
  saveSessionFile(session);
389
407
  try {
390
408
  var preservedTime = new Date(activityTime);
391
- fs.utimesSync(sessionFilePath(session.cliSessionId), preservedTime, preservedTime);
409
+ fs.utimesSync(sessionFilePath(sessionRecordId(session)), preservedTime, preservedTime);
392
410
  } catch (e) {}
393
411
  delete session._provenanceMigrationNeeded;
394
412
  });
@@ -917,10 +935,9 @@ function createSessionManager(opts) {
917
935
  session.worker = null;
918
936
  }
919
937
 
920
- if (session.cliSessionId) {
921
- rememberDeletedCliSession(session.cliSessionId);
922
- try { fs.unlinkSync(sessionFilePath(session.cliSessionId)); } catch(e) {}
923
- }
938
+ if (session.cliSessionId) rememberDeletedCliSession(session.cliSessionId);
939
+ var recordId = existingSessionRecordId(session);
940
+ if (recordId) try { fs.unlinkSync(sessionFilePath(recordId)); } catch(e) {}
924
941
 
925
942
  sessions.delete(localId);
926
943
  try { onSessionDeleted(localId, session); } catch (e) {
@@ -964,10 +981,9 @@ function createSessionManager(opts) {
964
981
  try { session.worker.kill(); } catch(e) {}
965
982
  session.worker = null;
966
983
  }
967
- if (session.cliSessionId) {
968
- rememberDeletedCliSession(session.cliSessionId);
969
- try { fs.unlinkSync(sessionFilePath(session.cliSessionId)); } catch(e) {}
970
- }
984
+ if (session.cliSessionId) rememberDeletedCliSession(session.cliSessionId);
985
+ var recordId = existingSessionRecordId(session);
986
+ if (recordId) try { fs.unlinkSync(sessionFilePath(recordId)); } catch(e) {}
971
987
  sessions.delete(localId);
972
988
  try { onSessionDeleted(localId, session); } catch (e) {
973
989
  console.error("[session] Session deletion hook failed:", e.message || e);
@@ -1093,7 +1109,8 @@ function createSessionManager(opts) {
1093
1109
  }
1094
1110
 
1095
1111
  function sendAndRecordDurably(session, obj, clientObj) {
1096
- if (!session || !session.cliSessionId) return false;
1112
+ var recordId = existingSessionRecordId(session);
1113
+ if (!recordId || !fs.existsSync(sessionFilePath(recordId))) return false;
1097
1114
  var saved;
1098
1115
  try { saved = doSendAndRecord(session, obj, clientObj); }
1099
1116
  catch (error) {
@@ -1107,9 +1124,10 @@ function createSessionManager(opts) {
1107
1124
  }
1108
1125
 
1109
1126
  function hasDurableSessionRecord(session, predicate) {
1110
- if (!session || !session.cliSessionId || typeof predicate !== "function") return false;
1127
+ var recordId = existingSessionRecordId(session);
1128
+ if (!recordId || typeof predicate !== "function") return false;
1111
1129
  try {
1112
- var lines = fs.readFileSync(sessionFilePath(session.cliSessionId), "utf8").split("\n");
1130
+ var lines = fs.readFileSync(sessionFilePath(recordId), "utf8").split("\n");
1113
1131
  for (var i = lines.length - 1; i >= 0; i--) {
1114
1132
  if (!lines[i]) continue;
1115
1133
  try { if (predicate(JSON.parse(lines[i]))) return true; } catch (error) {}
package/lib/ws-schema.js CHANGED
@@ -41,6 +41,8 @@ var schema = {
41
41
  "split_group_rename": { direction: "c2s", handler: "lib/session-split-groups.js", description: "Rename a split group" },
42
42
  "split_group_dissolve": { direction: "c2s", handler: "lib/session-split-groups.js", description: "Separate a split group without deleting its sessions" },
43
43
  "split_group_set_pair": { direction: "c2s", handler: "lib/session-split-groups.js", description: "Assign Driver/Worker roles on a split group (driverId null clears them)" },
44
+ "split_worker_close": { direction: "c2s", handler: "lib/project-pair-worker-close.js", description: "Close one exact authenticated Worker generation while preserving its peer and session history" },
45
+ "split_worker_close_result": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Correlated result of an exact targeted Worker close" },
44
46
  "pair_session_options": { direction: "c2s", handler: "lib/project-session-pair.js", description: "Request vendors/models for the pair-session dialog (response reuses the same type)" },
45
47
  "pair_session_create": { direction: "c2s", handler: "lib/project-session-pair.js", description: "Create a Driver/Split Worker session pair as a split group" },
46
48
  "worker_proposal_response": { direction: "c2s", handler: "lib/project-worker-proposal.js", description: "Accept or decline an exact Driver Split Worker configuration with the selected runtime" },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clay-server",
3
- "version": "4.3.0-beta.5",
3
+ "version": "4.3.0-beta.7",
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"