clay-server 5.5.0-beta.5 → 5.5.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 (40) hide show
  1. package/lib/project-split-views.js +3 -1
  2. package/lib/project-view-workspace.js +10 -1
  3. package/lib/public/modules/view-presentation-state.js +2 -0
  4. package/lib/public/pane-assets/{app-C27ONMHZ.js → app-BNMUDWD5.js} +1 -1
  5. package/lib/public/pane-assets/chunk-3EA7GMQB.js +1 -0
  6. package/lib/public/pane-assets/{chunk-OERDYIHU.js → chunk-4FKXTJ6O.js} +1 -1
  7. package/lib/public/pane-assets/chunk-4WIT6JEU.js +1 -0
  8. package/lib/public/pane-assets/chunk-A5YYSG4B.js +1 -0
  9. package/lib/public/pane-assets/{chunk-YBYNU4VI.js → chunk-AF5TDQTT.js} +1 -1
  10. package/lib/public/pane-assets/{chunk-QOL6PHMZ.js → chunk-B4JTSONS.js} +1 -1
  11. package/lib/public/pane-assets/{chunk-OQBKX36J.js → chunk-D2BDBUG5.js} +2 -2
  12. package/lib/public/pane-assets/{chunk-I5Q4CZDJ.js → chunk-ENPPQJSX.js} +1 -1
  13. package/lib/public/pane-assets/chunk-ENWMPEO2.js +1 -0
  14. package/lib/public/pane-assets/{chunk-KJN6KGEN.js → chunk-FA454EFE.js} +1 -1
  15. package/lib/public/pane-assets/{chunk-W7XXER5Z.js → chunk-FDZ37JGF.js} +5 -5
  16. package/lib/public/pane-assets/{chunk-UMXZTYOY.js → chunk-FWEHZLDA.js} +1 -1
  17. package/lib/public/pane-assets/{chunk-4REWYEXI.js → chunk-HY74JLC5.js} +1 -1
  18. package/lib/public/pane-assets/{chunk-TVKTFEST.js → chunk-LH46GC2K.js} +1 -1
  19. package/lib/public/pane-assets/chunk-PDHV6Q3E.js +1 -0
  20. package/lib/public/pane-assets/{chunk-MR4FHQFA.js → chunk-SQC7XIGG.js} +1 -1
  21. package/lib/public/pane-assets/chunk-TIEOZRO2.js +1 -0
  22. package/lib/public/pane-assets/{chunk-NRC5RDIL.js → chunk-U3KC4P2F.js} +1 -1
  23. package/lib/public/pane-assets/{chunk-76JE6SO6.js → chunk-ULGL2YFP.js} +1 -1
  24. package/lib/public/pane-assets/chunk-W3KJANR2.js +1 -0
  25. package/lib/public/pane-assets/{chunk-DTNAWMGQ.js → chunk-WI3BTTUY.js} +1 -1
  26. package/lib/public/pane-assets/{chunk-OGIHAXLD.js → chunk-YXTZSYUM.js} +1 -1
  27. package/lib/public/pane-assets/{chunk-W5MFUKXJ.js → chunk-ZY4WB3PH.js} +1 -1
  28. package/lib/public/pane-assets/manifest.json +381 -381
  29. package/lib/sdk-bridge.js +12 -0
  30. package/lib/yoke/adapters/codex.js +2 -2
  31. package/lib/yoke/codex-idle-recovery.js +80 -12
  32. package/lib/yoke/codex-recovery-admission.js +24 -9
  33. package/package.json +1 -1
  34. package/lib/public/pane-assets/chunk-6EDR4PMU.js +0 -1
  35. package/lib/public/pane-assets/chunk-AFATDM32.js +0 -1
  36. package/lib/public/pane-assets/chunk-ET4NUBYW.js +0 -1
  37. package/lib/public/pane-assets/chunk-EYJZ3R7Y.js +0 -1
  38. package/lib/public/pane-assets/chunk-KZ3CYS5U.js +0 -1
  39. package/lib/public/pane-assets/chunk-NRP4NXVZ.js +0 -1
  40. package/lib/public/pane-assets/chunk-Y77DR2RU.js +0 -1
package/lib/sdk-bridge.js CHANGED
@@ -1972,6 +1972,18 @@ function createSDKBridge(opts) {
1972
1972
  ? 'Tool recovery is waiting for this conversation’s runtime to become safely available. Your message is retained; Stop cancels the wait.'
1973
1973
  : 'Tool recovery is ready. Continuing your retained message.' });
1974
1974
  },
1975
+ onRecoveryRepair: function(info) {
1976
+ if (!queryContextAvailable()) return;
1977
+ var commands = (info && Array.isArray(info.commands) ? info.commands : []).map(function(command) {
1978
+ command = String(command).replace(/\s+/g, ' ').trim();
1979
+ return command.length > 80 ? command.slice(0, 77) + '...' : command;
1980
+ }).filter(Boolean);
1981
+ var count = Number(info && info.terminatedBackgroundTerminals) || commands.length || 0;
1982
+ if (!count) return;
1983
+ sendAndRecord(session, { type: 'informational', level: 'info', content: 'Tool recovery stopped '
1984
+ + count + ' background terminal' + (count === 1 ? '' : 's') + ' this conversation left running'
1985
+ + (commands.length ? ' (' + commands.join('; ') + ')' : '') + ' so your message can continue.' });
1986
+ },
1975
1987
  callDynamicTool: function (toolName, args) {
1976
1988
  if (queryRetired() || !queryContextAvailable()) return Promise.reject(new Error("This session tool query is no longer available."));
1977
1989
  var toolDef = findSessionToolDef(toolName);
@@ -1899,8 +1899,8 @@ function createCodexAdapter(opts) {
1899
1899
  return null;
1900
1900
  },
1901
1901
 
1902
- retireIdleForRecovery: function(checkCurrent) {
1903
- return idleRecovery.retireIdleQueries(_activeQueries.slice(), _appServer, checkCurrent);
1902
+ retireIdleForRecovery: function(checkCurrent, repair) {
1903
+ return idleRecovery.retireIdleQueries(_activeQueries.slice(), _appServer, checkCurrent, repair);
1904
1904
  },
1905
1905
 
1906
1906
  createQuery: async function(queryOpts) {
@@ -10,15 +10,24 @@ function checkAbort(queryOpts) {
10
10
  }
11
11
  }
12
12
 
13
- function terminalsAreIdle(result) {
14
- if (!result || result.nextCursor) return false;
13
+ function listedTerminals(result) {
14
+ if (!result || result.nextCursor) return null;
15
15
  var terminals = Array.isArray(result) ? result
16
16
  : result.data || result.terminals || result.backgroundTerminals || result.items;
17
- if (!Array.isArray(terminals)) return false;
18
- return terminals.every(function(terminal) {
19
- var status = String(terminal && (terminal.status || terminal.state) || "").toLowerCase();
20
- return /^(exited|terminated|completed|failed)$/.test(status);
21
- });
17
+ return Array.isArray(terminals) ? terminals : null;
18
+ }
19
+
20
+ // Current Codex lists only live processes without a status field; legacy
21
+ // builds reported finished states, so an explicit finished status still counts.
22
+ function terminalFinished(terminal) {
23
+ var status = String(terminal && (terminal.status || terminal.state) || "").toLowerCase();
24
+ return /^(exited|terminated|completed|failed)$/.test(status);
25
+ }
26
+
27
+ function terminalsAreIdle(result) {
28
+ var terminals = listedTerminals(result);
29
+ if (!terminals) return false;
30
+ return terminals.every(terminalFinished);
22
31
  }
23
32
 
24
33
  function observe(checkCurrent, reason, method, rpcCode, threadId) {
@@ -46,7 +55,55 @@ function probeFailure(error, method) {
46
55
  return false;
47
56
  }
48
57
 
49
- async function retireIdleQueries(entries, server, checkCurrent) {
58
+ // Owner-approved unblock for the conversation being resumed: its own live
59
+ // background terminals would be orphaned in the retired process anyway, so
60
+ // terminate them instead of retaining the conversation blocked forever.
61
+ // Only the recovery target's thread is ever touched; sibling conversations
62
+ // keep their background work. Requires the provider to report the thread
63
+ // idle first so an active turn's terminals are never stopped.
64
+ async function terminateTargetTerminals(server, threadId, checkCurrent, onTerminated) {
65
+ var method = 'thread/read';
66
+ var live;
67
+ try {
68
+ var status = await probe(server, method, { threadId: threadId, includeTurns: false }, 5000, checkCurrent);
69
+ checkCurrent();
70
+ if (!status || !status.thread || status.thread.id !== threadId
71
+ || !status.thread.status || status.thread.status.type !== 'idle') return false;
72
+ method = 'thread/backgroundTerminals/list';
73
+ var terminals = listedTerminals(await probe(server, method, { threadId: threadId }, 5000, checkCurrent));
74
+ checkCurrent();
75
+ if (!terminals) return false;
76
+ live = terminals.filter(function(terminal) { return !terminalFinished(terminal); });
77
+ } catch (error) { checkCurrent(); if (!server.started) throw error; return probeFailure(error, method); }
78
+ if (!live.length) return false;
79
+ var commands = [];
80
+ for (var i = 0; i < live.length; i++) {
81
+ var processId = live[i] && (live[i].processId !== undefined && live[i].processId !== null ? live[i].processId : live[i].id);
82
+ if (processId === undefined || processId === null || processId === '') return false;
83
+ var outcome;
84
+ try {
85
+ outcome = await probe(server, 'thread/backgroundTerminals/terminate', { threadId: threadId, processId: String(processId) }, 5000, checkCurrent);
86
+ } catch (error) {
87
+ checkCurrent();
88
+ if (!server.started) throw error;
89
+ // An unsupported or failed terminate leaves the conversation retained,
90
+ // exactly like before this repair existed. Never escalate it.
91
+ return false;
92
+ }
93
+ checkCurrent();
94
+ observe(checkCurrent, outcome && outcome.terminated === true
95
+ ? 'background_terminal_terminated' : 'background_terminal_terminate_unconfirmed',
96
+ 'thread/backgroundTerminals/terminate', null, threadId);
97
+ if (!outcome || outcome.terminated !== true) return false;
98
+ if (typeof live[i].command === 'string' && live[i].command) commands.push(live[i].command);
99
+ }
100
+ if (typeof onTerminated === 'function') {
101
+ try { onTerminated(commands, live.length); } catch (error) {}
102
+ }
103
+ return true;
104
+ }
105
+
106
+ async function retireIdleQueries(entries, server, checkCurrent, repair) {
50
107
  checkCurrent();
51
108
  var candidates = [];
52
109
  for (var i = 0; i < entries.length; i++) {
@@ -63,7 +120,13 @@ async function retireIdleQueries(entries, server, checkCurrent) {
63
120
  checkCurrent();
64
121
  for (var ci = 0; ci < candidates.length; ci++) {
65
122
  var current = candidates[ci].handle.getIdleRecoverySnapshot();
66
- if (!safe[ci]) { observe(checkCurrent, 'background_state_not_proven_idle', null, null, candidates[ci].snapshot.threadId); return false; }
123
+ if (!safe[ci]) {
124
+ observe(checkCurrent, 'background_state_not_proven_idle', null, null, candidates[ci].snapshot.threadId);
125
+ if (repair && repair.threadId && repair.threadId === candidates[ci].snapshot.threadId) {
126
+ await terminateTargetTerminals(server, repair.threadId, checkCurrent, repair.onTerminated);
127
+ }
128
+ return false;
129
+ }
67
130
  if (!current || current.revision !== candidates[ci].snapshot.revision) { observe(checkCurrent, 'input_changed_during_probe', null, null, candidates[ci].snapshot.threadId); return false; }
68
131
  }
69
132
  // No awaits between validation and closing every input. A new send either
@@ -144,19 +207,24 @@ async function threadIsIdle(server, threadId, checkCurrent) {
144
207
  } catch (error) { checkCurrent(); if (!server.started) throw error; return probeFailure(error, method); }
145
208
  }
146
209
 
147
- async function releaseThread(entries, server, threadId, checkCurrent) {
210
+ async function releaseThread(entries, server, threadId, checkCurrent, repair) {
148
211
  if (!server || !server.started) throw new Error('Codex disconnected while checking recovery admission');
149
212
  var matching = entries.filter(function(entry) {
150
213
  return entry.handle && entry.handle.getRecoveryThreadId() === threadId;
151
214
  });
152
215
  if (!matching.length && await threadIsAbsent(server, threadId, checkCurrent)) return true;
153
216
  if (matching.length) {
154
- if (!await retireIdleQueries(matching, server, checkCurrent)) return false;
217
+ if (!await retireIdleQueries(matching, server, checkCurrent, repair)) return false;
155
218
  checkCurrent();
156
219
  } else {
157
220
  // A closed SDK handle can leave a loaded provider thread behind. Only an
158
221
  // explicitly idle provider thread with verified terminal state may unload.
159
- if (!await threadIsIdle(server, threadId, checkCurrent)) return false;
222
+ if (!await threadIsIdle(server, threadId, checkCurrent)) {
223
+ if (repair && repair.threadId === threadId) {
224
+ await terminateTargetTerminals(server, threadId, checkCurrent, repair.onTerminated);
225
+ }
226
+ return false;
227
+ }
160
228
  }
161
229
  try {
162
230
  var result = await probe(server, 'thread/unsubscribe', { threadId: threadId }, 5000, checkCurrent);
@@ -71,20 +71,33 @@ function attachRecoveryAdmission(ctx) {
71
71
  }
72
72
  }
73
73
 
74
- async function releaseTarget(threadId, checkCurrent) {
74
+ // The owner's new message to this conversation authorizes stopping the
75
+ // conversation's OWN leftover background terminals: they would be orphaned
76
+ // by the runtime handoff anyway. Sibling conversations are never repaired.
77
+ function repairOptions(queryOpts) {
78
+ return {
79
+ threadId: queryOpts.resumeSessionId,
80
+ onTerminated: function(commands, count) {
81
+ if (typeof queryOpts.onRecoveryRepair !== 'function') return;
82
+ try { queryOpts.onRecoveryRepair({ terminatedBackgroundTerminals: count, commands: commands }); } catch (error) {}
83
+ },
84
+ };
85
+ }
86
+
87
+ async function releaseTarget(threadId, checkCurrent, repair) {
75
88
  checkCurrent.phase('isolated_target');
76
89
  var prior = targets.get(threadId);
77
90
  if (!prior) return true;
78
91
  checkCurrent.observe('isolated_target_reserved');
79
92
  if (cleanupFailures.has(prior)) throw cleanupFailures.get(prior);
80
- await prior.retireIdleForRecovery(checkCurrent);
93
+ await prior.retireIdleForRecovery(checkCurrent, repair);
81
94
  await cleanup;
82
95
  checkCurrent();
83
96
  if (cleanupFailures.has(prior)) throw cleanupFailures.get(prior);
84
97
  return !targets.has(threadId);
85
98
  }
86
99
 
87
- async function prepareShared(threadId, checkCurrent) {
100
+ async function prepareShared(threadId, checkCurrent, repair) {
88
101
  checkCurrent.phase('shared_target');
89
102
  var state = ctx.state();
90
103
  if (state.initializing) { checkCurrent.observe('shared_runtime_initializing'); return false; }
@@ -94,14 +107,14 @@ function attachRecoveryAdmission(ctx) {
94
107
  if (state.server && state.entries.some(function(entry) {
95
108
  return !entry.handle || entry.handle.getRecoveryThreadId() !== threadId;
96
109
  })) {
97
- if (await idleRecovery.releaseThread(state.entries, state.server, threadId, checkCurrent)) return true;
110
+ if (await idleRecovery.releaseThread(state.entries, state.server, threadId, checkCurrent, repair)) return true;
98
111
  checkCurrent();
99
112
  }
100
113
  checkCurrent.phase('shared_idle_retirement');
101
114
  var retiredThreads = [];
102
115
  if (state.refCount > 0) {
103
116
  var snapshots = state.entries.map(function(entry) { return entry.handle && entry.handle.getIdleRecoverySnapshot(); });
104
- if (await idleRecovery.retireIdleQueries(state.entries, state.server, checkCurrent)) {
117
+ if (await idleRecovery.retireIdleQueries(state.entries, state.server, checkCurrent, repair)) {
105
118
  retiredThreads = snapshots.filter(Boolean).map(function(snapshot) { return snapshot.threadId; });
106
119
  }
107
120
  }
@@ -115,7 +128,7 @@ function attachRecoveryAdmission(ctx) {
115
128
  return stopped;
116
129
  }
117
130
  checkCurrent.phase('shared_target');
118
- return idleRecovery.releaseThread(state.entries, state.server, threadId, checkCurrent);
131
+ return idleRecovery.releaseThread(state.entries, state.server, threadId, checkCurrent, repair);
119
132
  }
120
133
 
121
134
  async function waitUntilAvailable(queryOpts, action) {
@@ -179,9 +192,10 @@ function attachRecoveryAdmission(ctx) {
179
192
  }
180
193
 
181
194
  function createQuery(queryOpts) {
195
+ var repair = repairOptions(queryOpts);
182
196
  return waitUntilAvailable(queryOpts, async function(checkCurrent) {
183
- if (!await releaseTarget(queryOpts.resumeSessionId, checkCurrent)) return false;
184
- if (!await prepareShared(queryOpts.resumeSessionId, checkCurrent)) return false;
197
+ if (!await releaseTarget(queryOpts.resumeSessionId, checkCurrent, repair)) return false;
198
+ if (!await prepareShared(queryOpts.resumeSessionId, checkCurrent, repair)) return false;
185
199
  // Once ownership is reserved, normal startup/cleanup owns cancellation.
186
200
  // A deadline must not discard a handle being created after admission.
187
201
  checkCurrent.admit();
@@ -206,8 +220,9 @@ function attachRecoveryAdmission(ctx) {
206
220
  checkSharedExit: checkSharedExit,
207
221
  hasTarget: function(threadId) { return targets.has(threadId); },
208
222
  waitForTarget: function(queryOpts) {
223
+ var repair = repairOptions(queryOpts);
209
224
  return waitUntilAvailable(queryOpts, async function(checkCurrent) {
210
- var released = await releaseTarget(queryOpts.resumeSessionId, checkCurrent);
225
+ var released = await releaseTarget(queryOpts.resumeSessionId, checkCurrent, repair);
211
226
  if (released) checkCurrent.admit();
212
227
  return released;
213
228
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clay-server",
3
- "version": "5.5.0-beta.5",
3
+ "version": "5.5.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"
@@ -1 +0,0 @@
1
- import{C as a,D as b,E as c}from"./chunk-W7XXER5Z.js";import"./chunk-TVKTFEST.js";import"./chunk-YHBTK56T.js";import"./chunk-OGIHAXLD.js";import"./chunk-QOL6PHMZ.js";import"./chunk-DTNAWMGQ.js";import"./chunk-R7QVVFUG.js";import"./chunk-OQBKX36J.js";import"./chunk-EGCR2MH4.js";export{a as closeLoginBrowser,c as handleLoginBrowserMessage,b as openLoginBrowser};
@@ -1 +0,0 @@
1
- import{Tc as a,Uc as b,Vc as c,Wc as d,Xc as e,Yc as f,Zc as g,_c as h}from"./chunk-W7XXER5Z.js";import"./chunk-TVKTFEST.js";import"./chunk-YHBTK56T.js";import"./chunk-OGIHAXLD.js";import"./chunk-QOL6PHMZ.js";import"./chunk-DTNAWMGQ.js";import"./chunk-R7QVVFUG.js";import"./chunk-OQBKX36J.js";import"./chunk-EGCR2MH4.js";export{f as closeNotesBrowser,b as closedNotes,c as handleNoteDeleteResult,h as initNotesBrowser,g as isNotesBrowserOpen,a as openNotes,e as openNotesBrowser,d as renderNotesBrowser};
@@ -1 +0,0 @@
1
- import{jc as a,kc as b,lc as c,mc as d}from"./chunk-W7XXER5Z.js";import"./chunk-TVKTFEST.js";import"./chunk-YHBTK56T.js";import"./chunk-OGIHAXLD.js";import"./chunk-QOL6PHMZ.js";import"./chunk-DTNAWMGQ.js";import"./chunk-R7QVVFUG.js";import"./chunk-OQBKX36J.js";import"./chunk-EGCR2MH4.js";export{c as closeDebatesWorkbench,a as handleDebatesWorkbenchState,d as initDebatesWorkbench,b as openDebatesWorkbench};
@@ -1 +0,0 @@
1
- import{n as a,o as b,p as c,q as d}from"./chunk-W7XXER5Z.js";import"./chunk-TVKTFEST.js";import"./chunk-YHBTK56T.js";import"./chunk-OGIHAXLD.js";import"./chunk-QOL6PHMZ.js";import"./chunk-DTNAWMGQ.js";import"./chunk-R7QVVFUG.js";import"./chunk-OQBKX36J.js";import"./chunk-EGCR2MH4.js";export{a as closeLinear,c as handleLinearResult,d as initLinearPanel,b as openLinear};
@@ -1 +0,0 @@
1
- import{g as a,h as b,i as c}from"./chunk-QOL6PHMZ.js";import"./chunk-DTNAWMGQ.js";import"./chunk-R7QVVFUG.js";import"./chunk-OQBKX36J.js";import"./chunk-EGCR2MH4.js";export{a as closeWireframe,b as openWireframe,c as restoreWireframeWorkbench};
@@ -1 +0,0 @@
1
- import{r as a,s as b,t as c,u as d,v as e,w as f}from"./chunk-W7XXER5Z.js";import"./chunk-TVKTFEST.js";import"./chunk-YHBTK56T.js";import"./chunk-OGIHAXLD.js";import"./chunk-QOL6PHMZ.js";import"./chunk-DTNAWMGQ.js";import"./chunk-R7QVVFUG.js";import"./chunk-OQBKX36J.js";import"./chunk-EGCR2MH4.js";export{c as closeIssues,f as handleIssuesMessage,e as initIssues,d as openIssues,b as refreshGithubIssue,a as requestIssue};
@@ -1 +0,0 @@
1
- import{Ta as a,Ua as b,Va as c,Wa as d,Xa as e}from"./chunk-W7XXER5Z.js";import"./chunk-TVKTFEST.js";import"./chunk-YHBTK56T.js";import"./chunk-OGIHAXLD.js";import"./chunk-QOL6PHMZ.js";import"./chunk-DTNAWMGQ.js";import"./chunk-R7QVVFUG.js";import"./chunk-OQBKX36J.js";import"./chunk-EGCR2MH4.js";export{c as closeYou,d as handleYouMessage,e as initYou,b as openYou,a as sendYou};