clay-server 5.2.0-beta.5 → 5.2.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 (246) hide show
  1. package/lib/claude-hook-installer.js +1 -267
  2. package/lib/claude-jsonl-watcher.js +1 -204
  3. package/lib/clay-control-presentation-binding.js +96 -0
  4. package/lib/clay-control-presentation.js +128 -0
  5. package/lib/clay-control-target-projection.js +26 -0
  6. package/lib/clay-mate-chat.js +1 -0
  7. package/lib/clay-mate-knowledge.js +1 -1
  8. package/lib/clay-product-control-tools.js +1 -1
  9. package/lib/clay-product-control.js +22 -12
  10. package/lib/clay-project-records.js +2 -1
  11. package/lib/clay-scheduling.js +1 -1
  12. package/lib/clay-screen-controls.js +13 -5
  13. package/lib/clay-skill-install.js +1 -33
  14. package/lib/clay-workspace-controls.js +2 -1
  15. package/lib/cli-sessions.js +1 -300
  16. package/lib/codex-defaults.js +1 -28
  17. package/lib/context-usage.js +3 -2
  18. package/lib/contextual-screen-snapshot.js +38 -0
  19. package/lib/daemon.js +5 -55
  20. package/lib/default-ai-runtime.js +4 -8
  21. package/lib/email-mcp-server.js +1 -1
  22. package/lib/kiro-defaults.js +1 -22
  23. package/lib/mate-context-service.js +138 -0
  24. package/lib/mate-context-tools.js +21 -0
  25. package/lib/mcp-proxy.js +2 -32
  26. package/lib/mcp-query-bridge.js +1 -1
  27. package/lib/mcp-source-adapters.js +1 -91
  28. package/lib/mcp-source-context.js +1 -42
  29. package/lib/mcp-source-discovery.js +1 -169
  30. package/lib/mcp-source-plugins.js +1 -68
  31. package/lib/mcp-source-restrictions.js +1 -47
  32. package/lib/mcp-tool-policy.js +1 -26
  33. package/lib/os-user-diagnostics.js +2 -4
  34. package/lib/os-users.js +4 -65
  35. package/lib/project-connection.js +14 -10
  36. package/lib/project-control-git-view.js +12 -0
  37. package/lib/project-filesystem.js +5 -1
  38. package/lib/project-http.js +7 -4
  39. package/lib/project-mcp.js +2 -2
  40. package/lib/project-notifications.js +5 -4
  41. package/lib/project-pair-result-delivery.js +2 -2
  42. package/lib/project-pair-usage.js +12 -13
  43. package/lib/project-pending-message-queue.js +3 -1
  44. package/lib/project-product-control.js +3 -1
  45. package/lib/project-scheduled-messages.js +4 -4
  46. package/lib/project-scheduled-task-control.js +1 -1
  47. package/lib/project-session-document.js +20 -5
  48. package/lib/project-session-history-actions.js +5 -3
  49. package/lib/project-session-pair.js +1 -1
  50. package/lib/project-session-runtime-actions.js +2 -1
  51. package/lib/project-session-spawn.js +2 -3
  52. package/lib/project-session-vendor.js +2 -1
  53. package/lib/project-sessions.js +18 -26
  54. package/lib/project-shared-browser.js +22 -5
  55. package/lib/project-studio-presentation.js +58 -0
  56. package/lib/project-user-message.js +37 -0
  57. package/lib/project-vendor-login.js +6 -36
  58. package/lib/project-workspace-query.js +11 -1
  59. package/lib/project.js +24 -7
  60. package/lib/public/app.js +25 -0
  61. package/lib/public/appearance-bootstrap.js +2 -0
  62. package/lib/public/css/contextual-clay.css +240 -0
  63. package/lib/public/css/messages.css +6 -2
  64. package/lib/public/modules/app-favicon.js +18 -9
  65. package/lib/public/modules/app-header.js +4 -2
  66. package/lib/public/modules/app-loop-ui.js +2 -0
  67. package/lib/public/modules/app-message-router.js +5 -1
  68. package/lib/public/modules/app-messages.js +50 -6
  69. package/lib/public/modules/app-notifications.js +11 -5
  70. package/lib/public/modules/app-panels.js +61 -123
  71. package/lib/public/modules/app-projects.js +3 -0
  72. package/lib/public/modules/app-rate-limit.js +7 -27
  73. package/lib/public/modules/chat-render-runtime.js +3 -0
  74. package/lib/public/modules/clay-control-activity.js +214 -0
  75. package/lib/public/modules/clay-control-copy.js +36 -0
  76. package/lib/public/modules/clay-control-detail-targets.js +44 -0
  77. package/lib/public/modules/clay-control-object-targets.js +52 -0
  78. package/lib/public/modules/clay-control-target-visibility.js +12 -0
  79. package/lib/public/modules/clay-control-targets.js +44 -0
  80. package/lib/public/modules/clay-navigation-render.js +52 -0
  81. package/lib/public/modules/clay-owner-navigation.js +11 -2
  82. package/lib/public/modules/clay-session-links.js +31 -0
  83. package/lib/public/modules/clay-studio-bridge.js +20 -0
  84. package/lib/public/modules/clay-studio-document.js +67 -0
  85. package/lib/public/modules/clay-studio-render.js +144 -0
  86. package/lib/public/modules/clay-tool-presentation.js +4 -0
  87. package/lib/public/modules/command-palette.js +26 -31
  88. package/lib/public/modules/contextual-ask-session.js +26 -0
  89. package/lib/public/modules/contextual-clay-geometry.js +155 -0
  90. package/lib/public/modules/contextual-clay-session.js +130 -0
  91. package/lib/public/modules/contextual-clay-workspace.js +157 -0
  92. package/lib/public/modules/contextual-clay.js +237 -0
  93. package/lib/public/modules/contextual-composer.js +91 -0
  94. package/lib/public/modules/contextual-mate-context.js +115 -0
  95. package/lib/public/modules/contextual-mate-hint.js +40 -0
  96. package/lib/public/modules/contextual-mate-identity.js +23 -0
  97. package/lib/public/modules/contextual-mate-sessions.js +134 -0
  98. package/lib/public/modules/contextual-search.js +137 -0
  99. package/lib/public/modules/contextual-session-citations.js +58 -0
  100. package/lib/public/modules/filebrowser.js +20 -1
  101. package/lib/public/modules/generation-stop.js +8 -5
  102. package/lib/public/modules/git-panel.js +3 -1
  103. package/lib/public/modules/icon-strip-tooltip.js +2 -1
  104. package/lib/public/modules/input.js +35 -1
  105. package/lib/public/modules/issues-render.js +3 -2
  106. package/lib/public/modules/knowledge-db-workbench.js +4 -4
  107. package/lib/public/modules/markdown.js +2 -34
  108. package/lib/public/modules/mate-knowledge-workbench.js +1 -1
  109. package/lib/public/modules/mcp-connection-view.js +1 -1
  110. package/lib/public/modules/message-delivery.js +10 -0
  111. package/lib/public/modules/pane-bridge.js +2 -2
  112. package/lib/public/modules/pending-message-queue.js +1 -0
  113. package/lib/public/modules/permission-control.js +5 -4
  114. package/lib/public/modules/product-review.js +26 -0
  115. package/lib/public/modules/project-chat-identity.js +13 -4
  116. package/lib/public/modules/project-logs-render.js +3 -0
  117. package/lib/public/modules/project-mate-navigation.js +58 -21
  118. package/lib/public/modules/scheduled-tasks.js +2 -0
  119. package/lib/public/modules/scheduler.js +2 -0
  120. package/lib/public/modules/search-clay-chat.js +5 -454
  121. package/lib/public/modules/session-create-form.js +10 -10
  122. package/lib/public/modules/settings-defaults.js +27 -28
  123. package/lib/public/modules/shared-browser.js +2 -0
  124. package/lib/public/modules/sidebar-mobile.js +4 -3
  125. package/lib/public/modules/sidebar-projects.js +1 -0
  126. package/lib/public/modules/sidebar-sessions.js +1 -0
  127. package/lib/public/modules/split-view.js +7 -16
  128. package/lib/public/modules/terminal.js +9 -0
  129. package/lib/public/modules/tools.js +6 -2
  130. package/lib/public/modules/tui-grab.js +1 -2
  131. package/lib/public/modules/vendor-login-browser.js +5 -3
  132. package/lib/public/modules/vendor-login.js +2 -2
  133. package/lib/public/modules/vendor-priority.js +15 -1
  134. package/lib/public/modules/wireframe-workbench.js +5 -2
  135. package/lib/public/muse-avatar.svg +4 -0
  136. package/lib/public/style.css +2 -0
  137. package/lib/safe-bash-commands.js +3 -45
  138. package/lib/sdk-bridge.js +131 -395
  139. package/lib/sdk-foreground-admission.js +16 -0
  140. package/lib/sdk-mcp-descriptors.js +2 -37
  141. package/lib/sdk-message-processor.js +50 -17
  142. package/lib/sdk-quota-snapshot.js +1 -1
  143. package/lib/sdk-response-recovery.js +1 -155
  144. package/lib/sdk-worker.js +2 -677
  145. package/lib/server-contextual-clay-settings.js +40 -0
  146. package/lib/server-contextual-mate-sessions.js +70 -0
  147. package/lib/server-home-chat.js +7 -1
  148. package/lib/server-home-clay-entry.js +23 -4
  149. package/lib/server-home-clay-session-links.js +12 -0
  150. package/lib/server-owner-ui.js +49 -8
  151. package/lib/server-settings.js +2 -0
  152. package/lib/server.js +18 -1
  153. package/lib/session-fork-history.js +21 -0
  154. package/lib/session-hygiene.js +3 -35
  155. package/lib/session-permission-mode.js +4 -3
  156. package/lib/session-skill-scope.js +11 -0
  157. package/lib/sessions.js +24 -89
  158. package/lib/terminal-manager.js +18 -11
  159. package/lib/tool-llm.js +1 -1
  160. package/lib/tui-transcript-index.js +1 -132
  161. package/lib/users-contextual-clay-preferences.js +31 -0
  162. package/lib/users.js +3 -0
  163. package/lib/vendor-login-browser.js +1 -11
  164. package/lib/workspace-assignment-service.js +25 -0
  165. package/lib/workspace-query-mcp-server.js +1 -1
  166. package/lib/ws-request.js +1 -0
  167. package/lib/ws-schema.js +10 -0
  168. package/lib/yoke/acp-agent-profiles.js +5 -3
  169. package/lib/yoke/adapters/antigravity.js +3 -14
  170. package/lib/yoke/adapters/claude-query-contract.js +9 -0
  171. package/lib/yoke/adapters/claude.js +7 -42
  172. package/lib/yoke/adapters/codex-query-contract.js +24 -0
  173. package/lib/yoke/adapters/codex.js +1 -0
  174. package/lib/yoke/adapters/kiro.js +3 -1
  175. package/lib/yoke/adapters/muse-query-contract.js +7 -0
  176. package/lib/yoke/adapters/muse.js +122 -0
  177. package/lib/yoke/auth-events.js +17 -0
  178. package/lib/yoke/claude-hook-installer.js +271 -0
  179. package/lib/yoke/claude-jsonl-watcher.js +204 -0
  180. package/lib/yoke/claude-maintenance.js +55 -0
  181. package/lib/yoke/claude-permission-patterns.js +53 -0
  182. package/lib/yoke/claude-response-recovery.js +155 -0
  183. package/lib/yoke/claude-transcript-hygiene.js +34 -0
  184. package/lib/yoke/clay-skill-install.js +33 -0
  185. package/lib/yoke/cli-sessions.js +300 -0
  186. package/lib/yoke/codex-app-server.js +7 -12
  187. package/lib/yoke/configuration.js +13 -0
  188. package/lib/yoke/discovery.js +70 -0
  189. package/lib/yoke/errors.js +8 -0
  190. package/lib/yoke/host-environment.js +7 -0
  191. package/lib/yoke/index.js +9 -51
  192. package/lib/yoke/interface.js +116 -0
  193. package/lib/yoke/kiro-acp-server.js +5 -5
  194. package/lib/yoke/legacy-sessions.js +89 -0
  195. package/lib/yoke/login-protocol.js +25 -0
  196. package/lib/yoke/maintenance.js +12 -0
  197. package/lib/yoke/mcp-proxy.js +32 -0
  198. package/lib/yoke/mcp-source-adapters.js +91 -0
  199. package/lib/yoke/mcp-source-context.js +42 -0
  200. package/lib/yoke/mcp-source-discovery.js +169 -0
  201. package/lib/yoke/mcp-source-plugins.js +68 -0
  202. package/lib/yoke/mcp-source-restrictions.js +47 -0
  203. package/lib/yoke/mcp-tool-policy-normalize.js +28 -0
  204. package/lib/yoke/model-preferences.js +6 -0
  205. package/lib/yoke/muse-artifacts.js +36 -0
  206. package/lib/yoke/muse-controls.js +50 -0
  207. package/lib/yoke/muse-input.js +64 -0
  208. package/lib/yoke/muse-items.js +157 -0
  209. package/lib/yoke/muse-policy.js +52 -0
  210. package/lib/yoke/muse-query.js +428 -0
  211. package/lib/yoke/muse-runtime.js +211 -0
  212. package/lib/yoke/muse-services.js +79 -0
  213. package/lib/yoke/muse-skills.js +79 -0
  214. package/lib/yoke/muse-turns.js +71 -0
  215. package/lib/yoke/options/acp.js +2 -0
  216. package/lib/yoke/options/antigravity.js +2 -0
  217. package/lib/yoke/options/claude.js +21 -0
  218. package/lib/yoke/options/codex-defaults.js +29 -0
  219. package/lib/yoke/options/codex-settings.js +21 -0
  220. package/lib/yoke/options/codex.js +6 -0
  221. package/lib/yoke/options/kiro-defaults.js +22 -0
  222. package/lib/yoke/options/kiro.js +5 -0
  223. package/lib/yoke/options/muse.js +5 -0
  224. package/lib/yoke/provider-contract.js +5 -0
  225. package/lib/yoke/providers/antigravity-executable.js +8 -0
  226. package/lib/yoke/providers/claude-executable.js +40 -0
  227. package/lib/yoke/providers/claude-model-descriptions.js +14 -0
  228. package/lib/yoke/providers/claude-process.js +97 -0
  229. package/lib/yoke/providers/claude-provision.js +75 -0
  230. package/lib/yoke/providers/codex-errors.js +8 -0
  231. package/lib/yoke/providers/muse-executable.js +17 -0
  232. package/lib/yoke/provisioning.js +6 -0
  233. package/lib/yoke/query-options.js +10 -0
  234. package/lib/yoke/response-recovery.js +41 -0
  235. package/lib/yoke/sdk-mcp-descriptors.js +37 -0
  236. package/lib/yoke/sdk-server-factory.js +7 -0
  237. package/lib/yoke/session-history.js +8 -0
  238. package/lib/yoke/session-tool-transport.js +31 -13
  239. package/lib/yoke/settings.js +23 -0
  240. package/lib/yoke/source-locations.js +4 -0
  241. package/lib/yoke/title-query.js +18 -7
  242. package/lib/yoke/tui-transcript-index.js +132 -0
  243. package/lib/yoke/usage.js +48 -0
  244. package/lib/yoke/user-input.js +13 -0
  245. package/lib/yoke/vendor-registry.js +81 -1
  246. package/package.json +3 -2
@@ -1,47 +1 @@
1
- var path = require('path');
2
- var SECURITY = /permission|policy|security|sandbox|registry|disabledMcp|restrict|approval|allowed|denied|managedMcp|strictPlugin|includeTools|excludeTools/i;
3
- function inspect(ctx) {
4
- var blocked = {}, files = [];
5
- function add(vendor, file, exclusive) { files.push({ vendor: vendor, file: file, exclusive: exclusive }); }
6
- var claudeManaged = process.platform === 'darwin' ? '/Library/Application Support/ClaudeCode' : process.platform === 'win32' ? path.join(process.env.ProgramFiles || 'C:\\Program Files', 'ClaudeCode') : '/etc/claude-code';
7
- ['managed-mcp.json', 'managed-settings.json'].forEach(function (file) { add('claude', path.join(claudeManaged, file), true); });
8
- add('claude', path.join(claudeManaged, 'managed-settings.d'), true);
9
- if (process.platform === 'darwin') {
10
- add('claude', '/Library/Managed Preferences/com.anthropic.claudecode.plist', true);
11
- if (ctx.username) add('claude', path.join('/Library/Managed Preferences', ctx.username, 'com.anthropic.claudecode.plist'), true);
12
- }
13
- [path.join(ctx.roots.claude, 'settings.json'), path.join(ctx.cwd, '.claude/settings.json'), path.join(ctx.cwd, '.claude/settings.local.json')].forEach(function (file) { add('claude', file); });
14
- [path.join(ctx.roots.codex, 'requirements.toml'), '/etc/codex/requirements.toml', '/etc/codex/managed_config.toml', path.join(ctx.roots.codex, 'managed_config.toml')].forEach(function (file) { add('codex', file, true); });
15
- var openCodeManaged = process.platform === 'darwin' ? '/Library/Application Support/opencode' : process.platform === 'win32' ? path.join(process.env.ProgramData || 'C:\\ProgramData', 'opencode') : '/etc/opencode';
16
- ['opencode.json', 'opencode.jsonc'].forEach(function (file) { add('opencode', path.join(openCodeManaged, file), true); });
17
- if (process.platform === 'darwin') {
18
- add('opencode', '/Library/Managed Preferences/ai.opencode.managed.plist', true);
19
- if (ctx.username) add('opencode', path.join('/Library/Managed Preferences', ctx.username, 'ai.opencode.managed.plist'), true);
20
- }
21
- ['settings.json', 'permissions-config.json'].forEach(function (file) { add('copilot', path.join(ctx.roots.copilot, file)); });
22
- ['settings.json', 'settings.local.json'].forEach(function (file) { add('copilot', path.join(ctx.cwd, '.github/copilot', file)); });
23
- var copilotManaged = process.platform === 'darwin' ? '/Library/Application Support/GitHubCopilot/managed-settings.json' : process.platform === 'win32' ? path.join(process.env.ProgramFiles || 'C:\\Program Files', 'GitHubCopilot/managed-settings.json') : '/etc/github-copilot/managed-settings.json';
24
- add('copilot', copilotManaged, true);
25
- if (process.platform === 'darwin') {
26
- add('copilot', '/Library/Managed Preferences/com.github.copilot.plist', true);
27
- if (ctx.username) add('copilot', path.join('/Library/Managed Preferences', ctx.username, 'com.github.copilot.plist'), true);
28
- }
29
- add('kimi', path.join(ctx.roots.kimi, 'config.toml'));
30
- var qwenSystem = process.platform === 'darwin' ? '/Library/Application Support/QwenCode' : process.platform === 'win32' ? 'C:\\ProgramData\\qwen-code' : '/etc/qwen-code';
31
- add('qwen', ctx.policyPaths.qwenSettings || path.join(qwenSystem, 'settings.json'), true);
32
- add('qwen', ctx.policyPaths.qwenDefaults || path.join(qwenSystem, 'system-defaults.json'), true);
33
- add('grok', '/etc/grok/requirements.toml', true);
34
- add('grok', '/etc/grok/managed_config.toml', true);
35
- add('grok', path.join(ctx.roots.grok, 'requirements.toml'), true);
36
- add('grok', path.join(ctx.roots.grok, 'managed_config.toml'), true);
37
- files.forEach(function (entry) {
38
- try {
39
- var value = ctx.load(entry.file);
40
- if (entry.exclusive || path.basename(entry.file) === 'permissions-config.json' && Object.keys(value).length || Object.keys(value).some(function (key) { return SECURITY.test(key); })) blocked[entry.vendor] = 'Local vendor security or managed policy is present. Clay cannot resolve its effective restrictions. Review that policy and configure an explicitly authorized Clay connection; vendor import is blocked.';
41
- } catch (error) {
42
- if (error.code !== 'ENOENT' && !/ENOENT|no such file/i.test(error.message)) blocked[entry.vendor] = 'Could not resolve a local vendor policy source. Check permissions and format before importing.';
43
- }
44
- });
45
- return blocked;
46
- }
47
- module.exports = { inspect: inspect, securityKey: function (key) { return SECURITY.test(key); } };
1
+ module.exports = require("./yoke/mcp-source-restrictions");
@@ -1,29 +1,4 @@
1
- // Restrictive policies are intersected; an empty allowlist means no tools.
2
- var ALLOW = ['enabled_tools', 'enabledTools', 'includeTools'];
3
- var DENY = ['disabled_tools', 'disabledTools', 'excludeTools'];
4
- function list(value, key) {
5
- if (!Array.isArray(value) || value.some(function (item) { return typeof item !== 'string' || !item || item.indexOf('\0') >= 0; })) throw new Error('Invalid tool policy: ' + key + '. Review the vendor configuration before importing.');
6
- return Array.from(new Set(value));
7
- }
8
- function normalize(vendor, value) {
9
- var allow = null, deny = [], keys = ALLOW.slice();
10
- if (vendor === 'copilot') keys.push('tools');
11
- keys.forEach(function (key) {
12
- if (!Object.prototype.hasOwnProperty.call(value, key)) return;
13
- var next = list(value[key], key);
14
- if (vendor === 'copilot' && key === 'tools' && next.indexOf('*') >= 0) next = null;
15
- if (next !== null) allow = allow === null ? next : allow.filter(function (name) { return next.indexOf(name) >= 0; });
16
- });
17
- DENY.forEach(function (key) { if (Object.prototype.hasOwnProperty.call(value, key)) deny = deny.concat(list(value[key], key)); });
18
- var unsupported = Object.keys(value).filter(function (key) {
19
- if (ALLOW.concat(DENY, vendor === 'copilot' ? ['tools'] : [], ['autoApprove', 'trust', 'oauth', 'oauthScopes']).indexOf(key) >= 0) return false;
20
- return /tool|permission|policy|security|authProvider|allowed|denied|restrict|approval/i.test(key) && ['tool_timeout_sec'].indexOf(key) < 0;
21
- });
22
- if (unsupported.length) throw new Error('Unsupported restriction or security configuration: ' + unsupported.join(', ') + '. Configure and review a direct Clay connection instead.');
23
- if (value.autoApprove !== undefined && (!Array.isArray(value.autoApprove) || value.autoApprove.some(function (item) { return typeof item !== 'string'; }))) throw new Error('Invalid autoApprove policy.');
24
- if (value.trust !== undefined && typeof value.trust !== 'boolean') throw new Error('Invalid trust policy.');
25
- return { enabledTools: allow, disabledTools: Array.from(new Set(deny)) };
26
- }
1
+ var normalize = require("./yoke/mcp-tool-policy-normalize").normalize;
27
2
  function matches(pattern, name) {
28
3
  return new RegExp('^' + pattern.split('').map(function (letter) { return letter === '*' ? '.*' : letter === '?' ? '.' : letter.replace(/[\\^$+.[\]{}()|]/g, '\\$&'); }).join('') + '$').test(name);
29
4
  }
@@ -17,12 +17,10 @@ function commandAvailable(command, execFile) {
17
17
 
18
18
  function credentialPaths(home) {
19
19
  if (!home) return [];
20
- return [
21
- home + "/.claude",
22
- home + "/.codex",
20
+ return require("./yoke/maintenance").credentialPaths(home).concat([
23
21
  home + "/.config/gh",
24
22
  home + "/.config/gcloud",
25
- ];
23
+ ]);
26
24
  }
27
25
 
28
26
  function accessStatus(checks) {
package/lib/os-users.js CHANGED
@@ -475,72 +475,11 @@ function getLinuxUserUid(username) {
475
475
  * hardcoded ~/.local/bin/claude path.
476
476
  * Returns true if claude resolves, false otherwise.
477
477
  */
478
- function claudeAvailableForUser(linuxName) {
479
- if (!isSafeLinuxUsername(linuxName)) return false;
480
- try {
481
- var out = execFileSync("su", ["-", linuxName, "-c", "command -v claude"], {
482
- encoding: "utf8",
483
- timeout: 10000,
484
- stdio: "pipe",
485
- }).trim();
486
- return out.length > 0;
487
- } catch (e) {
488
- return false;
489
- }
490
- }
491
-
492
- /**
493
- * Install Claude CLI for a Linux user account.
494
- * Downloads and runs the install script, then ensures PATH is configured.
495
- * Non-fatal: logs errors but does not throw.
496
- * Skips entirely when claude is already resolvable for the user, or when
497
- * CLAY_SKIP_CLAUDE_INSTALL is set (bring-your-own-claude).
498
- */
499
- function installClaudeCli(linuxName) {
500
- if (process.env.CLAY_SKIP_CLAUDE_INSTALL) {
501
- console.log("[os-users] CLAY_SKIP_CLAUDE_INSTALL set, skipping Claude CLI install for " + linuxName);
502
- return;
503
- }
504
- if (claudeAvailableForUser(linuxName)) {
505
- console.log("[os-users] Claude CLI already available for " + linuxName + ", skipping install");
506
- return;
507
- }
508
- try {
509
- if (!isSafeLinuxUsername(linuxName)) throw new Error("Invalid Linux username");
510
- // Download and run the Claude CLI install script as the target user
511
- execFileSync("su", ["-", linuxName, "-c", "curl -fsSL https://claude.ai/install.sh | bash"], {
512
- encoding: "utf8",
513
- timeout: 60000,
514
- stdio: "pipe",
515
- });
516
- console.log("[os-users] Claude CLI installed for " + linuxName);
517
- } catch (e) {
518
- var msg = (e.stderr || e.message || "").trim();
519
- console.error("[os-users] Failed to install Claude CLI for " + linuxName + ": " + msg);
520
- return;
521
- }
522
-
523
- // Append PATH export to the user's shell config if not already present
524
- try {
525
- var userInfo = resolveOsUserInfo(linuxName);
526
- var home = userInfo.home || ("/home/" + linuxName);
527
- var rcPath = fs.existsSync(path.join(home, ".zshrc")) ? path.join(home, ".zshrc") : path.join(home, ".bashrc");
528
- var exportLine = 'export PATH="$HOME/.local/bin:$PATH"';
529
- var existing = "";
530
- try { existing = fs.readFileSync(rcPath, "utf8"); } catch (e2) {}
531
- if (existing.indexOf(exportLine) === -1) {
532
- var prefix = existing && !/\n$/.test(existing) ? "\n" : "";
533
- fs.appendFileSync(rcPath, prefix + exportLine + "\n", "utf8");
534
- try { fs.chownSync(rcPath, userInfo.uid, userInfo.gid); } catch (e3) {}
535
- console.log("[os-users] PATH export appended to " + rcPath + " for " + linuxName);
536
- } else {
537
- console.log("[os-users] PATH already configured in " + rcPath + " for " + linuxName);
538
- }
539
- } catch (e) {
540
- var rcMsg = (e.stderr || e.message || "").trim();
541
- console.error("[os-users] Failed to configure PATH for " + linuxName + ": " + rcMsg);
542
- }
478
+ function defaultRuntimeProvisioning() {
479
+ return require("./yoke/provisioning").forProvider("claude", { isSafeLinuxUsername: isSafeLinuxUsername, resolveOsUserInfo: resolveOsUserInfo });
543
480
  }
481
+ function claudeAvailableForUser(user) { return defaultRuntimeProvisioning().available(user); }
482
+ function installClaudeCli(user) { return defaultRuntimeProvisioning().install(user); }
544
483
 
545
484
  /**
546
485
  * Provision a Linux user account for a Clay user.
@@ -3,7 +3,7 @@ var path = require("path");
3
3
  var usersModule = require("./users");
4
4
  var userPresence = require("./user-presence");
5
5
  var emailAccounts = require("./email-accounts");
6
- var { getCodexConfig } = require("./codex-defaults");
6
+ var providerSettings = require("./yoke/settings");
7
7
  var yoke = require("./yoke");
8
8
  var permissionModes = require("./session-permission-mode");
9
9
  var sessionProvenance = require("./session-provenance");
@@ -143,6 +143,9 @@ function attachConnection(ctx) {
143
143
  var _warmedUp = false;
144
144
 
145
145
  function findRestoredActiveSession(ws, wsUser, allSessions) {
146
+ if (ws._clayContextual && ws._clayPaneSession) {
147
+ return { active: allSessions.find(function (session) { return session.localId === ws._clayPaneSession; }) || null, storedPresence: null };
148
+ }
146
149
  var active = null;
147
150
  var presenceKey = wsUser ? wsUser.id : "_default";
148
151
  var storedPresence = userPresence.getPresence(slug, presenceKey);
@@ -202,7 +205,7 @@ function attachConnection(ctx) {
202
205
  } else if (!usersModule.isMultiUser()) {
203
206
  allSessions = allSessions.filter(function (s) { return !s.ownerId; });
204
207
  }
205
- var restoredState = ws._clayPane
208
+ var restoredState = ws._clayPane && !ws._clayContextual
206
209
  ? { active: null, storedPresence: null }
207
210
  : findRestoredActiveSession(ws, wsUser, allSessions);
208
211
  var restoredActive = restoredState.active;
@@ -217,7 +220,7 @@ function attachConnection(ctx) {
217
220
  sm.saveSessionFile(restoredActive);
218
221
  }
219
222
  var initialModels = (sm.modelsByVendor && sm.modelsByVendor[initialVendor]) || sm.availableModels || [];
220
- sendTo(ws, { type: "info", cwd: cwd, slug: slug, project: title || project, version: currentVersion, debug: !!debug, dangerouslySkipPermissions: dangerouslySkipPermissions, osUsers: osUsers, lanHost: lanHost, projectCount: _filteredProjects.length, projects: _filteredProjects, projectOwnerId: projectOwnerId, ownerLocked: ownerLocked, vendors: yoke.VENDOR_REGISTRY, isMate: isMate, mateId: isMate ? mateId : null, mateDisplayName: isMate ? (mateDisplayName || title || project || "Mate") : "" });
223
+ sendTo(ws, { type: "info", controlRuntimeId: require("./clay-control-target-projection").runtime(sm), cwd: cwd, slug: slug, project: title || project, version: currentVersion, debug: !!debug, dangerouslySkipPermissions: dangerouslySkipPermissions, osUsers: osUsers, lanHost: lanHost, projectCount: _filteredProjects.length, projects: _filteredProjects, projectOwnerId: projectOwnerId, ownerLocked: ownerLocked, vendors: require("./yoke/vendor-registry").publicInfo(), isMate: isMate, mateId: isMate ? mateId : null, mateDisplayName: isMate ? (mateDisplayName || title || project || "Mate") : "" });
221
224
  if (typeof sendCursorSharingState === "function") sendCursorSharingState(ws);
222
225
  // Update notifications are pushed on a scheduled interval (see
223
226
  // scheduleUpdateBroadcast). We no longer push on connect to avoid
@@ -234,8 +237,8 @@ function attachConnection(ctx) {
234
237
  var initialPermissionState = permissionModes.clientState(restoredActive, sm.currentPermissionMode, sm.defaultVendor);
235
238
  sendTo(ws, { type: "config_state", model: initialModelInfo.model, vendor: initialVendor, sessionId: restoredActive ? restoredActive.localId : null, mode: initialPermissionState.requestedPermissionMode, effectivePermissionMode: initialPermissionState.effectivePermissionMode, permissionCapabilities: initialPermissionState.permissionCapabilities, mcpPermissionModeOverrides: initialPermissionState.mcpPermissionModeOverrides, effort: initialEffort || "medium", betas: sm.currentBetas || [], thinking: sm.currentThinking || "adaptive", thinkingBudget: sm.currentThinkingBudget || 10000 });
236
239
  sendTo(ws, { type: "last_vendor", vendor: sm.lastVendor || "" });
237
- sendTo(ws, Object.assign({ type: "codex_config" }, getCodexConfig(sm)));
238
- sendTo(ws, { type: "term_list", terminals: tm.list() });
240
+ providerSettings.messages(sm).forEach(function(message) { sendTo(ws, message); });
241
+ sendTo(ws, { type: "term_list", terminals: tm.list(true) });
239
242
  // Any login flow already running in this project, so a reconnecting client
240
243
  // re-attaches to the existing login terminal instead of starting another.
241
244
  if (_vendorLogin) _vendorLogin.sendStateTo(ws);
@@ -267,7 +270,7 @@ function attachConnection(ctx) {
267
270
  var presenceKey = wsUser ? wsUser.id : "_default";
268
271
  var storedPresence = restoredState.storedPresence;
269
272
  var autoCreated = false;
270
- if (!active && !ws._clayPane) {
273
+ if (!active && (!ws._clayPane || (ws._clayContextual && !ws._clayPaneSession))) {
271
274
  var autoOpts = {};
272
275
  if (wsUser && usersModule.isMultiUser()) autoOpts.ownerId = wsUser.id;
273
276
  active = sm.createSession(autoOpts, ws);
@@ -301,12 +304,13 @@ function attachConnection(ctx) {
301
304
  }
302
305
  sendTo(ws, { type: "history_meta", total: total, from: fromIndex });
303
306
  for (var i = fromIndex; i < total; i++) {
304
- sendTo(ws, Object.assign({}, hydrateImageRefs(active.history[i]), { _historyIndex: i }));
307
+ sendTo(ws, Object.assign({}, hydrateImageRefs(require("./yoke/usage").clientEvent(active.history[i], active.vendor || "claude")), { _historyIndex: i }));
305
308
  }
306
- var _lastUsage = null, _lastModelUsage = null, _lastCost = null, _lastStreamInputTokens = null;
309
+ var _lastContext = null, _lastUsage = null, _lastModelUsage = null, _lastCost = null, _lastStreamInputTokens = null;
307
310
  for (var _ri = total - 1; _ri >= 0; _ri--) {
308
311
  if (active.history[_ri].type === "result") {
309
- var _r = active.history[_ri];
312
+ var _r = require("./yoke/usage").clientEvent(active.history[_ri], active.vendor || "claude");
313
+ _lastContext = _r.context;
310
314
  _lastUsage = _r.usage || null;
311
315
  _lastModelUsage = _r.modelUsage || null;
312
316
  _lastCost = _r.cost != null ? _r.cost : null;
@@ -314,7 +318,7 @@ function attachConnection(ctx) {
314
318
  break;
315
319
  }
316
320
  }
317
- sendTo(ws, { type: "history_done", lastUsage: _lastUsage, lastModelUsage: _lastModelUsage, lastCost: _lastCost, lastStreamInputTokens: _lastStreamInputTokens, contextUsage: active.lastContextUsage || null });
321
+ sendTo(ws, { type: "history_done", controlHistoryRevision: require("./clay-control-target-projection").digest(active.history || []), sessionId: active.localId, sessionOriginId: active.sessionOriginId || null, lastUsage: _lastUsage, lastModelUsage: _lastModelUsage, lastCost: _lastCost, lastStreamInputTokens: _lastStreamInputTokens, context: _lastContext, contextUsage: require("./yoke/usage").context(active.lastContextUsage, active.vendor || "claude") });
318
322
  if (scheduledMessages) scheduledMessages.sendState(active, ws);
319
323
  if (pairResultRecovery) pairResultRecovery.sendState(active, ws);
320
324
 
@@ -0,0 +1,12 @@
1
+ var fs = require('fs'), path = require('path');
2
+ var projection = require('./clay-control-target-projection');
3
+ function project(service, principal, status, manager) {
4
+ var before = service.rootIdentity(principal, 'git');
5
+ var directory = fs.realpathSync(status.gitDir), stat = fs.statSync(directory), indexPath = path.join(directory, 'index');
6
+ var index = fs.existsSync(indexPath) ? require('crypto').createHash('sha256').update(fs.readFileSync(indexPath)).digest('hex') : null;
7
+ var view = { runtimeId: projection.runtime(manager), repository: projection.digest([directory, [stat.dev, stat.ino, stat.mode, stat.uid, stat.gid]]),
8
+ files: status.files.slice(0, 200).map(function (file) { return { path: file.path, revision: projection.digest([status.oid, index, file]) }; }) };
9
+ if (service.rootIdentity(principal, 'git') !== before) throw new Error('Workspace identity changed during projection.');
10
+ return view;
11
+ }
12
+ module.exports = { project: project };
@@ -116,7 +116,11 @@ function attachFilesystem(ctx) {
116
116
  // --- fs_read ---
117
117
  if (msg.type === "fs_read") {
118
118
  var metadata = { type: 'fs_read_result', path: msg.path, requestId: msg.requestId, projectSlug: msg.projectSlug, sessionId: msg.sessionId, accountId: msg.accountId };
119
- try { sendTo(ws, Object.assign(metadata, operations.read(fileScope, msg.path))); }
119
+ try {
120
+ var readResult = operations.read(fileScope, msg.path);
121
+ try { var fileSnapshot = operations.snapshot(fileScope, msg.path); if (fileSnapshot.hash === require('crypto').createHash('sha256').update(readResult.content).digest('hex')) readResult.controlRevision = require('./clay-control-target-projection').digest(fileSnapshot); } catch (ignored) {}
122
+ sendTo(ws, Object.assign(metadata, readResult));
123
+ }
120
124
  catch (e) { sendTo(ws, Object.assign(metadata, { error: fileError(e).message, errorCode: fileError(e).code })); }
121
125
  return true;
122
126
  }
@@ -419,9 +419,9 @@ function attachHTTP(ctx) {
419
419
  var uninstallUserInfo = getOsUserInfoForReq(req);
420
420
  var baseDir = scope === "global" ? (uninstallUserInfo ? uninstallUserInfo.home : require("./config").REAL_HOME) : cwd;
421
421
  if (osUsers && !uninstallUserInfo) throw new Error("A mapped OS user is required to uninstall skills");
422
- var skillDir = claySkillStorage.isClaySkill(skill) ? path.join(claySkillStorage.privateRoot(baseDir), skill) : path.join(baseDir, ".claude", "skills", skill);
422
+ var skillDir = claySkillStorage.isClaySkill(skill) ? path.join(claySkillStorage.privateRoot(baseDir), skill) : path.join(require("./yoke/source-locations").legacySkillRoot(baseDir), skill);
423
423
  // Safety: keep removal inside the selected skill storage root.
424
- var expectedParent = claySkillStorage.isClaySkill(skill) ? claySkillStorage.privateRoot(baseDir) : path.join(baseDir, ".claude", "skills");
424
+ var expectedParent = claySkillStorage.isClaySkill(skill) ? claySkillStorage.privateRoot(baseDir) : require("./yoke/source-locations").legacySkillRoot(baseDir);
425
425
  var resolved = path.resolve(skillDir);
426
426
  if (!resolved.startsWith(expectedParent + path.sep)) {
427
427
  res.writeHead(403, { "Content-Type": "application/json" });
@@ -528,8 +528,8 @@ function attachHTTP(ctx) {
528
528
  var sui = getOsUserInfoForReq(req);
529
529
  return sui ? sui.home : require("./config").REAL_HOME;
530
530
  })();
531
- var globalSkillsDir = path.join(skillUserHome, ".claude", "skills");
532
- var projectSkillsDir = path.join(cwd, ".claude", "skills");
531
+ var globalSkillsDir = require("./yoke/source-locations").legacySkillRoot(skillUserHome);
532
+ var projectSkillsDir = require("./yoke/source-locations").legacySkillRoot(cwd);
533
533
  var results = [];
534
534
  var pending = skills.length;
535
535
 
@@ -663,6 +663,9 @@ function attachHTTP(ctx) {
663
663
  var gitUserInfo = getOsUserInfoForReq(req);
664
664
  try {
665
665
  var gitStatus = gitCli.getStatus(cwd, gitUserInfo);
666
+ if (ctx.workspaceOperations) {
667
+ try { gitStatus.controlView = require('./project-control-git-view').project(ctx.workspaceOperations, { userId: req._clayUser && req._clayUser.id || null, singleUserOwnerId: ctx.getProjectOwnerId && ctx.getProjectOwnerId() || null }, gitStatus, sm); } catch (ignored) {}
668
+ }
666
669
  if (gitAttribution) {
667
670
  var visibleGitSessions = sm.sessions;
668
671
  if (usersModule.isMultiUser() && req._clayUser) {
@@ -26,7 +26,7 @@ function attachMcp(ctx) {
26
26
  }
27
27
  function isSession(subject) { return !!subject && ctx.canUseSession(subject); }
28
28
  function sourceIdentity(subject) { return isSession(subject) ? ctx.ownerIdentity(ctx.isMultiUser() ? String(subject.ownerId) : 'default') : ctx.identity(subject); }
29
- var discovery = require('./mcp-source-discovery').createDiscovery({ cwd: ctx.cwd || process.cwd(), home: ctx.discoveryHome,
29
+ var discovery = require('./yoke/mcp-source-discovery').createDiscovery({ cwd: ctx.cwd || process.cwd(), home: ctx.discoveryHome,
30
30
  identity: sourceIdentity, runtimeEnv: function (owner, subject) { return isSession(subject) ? ctx.runtimeEnvForSession(subject) : ctx.runtimeEnv ? ctx.runtimeEnv(owner, subject) : {}; }, authorize: function (owner, subject) {
31
31
  var permitted = isSession(subject) ? (ctx.isMultiUser() ? String(subject.ownerId) : 'default') === owner && ctx.canDiscoverSession(subject) : ownerFor(subject) === owner && ctx.canDiscover && ctx.canDiscover(subject);
32
32
  if (!permitted) throw new Error('Vendor discovery requires your authorized OS user mapping or a sole account installation.');
@@ -152,7 +152,7 @@ function attachMcp(ctx) {
152
152
  });
153
153
  }
154
154
  function getMcpServers(session, allowManagement) {
155
- if (!session || !ctx.canUseSession(session) || (ctx.sessionVendor ? ctx.sessionVendor(session) : session.vendor) === 'antigravity') return {};
155
+ if (!session || !ctx.canUseSession(session) || !require('./yoke/session-tool-transport').capability(ctx.sessionVendor ? ctx.sessionVendor(session) : session.vendor || 'claude').supported) return {};
156
156
  var owner = ctx.isMultiUser() ? String(session.ownerId) : 'default';
157
157
  function authorize() {
158
158
  authorizeOwner(owner);
@@ -154,8 +154,9 @@ function attachNotifications(ctx) {
154
154
  return notifications.length;
155
155
  }
156
156
 
157
+ function controlProjection(item) { var value = Object.assign({}, item); delete value.controlRevision; return Object.assign(value, { controlRevision: require('./clay-control-target-projection').digest(value) }); }
157
158
  function visible(ws) {
158
- return notifications.filter(function (notification) { return !ctx.canView || ctx.canView(ws, notification); });
159
+ return notifications.filter(function (notification) { return !ctx.canView || ctx.canView(ws, notification); }).map(controlProjection);
159
160
  }
160
161
 
161
162
  function publish(payload) {
@@ -164,7 +165,7 @@ function attachNotifications(ctx) {
164
165
  if (ws.readyState !== 1) return;
165
166
  if (payload.notification && !ctx.canView(ws, payload.notification)) return;
166
167
  var list = visible(ws);
167
- ws.send(JSON.stringify(Object.assign({}, payload, { unreadCount: list.length })));
168
+ ws.send(JSON.stringify(Object.assign({}, payload, { notification: payload.notification ? controlProjection(payload.notification) : undefined, unreadCount: list.length })));
168
169
  });
169
170
  }
170
171
 
@@ -197,7 +198,7 @@ function attachNotifications(ctx) {
197
198
  saveNotifications();
198
199
  var payload = {
199
200
  type: "notification_created",
200
- notification: notif,
201
+ notification: controlProjection(notif),
201
202
  unreadCount: getUnreadCount(),
202
203
  };
203
204
  // Project/session previews are projected for each authenticated viewer.
@@ -235,7 +236,7 @@ function attachNotifications(ctx) {
235
236
  if (items.some(function (item) { return !canReadOwned(principal, item); })) throw new Error('Owned notification access changed.');
236
237
  }
237
238
  function listOwned(principal) {
238
- return notifications.filter(function (item) { return canReadOwned(principal, item); }).slice(0, 100).map(function (item) { return JSON.parse(JSON.stringify(item)); });
239
+ return notifications.filter(function (item) { return canReadOwned(principal, item); }).slice(0, 100).map(function (item) { return JSON.parse(JSON.stringify(controlProjection(item))); });
239
240
  }
240
241
  function inspectOwned(principal, ids) {
241
242
  var owned = listOwned(principal);
@@ -125,8 +125,8 @@ function attachPairResultDelivery(ctx) {
125
125
  if (!currentAttempt(caller, partner, token, attemptId)) return;
126
126
  // A known, cancellable provider admission wait has not sent input.
127
127
  // Keep the correlated completion eligible until admission or Stop.
128
- if (caller._queryStarting && Number(caller._codexRecoveryWaiting) > 0
129
- && caller._codexRecoveryWaiting === caller._sdkQueryGeneration) {
128
+ if (caller._queryStarting && Number(caller._providerRecoveryWaiting) > 0
129
+ && caller._providerRecoveryWaiting === caller._sdkQueryGeneration) {
130
130
  token._deliveryTimer = { attemptId: attemptId, timer: setTimeout(acceptanceTimeout, timeoutMs) };
131
131
  return;
132
132
  }
@@ -16,16 +16,14 @@ function contextStatus(session) {
16
16
  var used = null;
17
17
  var window = null;
18
18
  var source = "unavailable";
19
+ var measurement = "unavailable";
19
20
  if (snapshot && typeof snapshot === "object") {
20
- if (session.vendor === "claude") {
21
- used = firstNumber(snapshot.totalTokens);
22
- window = firstNumber(snapshot.maxTokens);
23
- if (used !== null || window !== null) source = "claude_sdk_context_usage";
24
- } else {
25
- used = firstNumber(snapshot.input_tokens);
26
- window = firstNumber(snapshot.contextWindow);
27
- if (used !== null || window !== null) source = "adapter_context_usage";
28
- }
21
+ var current = require("./yoke/usage").context(snapshot, session.vendor || "claude").context;
22
+ measurement = current.measurement;
23
+ used = firstNumber(current.usedTokens);
24
+ window = firstNumber(current.windowTokens);
25
+ if (used !== null || window !== null) source = current.source;
26
+
29
27
  }
30
28
  var history = session.history || [];
31
29
  var cumulativeInput = 0;
@@ -46,9 +44,9 @@ function contextStatus(session) {
46
44
  if (typeof item._ts === "number") lastCompactionAt = item._ts;
47
45
  }
48
46
  if (item.type !== "result") continue;
49
- var usage = item.usage || {};
50
- var input = firstNumber(usage.input_tokens, usage.inputTokens);
51
- var output = firstNumber(usage.output_tokens, usage.outputTokens);
47
+ var usage = require("./yoke/usage").accounting(item.usage) || {};
48
+ var input = firstNumber(usage.inputTokens);
49
+ var output = firstNumber(usage.outputTokens);
52
50
  if (input !== null) { cumulativeInput += input; observedInput = true; }
53
51
  if (output !== null) { cumulativeOutput += output; observedOutput = true; }
54
52
  results++;
@@ -59,13 +57,14 @@ function contextStatus(session) {
59
57
  if (used !== null && window !== null && used > window) {
60
58
  used = null;
61
59
  source = "unavailable";
60
+ measurement = "unavailable";
62
61
  }
63
62
  // Older result records contain lastStreamInputTokens, but that field was
64
63
  // populated from adapter snapshots that could be cumulative or inflated by
65
64
  // cache accounting. It is not safe evidence of current context occupancy.
66
65
  var ratio = used !== null && window !== null && window > 0 ? Math.min(1, Math.round((used / window) * 1000) / 1000) : null;
67
66
  return {
68
- current: { source: source, usedTokens: used, windowTokens: window, usedRatio: ratio,
67
+ current: { source: source, measurement: measurement, usedTokens: used, windowTokens: window, usedRatio: ratio,
69
68
  scope: used === null && window === null ? "unknown" : (used !== null && window !== null ? "current_context" : "partial_current_context") },
70
69
  cumulative: { inputTokens: observedInput ? cumulativeInput : null, outputTokens: observedOutput ? cumulativeOutput : null, tasksObserved: results, scope: "loaded_session_history", complete: false },
71
70
  lastTask: lastTask,
@@ -249,7 +249,8 @@ function createPendingMessageQueue(opts) {
249
249
  return true;
250
250
  }
251
251
  function consumeOne(session, dispatch) {
252
- if (loadError || !session || session._pendingMessageClaimId || isPaused(session) || session._pendingMessageDrainPaused || session.taskStopRequested || session._queryStarting || session._awaitingTurnResult || session.isProcessing) return null;
252
+ var foreground = require('./sdk-foreground-admission');
253
+ if (loadError || !session || session._pendingMessageClaimId || isPaused(session) || session._pendingMessageDrainPaused || session.taskStopRequested || session._queryStarting || (session._awaitingTurnResult || session.isProcessing) && !foreground.available(session)) return null;
253
254
  if (session.rateLimitResetsAt && session.rateLimitResetsAt > Date.now()) return null;
254
255
  for (var i = 0; i < data.items.length; i++) {
255
256
  var item = data.items[i];
@@ -262,6 +263,7 @@ function createPendingMessageQueue(opts) {
262
263
  data.revision++;
263
264
  try { persist(); } catch (e) { restore(before); return null; }
264
265
  session._pendingMessageClaimId = item.id;
266
+ foreground.take(session);
265
267
  broadcast(session, { type: "pending_message_claimed", sessionId: session.localId, projectSlug: opts.slug, paused: isPaused(session), id: item.id, revision: data.revision });
266
268
  var completed = false;
267
269
  function complete(ok, error) { if (completed) return false; completed = true; return finishClaim(session, item.id, ok, error); }
@@ -18,9 +18,11 @@ function attachProjectProductControl(ctx) {
18
18
  if (session.ownerId && session.ownerId !== principal.singleUserOwnerId) throw new Error("Live target ownership denied.");
19
19
  return null;
20
20
  }
21
- function review(session, question, revalidate) {
21
+ function review(session, question, revalidate, presentation) {
22
22
  var owner = session.ownerId || null;
23
23
  return userInput.dispatchUserInput(function (request, respond) {
24
+ // Presentation comes only from the bound product service, after normalization.
25
+ if (presentation) request.questions[0].review = presentation;
24
26
  request.authorizeResponse = function (ws) {
25
27
  try {
26
28
  revalidate();
@@ -234,7 +234,7 @@ function attachProjectScheduledMessages(ctx) {
234
234
  images: images,
235
235
  pastes: pastes,
236
236
  clientMessageId: requestId ? "scheduled-request-" + requestId : "scheduled-" + job.id,
237
- schedule: { jobId: job.id, revision: job.payload.revision, notBefore: job.runAt, ready: false },
237
+ schedule: { jobId: job.id, revision: job.payload.revision, notBefore: job.runAt, ready: false, logicalScheduleId: job.payload.logicalScheduleId || null, logicalRevision: job.payload.logicalRevision || null },
238
238
  }, queueActor(actorId));
239
239
  if (!queuedScheduled.ok) {
240
240
  if (current) {
@@ -304,7 +304,7 @@ function attachProjectScheduledMessages(ctx) {
304
304
  images: existing ? existing.message.images : undefined,
305
305
  pastes: existing ? existing.message.pastes : undefined,
306
306
  clientMessageId: existing ? existing.clientMessageId : "scheduled-" + updated.id,
307
- schedule: { jobId: updated.id, revision: updated.payload.revision, notBefore: updated.runAt, ready: false },
307
+ schedule: { jobId: updated.id, revision: updated.payload.revision, notBefore: updated.runAt, ready: false, logicalScheduleId: updated.payload.logicalScheduleId || null, logicalRevision: updated.payload.logicalRevision || null },
308
308
  }, queueActor(actorUser && actorUser.id));
309
309
  if (!queued.ok) {
310
310
  try { scheduler.replaceQueued(job.id, job); }
@@ -375,7 +375,7 @@ function attachProjectScheduledMessages(ctx) {
375
375
  if (!ensured && job.payload.queueAdmissionRequired === true) throw new Error("Scheduled queue item is missing; refusing to recreate message payload.");
376
376
  if (!ensured) ensured = pendingMessageQueue.upsertScheduled(session, {
377
377
  type: "message", text: job.payload.text, clientMessageId: "scheduled-" + job.id,
378
- schedule: { jobId: job.id, revision: job.payload.revision, notBefore: job.runAt, ready: false },
378
+ schedule: { jobId: job.id, revision: job.payload.revision, notBefore: job.runAt, ready: false, logicalScheduleId: job.payload.logicalScheduleId || null, logicalRevision: job.payload.logicalRevision || null },
379
379
  }, queueActor(job.payload.actorId));
380
380
  if (!ensured || ensured.ok === false) throw new Error(ensured.error || "Scheduled queue handoff could not be prepared.");
381
381
  var released = pendingMessageQueue.releaseScheduled(session, job.id, job.payload.revision);
@@ -438,7 +438,7 @@ function attachProjectScheduledMessages(ctx) {
438
438
  if (jobs[i].state === "queued" && !queueItemForJob(session, jobs[i], jobs[i].payload.actorId)) {
439
439
  if (jobs[i].payload.queueAdmissionRequired !== true || (jobs[i].payload.mateScheduleId && ctx.mateLifecycle && ctx.mateLifecycle().current(session, jobs[i]))) pendingMessageQueue.upsertScheduled(session, {
440
440
  type: "message", text: jobs[i].payload.text, clientMessageId: "scheduled-" + jobs[i].id,
441
- schedule: { jobId: jobs[i].id, revision: jobs[i].payload.revision, notBefore: jobs[i].runAt, ready: false },
441
+ schedule: { jobId: jobs[i].id, revision: jobs[i].payload.revision, notBefore: jobs[i].runAt, ready: false, logicalScheduleId: jobs[i].payload.logicalScheduleId || null, logicalRevision: jobs[i].payload.logicalRevision || null },
442
442
  }, actor);
443
443
  }
444
444
  else if (["interrupted", "completed"].indexOf(jobs[i].state) >= 0) {
@@ -31,7 +31,7 @@ function attachScheduledTaskControl(ctx) {
31
31
  var begun = ctx.beginManual(session, requestId);
32
32
  if (!begun.ok) throw new Error(begun.error);
33
33
  var prior = session.scheduledTaskDraft;
34
- var draft = Object.assign({}, prior, checked.value, { canonicalScheduling: true });
34
+ var draft = Object.assign({}, prior, checked.value, { canonicalScheduling: true, version: Number(prior.version || 0) + 1 });
35
35
  session.scheduledTaskDraft = draft;
36
36
  if (ctx.sm.saveSessionFile(session) !== true) { session.scheduledTaskDraft = prior; throw new Error('The canonical schedule proposal could not be saved.'); }
37
37
  if (drafts.size >= 100) drafts.delete(drafts.keys().next().value);
@@ -63,23 +63,34 @@ function attachSessionDocument(ctx) {
63
63
 
64
64
  function canUse(caller) { return !!caller && (!ctx.canUseSession || ctx.canUseSession(caller)); }
65
65
 
66
+ function activity(caller, action) {
67
+ if (ctx.studioPresentation) return ctx.studioPresentation.begin(caller, action);
68
+ try { var display = ctx.getPresentation && ctx.getPresentation(caller); return display && display.track('studio', action); } catch (error) { return null; }
69
+ }
66
70
  function present(args, caller) {
67
71
  if (!canUse(caller)) return toolError("Documents require a live authorized session.");
68
72
  if (!caller) return toolError("present_markdown_edit requires a session-bound tool server");
69
73
  var target = resolveMarkdownPath(args.path);
70
74
  if (!target) return toolError("path must be a .md or .mdx file inside the project");
75
+ var observer;
71
76
  try {
77
+ observer = activity(caller, 'document');
72
78
  var snapshot = readSnapshot(target, caller);
73
79
  var relativePath = path.relative(cwd, target).split(path.sep).join("/");
74
- sendToSession(caller.localId, {
80
+ var message = {
75
81
  type: "markdown_edit_present",
76
82
  path: relativePath,
77
83
  content: snapshot.content,
78
84
  exists: snapshot.exists,
79
- size: snapshot.size,
80
- });
85
+ size: snapshot.size, sessionId: caller.localId,
86
+ };
87
+ var delivered;
88
+ if (observer && observer.present) delivered = observer.present(message, "document", relativePath, require("./clay-control-target-projection").digest(snapshot), function () { if (!canUse(caller) || require("./clay-control-target-projection").digest(readSnapshot(target, caller)) !== require("./clay-control-target-projection").digest(snapshot)) throw new Error("Document revision changed."); }, function (value, onSent) { return sendToSession(caller.localId, value, onSent); });
89
+ else delivered = sendToSession(caller.localId, message);
90
+ if (observer && !observer.present) observer.finish(delivered > 0 ? { status: 'delivered' } : { outcome: 'uncertain' });
81
91
  return toolResult({ ready: true, path: relativePath });
82
92
  } catch (e) {
93
+ if (observer) { if (observer.unknown) observer.unknown(); else observer.finish({ outcome: 'uncertain' }); }
83
94
  return toolError(e.message || String(e));
84
95
  }
85
96
  }
@@ -95,11 +106,15 @@ function attachSessionDocument(ctx) {
95
106
  } catch (e) {
96
107
  return toolError(e.message || String(e));
97
108
  }
109
+ var observer = activity(caller, 'sketch');
98
110
  return wireframeRenderer.renderPlantUml(source).then(function () {
99
- if (!canUse(caller)) return toolError("Document access changed.");
100
- sendToSession(caller.localId, { type: "wireframe_present", source: source, id: args.id, title: String(args.title || "clay-sketch").slice(0, 100), sessionId: caller.localId });
111
+ if (!canUse(caller)) { if (observer) { if (observer.fail) observer.fail(); else observer.cancel(); } return toolError("Document access changed."); }
112
+ var message = { type: "wireframe_present", source: source, id: args.id, title: String(args.title || "clay-sketch").slice(0, 100), sessionId: caller.localId };
113
+ var delivered = observer && observer.present ? observer.present(message, "sketch", args.id, require("./clay-control-target-projection").digest(source), function () { if (!canUse(caller)) throw new Error("Sketch source changed."); }, function (value, onSent) { return sendToSession(caller.localId, value, onSent); }) : sendToSession(caller.localId, message);
114
+ if (observer && !observer.present) observer.finish(delivered > 0 ? { status: 'delivered' } : { outcome: 'uncertain' });
101
115
  return toolResult({ ready: true, id: args.id, saved: false });
102
116
  }).catch(function (e) {
117
+ if (observer) { if (observer.unknown) observer.unknown(); else observer.finish({ outcome: 'uncertain' }); }
103
118
  return toolError(e && e.message ? e.message : String(e));
104
119
  });
105
120
  }
@@ -105,7 +105,10 @@ function attachSessionHistoryActions(ctx) {
105
105
  if (session.messageUUIDs[fi].uuid === uuid) { targetIdx = fi; break; }
106
106
  }
107
107
  var forkHistory = [];
108
- if (targetIdx >= 0) {
108
+ if (result.localHistoryEndIndex !== undefined) {
109
+ if (!Number.isInteger(result.localHistoryEndIndex) || result.localHistoryEndIndex < 0 || result.localHistoryEndIndex > session.history.length) throw new Error("The provider fork exists but its local history boundary is unavailable; inspect before retrying");
110
+ forkHistory = session.history.slice(0, result.localHistoryEndIndex);
111
+ } else if (targetIdx >= 0) {
109
112
  var trimTo = session.messageUUIDs[targetIdx].historyIndex;
110
113
  forkHistory = session.history.slice(0, trimTo);
111
114
  } else {
@@ -133,8 +136,7 @@ function attachSessionHistoryActions(ctx) {
133
136
  return forked;
134
137
  } else {
135
138
  // Read history from CLI session files
136
- var cliSess = require("./cli-sessions");
137
- var history = await cliSess.readCliSessionHistory(resolveSessionHome(session), cwd, result.sessionId);
139
+ var history = await require("./yoke/session-history").read(session.vendor || "claude", resolveSessionHome(session), cwd, result.sessionId);
138
140
  revalidate();
139
141
  var options = {
140
142
  history: history,
@@ -350,7 +350,7 @@ function attachSessionPair(ctx) {
350
350
  if (boundRoles && boundRoles.workers.indexOf(boundSession) !== -1) return taskControl.workerToolDefs(boundSession);
351
351
  var adHocSplit = !!(group && !group.pair);
352
352
  if (!driverEligibility.isEligibleDriverSession(boundSession, sm)) return [];
353
- var persistentCatalog = boundSession.vendor === "codex" && (!group || !!group.pair);
353
+ var persistentCatalog = require("./yoke/session-tool-transport").contract(boundSession.vendor || "claude").catalogLifetime === "conversation" && (!group || !!group.pair);
354
354
  if (!group && !persistentCatalog) {
355
355
  return workerProposal.getToolDefs(boundSession)
356
356
  .concat(workerPermission.getToolDefs(boundSession, { dormantDriver: true }));