clay-server 5.2.0-beta.4 → 5.2.0-beta.5

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 (171) hide show
  1. package/lib/account-auth-socket.js +1 -0
  2. package/lib/builtin-mates.js +17 -14
  3. package/lib/canonical-clay-record-authority.js +15 -0
  4. package/lib/canonical-clay-scheduling-authority.js +19 -0
  5. package/lib/claude-hook-installer.js +28 -0
  6. package/lib/clay-mate-chat-receipts.js +26 -0
  7. package/lib/clay-mate-chat.js +87 -0
  8. package/lib/clay-mate-knowledge-input.js +26 -0
  9. package/lib/clay-mate-knowledge.js +95 -0
  10. package/lib/clay-mate-lifecycle.js +95 -0
  11. package/lib/clay-product-control-input.js +33 -0
  12. package/lib/clay-product-control-tools.js +51 -0
  13. package/lib/clay-product-control.js +237 -0
  14. package/lib/clay-project-lifecycle.js +154 -0
  15. package/lib/clay-project-record-input.js +44 -0
  16. package/lib/clay-project-records.js +83 -0
  17. package/lib/clay-scheduling-input.js +21 -0
  18. package/lib/clay-scheduling-receipts.js +26 -0
  19. package/lib/clay-scheduling.js +119 -0
  20. package/lib/clay-screen-controls.js +96 -0
  21. package/lib/clay-workspace-controls.js +83 -0
  22. package/lib/clay-workspace-input.js +17 -0
  23. package/lib/clay-workspace-receipts.js +26 -0
  24. package/lib/daemon-project-access.js +11 -0
  25. package/lib/daemon-project-lifecycle-callbacks.js +15 -0
  26. package/lib/daemon-project-lifecycle.js +262 -0
  27. package/lib/daemon-projects.js +1 -1
  28. package/lib/daemon.js +73 -221
  29. package/lib/issues-service.js +29 -15
  30. package/lib/knowledge-db-import.js +5 -4
  31. package/lib/knowledge-db-service.js +9 -6
  32. package/lib/knowledge-document-index.js +2 -1
  33. package/lib/knowledge-product-operations.js +46 -0
  34. package/lib/mate-creation-mcp-server.js +2 -0
  35. package/lib/mate-knowledge-canonical-access.js +10 -0
  36. package/lib/mate-knowledge-service.js +7 -4
  37. package/lib/mate-lifecycle-service.js +94 -0
  38. package/lib/mate-message-delivery.js +43 -0
  39. package/lib/mate-ready-creation.js +9 -1
  40. package/lib/mate-schedule-input.js +29 -0
  41. package/lib/mate-schedule-lifecycle.js +151 -0
  42. package/lib/mate-scheduling.js +103 -0
  43. package/lib/mates.js +4 -3
  44. package/lib/notes-checklist.js +50 -0
  45. package/lib/notes.js +49 -3
  46. package/lib/os-users.js +20 -5
  47. package/lib/project-admin-overview.js +51 -0
  48. package/lib/project-ask-user.js +4 -2
  49. package/lib/project-delegated-follow-up.js +2 -1
  50. package/lib/project-directory-claim.js +65 -0
  51. package/lib/project-file-operations.js +61 -0
  52. package/lib/project-filesystem.js +13 -87
  53. package/lib/project-full-access.js +23 -2
  54. package/lib/project-knowledge.js +32 -18
  55. package/lib/project-lifecycle-operations.js +98 -0
  56. package/lib/project-lifecycle-process-witness.js +32 -0
  57. package/lib/project-lifecycle-runtime.js +50 -0
  58. package/lib/project-linux-access.js +22 -0
  59. package/lib/project-logs-canonical-clay.js +27 -0
  60. package/lib/project-logs-clay-query.js +16 -0
  61. package/lib/project-logs-schema.js +4 -2
  62. package/lib/project-logs-service.js +8 -7
  63. package/lib/project-logs.js +1 -0
  64. package/lib/project-loop.js +1 -0
  65. package/lib/project-mate-chat-control.js +55 -0
  66. package/lib/project-mate-creation-proposal.js +18 -3
  67. package/lib/project-models.js +14 -31
  68. package/lib/project-notifications.js +74 -16
  69. package/lib/project-ownership.js +77 -0
  70. package/lib/project-pair-result-delivery.js +14 -3
  71. package/lib/project-pending-message-queue.js +1 -0
  72. package/lib/project-product-control.js +87 -0
  73. package/lib/project-product-session-queue.js +46 -0
  74. package/lib/project-push-access.js +28 -0
  75. package/lib/project-scheduled-message-queue.js +30 -0
  76. package/lib/project-scheduled-messages.js +63 -64
  77. package/lib/project-scheduled-task-control.js +83 -0
  78. package/lib/project-scheduled-tasks.js +13 -2
  79. package/lib/project-scheduling-access.js +10 -0
  80. package/lib/project-session-actions.js +65 -0
  81. package/lib/project-session-delete.js +8 -1
  82. package/lib/project-session-github.js +47 -12
  83. package/lib/project-session-history-actions.js +165 -0
  84. package/lib/project-session-runtime-actions.js +63 -0
  85. package/lib/project-session-vendor.js +54 -0
  86. package/lib/project-sessions.js +64 -241
  87. package/lib/project-user-message.js +30 -1
  88. package/lib/project-workspace-operations.js +126 -0
  89. package/lib/project-workspace-query.js +9 -2
  90. package/lib/project.js +95 -3
  91. package/lib/public/css/clay-tool-activity.css +51 -0
  92. package/lib/public/css/home-mate-creation.css +5 -0
  93. package/lib/public/css/overlays.css +4 -2
  94. package/lib/public/css/project-admin-overview.css +13 -0
  95. package/lib/public/css/session-folders.css +11 -0
  96. package/lib/public/css/session-github.css +3 -0
  97. package/lib/public/css/sticky-notes.css +61 -1
  98. package/lib/public/index.html +1 -1
  99. package/lib/public/modules/admin-settings-projects.js +6 -2
  100. package/lib/public/modules/app-connection.js +8 -1
  101. package/lib/public/modules/app-message-router.js +1 -1
  102. package/lib/public/modules/app-messages.js +18 -3
  103. package/lib/public/modules/app-projects.js +5 -0
  104. package/lib/public/modules/clay-owner-navigation.js +27 -0
  105. package/lib/public/modules/clay-tool-activity.js +140 -0
  106. package/lib/public/modules/clay-tool-presentation.js +338 -0
  107. package/lib/public/modules/command-palette.js +1 -1
  108. package/lib/public/modules/default-ai.js +11 -3
  109. package/lib/public/modules/home-debate-planning.js +5 -5
  110. package/lib/public/modules/home-mate-chat.js +8 -6
  111. package/lib/public/modules/home-mate-creation.js +73 -1
  112. package/lib/public/modules/personal-settings-dialog.js +7 -1
  113. package/lib/public/modules/project-access-recovery.js +17 -0
  114. package/lib/public/modules/project-admin-overview.js +21 -0
  115. package/lib/public/modules/session-folder-layout.js +1 -1
  116. package/lib/public/modules/session-folder-view-menu.js +9 -0
  117. package/lib/public/modules/session-github.js +65 -8
  118. package/lib/public/modules/sidebar-session-folders-ui.js +1 -1
  119. package/lib/public/modules/sticky-note-markdown.js +72 -6
  120. package/lib/public/modules/sticky-notes-actions.js +30 -0
  121. package/lib/public/modules/sticky-notes-browser.js +9 -2
  122. package/lib/public/modules/sticky-notes-card.js +23 -7
  123. package/lib/public/modules/sticky-notes-checklist.js +71 -0
  124. package/lib/public/modules/sticky-notes-editor.js +108 -44
  125. package/lib/public/modules/sticky-notes-shared.js +50 -2
  126. package/lib/public/modules/sticky-notes-state.js +66 -0
  127. package/lib/public/modules/sticky-notes.js +34 -51
  128. package/lib/public/modules/thinking-lifecycle.js +33 -3
  129. package/lib/public/modules/thinking-view.js +23 -1
  130. package/lib/public/modules/tools.js +34 -6
  131. package/lib/public/modules/user-settings-appearance.js +10 -3
  132. package/lib/public/modules/user-settings-editors.js +1 -1
  133. package/lib/public/modules/user-settings-layout.js +11 -2
  134. package/lib/public/modules/user-settings-preferences.js +6 -2
  135. package/lib/public/modules/user-settings-profile.js +12 -2
  136. package/lib/public/modules/user-settings.js +3 -2
  137. package/lib/public/style.css +2 -0
  138. package/lib/scheduled-task-records.js +3 -1
  139. package/lib/scheduler.js +2 -0
  140. package/lib/sdk-bridge.js +220 -21
  141. package/lib/sdk-message-processor.js +2 -0
  142. package/lib/sdk-response-recovery.js +155 -0
  143. package/lib/server-admin.js +63 -16
  144. package/lib/server-dm.js +1 -1
  145. package/lib/server-global-ws.js +10 -20
  146. package/lib/server-home-chat-events.js +7 -3
  147. package/lib/server-home-chat.js +4 -16
  148. package/lib/server-home-mate-creation.js +1 -0
  149. package/lib/server-home-mcp-consent.js +3 -3
  150. package/lib/server-mates.js +25 -67
  151. package/lib/server-owner-ui.js +77 -0
  152. package/lib/server-path-access.js +38 -0
  153. package/lib/server-project-removal.js +22 -0
  154. package/lib/server.js +99 -29
  155. package/lib/session-config-transition.js +45 -0
  156. package/lib/session-folders.js +7 -2
  157. package/lib/session-github.js +9 -2
  158. package/lib/session-visibility.js +5 -2
  159. package/lib/sessions.js +10 -0
  160. package/lib/terminal-manager.js +52 -0
  161. package/lib/users-permissions.js +2 -8
  162. package/lib/users.js +4 -0
  163. package/lib/workspace-query-access.js +4 -1
  164. package/lib/ws-schema.js +8 -2
  165. package/lib/yoke/adapters/claude.js +47 -8
  166. package/lib/yoke/adapters/codex.js +52 -111
  167. package/lib/yoke/claude-worker-startup.js +28 -0
  168. package/lib/yoke/codex-idle-recovery.js +81 -1
  169. package/lib/yoke/codex-recovery-admission.js +164 -0
  170. package/lib/yoke/session-tool-transport.js +3 -0
  171. package/package.json +1 -1
@@ -1,3 +1,4 @@
1
+ import { leaveRevokedProject, reconnectDestination } from './project-access-recovery.js';
1
2
  // app-connection.js - WebSocket connection, reconnect, status
2
3
  // Extracted from app.js (PR-22)
3
4
 
@@ -355,6 +356,7 @@ export function connect() {
355
356
  console.log("[clay] WebSocket reconnected after " + (Date.now() - disconnectedAt) + "ms");
356
357
  }
357
358
  disconnectedAt = 0;
359
+ newWs.send(JSON.stringify({ type: "clay_ui_ready", version: 1 }));
358
360
  setStatus("connected");
359
361
  startHeartbeat(newWs, attemptEpoch);
360
362
  lifecycle.clearRetry();
@@ -460,7 +462,12 @@ export function scheduleReconnect(reason) {
460
462
  location.reload();
461
463
  return;
462
464
  }
463
- connect();
465
+ return res.json().then(function (info) {
466
+ if (!lifecycle.canProceed(authEpoch)) return;
467
+ var destination = reconnectDestination(info, store.get('currentSlug'));
468
+ if (destination) { leaveRevokedProject(destination); return; }
469
+ connect();
470
+ });
464
471
  }).catch(function () {
465
472
  var authFinished = lifecycle.finishAuth(authEpoch, authToken);
466
473
  if (authFinished && lifecycle.canProceed(authEpoch)) connect();
@@ -58,7 +58,7 @@ export function handleHomeProtocolMessage(msg) {
58
58
  handleHomeDebateTranscript(msg);
59
59
  return true;
60
60
  }
61
- if (msg.type === "home_mcp_import_question" || msg.type === "home_mcp_import_question_resolved") {
61
+ if (msg.type === "home_mcp_import_question" || msg.type === "home_mcp_import_question_resolved" || msg.type === "home_mate_question" || msg.type === "home_mate_question_resolved") {
62
62
  if (!handleSearchClayMessage(msg)) handleHomeDebateTranscript(msg);
63
63
  return true;
64
64
  }
@@ -1,3 +1,5 @@
1
+ import { handleOwnerNavigation } from './clay-owner-navigation.js';
2
+ import { leaveRevokedProject } from './project-access-recovery.js';
1
3
  import { handlePersonalSettingsMessage } from './user-settings-preferences.js';
2
4
  import { handleServerUpdate } from './server-settings-controls.js';
3
5
  import { handleYouMessage } from './you.js';
@@ -9,7 +11,7 @@ import { handleLinearResult } from './linear-panel.js';
9
11
  import { handleSharedBrowserMessage } from './shared-browser.js';
10
12
  import { openWireframe } from './wireframe-workbench.js';
11
13
  import { handlePrimaryWorkResult } from './session-work-primary.js';
12
- import { syncGithubSessions } from './session-github.js';
14
+ import { syncGithubSessions, applyGithubPersonalReviewStatus } from './session-github.js';
13
15
  import { handleIssuesMessageInContext as handleIssuesMessage } from './issues-context.js';
14
16
  // app-messages.js - WebSocket message router
15
17
  // Extracted from app.js (PR-23)
@@ -66,7 +68,7 @@ import { handleTuiTranscriptState } from './tui-grab.js';
66
68
  import { tuiModalHandleTermOutput, tuiModalHandleTermResized, tuiModalHandleTermExited, tuiModalHandleTermClosed } from './tui-attention.js';
67
69
  import { updateTerminalList, handleContextSourcesState, updateEmailAccountList, updateEmailUnreadCounts, handleEmailTestResult, handleEmailAddResult, handleEmailRemoveResult } from './context-sources.js';
68
70
  import { refreshEmailSettings } from './user-settings.js';
69
- import { handleNotesList, handleNoteCreated, handleNoteUpdated, handleNoteDeleted, handleNoteWritten } from './sticky-notes.js';
71
+ import { handleNotesList, handleNoteCreated, handleNoteUpdated, handleNoteUpdateResult, handleNoteDeleted, handleNoteWritten } from './sticky-notes.js';
70
72
  import { handleNoteDeleteResult } from './sticky-notes-browser.js';
71
73
  import { handleProjectLogsState, handleProjectLogEntry, handleProjectLogCommented, handleProjectLogCommentReviewed, handleProjectLogDeleted, handleProjectLogUpdated, handleProjectLogsError, handleScheduledTaskResultsState, handleScheduledTaskResultCreated, handleScheduledTaskResultSession } from './project-logs.js';
72
74
  import { handleSkillInstalled, handleSkillUninstalled } from './skills.js';
@@ -119,6 +121,7 @@ var connectOverlay = document.getElementById("connect-overlay");
119
121
  initMessageDeliveryUi();
120
122
 
121
123
  export function processMessage(msg) {
124
+ if (handleOwnerNavigation(msg)) return;
122
125
  if (["update_available", "up_to_date", "update_started", "update_check_failed"].indexOf(msg.type) !== -1) handleServerUpdate(msg);
123
126
  if (/^(set_pin|set_keep_awake|set_inherit_groups|set_image_retention|set_update_channel|restart_server|shutdown_server)_result$/.test(msg.type)) handleServerSettingResult(msg);
124
127
  if (handleProjectDebateMessage(msg)) return;
@@ -146,7 +149,7 @@ export function processMessage(msg) {
146
149
  return;
147
150
  }
148
151
  if (msg.type === "access_revoked") {
149
- window.location.assign("/");
152
+ leaveRevokedProject("/");
150
153
  return;
151
154
  }
152
155
  // Preserve original timestamp from history replay
@@ -775,6 +778,9 @@ export function processMessage(msg) {
775
778
  handlePaletteSessionSwitch();
776
779
  maybeRestoreSplitGroup();
777
780
  break;
781
+ case "session_github_status":
782
+ applyGithubPersonalReviewStatus(msg);
783
+ break;
778
784
 
779
785
  case "split_groups":
780
786
  applyProjectSplitGroups(msg.groups || []);
@@ -1279,6 +1285,11 @@ export function processMessage(msg) {
1279
1285
  renderGeneratedImage(msg);
1280
1286
  break;
1281
1287
 
1288
+ case 'mate_schedule_question_expired':
1289
+ markAskUserAnswered(msg.toolId, { 0: msg.error || 'This schedule review expired.' });
1290
+ stopUrgentBlink();
1291
+ break;
1292
+
1282
1293
  case 'mcp_import_consent_expired':
1283
1294
  markAskUserAnswered(msg.toolId, { 0: msg.error || 'No import was approved.' });
1284
1295
  stopUrgentBlink();
@@ -1755,6 +1766,10 @@ export function processMessage(msg) {
1755
1766
  handleNoteUpdated(msg);
1756
1767
  break;
1757
1768
 
1769
+ case "note_update_result":
1770
+ handleNoteUpdateResult(msg);
1771
+ break;
1772
+
1758
1773
  case "note_written":
1759
1774
  showToast((msg.byTitle || "Agent") + ' left a note: "' + (msg.preview || "") + '"');
1760
1775
  handleNoteWritten(msg);
@@ -389,6 +389,11 @@ export function resetClientState() {
389
389
 
390
390
  export function switchProject(slug) {
391
391
  if (!slug) return;
392
+ var target = cachedProjects.find(function (project) { return project.slug === slug; });
393
+ if (target && target.contentAccess === false && target.administrativeAccess === true) {
394
+ window.location.assign("/p/" + encodeURIComponent(slug) + "/");
395
+ return;
396
+ }
392
397
  document.cookie = "clay_last_project=" + encodeURIComponent(slug) + "; Path=/; SameSite=Strict; Max-Age=31536000";
393
398
  var st = store.snap();
394
399
  var ws = getWs();
@@ -0,0 +1,27 @@
1
+ import { store } from './store.js';
2
+ import { getWs } from './ws-ref.js';
3
+ import { navigateToSession } from './command-palette.js';
4
+ function context() {
5
+ return JSON.stringify([store.get('currentSlug'), store.get('activeSessionId'), store.get('dmMode'), store.get('homeShellVisible'), store.get('homeChatMateId'), store.get('homeChatSessionId'), store.get('homeSubSurface')]);
6
+ }
7
+ export function handleOwnerNavigation(msg) {
8
+ if (msg.type !== 'clay_navigation_offer' && msg.type !== 'clay_navigation_apply') return false;
9
+ var ws = getWs();
10
+ if (!ws || ws.readyState !== 1) return true;
11
+ if (msg.type === 'clay_navigation_offer') {
12
+ if (document.hidden || store.get('paneMode')) return true;
13
+ store.set({ clayNavigationOffer: { id: msg.deliveryId, context: context(), socket: ws } });
14
+ ws.send(JSON.stringify({ type: 'clay_navigation_claim', deliveryId: msg.deliveryId }));
15
+ return true;
16
+ }
17
+ var offer = store.get('clayNavigationOffer');
18
+ store.set({ clayNavigationOffer: null });
19
+ var accepted = offer && offer.id === msg.deliveryId && offer.socket === ws && offer.context === context() && !document.hidden && !store.get('paneMode');
20
+ var status = 'refused';
21
+ if (accepted && msg.target && typeof msg.target.projectSlug === 'string' && Number.isSafeInteger(msg.target.sessionId)) {
22
+ try { navigateToSession(msg.target); status = 'accepted'; } catch (error) { status = 'failed'; }
23
+ }
24
+ // This acknowledges navigator invocation only. Rendering may finish after reconnect.
25
+ if (ws.readyState === 1) ws.send(JSON.stringify({ type: 'clay_navigation_ack', deliveryId: msg.deliveryId, status: status }));
26
+ return true;
27
+ }
@@ -0,0 +1,140 @@
1
+ import { clayToolPresentation, clayToolOutcome } from './clay-tool-presentation.js';
2
+ import { store } from './store.js';
3
+ import { iconHtml, refreshIcons } from './icons.js';
4
+
5
+ function bindDisclosure(header, toggle) {
6
+ header.setAttribute('role', 'button');
7
+ header.tabIndex = 0;
8
+ header.addEventListener('click', toggle);
9
+ header.addEventListener('keydown', function (event) {
10
+ if (event.key !== 'Enter' && event.key !== ' ') return;
11
+ event.preventDefault();
12
+ toggle();
13
+ });
14
+ }
15
+
16
+ export function syncToolGroupDisclosure(el) {
17
+ var expanded = !el.classList.contains('collapsed');
18
+ el.querySelector('.tool-group-header').setAttribute('aria-expanded', String(expanded));
19
+ var items = el.querySelector('.tool-group-items');
20
+ items.inert = !expanded;
21
+ items.setAttribute('aria-hidden', String(!expanded));
22
+ }
23
+
24
+ export function bindToolGroupDisclosure(el, onToggle) {
25
+ // History links may expand a group directly; keep disclosure semantics in sync.
26
+ if (typeof MutationObserver !== 'undefined') {
27
+ var observer = new MutationObserver(function () { syncToolGroupDisclosure(el); });
28
+ observer.observe(el, { attributes: true, attributeFilter: ['class'] });
29
+ }
30
+ bindDisclosure(el.querySelector('.tool-group-header'), function () {
31
+ el.classList.toggle('collapsed');
32
+ onToggle();
33
+ syncToolGroupDisclosure(el);
34
+ });
35
+ }
36
+
37
+ function detailsFor(tool) {
38
+ var block = tool.el.querySelector('.clay-tool-evidence');
39
+ if (!block) {
40
+ block = document.createElement('div');
41
+ block.className = 'tool-result-block clay-tool-evidence collapsed';
42
+ tool.el.appendChild(block);
43
+ }
44
+ return block;
45
+ }
46
+ function renderEvidence(tool, block) {
47
+ block.replaceChildren();
48
+ var name = document.createElement('div');
49
+ name.className = 'clay-tool-technical-name';
50
+ name.textContent = tool.name;
51
+ block.appendChild(name);
52
+ [['Input', tool.input], ['Output', tool.clayRawResult]].forEach(function (entry) {
53
+ if (entry[1] === undefined || entry[1] === null) return;
54
+ var heading = document.createElement('div');
55
+ heading.className = 'clay-tool-evidence-label';
56
+ heading.textContent = entry[0];
57
+ var pre = document.createElement('pre');
58
+ pre.textContent = typeof entry[1] === 'string' ? entry[1] : JSON.stringify(entry[1], null, 2);
59
+ block.appendChild(heading);
60
+ block.appendChild(pre);
61
+ });
62
+ }
63
+ function statusIcon(el, selector, phase) {
64
+ var icon = el.querySelector(selector);
65
+ var name = phase === 'error' || phase === 'cancelled' ? 'alert-triangle' : phase === 'pending' ? 'clock' : phase === 'running' ? 'loader' : phase === 'returned' ? 'circle-help' : 'check';
66
+ var cls = phase === 'running' ? 'icon-spin' : phase === 'error' || phase === 'cancelled' ? 'err-icon' : 'check';
67
+ icon.innerHTML = iconHtml(name, cls);
68
+ }
69
+
70
+ export function applyClayToolItem(tool) {
71
+ if (!tool || !tool.el) return false;
72
+ var presentation = clayToolPresentation(tool.name, tool.input, tool.clayOutcome);
73
+ if (!presentation) return false;
74
+ tool.clayActivity = presentation;
75
+ var el = tool.el;
76
+ var header = el.querySelector('.tool-header');
77
+ el.classList.add('clay-tool-activity');
78
+ el.dataset.activityPhase = presentation.phase;
79
+ el.classList.toggle('error', presentation.phase === 'error' || presentation.phase === 'cancelled');
80
+ el.querySelector('.tool-name').textContent = presentation.label;
81
+ el.querySelector('.tool-desc').textContent = presentation.subject;
82
+ el.querySelector('.tool-subtitle').hidden = true;
83
+ header.setAttribute('aria-label', presentation.label + (presentation.subject ? ' · ' + presentation.subject : '') + '. Show technical details');
84
+ if (!tool.clayDisclosureBound) {
85
+ tool.clayDisclosureBound = true;
86
+ bindDisclosure(header, function () {
87
+ var expanded = el.classList.toggle('expanded');
88
+ var block = detailsFor(tool);
89
+ if (expanded) renderEvidence(tool, block);
90
+ block.classList.toggle('collapsed', !expanded);
91
+ header.setAttribute('aria-expanded', String(expanded));
92
+ });
93
+ }
94
+ header.setAttribute('aria-expanded', String(el.classList.contains('expanded')));
95
+ if (el.classList.contains('expanded')) renderEvidence(tool, detailsFor(tool));
96
+ statusIcon(el, '.tool-status-icon', presentation.phase);
97
+ refreshIcons();
98
+ return true;
99
+ }
100
+
101
+ export function finishClayToolItem(tool, group) {
102
+ if (!tool || !clayToolPresentation(tool.name, tool.input)) return false;
103
+ if (!tool.clayOutcome) {
104
+ tool.clayOutcome = { phase: 'returned', status: 'result_unavailable' };
105
+ tool.clayEndedGroup = group;
106
+ var cache = store.get('clayEndedToolRecords');
107
+ if (!cache) { cache = new WeakMap(); store.set({ clayEndedToolRecords: cache }); }
108
+ cache.set(tool.el, tool);
109
+ }
110
+ return applyClayToolItem(tool);
111
+ }
112
+
113
+ // Done clears the live registry; only rows still in this transcript can receive late results.
114
+ export function findEndedClayTool(root, id) {
115
+ var cache = store.get('clayEndedToolRecords');
116
+ if (!cache || !root) return null;
117
+ var rows = root.querySelectorAll('.clay-tool-activity[data-tool-id]');
118
+ for (var i = rows.length - 1; i >= 0; i--) {
119
+ if (rows[i].dataset.toolId === id && cache.has(rows[i])) return cache.get(rows[i]);
120
+ }
121
+ return null;
122
+ }
123
+
124
+ export function clayToolResultGroup(tool, liveGroup) {
125
+ return liveGroup && liveGroup.el.contains(tool.el) ? liveGroup : tool.clayEndedGroup;
126
+ }
127
+
128
+ export function applyClayToolResult(tool, content, isError) {
129
+ if (!clayToolPresentation(tool.name, tool.input)) return false;
130
+ tool.clayRawResult = content;
131
+ tool.clayOutcome = clayToolOutcome(content, isError);
132
+ return applyClayToolItem(tool);
133
+ }
134
+
135
+ export function syncClayToolGroup(group) {
136
+ if (!group.clayActivityPhase) { delete group.el.dataset.activityPhase; return; }
137
+ group.el.dataset.activityPhase = group.clayActivityPhase;
138
+ statusIcon(group.el, '.tool-group-status-icon', group.clayActivityPhase);
139
+ refreshIcons();
140
+ }