clay-server 5.3.0-beta.5 → 5.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 (185) hide show
  1. package/lib/clay-product-control-tools.js +2 -2
  2. package/lib/clay-product-control.js +1 -1
  3. package/lib/clay-screen-controls.js +1 -0
  4. package/lib/clay-split-view-awareness.js +5 -2
  5. package/lib/clay-split-view-controls.js +14 -7
  6. package/lib/clay-split-view-input.js +5 -4
  7. package/lib/clay-split-view-render-proof.js +14 -4
  8. package/lib/clay-view-workspace-input.js +71 -0
  9. package/lib/project-admin-overview.js +22 -11
  10. package/lib/project-background-tasks.js +76 -0
  11. package/lib/project-connection.js +1 -1
  12. package/lib/project-file-history.js +11 -9
  13. package/lib/project-file-watch.js +26 -7
  14. package/lib/project-filesystem.js +5 -4
  15. package/lib/project-sessions.js +12 -5
  16. package/lib/project-shared-browser.js +7 -3
  17. package/lib/project-split-view-document.js +16 -2
  18. package/lib/project-split-views.js +27 -297
  19. package/lib/project-user-message.js +4 -3
  20. package/lib/project-vendor-login.js +2 -2
  21. package/lib/project-view-input.js +11 -0
  22. package/lib/project-view-pair.js +26 -0
  23. package/lib/project-view-store.js +348 -0
  24. package/lib/project-view-terminal-binding.js +16 -0
  25. package/lib/project-view-workspace.js +126 -0
  26. package/lib/project-worker-conversation.js +5 -0
  27. package/lib/project.js +1 -0
  28. package/lib/public/app.js +9 -0
  29. package/lib/public/css/contextual-clay.css +2 -1
  30. package/lib/public/css/issues.css +3 -0
  31. package/lib/public/css/mate-conversation.css +1 -1
  32. package/lib/public/css/pane.css +5 -1
  33. package/lib/public/css/pending-message-queue.css +24 -19
  34. package/lib/public/css/project-admin-overview.css +42 -13
  35. package/lib/public/css/right-workbench.css +0 -1
  36. package/lib/public/css/scheduled-tasks.css +7 -0
  37. package/lib/public/css/shared-browser.css +31 -8
  38. package/lib/public/css/split-view-layout.css +129 -10
  39. package/lib/public/css/tool-pane.css +124 -0
  40. package/lib/public/index.html +6 -6
  41. package/lib/public/modules/agent-message-bubbles.js +96 -0
  42. package/lib/public/modules/app-header.js +6 -0
  43. package/lib/public/modules/app-messages.js +26 -9
  44. package/lib/public/modules/background-task-previews.js +8 -0
  45. package/lib/public/modules/background-task-viewer.js +97 -0
  46. package/lib/public/modules/background-tasks-ui.js +34 -125
  47. package/lib/public/modules/chat-bubble-renderer.js +11 -5
  48. package/lib/public/modules/clay-file-links.js +2 -1
  49. package/lib/public/modules/clay-split-view-screen.js +6 -3
  50. package/lib/public/modules/clay-tool-pane-screen.js +35 -0
  51. package/lib/public/modules/contextual-clay-geometry.js +7 -3
  52. package/lib/public/modules/file-history-snapshot.js +12 -0
  53. package/lib/public/modules/file-pane-entrypoints.js +55 -0
  54. package/lib/public/modules/file-pane-history.js +78 -0
  55. package/lib/public/modules/file-pane-instances.js +140 -0
  56. package/lib/public/modules/file-pane-links.js +41 -0
  57. package/lib/public/modules/file-pane-render.js +48 -0
  58. package/lib/public/modules/file-pane-review.js +26 -0
  59. package/lib/public/modules/file-pane-toolbar.js +54 -0
  60. package/lib/public/modules/file-viewer-source.js +70 -0
  61. package/lib/public/modules/filebrowser.js +43 -82
  62. package/lib/public/modules/git-panel.js +5 -0
  63. package/lib/public/modules/issues.js +3 -4
  64. package/lib/public/modules/linear-panel.js +2 -2
  65. package/lib/public/modules/mate-knowledge-workbench.js +10 -6
  66. package/lib/public/modules/notifications.js +4 -1
  67. package/lib/public/modules/pane-activation.js +7 -3
  68. package/lib/public/modules/pane-binding.js +5 -1
  69. package/lib/public/modules/pane-browser-bridge.js +6 -2
  70. package/lib/public/modules/pane-browser-transport.js +110 -0
  71. package/lib/public/modules/pane-header-tooltips.js +32 -0
  72. package/lib/public/modules/pending-message-queue-drag.js +69 -0
  73. package/lib/public/modules/pending-message-queue.js +3 -33
  74. package/lib/public/modules/permission-control.js +6 -5
  75. package/lib/public/modules/plantuml-preview.js +12 -12
  76. package/lib/public/modules/project-admin-overview.js +125 -10
  77. package/lib/public/modules/project-grouping.js +9 -0
  78. package/lib/public/modules/right-workbench.js +34 -15
  79. package/lib/public/modules/scheduled-tasks.js +7 -6
  80. package/lib/public/modules/server-settings-controls.js +33 -4
  81. package/lib/public/modules/session-folder-toolbar.js +1 -1
  82. package/lib/public/modules/session-tool-item.js +86 -0
  83. package/lib/public/modules/settings-select.js +2 -1
  84. package/lib/public/modules/shared-browser-tabs.js +37 -21
  85. package/lib/public/modules/shared-browser.js +69 -9
  86. package/lib/public/modules/sidebar-projects.js +1 -10
  87. package/lib/public/modules/split-pane-renderer.js +1 -0
  88. package/lib/public/modules/split-session-empty-pane.js +47 -0
  89. package/lib/public/modules/split-view-create-menu.js +74 -65
  90. package/lib/public/modules/split-view-drag-preview.js +90 -0
  91. package/lib/public/modules/split-view-layout-client.js +1 -1
  92. package/lib/public/modules/split-view-layout-scope.js +4 -0
  93. package/lib/public/modules/split-view-picker-actions.js +71 -0
  94. package/lib/public/modules/split-view-picker-render.js +81 -0
  95. package/lib/public/modules/split-view-presentation-drag.js +36 -13
  96. package/lib/public/modules/split-view-presentation-drop.js +21 -18
  97. package/lib/public/modules/split-view-presentation-renderer.js +22 -17
  98. package/lib/public/modules/split-view-presentations.js +25 -33
  99. package/lib/public/modules/split-view-worker-activity.js +2 -2
  100. package/lib/public/modules/split-view-worker-transcript.js +70 -33
  101. package/lib/public/modules/split-view-workers.js +6 -4
  102. package/lib/public/modules/split-view.js +9 -2
  103. package/lib/public/modules/sticky-notes-browser.js +2 -2
  104. package/lib/public/modules/terminal-viewport.js +14 -0
  105. package/lib/public/modules/terminal.js +49 -23
  106. package/lib/public/modules/theme-palette.js +177 -0
  107. package/lib/public/modules/theme.js +2 -180
  108. package/lib/public/modules/tool-pane-chrome.js +88 -0
  109. package/lib/public/modules/tool-pane-drag.js +134 -0
  110. package/lib/public/modules/tool-pane-layout.js +95 -0
  111. package/lib/public/modules/tool-pane-legacy.js +60 -0
  112. package/lib/public/modules/tool-pane-tree.js +51 -0
  113. package/lib/public/modules/tools.js +5 -89
  114. package/lib/public/modules/tooltip.js +32 -6
  115. package/lib/public/modules/view-legacy-coordinates.js +16 -0
  116. package/lib/public/modules/view-mutations.js +24 -0
  117. package/lib/public/modules/view-pair-presentation.js +74 -0
  118. package/lib/public/modules/view-pair-validation.js +12 -0
  119. package/lib/public/modules/view-presentation-state.js +37 -0
  120. package/lib/public/modules/view-workspace-controller.js +210 -0
  121. package/lib/public/modules/view-workspace-state.js +103 -0
  122. package/lib/public/modules/view-workspace-validation.js +47 -0
  123. package/lib/public/modules/wireframe-workbench.js +13 -2
  124. package/lib/public/modules/workbench-layout-preference.js +12 -5
  125. package/lib/public/modules/workbench-panels.js +21 -0
  126. package/lib/public/modules/worker-message-previews.js +39 -0
  127. package/lib/public/pane-assets/app-5W6KJXK5.js +1 -0
  128. package/lib/public/pane-assets/chunk-2IJUTAB6.js +2 -0
  129. package/lib/public/pane-assets/chunk-6QFLYOVY.js +1 -0
  130. package/lib/public/pane-assets/chunk-7OVNC5HT.js +1 -0
  131. package/lib/public/pane-assets/chunk-BQN7B62H.js +3 -0
  132. package/lib/public/pane-assets/chunk-EMLN2Y7L.js +1 -0
  133. package/lib/public/pane-assets/chunk-ENPAHXLB.js +1 -0
  134. package/lib/public/pane-assets/chunk-EPBGGZRT.js +1 -0
  135. package/lib/public/pane-assets/chunk-EYTE56IA.js +1 -0
  136. package/lib/public/pane-assets/chunk-FQTNR4ZS.js +1 -0
  137. package/lib/public/pane-assets/chunk-JIV7O5GN.js +1 -0
  138. package/lib/public/pane-assets/chunk-L6LSA5WI.js +1 -0
  139. package/lib/public/pane-assets/chunk-M57UIGZV.js +1 -0
  140. package/lib/public/pane-assets/chunk-MGTFTEJP.js +1 -0
  141. package/lib/public/pane-assets/chunk-MMG6MYCN.js +1 -0
  142. package/lib/public/pane-assets/chunk-MZLMEXMW.js +1 -0
  143. package/lib/public/pane-assets/chunk-OG3E5SVA.js +166 -0
  144. package/lib/public/pane-assets/chunk-R7QVVFUG.js +1 -0
  145. package/lib/public/pane-assets/chunk-RGAFZ3T2.js +1 -0
  146. package/lib/public/pane-assets/chunk-RNDRURMF.js +1 -0
  147. package/lib/public/pane-assets/chunk-SJOT74VW.js +2 -0
  148. package/lib/public/pane-assets/chunk-SJTGHIN5.js +1 -0
  149. package/lib/public/pane-assets/chunk-SLMAFKI2.js +1 -0
  150. package/lib/public/pane-assets/chunk-U3I5KY3B.js +1 -0
  151. package/lib/public/pane-assets/chunk-UPXU2IPC.js +1 -0
  152. package/lib/public/pane-assets/chunk-VEBYD7TL.js +1 -0
  153. package/lib/public/pane-assets/chunk-VZ2OKDAO.js +1 -0
  154. package/lib/public/pane-assets/chunk-XBHYNXXT.js +1 -0
  155. package/lib/public/pane-assets/manifest.json +438 -381
  156. package/lib/public/pane-assets/style-BSKKK54Q.css +1 -0
  157. package/lib/public/style.css +1 -0
  158. package/lib/sdk-message-processor.js +5 -2
  159. package/lib/server-storage.js +27 -3
  160. package/lib/server-workbench-layout-settings.js +3 -3
  161. package/lib/server.js +4 -2
  162. package/lib/sessions.js +1 -1
  163. package/lib/split-view-layout.js +11 -7
  164. package/lib/storage-usage.js +29 -7
  165. package/lib/terminal-manager.js +2 -2
  166. package/lib/users-workbench-layout-preferences.js +14 -4
  167. package/lib/workbench-window-layout.js +30 -0
  168. package/lib/worker-message-previews.js +50 -0
  169. package/lib/ws-schema.js +1 -1
  170. package/lib/yoke/adapters/claude-worker.js +8 -0
  171. package/lib/yoke/adapters/claude.js +17 -2
  172. package/lib/yoke/claude-task-reader.js +94 -0
  173. package/lib/yoke/interface.js +1 -0
  174. package/package.json +1 -1
  175. package/lib/public/pane-assets/app-6H5S2Y3Z.js +0 -1
  176. package/lib/public/pane-assets/chunk-3QV5SDC4.js +0 -1
  177. package/lib/public/pane-assets/chunk-4FM3U3DW.js +0 -1
  178. package/lib/public/pane-assets/chunk-7GZH3FZY.js +0 -1
  179. package/lib/public/pane-assets/chunk-AGJTF5WQ.js +0 -1
  180. package/lib/public/pane-assets/chunk-DGOFGEUK.js +0 -1
  181. package/lib/public/pane-assets/chunk-I57URYSL.js +0 -1
  182. package/lib/public/pane-assets/chunk-NPKRTUNG.js +0 -2
  183. package/lib/public/pane-assets/chunk-VGA2Y3D2.js +0 -2
  184. package/lib/public/pane-assets/chunk-WKO3T75E.js +0 -167
  185. package/lib/public/pane-assets/style-TSRDU54Z.css +0 -1
@@ -79,6 +79,7 @@
79
79
  @import url("css/skills-workbench.css");
80
80
 
81
81
  @import url("css/right-workbench.css?v=20261020-workbench-overlay");
82
+ @import url("css/tool-pane.css");
82
83
 
83
84
  @import url("css/mate-conversation.css");
84
85
 
@@ -229,7 +229,8 @@ function attachMessageProcessor(ctx) {
229
229
  // only clears ambient watchers must not rebroadcast the session list.
230
230
  var previousBackgroundTaskCount = backgroundTaskTiming.countUserTasks(session.activeBackgroundTasks);
231
231
  session.activeBackgroundTasks = [];
232
- sendToSession(session, { type: "active_background_tasks", tasks: [] });
232
+ session.backgroundTaskCatalog = null;
233
+ sendToSession(session, { type: "active_background_tasks", tasks: [], catalog: [] });
233
234
  if (previousBackgroundTaskCount !== 0) sm.broadcastSessionList();
234
235
  var fsSkills = discoverSkillDirs(getSessionSkillOptions(session));
235
236
  session.skillNames = Array.from(mergeSkills(parsed.skills, fsSkills));
@@ -463,6 +464,7 @@ function attachMessageProcessor(ctx) {
463
464
 
464
465
  } else if (parsed.yokeType === "subagent_message") {
465
466
  // Sub-agent messages: extract tool_use blocks for activity display
467
+ require("./project-background-tasks").observeMessage(session, parsed, sendToSession);
466
468
  processSubagentMessage(session, parsed);
467
469
 
468
470
  } else if (parsed.yokeType === "message") {
@@ -864,7 +866,8 @@ function attachMessageProcessor(ctx) {
864
866
  Array.isArray(parsed.tasks) ? parsed.tasks : []
865
867
  );
866
868
  session.activeBackgroundTasks = activeBackgroundTasks;
867
- sendToSession(session, { type: "active_background_tasks", tasks: activeBackgroundTasks });
869
+ var taskCatalog = require("./project-background-tasks").remember(session, activeBackgroundTasks);
870
+ sendToSession(session, { type: "active_background_tasks", tasks: activeBackgroundTasks, catalog: taskCatalog });
868
871
  if (previousBackgroundTaskCount !== backgroundTaskTiming.countUserTasks(activeBackgroundTasks)) {
869
872
  sm.broadcastSessionList();
870
873
  }
@@ -6,6 +6,21 @@ function attachStorage(ctx) {
6
6
  var progress = null;
7
7
  var expires = 0;
8
8
  var failed = false;
9
+ var scanIdentity = null;
10
+ function projectIdentity() {
11
+ return JSON.stringify((ctx.getProjects ? ctx.getProjects() : []).map(function (project) { return [project.slug, project.path, project.projectOwnerId, project.isMate]; }));
12
+ }
13
+ function targetProject(slug) {
14
+ return (ctx.getProjects ? ctx.getProjects() : []).find(function (project) { return project.slug === slug && !project.isMate && !/^mate-/.test(slug); });
15
+ }
16
+ function usageReply(req, res, result, slug) {
17
+ if (!authorized(req, res)) return;
18
+ if (!slug) { reply(res, result.scanning ? 202 : 200, result); return; }
19
+ if (!targetProject(slug)) { reply(res, 404, { error: 'Project is no longer registered.' }); return; }
20
+ if (scanIdentity !== projectIdentity()) { reply(res, 409, { error: 'Project registration changed. Refresh usage.' }); return; }
21
+ var usage = (result.projects || []).find(function (project) { return project.slug === slug; });
22
+ reply(res, result.scanning ? 202 : 200, { slug: slug, scanning: !!result.scanning, partial: !!result.partial, checkedAt: result.checkedAt || null, usage: usage || null });
23
+ }
9
24
  function reply(res, status, data) {
10
25
  res.writeHead(status, { 'Content-Type': 'application/json', 'Cache-Control': 'no-store' });
11
26
  res.end(JSON.stringify(data));
@@ -19,14 +34,23 @@ function attachStorage(ctx) {
19
34
  return true;
20
35
  }
21
36
  function handleRequest(req, res, url) {
22
- if (url.split('?')[0] !== '/api/server/storage') return false;
37
+ var pathname = url.split('?')[0];
38
+ var match = /^\/api\/server\/storage\/project\/([^/]+)$/.exec(pathname);
39
+ if (pathname !== '/api/server/storage' && !match) return false;
40
+ var slug = null;
23
41
  var refresh = new URL(url, 'http://localhost').searchParams.get('refresh') === '1';
24
42
  if (!authorized(req, res)) return true;
25
43
  if (req.method !== 'GET') { reply(res, 405, { error: 'Method not allowed.' }); return true; }
44
+ if (match) {
45
+ try { slug = decodeURIComponent(match[1]); } catch (error) { reply(res, 400, { error: 'Invalid project.' }); return true; }
46
+ if (!targetProject(slug)) { reply(res, 404, { error: 'Project not found.' }); return true; }
47
+ }
48
+ if (!pending && scanIdentity !== projectIdentity()) { cached = null; failed = false; }
26
49
  if (failed && Date.now() < expires) { reply(res, 500, { error: 'Disk usage could not be measured. Try again in a few seconds.' }); return true; }
27
- if (!pending && !refresh && cached && Date.now() < expires) { reply(res, 200, cached); return true; }
50
+ if (!pending && !refresh && cached && Date.now() < expires) { usageReply(req, res, cached, slug); return true; }
28
51
  if (!pending) {
29
52
  progress = null; failed = false;
53
+ scanIdentity = projectIdentity();
30
54
  var users = ctx.users.getAllUsers();
31
55
  var projects = ctx.getProjects ? ctx.getProjects() : [];
32
56
  if (!ctx.users.isMultiUser() && users.length <= 1) {
@@ -45,7 +69,7 @@ function attachStorage(ctx) {
45
69
  timer = setTimeout(function () { resolve(progress || { scanning: true }); }, 750);
46
70
  })]).then(function (result) {
47
71
  clearTimeout(timer);
48
- if (!res.destroyed && authorized(req, res)) reply(res, result.scanning ? 202 : 200, result);
72
+ if (!res.destroyed) usageReply(req, res, result, slug);
49
73
  }).catch(function () {
50
74
  clearTimeout(timer);
51
75
  if (!res.destroyed) reply(res, 500, { error: 'Disk usage could not be measured. Try refreshing.' });
@@ -32,7 +32,7 @@ function attachWorkbenchLayoutSettings(ctx) {
32
32
  req.on('data', function (chunk) {
33
33
  if (finished) return;
34
34
  body += chunk;
35
- if (Buffer.byteLength(body) > 1024) { finished = true; body = ''; reply(res, 413, { error: 'Request too large.' }); }
35
+ if (Buffer.byteLength(body) > 65536) { finished = true; body = ''; reply(res, 413, { error: 'Request too large.' }); }
36
36
  });
37
37
  req.on('aborted', function () { finished = true; });
38
38
  req.on('error', function () { finished = true; });
@@ -41,11 +41,11 @@ function attachWorkbenchLayoutSettings(ctx) {
41
41
  if (identity(req) !== accountId) { reply(res, 401, { error: 'Account changed. Sign in again.' }); return; }
42
42
  var input;
43
43
  try { input = JSON.parse(body); } catch (error) { reply(res, 400, { error: 'Invalid JSON.' }); return; }
44
- if (!input || Array.isArray(input) || Object.keys(input).length !== 2 || !Object.prototype.hasOwnProperty.call(input, 'mode') || !Object.prototype.hasOwnProperty.call(input, 'revision')) {
44
+ if (!input || Array.isArray(input) || Object.keys(input).some(function (key) { return ['mode', 'revision', 'layout'].indexOf(key) < 0; }) || !Object.prototype.hasOwnProperty.call(input, 'mode') || !Object.prototype.hasOwnProperty.call(input, 'revision')) {
45
45
  reply(res, 400, { error: 'Invalid workbench preference.' }); return;
46
46
  }
47
47
  try {
48
- var result = ctx.users.setWorkbenchLayoutPreference(accountId, input.mode, input.revision);
48
+ var result = ctx.users.setWorkbenchLayoutPreference(accountId, input.mode, input.revision, input.layout);
49
49
  reply(res, result.error ? result.status || 400 : 200, result);
50
50
  } catch (error) { reply(res, 500, { error: 'Workbench layout could not be saved. Your previous layout is retained.' }); }
51
51
  });
package/lib/server.js CHANGED
@@ -519,7 +519,7 @@ function createServer(opts) {
519
519
  } : null;
520
520
  var payload = JSON.stringify(seed).replace(/</g, "\\u003c").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
521
521
  var script = '<script>window.__clayAppearanceSeed=' + payload + ';</script>';
522
- return html.replace('<script src="appearance-bootstrap.js"></script>', script + '<script src="appearance-bootstrap.js"></script>');
522
+ return html.replace(/<script src="\/?appearance-bootstrap.js"><\/script>/, function (tag) { return script + tag; });
523
523
  }
524
524
 
525
525
  function sendAppShell(req, res, slug) {
@@ -1103,8 +1103,10 @@ function createServer(opts) {
1103
1103
  if (!httpUser || !canUserAccessSlug(httpUser.id, slug)) {
1104
1104
  var overview = httpUser && administrativeMetadata(httpUser.id, slug);
1105
1105
  if (overview && req.method === "GET" && (projectUrlForAuth === "/" || projectUrlForAuth === "/index.html")) {
1106
+ var overviewAppearance = null;
1107
+ try { overviewAppearance = settings.getInitialAppearance(req); } catch (error) {}
1106
1108
  res.writeHead(200, { "Content-Type": "text/html; charset=utf-8", "Cache-Control": "no-store" });
1107
- res.end(projectAdminOverview.page(overview, getVisibleProjects(httpUser.id)));
1109
+ res.end(injectAppearanceSeed(projectAdminOverview.page(overview, getVisibleProjects(httpUser.id)), overviewAppearance));
1108
1110
  return;
1109
1111
  }
1110
1112
  res.writeHead(403, { "Content-Type": "application/json", "Cache-Control": "no-store" });
package/lib/sessions.js CHANGED
@@ -886,7 +886,7 @@ function createSessionManager(opts) {
886
886
  if (session.isProcessing) {
887
887
  _send({ type: "status", status: "processing" });
888
888
  }
889
- _send({ type: "active_background_tasks", tasks: session.activeBackgroundTasks || [] });
889
+ _send({ type: "active_background_tasks", tasks: session.activeBackgroundTasks || [], catalog: session.backgroundTaskCatalog && session.backgroundTaskCatalog.tasks || [] });
890
890
 
891
891
  // Re-send any pending permission requests
892
892
  var pendingIds = Object.keys(session.pendingPermissions);
@@ -12,7 +12,7 @@ function validateTree(tree, expectedOrigins) {
12
12
  var invalid = null;
13
13
  function walk(node, depth) {
14
14
  if (invalid) return;
15
- if (!node || typeof node !== "object" || depth > MAX_SESSIONS) { invalid = "Layout tree is malformed"; return; }
15
+ if (!node || typeof node !== "object" || Array.isArray(node) || depth > MAX_SESSIONS) { invalid = "Layout tree is malformed"; return; }
16
16
  if (Object.prototype.hasOwnProperty.call(node, "id")) {
17
17
  if (Object.keys(node).length !== 1 || !validOrigin(node.id) || origins.indexOf(node.id) !== -1) {
18
18
  invalid = "Layout leaves must have unique valid session origins"; return;
@@ -28,11 +28,11 @@ function validateTree(tree, expectedOrigins) {
28
28
  }
29
29
  walk(node.left, depth + 1); walk(node.right, depth + 1);
30
30
  }
31
- walk(tree, 0);
31
+ if (tree !== null) walk(tree, 0);
32
32
  if (invalid) return { ok: false, error: invalid, origins: origins };
33
- if (origins.length < 2 || origins.length > MAX_SESSIONS) return { ok: false, error: "A Split view requires two to eight sessions", origins: origins };
33
+ if (origins.length > MAX_SESSIONS) return { ok: false, error: "A View supports zero to eight sessions", origins: origins };
34
34
  if (expectedOrigins) {
35
- if (!Array.isArray(expectedOrigins) || expectedOrigins.length !== origins.length ||
35
+ if (!Array.isArray(expectedOrigins) || expectedOrigins.length !== origins.length || new Set(expectedOrigins).size !== expectedOrigins.length ||
36
36
  expectedOrigins.some(function (id) { return !validOrigin(id) || origins.indexOf(id) === -1; })) {
37
37
  return { ok: false, error: "Layout leaves must exactly match the selected sessions", origins: origins };
38
38
  }
@@ -100,9 +100,9 @@ function validateViewShape(view) {
100
100
  var name = validateName(view.name);
101
101
  if (!name.ok) return name;
102
102
  if (view.nameMode != null && ["automatic", "custom"].indexOf(view.nameMode) === -1) return { ok: false, error: "Split view name mode is invalid" };
103
- if (!Number.isInteger(view.revision) || view.revision < 1 || !validatePreset(view.layout)) return { ok: false, error: "Split view revision or layout is invalid" };
103
+ if (!Number.isSafeInteger(view.revision) || view.revision < 1 || !validatePreset(view.layout)) return { ok: false, error: "Split view revision or layout is invalid" };
104
104
  var refs = view.sessionRefs;
105
- if (!Array.isArray(refs) || refs.length < 2 || refs.length > MAX_SESSIONS) return { ok: false, error: "A Split view requires two to eight sessions" };
105
+ if (!Array.isArray(refs) || refs.length > MAX_SESSIONS) return { ok: false, error: "A View supports zero to eight sessions" };
106
106
  var ids = [];
107
107
  for (var i = 0; i < refs.length; i++) {
108
108
  var ref = refs[i];
@@ -113,7 +113,11 @@ function validateViewShape(view) {
113
113
  }
114
114
  var tree = validateTree(view.tree, ids);
115
115
  if (!tree.ok) return tree;
116
- if (typeof view.activeSessionOriginId !== "string" || ids.indexOf(view.activeSessionOriginId) === -1) return { ok: false, error: "Active session must belong to the Split view" };
116
+ if (ids.length ? typeof view.activeSessionOriginId !== "string" || ids.indexOf(view.activeSessionOriginId) === -1 : view.activeSessionOriginId !== null) return { ok: false, error: "Active session must belong to the Split view" };
117
+ if (view.workspace !== undefined) {
118
+ var checked = require("./project-view-workspace").validate(view.workspace, view);
119
+ if (!checked.ok) return checked;
120
+ }
117
121
  return { ok: true, name: name.name, origins: ids };
118
122
  }
119
123
 
@@ -39,10 +39,26 @@ async function scanStorage(options) {
39
39
  if (identity && !owners.get(identity.uid)) rows[index].mapped = false;
40
40
  });
41
41
  var projectRoots = new Map();
42
+ var projectUsage = [];
43
+ var usageRoots = new Map();
42
44
  var projectErrors = 0;
43
45
  for (var project of projects) {
46
+ var usage = { slug: project.slug, available: false, bytes: 0, files: 0, capacityBytes: null, availableBytes: null };
47
+ if (!project.isMate && project.slug) projectUsage.push(usage);
44
48
  try {
45
49
  var projectPath = await fs.realpath(project.path);
50
+ usage.available = true;
51
+ if (usageRoots.has(projectPath)) {
52
+ var priorUsage = usageRoots.get(projectPath);
53
+ if (priorUsage) priorUsage.available = false;
54
+ usage.available = false;
55
+ usageRoots.set(projectPath, null);
56
+ } else usageRoots.set(projectPath, project.isMate ? null : usage);
57
+ try {
58
+ var volume = await fs.statfs(projectPath);
59
+ usage.capacityBytes = volume.blocks * volume.bsize;
60
+ usage.availableBytes = volume.bavail * volume.bsize;
61
+ } catch (error) { /* Volume capacity can be unavailable independently of allocation. */ }
46
62
  var row = rows.find(function (item) { return item.id === project.projectOwnerId; }) || null;
47
63
  if (row) row.projects++;
48
64
  if (projectRoots.has(projectPath) && projectRoots.get(projectPath) !== row) row = null;
@@ -50,21 +66,20 @@ async function scanStorage(options) {
50
66
  } catch (error) { projectErrors++; }
51
67
  }
52
68
  var orderedProjects = Array.from(projectRoots.keys()).sort(function (a, b) { return b.length - a.length; });
53
- function projectOwner(filePath) {
54
- var root = orderedProjects.find(function (candidate) { return filePath === candidate || filePath.indexOf(candidate + path.sep) === 0; });
55
- return root ? projectRoots.get(root) : null;
69
+ function projectRoot(filePath) {
70
+ return orderedProjects.find(function (candidate) { return filePath === candidate || filePath.indexOf(candidate + path.sep) === 0; });
56
71
  }
57
72
  var roots = orderedProjects.concat(options.projectPaths || []);
58
73
  identities.forEach(function (identity) { if (identity) roots.push(identity.home); });
59
74
  roots.push(options.configDir);
60
- var report = { attribution: projectMode ? "project" : "filesystem", users: rows, totalBytes: 0, totalFiles: 0, unattributedBytes: 0, unattributedFiles: 0,
75
+ var report = { attribution: projectMode ? "project" : "filesystem", users: rows, projects: projectUsage, totalBytes: 0, totalFiles: 0, unattributedBytes: 0, unattributedFiles: 0,
61
76
  partial: false, errors: projectErrors + users.filter(function (user, index) { return !projectMode && options.platform === 'linux' && user.linuxUser && !identities[index]; }).length, skippedLinks: 0, skippedMounts: 0, scannedEntries: 0, roots: 0, platform: options.platform };
62
77
  var lastProgress = 0;
63
78
  function progress() {
64
79
  if (!options.onProgress || Date.now() - lastProgress < 500) return;
65
80
  lastProgress = Date.now();
66
81
  options.onProgress(Object.assign({}, report, { scanning: true, checkedAt: new Date().toISOString(),
67
- users: rows.map(function (row) { return Object.assign({}, row); }) }));
82
+ users: rows.map(function (row) { return Object.assign({}, row); }), projects: projectUsage.map(function (row) { return Object.assign({}, row); }) }));
68
83
  }
69
84
  progress();
70
85
  var seen = new Map();
@@ -118,9 +133,15 @@ async function scanStorage(options) {
118
133
  if (stat.isSymbolicLink()) { report.skippedLinks++; return; }
119
134
  if (stat.dev !== device) { report.skippedMounts++; return; }
120
135
  var key = stat.dev + ':' + stat.ino;
121
- var owner = projectMode ? projectOwner(filePath) : owners.get(stat.uid);
136
+ var usageRoot = projectRoot(filePath);
137
+ var owner = projectMode ? projectRoots.get(usageRoot) : owners.get(stat.uid);
138
+ var usage = usageRoot && usageRoots.get(usageRoot);
122
139
  if (seen.has(key)) {
123
140
  var previous = seen.get(key);
141
+ if (stat.isFile() && previous.project && usage && previous.project !== usage) {
142
+ previous.project.bytes -= previous.bytes; previous.project.files--;
143
+ previous.project = null;
144
+ }
124
145
  if (projectMode && stat.isFile() && previous.owner && previous.owner !== owner) {
125
146
  previous.owner.bytes -= previous.bytes; previous.owner.files--;
126
147
  report.unattributedBytes += previous.bytes; report.unattributedFiles++;
@@ -132,7 +153,8 @@ async function scanStorage(options) {
132
153
  if (!stat.isDirectory() && !stat.isFile()) return;
133
154
  var bytes = typeof stat.blocks === 'number' ? stat.blocks * 512 : stat.size;
134
155
  // Only directories and multiply-linked files need identity deduplication.
135
- if (stat.isDirectory() || stat.nlink > 1) seen.set(key, { owner: owner, bytes: bytes });
156
+ if (stat.isDirectory() || stat.nlink > 1) seen.set(key, { owner: owner, project: usage, bytes: bytes });
157
+ if (usage) { usage.bytes += bytes; if (stat.isFile()) usage.files++; }
136
158
  report.totalBytes += bytes;
137
159
  if (stat.isFile()) report.totalFiles++;
138
160
  if (owner) { owner.bytes += bytes; if (stat.isFile()) owner.files++; }
@@ -136,9 +136,9 @@ function createTerminalManager(opts) {
136
136
  sendTo(ws, { type: "term_output", id: id, data: replay });
137
137
  }
138
138
 
139
- // Send current terminal dimensions so the client renders at the correct size
139
+ // This snapshot may arrive after the attaching client has fitted a new viewport.
140
140
  if (!alreadySubscribed && session.cols && session.rows) {
141
- sendTo(ws, { type: "term_resized", id: id, cols: session.cols, rows: session.rows });
141
+ sendTo(ws, { type: "term_resized", id: id, cols: session.cols, rows: session.rows, initial: true });
142
142
  }
143
143
 
144
144
  // If already exited, notify
@@ -1,3 +1,4 @@
1
+ var windowLayouts = require('./workbench-window-layout');
1
2
  // Account UI preferences are excluded from canonical Clay controls.
2
3
  function validMode(mode) { return mode === 'overlay' || mode === 'docked'; }
3
4
  function attachWorkbenchLayoutPreferences(deps) {
@@ -12,16 +13,25 @@ function attachWorkbenchLayoutPreferences(deps) {
12
13
  function value(accountId, saved) {
13
14
  if (saved === undefined) return { accountId: accountId, mode: 'overlay', revision: 0 };
14
15
  if (!saved || !validMode(saved.mode) || !Number.isSafeInteger(saved.revision) || saved.revision < 1) throw new Error('Invalid stored preference.');
15
- return { accountId: accountId, mode: saved.mode, revision: saved.revision };
16
+ if (saved.layouts && !windowLayouts.validLayouts(saved.layouts)) throw new Error('Invalid stored window layout.');
17
+ return Object.assign({ accountId: accountId, mode: saved.mode, revision: saved.revision }, saved.layouts ? { layouts: saved.layouts } : {});
16
18
  }
17
19
  function get(accountId) { return value(accountId, context(accountId).saved); }
18
- function set(accountId, mode, revision) {
20
+ function set(accountId, mode, revision, layout) {
21
+ if (layout !== undefined && !windowLayouts.validLayout(layout)) return { status: 400, error: 'Invalid window layout.' };
19
22
  if (!validMode(mode) || !Number.isSafeInteger(revision) || revision < 0) return { status: 400, error: 'Invalid workbench layout.' };
20
23
  var current = context(accountId), prior = value(accountId, current.saved);
21
24
  if (prior.revision !== revision) return { status: 409, error: 'Workbench layout changed elsewhere. Reload the preference and try again.' };
22
- if (prior.mode === mode) return prior;
25
+ var layouts = Object.assign({}, prior.layouts || {});
26
+ var changed = layout && JSON.stringify(layouts[layout.key]) !== JSON.stringify({ base: layout.base, tree: layout.tree });
27
+ if (prior.mode === mode && !changed) return prior;
28
+ if (layout) {
29
+ delete layouts[layout.key];
30
+ if (Object.keys(layouts).length >= 32) delete layouts[Object.keys(layouts)[0]];
31
+ layouts[layout.key] = { base: layout.base, tree: layout.tree };
32
+ }
23
33
  if (revision === Number.MAX_SAFE_INTEGER) throw new Error('Preference revision exhausted.');
24
- var saved = { mode: mode, revision: revision + 1 };
34
+ var saved = Object.assign({ mode: mode, revision: revision + 1 }, Object.keys(layouts).length ? { layouts: layouts } : {});
25
35
  if (current.settings) config.saveConfig(Object.assign({}, current.settings, { workbenchLayoutPreference: saved }));
26
36
  else deps.saveUsers(Object.assign({}, current.data, { users: current.data.users.map(function (entry) {
27
37
  return entry === current.user ? Object.assign({}, entry, { workbenchLayoutPreference: saved }) : entry;
@@ -0,0 +1,30 @@
1
+ // Display preferences only: these records never open sessions or run tools.
2
+ var tools = ['tool-terminal', 'tool-browser', 'tool-file-viewer'];
3
+ function validTree(tree, allowTools) {
4
+ var ids = [], nodes = 0;
5
+ function walk(node, depth) {
6
+ if (!node || typeof node !== 'object' || Array.isArray(node) || ++nodes > 21 || depth > 10) return false;
7
+ if (Object.prototype.hasOwnProperty.call(node, 'id')) {
8
+ if (Object.keys(node).length !== 1 || typeof node.id !== 'string' || !/^[A-Za-z0-9_-]{1,128}$/.test(node.id)) return false;
9
+ if (node.id.indexOf('tool-') === 0 && (!allowTools || tools.indexOf(node.id) < 0)) return false;
10
+ ids.push(node.id); return true;
11
+ }
12
+ return Object.keys(node).sort().join(',') === 'axis,left,ratio,right' && ['horizontal', 'vertical'].indexOf(node.axis) >= 0 &&
13
+ Number.isFinite(node.ratio) && node.ratio >= .05 && node.ratio <= .95 && walk(node.left, depth + 1) && walk(node.right, depth + 1);
14
+ }
15
+ return walk(tree, 0) && ids.length > 0 && ids.length <= (allowTools ? 11 : 8) && new Set(ids).size === ids.length;
16
+ }
17
+ function validLayout(input) {
18
+ return !!input && typeof input === 'object' && !Array.isArray(input) && Object.keys(input).sort().join(',') === 'base,key,tree' &&
19
+ typeof input.key === 'string' && input.key.length > 0 && input.key.length <= 512 && input.key !== '__proto__' &&
20
+ validTree(input.base, false) && validTree(input.tree, true) && sameSessions(input.base, input.tree);
21
+ }
22
+ function sameSessions(base, tree) {
23
+ function ids(node) { return node.id ? (tools.indexOf(node.id) >= 0 ? [] : [node.id]) : ids(node.left).concat(ids(node.right)); }
24
+ return JSON.stringify(ids(base).sort()) === JSON.stringify(ids(tree).sort());
25
+ }
26
+ function validLayouts(layouts) {
27
+ return !!layouts && typeof layouts === 'object' && !Array.isArray(layouts) && Object.keys(layouts).length <= 32 &&
28
+ Object.keys(layouts).every(function (key) { var item = layouts[key]; return item && validLayout({ key: key, base: item.base, tree: item.tree }); });
29
+ }
30
+ module.exports = { validLayout: validLayout, validLayouts: validLayouts };
@@ -0,0 +1,50 @@
1
+ // Bounded, exact-owner message previews use the Worker reader's existing authority.
2
+ function latest(history) {
3
+ var found = null;
4
+ for (var i = history.length - 1, stop = Math.max(0, history.length - 1000); i >= stop; i--) {
5
+ var entry = history[i];
6
+ if (entry && (entry.type === 'delta' || entry.type === 'assistant_message') && typeof entry.text === 'string' && entry.text.trim()) {
7
+ var index = i, text = entry.text;
8
+ while (index > stop && history[index - 1].type === 'delta' && entry.type === 'delta' ) { index--; text = (history[index].text + text).slice(0, 1200); }
9
+ found = { id: String(index), text: text.slice(0, 1200), timestamp: history[index]._ts || null }; break;
10
+ }
11
+ }
12
+ return found;
13
+ }
14
+ function attach(ctx) {
15
+ var subscriptions = new WeakMap();
16
+ function release(ws) {
17
+ var old = subscriptions.get(ws); if (!old) return;
18
+ subscriptions.delete(ws); old.entries.forEach(function (entry) { clearTimeout(entry.timer); if (entry.release) entry.release(); });
19
+ if (ws.removeListener) ws.removeListener('close', old.close);
20
+ }
21
+ function handleMessage(ws, msg) {
22
+ if (msg.type !== 'split_worker_preview_request') return false;
23
+ release(ws);
24
+ if (typeof msg.requestId !== 'string' || msg.requestId.length > 160 || !Array.isArray(msg.targets) || msg.targets.length > 32) return true;
25
+ var state = { entries: [], close: function () { release(ws); } }; subscriptions.set(ws, state);
26
+ msg.targets.forEach(function (target) {
27
+ var pair = ctx.resolve(ws, target); if (!pair || state.entries.some(function (entry) { return entry.worker === pair.worker; })) return;
28
+ var entry = { worker: pair.worker, timer: null, last: null };
29
+ function send(live) {
30
+ var current = ctx.resolve(ws, target);
31
+ if (subscriptions.get(ws) !== state || ws.readyState !== 1 || !current || current.driver !== pair.driver || current.worker !== pair.worker) { if (entry.release) entry.release(); return; }
32
+ var preview = latest(pair.worker.history || []); if (!preview) return;
33
+ preview.vendor = pair.worker.vendor || null;
34
+ if (entry.last === JSON.stringify(preview)) return;
35
+ entry.last = JSON.stringify(preview);
36
+ ctx.send(ws, { type: 'split_worker_message_preview', requestId: msg.requestId, parentSessionOriginId: target.parentSessionOriginId,
37
+ workerSessionOriginId: target.workerSessionOriginId, generation: target.generation, live: live, preview: preview });
38
+ }
39
+ entry.release = ctx.sm.subscribeSession(pair.worker.localId, function (event) {
40
+ if (!event || ['delta', 'assistant_message'].indexOf(event.type) < 0 || entry.timer) return;
41
+ entry.timer = setTimeout(function () { entry.timer = null; send(true); }, 60);
42
+ });
43
+ state.entries.push(entry); send(false);
44
+ });
45
+ if (ws.once) ws.once('close', state.close);
46
+ return true;
47
+ }
48
+ return { handleMessage: handleMessage };
49
+ }
50
+ module.exports = { attach: attach, latest: latest };
package/lib/ws-schema.js CHANGED
@@ -24,7 +24,7 @@ var schema = {
24
24
  "switch_session": { direction: "c2s", handler: "lib/project-sessions.js", description: "Switch the active session by local ID" },
25
25
  "resume_tui_session": { direction: "c2s", handler: "lib/project-sessions.js", description: "Spawn the claude --resume PTY for a TUI session shown read-only (lazy resume)" },
26
26
  "suspend_tui_session": { direction: "c2s", handler: "lib/project-sessions.js", description: "Close a live TUI session's PTY now but keep it resumable (explicit Close)" },
27
- "new_session": { direction: "c2s", handler: "lib/project-sessions.js", description: "Create a new blank session (opts: vendor, mode, sessionVisibility, dangerouslySkipPermissions)" },
27
+ "new_session": { direction: "c2s", handler: "lib/project-sessions.js", description: "Create a blank session; correlated ordinary-project requests may use activate:false to preserve the current View selection" },
28
28
  "new_session_result": { direction: "s2c", handler: "lib/public/modules/scheduled-tasks.js", description: "Correlated result for a requested new session" },
29
29
  "delete_session": { direction: "c2s", handler: "lib/project-sessions.js", description: "Delete a session by ID" },
30
30
  "rename_session": { direction: "c2s", handler: "lib/project-sessions.js", description: "Rename a session" },
@@ -97,6 +97,8 @@ function applyWorkerClaudeBinary(options) {
97
97
  // --- State ---
98
98
  var sdkModule = null;
99
99
  var queryInstance = null;
100
+ var taskReader = null;
101
+ var taskReaderModule = require("../claude-task-reader");
100
102
  var messageQueue = null;
101
103
  var abortController = null;
102
104
  var pendingPermissions = {}; // requestId -> resolve
@@ -284,6 +286,9 @@ function handleMessage(msg) {
284
286
  case "set_permission_mode":
285
287
  handleSetPermissionMode(msg);
286
288
  break;
289
+ case "read_background_task":
290
+ taskReaderModule.respond(taskReader, msg, sendToDaemon);
291
+ break;
287
292
  case "stop_task":
288
293
  handleStopTask(msg);
289
294
  break;
@@ -598,6 +603,8 @@ async function handleQueryStart(msg) {
598
603
  prompt: messageQueue,
599
604
  options: options,
600
605
  });
606
+ if (taskReader) taskReader.close();
607
+ taskReader = taskReaderModule.createReader(getSDK, function () { return queryInstance; }, options.cwd);
601
608
  perf("query instance created");
602
609
  } catch (e) {
603
610
  sendToDaemon({ type: "query_error", error: "Failed to create query: " + (e.message || e), exitCode: null, stderr: null });
@@ -618,6 +625,7 @@ async function handleQueryStart(msg) {
618
625
  var firstText = true;
619
626
  var eventCounts = {};
620
627
  for await (var event of queryInstance) {
628
+ taskReader.observe(event);
621
629
  var etype = (event && event.type || "?");
622
630
  var esubtype = (event && event.subtype || "");
623
631
  eventCounts[etype] = (eventCounts[etype] || 0) + 1;
@@ -18,6 +18,7 @@ var claudeUserInput = require("../claude-user-input");
18
18
  var claudeCorrelation = require("../claude-message-correlation");
19
19
  var claudeContextUsage = require("../claude-context-usage");
20
20
  var claudeModelSwitch = require("../claude-model-switch");
21
+ var taskReaderModule = require("../claude-task-reader");
21
22
 
22
23
  // SDK-specific knowledge stays in the adapter (not the relay): the host dialog
23
24
  // kinds this client can render, declared to the SDK when an onUserDialog
@@ -580,6 +581,8 @@ function normalizeBackgroundTasks(tasks) {
580
581
  task_id: task && task.task_id,
581
582
  task_type: taskType,
582
583
  description: (task && task.description) || "",
584
+ run_id: task && task.run_id || null,
585
+ parent_task_id: task && task.parent_task_id || null,
583
586
  // Housekeeping the CLI does not surface as user work (skip_transcript
584
587
  // tasks, auto-started live-update watchers). Clay keeps these out of its
585
588
  // activity indicators and lists them as a subdued secondary group.
@@ -597,7 +600,8 @@ function normalizeBackgroundTasks(tasks) {
597
600
  // --- QueryHandle ---
598
601
  // Wraps a raw SDK query object with the YOKE QueryHandle interface.
599
602
  // Events are flattened via flattenEvent before yielding.
600
- function createQueryHandle(rawQuery, messageQueue, abortController) {
603
+ function createQueryHandle(rawQuery, messageQueue, abortController, cwd) {
604
+ var taskReader = taskReaderModule.createReader(loadSDK, function () { return closed ? null : rawQuery; }, cwd);
601
605
  var correlation = claudeCorrelation.createCorrelation();
602
606
  var closed = false;
603
607
  var removeAbort = function () {};
@@ -615,6 +619,7 @@ function createQueryHandle(rawQuery, messageQueue, abortController) {
615
619
  return rawIter.next().then(function(result) {
616
620
  if (result.done) { closed = true; removeAbort(); resolveStopped(); }
617
621
  if (result.done) return result;
622
+ taskReader.observe(result.value);
618
623
  return { value: correlation.result(result.value, flattenEvent(result.value)), done: false };
619
624
  }, function(err) {
620
625
  closed = true;
@@ -676,6 +681,8 @@ function createQueryHandle(rawQuery, messageQueue, abortController) {
676
681
  return Promise.resolve();
677
682
  },
678
683
 
684
+ readBackgroundTask: taskReader.read,
685
+
679
686
  stopTask: function(taskId) {
680
687
  if (rawQuery && typeof rawQuery.stopTask === "function") {
681
688
  return rawQuery.stopTask(taskId);
@@ -1107,6 +1114,7 @@ function serializeWorkerValue(value, seen) {
1107
1114
  function createWorkerQueryHandle(worker, canUseTool, onElicitation, callMcpTool, onUserDialog, onUserInputRequest, onModelSwitch, preToolUsePolicy) {
1108
1115
  var interactionAbort = new AbortController();
1109
1116
  var contextRequests = claudeContextUsage.createRequests(worker);
1117
+ var taskRequests = taskReaderModule.createRequests(worker);
1110
1118
  var correlation = claudeCorrelation.createCorrelation();
1111
1119
  // Async iterable state
1112
1120
  var iterQueue = [];
@@ -1153,6 +1161,7 @@ function createWorkerQueryHandle(worker, canUseTool, onElicitation, callMcpTool,
1153
1161
  function endIter() {
1154
1162
  interactionAbort.abort();
1155
1163
  contextRequests.close();
1164
+ taskRequests.close();
1156
1165
  if (iterEnded) return;
1157
1166
  iterEnded = true;
1158
1167
  if (iterWaiting) {
@@ -1165,6 +1174,7 @@ function createWorkerQueryHandle(worker, canUseTool, onElicitation, callMcpTool,
1165
1174
  function errorIter(err) {
1166
1175
  interactionAbort.abort();
1167
1176
  contextRequests.close();
1177
+ taskRequests.close();
1168
1178
  if (iterEnded) return;
1169
1179
  iterEnded = true;
1170
1180
  iterError = err;
@@ -1292,6 +1302,9 @@ function createWorkerQueryHandle(worker, canUseTool, onElicitation, callMcpTool,
1292
1302
  case "model_switched":
1293
1303
  if (onModelSwitch) onModelSwitch(msg.change);
1294
1304
  break;
1305
+ case "background_task_response":
1306
+ taskRequests.receive(msg);
1307
+ break;
1295
1308
  case "context_usage_response":
1296
1309
  contextRequests.receive(msg);
1297
1310
  break;
@@ -1458,6 +1471,8 @@ function createWorkerQueryHandle(worker, canUseTool, onElicitation, callMcpTool,
1458
1471
  });
1459
1472
  },
1460
1473
 
1474
+ readBackgroundTask: function(input) { return iterEnded ? Promise.reject(new Error("Task connection closed.")) : taskRequests.request(input); },
1475
+
1461
1476
  stopTask: function(taskId) {
1462
1477
  worker.send({ type: "stop_task", taskId: taskId });
1463
1478
  return Promise.resolve();
@@ -1821,7 +1836,7 @@ function createClaudeAdapter(opts) {
1821
1836
 
1822
1837
  claudeModelSwitch.attach(sdkOptions, queryOpts.onModelSwitch);
1823
1838
  var rawQuery = sdk.query({ prompt: mq, options: sdkOptions });
1824
- return createQueryHandle(rawQuery, mq, ac);
1839
+ return createQueryHandle(rawQuery, mq, ac, sdkOptions.cwd || _cwd);
1825
1840
  },
1826
1841
 
1827
1842
  createOneShotQuery: async function(queryOpts) {