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
@@ -0,0 +1,204 @@
1
+ // claude-jsonl-watcher.js
2
+ //
3
+ // Tails the per-session jsonl that Claude Code writes to
4
+ // ~/.claude/projects/<encoded-cwd>/<cliSessionId>.jsonl, parsing new lines
5
+ // for title events:
6
+ //
7
+ // {"type":"ai-title","aiTitle":"..."} - auto-generated after the
8
+ // first exchange
9
+ // {"type":"custom-title","customTitle":"..."} - explicit /title or
10
+ // similar override
11
+ //
12
+ // Used by Clay TUI sessions to mirror claude's session naming into the
13
+ // sidebar so they don't sit as "New Session" forever.
14
+ //
15
+ // Public API:
16
+ // start(jsonlPath, onTitle) -> stop()
17
+ // onTitle(title, source) where source is "ai-title" or "custom-title".
18
+ // stop() detaches the watcher and clears any timer.
19
+
20
+ var fs = require("fs");
21
+ var path = require("path");
22
+
23
+ function pickLatestTitle(lines, current) {
24
+ // Walk lines in order; the final ai-title and custom-title win. Custom
25
+ // beats ai because users explicitly chose it.
26
+ var ai = current.aiTitle || null;
27
+ var custom = current.customTitle || null;
28
+ for (var i = 0; i < lines.length; i++) {
29
+ var raw = lines[i].trim();
30
+ if (!raw || raw[0] !== "{") continue;
31
+ var obj = null;
32
+ try { obj = JSON.parse(raw); } catch (e) { continue; }
33
+ if (!obj || !obj.type) continue;
34
+ if (obj.type === "ai-title" && typeof obj.aiTitle === "string") {
35
+ ai = obj.aiTitle;
36
+ } else if (obj.type === "custom-title" && typeof obj.customTitle === "string") {
37
+ custom = obj.customTitle;
38
+ }
39
+ }
40
+ current.aiTitle = ai;
41
+ current.customTitle = custom;
42
+ return custom || ai;
43
+ }
44
+
45
+ // Pull the latest visible "text" block from any new assistant entries.
46
+ // Thinking and tool_use blocks are skipped - they're internals, not the
47
+ // response surface. Returns the most recent text seen, or null.
48
+ function extractLatestAssistantText(lines, seenUuids) {
49
+ var latest = null;
50
+ for (var i = 0; i < lines.length; i++) {
51
+ var raw = lines[i].trim();
52
+ if (!raw || raw[0] !== "{") continue;
53
+ var obj = null;
54
+ try { obj = JSON.parse(raw); } catch (e) { continue; }
55
+ if (!obj || obj.type !== "assistant" || !obj.message) continue;
56
+ if (obj.uuid && seenUuids[obj.uuid]) continue;
57
+ if (obj.uuid) seenUuids[obj.uuid] = true;
58
+ var blocks = obj.message.content || [];
59
+ if (!Array.isArray(blocks)) continue;
60
+ for (var j = 0; j < blocks.length; j++) {
61
+ var b = blocks[j];
62
+ if (b && b.type === "text" && typeof b.text === "string" && b.text.trim()) {
63
+ latest = b.text;
64
+ }
65
+ }
66
+ }
67
+ return latest;
68
+ }
69
+
70
+ /**
71
+ * Watch a Claude Code session jsonl for title and response updates.
72
+ *
73
+ * start(jsonlPath, { onTitle, onResponse }) -> stop()
74
+ *
75
+ * onTitle(text, source) fires on ai-title / custom-title updates.
76
+ * onResponse(text) fires on new assistant text blocks (debounced so
77
+ * a multi-block turn coalesces to one callback,
78
+ * using only the *final* visible text). Pre-existing
79
+ * entries from before start() are ignored - the
80
+ * initial scan seeds the "seen" set so we don't
81
+ * spam notifications when the watcher boots up.
82
+ */
83
+ function start(jsonlPath, opts) {
84
+ if (!jsonlPath) return function () {};
85
+ var onTitle = (opts && typeof opts.onTitle === "function") ? opts.onTitle : null;
86
+ var onResponse = (opts && typeof opts.onResponse === "function") ? opts.onResponse : null;
87
+ if (!onTitle && !onResponse) return function () {};
88
+
89
+ var pos = 0;
90
+ var leftover = "";
91
+ var current = { aiTitle: null, customTitle: null };
92
+ var lastTitleEmitted = null;
93
+ var seenAssistantUuids = {};
94
+ var didInitialScan = false;
95
+ var responseDebounceTimer = null;
96
+ var pendingResponseText = null;
97
+ var lastResponseEmitted = null;
98
+ var watcher = null;
99
+ var pollTimer = null;
100
+ var stopped = false;
101
+
102
+ var RESPONSE_DEBOUNCE_MS = 1500;
103
+ function maybeFireResponse() {
104
+ if (responseDebounceTimer) clearTimeout(responseDebounceTimer);
105
+ responseDebounceTimer = setTimeout(function () {
106
+ responseDebounceTimer = null;
107
+ if (!pendingResponseText) return;
108
+ if (pendingResponseText === lastResponseEmitted) return;
109
+ var t = pendingResponseText;
110
+ pendingResponseText = null;
111
+ lastResponseEmitted = t;
112
+ try { if (onResponse) onResponse(t); } catch (e) {}
113
+ }, RESPONSE_DEBOUNCE_MS);
114
+ }
115
+
116
+ function readNew() {
117
+ if (stopped) return;
118
+ try {
119
+ var stat = fs.statSync(jsonlPath);
120
+ if (stat.size <= pos) return;
121
+ var fd = fs.openSync(jsonlPath, "r");
122
+ var len = stat.size - pos;
123
+ var buf = Buffer.alloc(len);
124
+ fs.readSync(fd, buf, 0, len, pos);
125
+ fs.closeSync(fd);
126
+ pos = stat.size;
127
+ var chunk = leftover + buf.toString("utf8");
128
+ var parts = chunk.split("\n");
129
+ // Last fragment may be a partial line; hold it for next read.
130
+ leftover = parts.pop();
131
+
132
+ // Title side: fire whenever we see a new value.
133
+ if (onTitle) {
134
+ var resolved = pickLatestTitle(parts, current);
135
+ if (resolved && resolved !== lastTitleEmitted) {
136
+ lastTitleEmitted = resolved;
137
+ try { onTitle(resolved, current.customTitle === resolved ? "custom-title" : "ai-title"); } catch (e) {}
138
+ }
139
+ }
140
+
141
+ // Response side: collect new assistant text blocks. Initial scan
142
+ // seeds the "seen" set silently so old history doesn't fire.
143
+ var latest = extractLatestAssistantText(parts, seenAssistantUuids);
144
+ if (onResponse && didInitialScan && latest) {
145
+ pendingResponseText = latest;
146
+ maybeFireResponse();
147
+ }
148
+ } catch (e) {
149
+ // File may not exist yet; readNew is called again on the next event.
150
+ }
151
+ }
152
+
153
+ function arm() {
154
+ if (stopped) return;
155
+ if (watcher) return;
156
+ try {
157
+ watcher = fs.watch(jsonlPath, { persistent: false }, function () {
158
+ readNew();
159
+ });
160
+ watcher.on("error", function () {
161
+ try { watcher.close(); } catch (e) {}
162
+ watcher = null;
163
+ // Fall back to polling; some filesystems (network mounts) don't
164
+ // deliver fs.watch events reliably.
165
+ });
166
+ } catch (e) {
167
+ // Path doesn't exist yet; rely on the poll loop to retry once it appears.
168
+ }
169
+ }
170
+
171
+ // Initial pass to capture anything already written before we attached.
172
+ // didInitialScan stays false during this pass so onResponse doesn't fire
173
+ // for old history; subsequent reads (live updates) will emit.
174
+ readNew();
175
+ didInitialScan = true;
176
+ arm();
177
+
178
+ // Poll every 2s as a safety net for fs.watch misses and for the
179
+ // "file doesn't exist yet" boot case.
180
+ pollTimer = setInterval(function () {
181
+ if (!watcher) arm();
182
+ readNew();
183
+ }, 2000);
184
+
185
+ return function stop() {
186
+ stopped = true;
187
+ if (pollTimer) { clearInterval(pollTimer); pollTimer = null; }
188
+ if (responseDebounceTimer) { clearTimeout(responseDebounceTimer); responseDebounceTimer = null; }
189
+ if (watcher) { try { watcher.close(); } catch (e) {} watcher = null; }
190
+ };
191
+ }
192
+
193
+ // Resolve the jsonl path for a (cwd, cliSessionId) without depending on
194
+ // utils.js so this module stays self-contained.
195
+ function jsonlPathFor(home, cwd, cliSessionId) {
196
+ if (!home || !cwd || !cliSessionId) return null;
197
+ var encoded = cwd.replace(/[^a-zA-Z0-9]/g, "-");
198
+ return path.join(home, ".claude", "projects", encoded, cliSessionId + ".jsonl");
199
+ }
200
+
201
+ module.exports = {
202
+ start: start,
203
+ jsonlPathFor: jsonlPathFor,
204
+ };
@@ -0,0 +1,55 @@
1
+ var fs = require("fs"), path = require("path");
2
+ function cleanupSdkWarmupSessions(REAL_HOME) {
3
+ var sessionHygiene = require("./claude-transcript-hygiene");
4
+ var projectsRoot = path.join(REAL_HOME, ".claude", "projects");
5
+ var projectDirs;
6
+ try { projectDirs = fs.readdirSync(projectsRoot, { withFileTypes: true }); }
7
+ catch (e) { return; }
8
+
9
+ var deleted = 0;
10
+ var nowMs = Date.now();
11
+ var MAX_BYTES = 64 * 1024;
12
+ var MIN_AGE_MS = 60 * 1000;
13
+
14
+ for (var di = 0; di < projectDirs.length; di++) {
15
+ if (!projectDirs[di].isDirectory()) continue;
16
+ var projDir = path.join(projectsRoot, projectDirs[di].name);
17
+ var files;
18
+ try { files = fs.readdirSync(projDir); } catch (e) { continue; }
19
+
20
+ for (var fi = 0; fi < files.length; fi++) {
21
+ var f = files[fi];
22
+ if (!f.endsWith(".jsonl")) continue;
23
+ var fp = path.join(projDir, f);
24
+
25
+ var st;
26
+ try { st = fs.statSync(fp); } catch (e) { continue; }
27
+ if (st.size >= MAX_BYTES) continue;
28
+ if (nowMs - st.mtimeMs < MIN_AGE_MS) continue;
29
+
30
+ var raw;
31
+ try { raw = fs.readFileSync(fp, "utf8"); } catch (e) { continue; }
32
+ var lines = raw.split("\n");
33
+ var events = [];
34
+ var parseOk = true;
35
+ for (var li = 0; li < lines.length; li++) {
36
+ var line = lines[li];
37
+ if (!line) continue;
38
+ var ev;
39
+ try { ev = JSON.parse(line); } catch (e) { parseOk = false; break; }
40
+ if (!ev || typeof ev !== "object") continue;
41
+ events.push(ev);
42
+ }
43
+ if (!parseOk) continue;
44
+ if (!sessionHygiene.isClaudeWarmupTranscript(events)) continue;
45
+
46
+ try { fs.unlinkSync(fp); deleted++; } catch (e) {}
47
+ }
48
+ }
49
+
50
+ if (deleted > 0) {
51
+ console.log("[daemon] Removed " + deleted + " stale SDK warmup session file(s)");
52
+ }
53
+ }
54
+
55
+ module.exports = { cleanup: cleanupSdkWarmupSessions };
@@ -0,0 +1,53 @@
1
+ function attachPatterns(policy) {
2
+ var STANDALONE = policy.STANDALONE, SUBCOMMAND = policy.SUBCOMMAND, EXACT = policy.EXACT;
3
+ var STANDALONE_SET = {};
4
+ STANDALONE.forEach(function(command) { STANDALONE_SET[command] = true; });
5
+ function buildClayBashAllowPatterns() {
6
+ var patterns = [];
7
+ for (var i = 0; i < STANDALONE.length; i++) {
8
+ patterns.push("Bash(" + STANDALONE[i] + ")");
9
+ patterns.push("Bash(" + STANDALONE[i] + " *)");
10
+ }
11
+ var bins = Object.keys(SUBCOMMAND);
12
+ for (var b = 0; b < bins.length; b++) {
13
+ var subs = SUBCOMMAND[bins[b]];
14
+ for (var s = 0; s < subs.length; s++) {
15
+ var base = bins[b] + " " + subs[s];
16
+ patterns.push("Bash(" + base + ")");
17
+ patterns.push("Bash(" + base + " *)");
18
+ }
19
+ }
20
+ for (var e = 0; e < EXACT.length; e++) {
21
+ patterns.push("Bash(" + EXACT[e] + ")");
22
+ patterns.push("Bash(" + EXACT[e] + " *)");
23
+ }
24
+ return patterns;
25
+ }
26
+
27
+ var GENERATED_SET = {};
28
+ (function () {
29
+ var p = buildClayBashAllowPatterns();
30
+ for (var i = 0; i < p.length; i++) GENERATED_SET[p[i]] = true;
31
+ })();
32
+
33
+ // Recognize a `permissions.allow` entry that Clay owns, so the installer can
34
+ // strip stale variants on upgrade (including shapes a previous version wrote)
35
+ // WITHOUT clobbering user-authored patterns. Rules:
36
+ // - Standalone commands: own every shape of `Bash(ls ...)` (any args), since
37
+ // a user pattern for a command we manage is redundant with our broad one.
38
+ // - Subcommand binaries (git, npm, ...) and exact commands: only strip the
39
+ // precise patterns we generate, so a user's `Bash(git push)` survives.
40
+ // Legacy colon shapes are handled separately by the installer.
41
+ function isClayManagedBashPattern(p) {
42
+ if (typeof p !== "string") return false;
43
+ if (p.indexOf("Bash(") !== 0 || p.charAt(p.length - 1) !== ")") return false;
44
+ if (GENERATED_SET[p]) return true;
45
+ var inner = p.slice(5, -1).trim();
46
+ if (!inner) return false;
47
+ var word = inner.split(/[\s:]/)[0];
48
+ return STANDALONE_SET[word] === true;
49
+ }
50
+
51
+ return { build: buildClayBashAllowPatterns, isManaged: isClayManagedBashPattern };
52
+ }
53
+ module.exports = { attachPatterns: attachPatterns };
@@ -0,0 +1,155 @@
1
+ // Claude delivery watchdog. Only an unanswered foreground turn is replayable.
2
+ function attachResponseRecovery(ctx) {
3
+ var states = new Map();
4
+ var schedule = ctx.setTimeout || setTimeout;
5
+ var unschedule = ctx.clearTimeout || clearTimeout;
6
+ var waitMs = ctx.waitMs || 180000;
7
+ var stopMs = ctx.stopMs || 10000;
8
+
9
+ function live(session, state) {
10
+ return (!ctx.isCurrentProvider || ctx.isCurrentProvider(session)) && states.get(session) === state && !session.destroying && !session.taskStopRequested
11
+ && state.ownerId === session.ownerId && state.vendor === session.vendor
12
+ && (!ctx.sessions || ctx.sessions.get(session.localId) === session);
13
+ }
14
+ function clearTimer(state) {
15
+ if (state.timer) unschedule(state.timer);
16
+ state.timer = null;
17
+ }
18
+ function cancel(session, expected) {
19
+ var state = states.get(session);
20
+ if (expected && state !== expected) return;
21
+ if (state) clearTimer(state);
22
+ states.delete(session);
23
+ }
24
+ function protectedWait(session) {
25
+ return [session.pendingPermissions, session.pendingAskUser, session.pendingElicitations,
26
+ session.pendingUserDialogs, session.activeTaskToolIds].some(function(map) {
27
+ return map && Object.keys(map).length > 0;
28
+ }) || (session.activeBackgroundTasks && session.activeBackgroundTasks.length > 0);
29
+ }
30
+ function arm(session, state) {
31
+ clearTimer(state);
32
+ if (!state.turns.length || state.turns[0].progress || state.stopping) return;
33
+ state.timer = schedule(function() { expire(session, state); }, waitMs);
34
+ if (state.timer && state.timer.unref) state.timer.unref();
35
+ }
36
+ function snapshot(payload) {
37
+ return Object.assign({}, payload, { images: payload.images ? JSON.parse(JSON.stringify(payload.images)) : payload.images });
38
+ }
39
+ function begin(session, payload, retryState) {
40
+ if (retryState) return retryState;
41
+ cancel(session);
42
+ var state = { turns: [snapshot(payload)], timer: null, stopping: false, retried: false, ownerId: session.ownerId, vendor: session.vendor };
43
+ states.set(session, state);
44
+ arm(session, state);
45
+ return state;
46
+ }
47
+ function enqueue(session, payload) {
48
+ var state = states.get(session);
49
+ if (!state) return begin(session, payload);
50
+ state.turns.push(snapshot(payload));
51
+ return state;
52
+ }
53
+ function progress(session) {
54
+ var state = states.get(session);
55
+ if (!state || state.stopping || !state.turns.length) return;
56
+ state.turns[0].progress = true;
57
+ clearTimer(state);
58
+ }
59
+ function event(session, msg) {
60
+ var state = states.get(session);
61
+ if (!state || state.stopping || !msg) return;
62
+ var type = msg.yokeType;
63
+ if (type === 'error' || type === 'interrupted') { cancel(session); return; }
64
+ if (type === 'result') {
65
+ if (msg.subtype === 'success' && !msg.is_error && msg.numTurns === 0
66
+ && msg.origin && msg.origin.kind === 'task-notification'
67
+ && !msg.localCommand && msg.resultText === '') return;
68
+ var remaining = require('../sdk-turn-queue').pendingSends(session, msg);
69
+ state.turns = remaining ? state.turns.slice(-remaining) : [];
70
+ state.retried = false;
71
+ arm(session, state);
72
+ if (!state.turns.length) cancel(session);
73
+ } else if ((type === 'text_delta' || type === 'thinking_delta') && (msg.text || msg.delta)
74
+ || type === 'tool_start' || type === 'tool_input_delta' || type === 'tool_progress'
75
+ || type === 'task_started' || type === 'task_progress' || type === 'subagent_message'
76
+ || type === 'message' && msg.messageRole === 'assistant' && msg.content && msg.content.length) {
77
+ progress(session);
78
+ }
79
+ }
80
+ function finish(session, state, text) {
81
+ if (!live(session, state)) { cancel(session, state); return; }
82
+ cancel(session);
83
+ // Fence late output when a transport cannot confirm termination.
84
+ session._sdkQueryGeneration = Number(session._sdkQueryGeneration || 0) + 1;
85
+ session.isProcessing = false;
86
+ session._awaitingTurnResult = false;
87
+ session._queuedTurnCount = 0;
88
+ ctx.changed();
89
+ ctx.record(session, { type: 'thinking_stop' });
90
+ ctx.record(session, { type: 'error', text: text });
91
+ ctx.record(session, { type: 'done', code: 1 });
92
+ ctx.broadcast();
93
+ }
94
+ async function expire(session, state) {
95
+ clearTimer(state);
96
+ if (!live(session, state)) { cancel(session, state); return; }
97
+ // Never stop a known tool or a human decision because prose is absent.
98
+ if (protectedWait(session)) { progress(session); return; }
99
+ state.stopping = true;
100
+ ctx.record(session, { type: 'info', text: 'Claude has not responded for three minutes. Stopping this attempt safely before retrying.' });
101
+ var timeout;
102
+ var stopped = false;
103
+ try {
104
+ var stopAttempt = (async function() {
105
+ var handle = session.queryInstance || state.handle;
106
+ if (!handle && session.abortController) session.abortController.abort();
107
+ if (!handle && state.starting) await state.starting;
108
+ handle = handle || session.queryInstance || state.handle;
109
+ if (handle) {
110
+ if (typeof handle.stopAndWait !== 'function') return false;
111
+ await handle.stopAndWait();
112
+ if (session.streamPromise) await session.streamPromise;
113
+ }
114
+ return true;
115
+ })();
116
+ session._responseRecoveryStopPending = stopAttempt;
117
+ stopAttempt.then(function(confirmed) {
118
+ if (confirmed && session._responseRecoveryStopPending === stopAttempt) session._responseRecoveryStopPending = null;
119
+ }, function() {});
120
+ stopped = await Promise.race([
121
+ stopAttempt,
122
+ new Promise(function(resolve) { timeout = schedule(function() { resolve(false); }, stopMs); }),
123
+ ]);
124
+ } catch (e) { stopped = false; }
125
+ if (timeout) unschedule(timeout);
126
+ if (!live(session, state)) { cancel(session, state); return; }
127
+ if (!stopped) {
128
+ finish(session, state, 'Claude could not be confirmed stopped. Automatic retry was cancelled to avoid running your request twice. Check your Claude connection before trying again.');
129
+ return;
130
+ }
131
+ if (state.retried || state.turns.length !== 1) {
132
+ finish(session, state, state.retried
133
+ ? 'Claude still has not responded after one automatic retry. Please send your message again or check your Claude connection.'
134
+ : 'Claude did not respond. Several messages were queued, so automatic replay was cancelled to avoid duplicating work. Please send the unanswered messages again.');
135
+ return;
136
+ }
137
+ state.retried = true;
138
+ state.stopping = false;
139
+ session.isProcessing = true;
140
+ ctx.changed();
141
+ ctx.record(session, { type: 'info', text: 'The previous attempt has stopped. Retrying your original message once.' });
142
+ ctx.send(session, { type: 'status', status: 'processing' });
143
+ arm(session, state);
144
+ var payload = state.turns[0];
145
+ await ctx.restart(session, payload, state);
146
+ }
147
+ return {
148
+ begin: begin, enqueue: enqueue, event: event, progress: progress, cancel: cancel,
149
+ current: function(session) { return states.get(session); },
150
+ valid: live,
151
+ stopping: function(session) { var state = states.get(session); return !!(state && state.stopping); },
152
+ dispose: function() { states.forEach(function(state) { clearTimer(state); }); states.clear(); },
153
+ };
154
+ }
155
+ module.exports = { attachResponseRecovery: attachResponseRecovery };
@@ -0,0 +1,34 @@
1
+ var CLAUDE_INTERRUPTED_TEXT = "[Request interrupted by user]";
2
+ function extractClaudeUserText(event) {
3
+ var content = event && event.message && event.message.content;
4
+ if (typeof content === "string") return content;
5
+ if (!Array.isArray(content)) return "";
6
+ var parts = [];
7
+ for (var i = 0; i < content.length; i++) {
8
+ if (content[i] && content[i].type === "text" && typeof content[i].text === "string") {
9
+ parts.push(content[i].text);
10
+ }
11
+ }
12
+ return parts.join("");
13
+ }
14
+
15
+ // SDK capability probes create a real Claude transcript with a single "hi"
16
+ // prompt. Newer Claude Code versions append an interruption record when Clay
17
+ // aborts the probe, so that bookkeeping record must not make the transcript
18
+ // look like a real two-turn conversation.
19
+ function isClaudeWarmupTranscript(events) {
20
+ var promptCount = 0;
21
+ for (var i = 0; i < events.length; i++) {
22
+ var event = events[i];
23
+ if (!event || typeof event !== "object") continue;
24
+ if (event.type === "assistant") return false;
25
+ if (event.type !== "user" || !event.message || event.message.role !== "user") continue;
26
+ var text = extractClaudeUserText(event);
27
+ if (text === CLAUDE_INTERRUPTED_TEXT) continue;
28
+ if (text !== "hi") return false;
29
+ promptCount++;
30
+ }
31
+ return promptCount === 1;
32
+ }
33
+
34
+ module.exports = { extractClaudeUserText: extractClaudeUserText, isClaudeWarmupTranscript: isClaudeWarmupTranscript };
@@ -0,0 +1,33 @@
1
+ // Runs under the requesting OS identity. The installer only sees a private staging project.
2
+ var fs = require('fs');
3
+ var path = require('path');
4
+ var spawn = require('child_process').spawn;
5
+ var storage = require('../clay-skill-storage');
6
+ var base = process.argv[2];
7
+ var name = process.argv[3];
8
+ var url = process.argv[4];
9
+ if (!storage.isClaySkill(name) || !/^https:\/\//i.test(url)) throw new Error('Invalid Clay skill install request');
10
+ storage.directory(path.join(base, '.clay'));
11
+ var stage = fs.mkdtempSync(path.join(base, '.clay', 'skill-install-'));
12
+ var child = spawn(process.platform === 'win32' ? 'npx.cmd' : 'npx', ['skills', 'add', url, '--skill', name, '--yes', '--agent', 'claude-code'], { cwd: stage, stdio: 'inherit', env: process.env });
13
+ function cleanup() { fs.rmSync(stage, { recursive: true, force: true }); }
14
+ child.on('error', function (error) { console.error(error.message); cleanup(); process.exitCode = 1; });
15
+ process.on('SIGTERM', function () { child.kill('SIGTERM'); });
16
+ child.on('close', function (code) {
17
+ try {
18
+ if (code !== 0) throw new Error('Skill installer exited with code ' + code);
19
+ var candidates = ['.agents', '.claude'].map(function (vendor) { return path.join(stage, vendor, 'skills', name); });
20
+ var source = candidates.find(function (dir) { return fs.existsSync(path.join(dir, 'SKILL.md')); });
21
+ if (!source) throw new Error('Installer did not create the requested skill in the private staging project');
22
+ var root = storage.privateRoot(base); storage.directory(root);
23
+ var destination = path.join(root, name);
24
+ var pending = path.join(root, '.' + name + '-' + process.pid);
25
+ fs.cpSync(source, pending, { recursive: true, dereference: true });
26
+ if (fs.existsSync(destination)) {
27
+ var backups = path.join(base, '.clay', 'skill-backups'); storage.directory(backups);
28
+ fs.renameSync(destination, path.join(backups, name + '-' + Date.now()));
29
+ }
30
+ fs.renameSync(pending, destination);
31
+ } catch (error) { console.error(error.message); process.exitCode = 1; }
32
+ finally { cleanup(); }
33
+ });