clay-server 3.8.1 → 4.0.0-beta.10

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 (222) hide show
  1. package/lib/ask-user-mcp-server.js +5 -119
  2. package/lib/background-task-timing.js +105 -0
  3. package/lib/builtin-mates.js +6 -6
  4. package/lib/claude-hook-installer.js +15 -0
  5. package/lib/clay-history-mcp-server.js +62 -297
  6. package/lib/daemon.js +23 -1
  7. package/lib/debate-model-selection.js +119 -0
  8. package/lib/home-debate-tool-policy.js +164 -0
  9. package/lib/home-response-notifications.js +26 -0
  10. package/lib/knowledge-import.js +436 -0
  11. package/lib/knowledge-record-store.js +188 -0
  12. package/lib/knowledge-search.js +224 -0
  13. package/lib/mate-creation-mcp-server.js +55 -0
  14. package/lib/mate-knowledge-mcp-server.js +151 -0
  15. package/lib/mate-knowledge-migration.js +283 -0
  16. package/lib/mate-knowledge-service.js +488 -0
  17. package/lib/mate-knowledge-sync.js +482 -0
  18. package/lib/mate-ready-creation.js +95 -0
  19. package/lib/mates.js +2 -0
  20. package/lib/notes.js +2 -1
  21. package/lib/project-ask-user.js +64 -0
  22. package/lib/project-capsule-catalog.js +101 -0
  23. package/lib/project-connection.js +17 -0
  24. package/lib/project-debate-proposal.js +133 -17
  25. package/lib/project-debate.js +214 -110
  26. package/lib/project-delegated-follow-up.js +84 -0
  27. package/lib/project-delegated-session.js +53 -0
  28. package/lib/project-filesystem.js +7 -9
  29. package/lib/project-knowledge.js +23 -0
  30. package/lib/project-logs-comments.js +137 -0
  31. package/lib/project-logs-mcp-server.js +290 -0
  32. package/lib/project-logs-query.js +167 -0
  33. package/lib/project-logs-root.js +67 -0
  34. package/lib/project-logs-schema.js +259 -0
  35. package/lib/project-logs-service.js +357 -0
  36. package/lib/project-logs-snapshot.js +202 -0
  37. package/lib/project-logs-store.js +498 -0
  38. package/lib/project-logs-versioning.js +128 -0
  39. package/lib/project-logs.js +347 -0
  40. package/lib/project-mate-creation-proposal.js +120 -0
  41. package/lib/project-mate-interaction.js +14 -0
  42. package/lib/project-mate-knowledge.js +110 -0
  43. package/lib/project-memory.js +36 -7
  44. package/lib/project-models.js +157 -32
  45. package/lib/project-session-notes.js +2 -2
  46. package/lib/project-session-pair.js +8 -8
  47. package/lib/project-sessions.js +66 -51
  48. package/lib/project-vendor-login.js +414 -0
  49. package/lib/project-worker-proposal.js +25 -25
  50. package/lib/project-workspace-query.js +144 -0
  51. package/lib/project.js +433 -102
  52. package/lib/public/app.js +109 -48
  53. package/lib/public/css/avatar-imprints.css +0 -1
  54. package/lib/public/css/base.css +4 -0
  55. package/lib/public/css/capsule-ui.css +389 -0
  56. package/lib/public/css/command-palette.css +306 -6
  57. package/lib/public/css/home-capsule-library.css +243 -0
  58. package/lib/public/css/home-capsule-source.css +192 -0
  59. package/lib/public/css/home-debate-live.css +146 -0
  60. package/lib/public/css/home-debate-planning.css +125 -0
  61. package/lib/public/css/home-debates-archive.css +281 -0
  62. package/lib/public/css/home-hub.css +896 -405
  63. package/lib/public/css/home-mate-creation.css +34 -0
  64. package/lib/public/css/home-mate-model-picker.css +131 -0
  65. package/lib/public/css/home-mate-settings.css +344 -0
  66. package/lib/public/css/home-session-actions.css +265 -0
  67. package/lib/public/css/home-sidebar.css +667 -0
  68. package/lib/public/css/icon-strip.css +1 -2
  69. package/lib/public/css/input.css +79 -25
  70. package/lib/public/css/mates.css +7 -34
  71. package/lib/public/css/pane.css +1 -1
  72. package/lib/public/css/playbook.css +0 -80
  73. package/lib/public/css/project-logs.css +234 -0
  74. package/lib/public/css/scheduler.css +1 -1
  75. package/lib/public/css/sticky-notes.css +53 -117
  76. package/lib/public/css/tui-attention.css +0 -44
  77. package/lib/public/css/workspace-assignment.css +45 -0
  78. package/lib/public/fonts/source-serif-4/LICENSE.md +93 -0
  79. package/lib/public/fonts/source-serif-4/SourceSerif4Caption-Semibold.ttf.woff2 +0 -0
  80. package/lib/public/index.html +96 -74
  81. package/lib/public/modules/app-connection.js +100 -5
  82. package/lib/public/modules/app-dm.js +43 -160
  83. package/lib/public/modules/app-home-hub.js +328 -585
  84. package/lib/public/modules/app-message-cards.js +242 -0
  85. package/lib/public/modules/app-message-router.js +84 -0
  86. package/lib/public/modules/app-messages.js +124 -38
  87. package/lib/public/modules/app-notifications.js +8 -119
  88. package/lib/public/modules/app-panels.js +43 -2
  89. package/lib/public/modules/app-projects.js +9 -1
  90. package/lib/public/modules/app-rendering.js +21 -602
  91. package/lib/public/modules/avatar-imprint.js +11 -8
  92. package/lib/public/modules/background-tasks-ui.js +139 -16
  93. package/lib/public/modules/chat-bubble-renderer.js +152 -0
  94. package/lib/public/modules/chat-render-runtime.js +198 -0
  95. package/lib/public/modules/command-palette.js +225 -436
  96. package/lib/public/modules/dm-render.js +82 -0
  97. package/lib/public/modules/home-capsule-creation-intent.js +31 -0
  98. package/lib/public/modules/home-capsule-library.js +146 -0
  99. package/lib/public/modules/home-capsule-source.js +383 -0
  100. package/lib/public/modules/home-chat-identity.js +35 -0
  101. package/lib/public/modules/home-chat-scroll.js +77 -0
  102. package/lib/public/modules/home-chat-stream-state.js +34 -0
  103. package/lib/public/modules/home-composer-focus.js +25 -0
  104. package/lib/public/modules/home-conversations-sheet.js +179 -0
  105. package/lib/public/modules/home-debate-activity.js +48 -0
  106. package/lib/public/modules/home-debate-controls.js +295 -0
  107. package/lib/public/modules/home-debate-launch.js +41 -0
  108. package/lib/public/modules/home-debate-live.js +284 -0
  109. package/lib/public/modules/home-debate-models.js +73 -0
  110. package/lib/public/modules/home-debate-planning.js +335 -0
  111. package/lib/public/modules/home-debates-archive.js +279 -0
  112. package/lib/public/modules/home-dock-resize.js +74 -0
  113. package/lib/public/modules/home-dock.js +446 -0
  114. package/lib/public/modules/home-mate-chat.js +496 -0
  115. package/lib/public/modules/home-mate-creation.js +109 -0
  116. package/lib/public/modules/home-mate-model-picker.js +281 -0
  117. package/lib/public/modules/home-mate-selection.js +38 -0
  118. package/lib/public/modules/home-mate-settings-menu.js +131 -0
  119. package/lib/public/modules/home-mate-settings.js +383 -0
  120. package/lib/public/modules/home-session-actions.js +373 -0
  121. package/lib/public/modules/home-session-model-confirmation.js +13 -0
  122. package/lib/public/modules/home-shell.js +9 -0
  123. package/lib/public/modules/home-sidebar-chat-list.js +63 -0
  124. package/lib/public/modules/home-sidebar.js +165 -0
  125. package/lib/public/modules/home-sub-surface.js +19 -0
  126. package/lib/public/modules/home-surface-boot.js +68 -0
  127. package/lib/public/modules/home-surface.js +141 -0
  128. package/lib/public/modules/home-tools.js +271 -0
  129. package/lib/public/modules/markdown.js +50 -2
  130. package/lib/public/modules/mate-management.js +86 -0
  131. package/lib/public/modules/mate-sidebar.js +0 -8
  132. package/lib/public/modules/mate-wizard.js +0 -7
  133. package/lib/public/modules/mention.js +17 -19
  134. package/lib/public/modules/pane-bridge.js +9 -0
  135. package/lib/public/modules/profile.js +4 -5
  136. package/lib/public/modules/project-logs-ambient.js +238 -0
  137. package/lib/public/modules/project-logs-render.js +359 -0
  138. package/lib/public/modules/project-logs.js +328 -0
  139. package/lib/public/modules/project-settings.js +34 -28
  140. package/lib/public/modules/project-switcher.js +11 -5
  141. package/lib/public/modules/project-workspace-assignment.js +23 -0
  142. package/lib/public/modules/scheduler.js +17 -3
  143. package/lib/public/modules/search-clay-chat.js +402 -0
  144. package/lib/public/modules/server-settings.js +14 -12
  145. package/lib/public/modules/session-actions.js +1 -1
  146. package/lib/public/modules/sidebar-mates.js +56 -552
  147. package/lib/public/modules/sidebar-mobile.js +12 -73
  148. package/lib/public/modules/sidebar-presence.js +37 -0
  149. package/lib/public/modules/sidebar-sessions.js +25 -1
  150. package/lib/public/modules/split-pair-ui.js +13 -13
  151. package/lib/public/modules/split-view.js +7 -0
  152. package/lib/public/modules/sticky-notes.js +106 -53
  153. package/lib/public/modules/tool-input-composition.js +48 -0
  154. package/lib/public/modules/tool-llm-status.js +167 -0
  155. package/lib/public/modules/tool-palette.js +76 -4
  156. package/lib/public/modules/tool-renderer-advanced.js +285 -0
  157. package/lib/public/modules/tool-renderer-chart.js +158 -0
  158. package/lib/public/modules/tool-renderer-semantics.js +35 -0
  159. package/lib/public/modules/tool-renderer.js +464 -0
  160. package/lib/public/modules/tool-runtime.js +309 -0
  161. package/lib/public/modules/tool-ui-evaluator.js +97 -0
  162. package/lib/public/modules/tool-ui-tree.js +21 -0
  163. package/lib/public/modules/tui-attention.js +10 -0
  164. package/lib/public/modules/user-settings.js +16 -23
  165. package/lib/public/modules/vendor-login.js +287 -0
  166. package/lib/public/modules/worker-proposal.js +2 -2
  167. package/lib/public/modules/workspace-assignment-card.js +149 -0
  168. package/lib/public/modules/workspace-assignment-routing.js +4 -0
  169. package/lib/public/style.css +14 -2
  170. package/lib/runtime-env.js +65 -0
  171. package/lib/sdk-bridge.js +120 -32
  172. package/lib/sdk-message-processor.js +34 -13
  173. package/lib/server-home-capsule-creation.js +36 -0
  174. package/lib/server-home-chat-events.js +287 -0
  175. package/lib/server-home-chat.js +518 -0
  176. package/lib/server-home-clay-entry.js +65 -0
  177. package/lib/server-home-clay-session-links.js +26 -0
  178. package/lib/server-home-debate-planning.js +214 -0
  179. package/lib/server-home-debates.js +137 -0
  180. package/lib/server-home-mate-creation.js +261 -0
  181. package/lib/server-home-models.js +241 -0
  182. package/lib/server-home-preferences.js +61 -0
  183. package/lib/server-mates.js +37 -0
  184. package/lib/server-tools.js +377 -0
  185. package/lib/server.js +166 -59
  186. package/lib/session-pair-mcp-server.js +6 -6
  187. package/lib/sessions.js +22 -4
  188. package/lib/tool-capsule-source.js +142 -0
  189. package/lib/tool-control-mcp-server.js +126 -0
  190. package/lib/tool-llm.js +48 -0
  191. package/lib/tool-storage.js +77 -0
  192. package/lib/tool-ui-spec-advanced.js +82 -0
  193. package/lib/tool-ui-spec.js +261 -0
  194. package/lib/tools-registry.js +286 -0
  195. package/lib/users-home-dock-preferences.js +77 -0
  196. package/lib/users-home-surface-preferences.js +99 -0
  197. package/lib/users-preferences.js +4 -7
  198. package/lib/users.js +12 -0
  199. package/lib/whats-new-content.js +3 -3
  200. package/lib/workspace-assignment-service.js +420 -0
  201. package/lib/workspace-query-mcp-server.js +154 -0
  202. package/lib/workspace-query-service.js +492 -0
  203. package/lib/ws-schema.js +107 -12
  204. package/lib/yoke/adapters/acp.js +2 -1
  205. package/lib/yoke/adapters/antigravity.js +2 -1
  206. package/lib/yoke/adapters/claude-worker.js +11 -4
  207. package/lib/yoke/adapters/claude.js +141 -46
  208. package/lib/yoke/adapters/codex.js +79 -11
  209. package/lib/yoke/adapters/kiro.js +2 -1
  210. package/lib/yoke/claude-user-input.js +38 -0
  211. package/lib/yoke/codex-app-server.js +20 -2
  212. package/lib/yoke/codex-background-tasks.js +8 -2
  213. package/lib/yoke/codex-user-input.js +64 -0
  214. package/lib/yoke/complete-once.js +164 -0
  215. package/lib/yoke/index.js +5 -1
  216. package/lib/yoke/interface.js +19 -0
  217. package/lib/yoke/mcp-bridge-server.js +2 -6
  218. package/lib/yoke/user-input.js +254 -0
  219. package/package.json +4 -3
  220. package/lib/public/css/home-chat.css +0 -380
  221. package/lib/public/modules/home-chat.js +0 -438
  222. package/lib/server-clay-home.js +0 -245
@@ -0,0 +1,328 @@
1
+ // Project Logs — the project's ledger of agent-authored decisions and work.
2
+ //
3
+ // A bounded right-side workbench pane, like the document viewer and terminal.
4
+ // Inside it there is one navigation stack rather than a master/detail split:
5
+ // the list fills the pane, opening an entry replaces it with the full-pane
6
+ // record, and Back returns to the list with the query, filter, and scroll
7
+ // position intact.
8
+ //
9
+ // People read and comment. Canonical entries are created and revised only by
10
+ // this project's agent sessions through the clay-logs MCP tools, so this
11
+ // module has no create or edit affordance at all.
12
+
13
+ import { store } from './store.js';
14
+ import { getWs } from './ws-ref.js';
15
+ import { refreshIcons } from './icons.js';
16
+ import { showToast } from './utils.js';
17
+ import { closeScheduler } from './scheduler.js';
18
+ import { closeArchive, hideNotes } from './sticky-notes.js';
19
+ import { closeFileViewer } from './filebrowser.js';
20
+ import { closeTerminal } from './terminal.js';
21
+ import { renderFilter, renderList, renderDetail } from './project-logs-render.js';
22
+ import {
23
+ initProjectLogsAmbient, bindPanelHover, noteCanonicalUpdate,
24
+ acknowledgeUpdates, resetAmbient, syncAmbient, hidePreview, pinFromPreview
25
+ } from './project-logs-ambient.js';
26
+
27
+ var panel = null;
28
+ var listEl = null;
29
+ var detailEl = null;
30
+ var searchEl = null;
31
+ var filterEl = null;
32
+ var backBtn = null;
33
+ var searchTimer = null;
34
+ var requestCounter = 0;
35
+
36
+ function nextRequestId(prefix) {
37
+ requestCounter += 1;
38
+ return prefix + "-" + Date.now() + "-" + requestCounter;
39
+ }
40
+
41
+ function send(message) {
42
+ var ws = getWs();
43
+ if (!ws || ws.readyState !== 1) return false;
44
+ ws.send(JSON.stringify(message));
45
+ return true;
46
+ }
47
+
48
+ function ensurePanel() {
49
+ if (panel) return;
50
+ var panels = document.getElementById("main-panels");
51
+ if (!panels) return;
52
+ panel = document.createElement("section");
53
+ panel.id = "project-logs-panel";
54
+ panel.className = "project-logs-panel hidden";
55
+ panel.setAttribute("aria-label", "Project Logs");
56
+ panel.innerHTML =
57
+ '<header class="project-logs-topbar">' +
58
+ '<button id="project-logs-back" class="project-logs-icon-btn hidden" type="button" title="Back to the log list" aria-label="Back to the log list"><i data-lucide="arrow-left"></i></button>' +
59
+ '<span class="project-logs-title"><i data-lucide="notebook-tabs"></i>Logs</span>' +
60
+ '<span class="project-logs-subtitle">Decisions and work recorded by this project</span>' +
61
+ '<div class="project-logs-window-actions" aria-label="Project Logs window controls">' +
62
+ '<button id="project-logs-wide" class="project-logs-icon-btn" type="button" title="Widen panel" aria-label="Widen Logs panel" aria-pressed="false"><i data-lucide="chevrons-left-right"></i></button>' +
63
+ '<button id="project-logs-fullscreen" class="project-logs-icon-btn" type="button" title="Toggle fullscreen" aria-label="Toggle Logs fullscreen" aria-pressed="false"><i data-lucide="maximize-2"></i></button>' +
64
+ '<button id="project-logs-close" class="project-logs-icon-btn" type="button" title="Close Logs" aria-label="Close Logs"><i data-lucide="x"></i></button>' +
65
+ '</div>' +
66
+ '</header>' +
67
+ '<div id="project-logs-toolbar" class="project-logs-toolbar">' +
68
+ '<label class="project-logs-search"><i data-lucide="search"></i><input id="project-logs-search" type="search" placeholder="Search logs" autocomplete="off"></label>' +
69
+ '<div id="project-logs-filter-slot"></div>' +
70
+ '</div>' +
71
+ '<div id="project-logs-list" class="project-logs-list" role="list"></div>' +
72
+ '<main id="project-logs-detail" class="project-logs-detail hidden"></main>';
73
+ panels.appendChild(panel);
74
+
75
+ listEl = panel.querySelector("#project-logs-list");
76
+ detailEl = panel.querySelector("#project-logs-detail");
77
+ searchEl = panel.querySelector("#project-logs-search");
78
+ filterEl = panel.querySelector("#project-logs-filter-slot");
79
+ backBtn = panel.querySelector("#project-logs-back");
80
+
81
+ panel.querySelector("#project-logs-close").addEventListener("click", closeProjectLogs);
82
+ backBtn.addEventListener("click", function () { pinOnInteraction(); showList(); });
83
+ // Any meaningful interaction inside a preview commits to it.
84
+ searchEl.addEventListener("focus", pinOnInteraction);
85
+ panel.addEventListener("pointerdown", pinOnInteraction);
86
+ bindPanelHover(panel);
87
+ panel.querySelector("#project-logs-wide").addEventListener("click", function () {
88
+ applyWindowState(!store.get('projectLogsWide'), store.get('projectLogsFullscreen'));
89
+ });
90
+ panel.querySelector("#project-logs-fullscreen").addEventListener("click", function () {
91
+ applyWindowState(store.get('projectLogsWide'), !store.get('projectLogsFullscreen'));
92
+ });
93
+ searchEl.addEventListener("input", function () {
94
+ if (searchTimer) clearTimeout(searchTimer);
95
+ searchTimer = setTimeout(requestList, 180);
96
+ });
97
+ refreshIcons();
98
+ }
99
+
100
+ // A preview reveals the pane without committing to it: no acknowledgement, no
101
+ // open state, and no list refresh beyond what is already on screen.
102
+ function revealPreview() {
103
+ ensurePanel();
104
+ if (!panel) return;
105
+ panel.classList.remove("hidden");
106
+ panel.classList.add("project-logs-previewing");
107
+ applyWindowState(store.get('projectLogsWide'), false);
108
+ if (!store.get('projectLogsEntries') || !store.get('projectLogsEntries').length) requestList();
109
+ }
110
+
111
+ function hideRevealedPreview() {
112
+ if (!panel) return;
113
+ panel.classList.remove("project-logs-previewing");
114
+ if (store.get('projectLogsOpen')) return;
115
+ panel.classList.add("hidden");
116
+ }
117
+
118
+ // Width and fullscreen are transient view state, so they live in the store like
119
+ // every other mutable UI value. `panel-fullscreen` is the shared class the
120
+ // document viewer and terminal already use.
121
+ function applyWindowState(wide, fullscreen) {
122
+ store.set({ projectLogsWide: !!wide, projectLogsFullscreen: !!fullscreen });
123
+ if (!panel) return;
124
+ panel.classList.toggle("project-logs-wide", !!wide && !fullscreen);
125
+ panel.classList.toggle("panel-fullscreen", !!fullscreen);
126
+ var wideBtn = panel.querySelector("#project-logs-wide");
127
+ var fullBtn = panel.querySelector("#project-logs-fullscreen");
128
+ if (wideBtn) {
129
+ wideBtn.setAttribute("aria-pressed", wide ? "true" : "false");
130
+ wideBtn.disabled = !!fullscreen;
131
+ }
132
+ if (fullBtn) fullBtn.setAttribute("aria-pressed", fullscreen ? "true" : "false");
133
+ }
134
+
135
+ // --- Navigation stack ----------------------------------------------------
136
+
137
+ function showList() {
138
+ if (!panel) return;
139
+ store.set({ projectLogsView: "list", projectLogsSelectedRef: null });
140
+ detailEl.classList.add("hidden");
141
+ listEl.classList.remove("hidden");
142
+ panel.querySelector("#project-logs-toolbar").classList.remove("hidden");
143
+ backBtn.classList.add("hidden");
144
+ // The list is re-shown rather than rebuilt, so the query and filter survive;
145
+ // the scroll offset is restored from where the user left it.
146
+ listEl.scrollTop = store.get('projectLogsListScroll') || 0;
147
+ }
148
+
149
+ // A preview becomes a real open the moment the user does something with it.
150
+ function pinOnInteraction() {
151
+ if (store.get('projectLogsOpen')) return;
152
+ if (!store.get('projectLogsPreview')) return;
153
+ pinFromPreview();
154
+ }
155
+
156
+ function showDetail(entry) {
157
+ if (!panel || !entry) return;
158
+ store.set({ projectLogsListScroll: listEl.scrollTop, projectLogsView: "detail", projectLogsSelectedRef: entry.ref });
159
+ listEl.classList.add("hidden");
160
+ panel.querySelector("#project-logs-toolbar").classList.add("hidden");
161
+ detailEl.classList.remove("hidden");
162
+ detailEl.scrollTop = 0;
163
+ backBtn.classList.remove("hidden");
164
+ renderDetail(detailEl, entry, { onComment: submitComment });
165
+ }
166
+
167
+ // --- Requests ------------------------------------------------------------
168
+
169
+ function requestList() {
170
+ var requestId = nextRequestId("list-logs");
171
+ store.set({ projectLogsListRequestId: requestId });
172
+ send({
173
+ type: "project_logs_list",
174
+ requestId: requestId,
175
+ query: searchEl ? searchEl.value.trim() : "",
176
+ category: store.get('projectLogsCategory') || "",
177
+ });
178
+ }
179
+
180
+ function requestEntry(ref) {
181
+ var requestId = nextRequestId("read-log");
182
+ store.set({ projectLogsReadRequestId: requestId });
183
+ send({ type: "project_log_read", requestId: requestId, ref: ref });
184
+ }
185
+
186
+ function submitComment(ref, body, statusEl, inputEl) {
187
+ pinOnInteraction();
188
+ var requestId = nextRequestId("comment-log");
189
+ store.set({ projectLogsCommentRequestId: requestId, projectLogsCommentInput: inputEl || null });
190
+ if (!send({ type: "project_log_comment", requestId: requestId, ref: ref, body: body })) {
191
+ statusEl.textContent = "Logs are unavailable while disconnected.";
192
+ return;
193
+ }
194
+ statusEl.textContent = "Posting...";
195
+ store.set({ projectLogsCommentStatusEl: statusEl });
196
+ }
197
+
198
+ function applyFilter(category) {
199
+ pinOnInteraction();
200
+ store.set({ projectLogsCategory: category || "" });
201
+ requestList();
202
+ }
203
+
204
+ // --- Lifecycle -----------------------------------------------------------
205
+
206
+ export function initProjectLogs() {
207
+ var button = document.getElementById("project-logs-btn");
208
+ if (!button) return;
209
+ button.addEventListener("click", function () {
210
+ if (store.get('projectLogsOpen')) closeProjectLogs();
211
+ else openProjectLogs();
212
+ });
213
+ initProjectLogsAmbient({
214
+ reveal: revealPreview,
215
+ hide: hideRevealedPreview,
216
+ pin: openProjectLogs,
217
+ });
218
+ store.subscribe(function (state, previous) {
219
+ if (state.currentSlug === previous.currentSlug) return;
220
+ closeProjectLogs();
221
+ applyWindowState(false, false);
222
+ // A different project must never inherit the previous project's unread
223
+ // state or animate its updates.
224
+ resetAmbient();
225
+ store.set({
226
+ projectLogsEntries: [],
227
+ projectLogsSelectedRef: null,
228
+ projectLogsView: "list",
229
+ projectLogsCategory: "",
230
+ projectLogsListScroll: 0,
231
+ projectLogsListRequestId: null,
232
+ projectLogsReadRequestId: null,
233
+ projectLogsCommentRequestId: null
234
+ });
235
+ });
236
+ }
237
+
238
+ // The conversation, composer, and title bar stay mounted and visible: Logs
239
+ // opens beside them, it does not replace them.
240
+ export function openProjectLogs() {
241
+ ensurePanel();
242
+ if (!panel) return;
243
+ closeScheduler();
244
+ closeArchive();
245
+ hideNotes();
246
+ // Claim the single right workbench slot.
247
+ try { closeFileViewer(); } catch (e) {}
248
+ try { closeTerminal(); } catch (e) {}
249
+ panel.classList.remove("hidden");
250
+ panel.classList.remove("project-logs-previewing");
251
+ applyWindowState(store.get('projectLogsWide'), false);
252
+ var button = document.getElementById("project-logs-btn");
253
+ if (button) button.classList.add("active");
254
+ store.set({ projectLogsOpen: true, projectLogsPinned: true, projectLogsPreview: false });
255
+ // Opening the ledger is the acknowledgement; the ambient cues clear here and
256
+ // nowhere else.
257
+ acknowledgeUpdates();
258
+ showList();
259
+ requestList();
260
+ }
261
+
262
+ export function closeProjectLogs() {
263
+ hidePreview();
264
+ if (!store.get('projectLogsOpen')) return;
265
+ if (panel) {
266
+ panel.classList.add("hidden");
267
+ panel.classList.remove("project-logs-previewing");
268
+ }
269
+ // Fullscreen is always dropped on close so the next open is a bounded pane
270
+ // and never silently hides the conversation.
271
+ applyWindowState(store.get('projectLogsWide'), false);
272
+ var button = document.getElementById("project-logs-btn");
273
+ if (button) button.classList.remove("active");
274
+ store.set({ projectLogsOpen: false, projectLogsPinned: false });
275
+ syncAmbient();
276
+ }
277
+
278
+ // --- Server messages -----------------------------------------------------
279
+
280
+ export function handleProjectLogsState(msg) {
281
+ if (msg.requestId && msg.requestId !== store.get('projectLogsListRequestId')) return;
282
+ store.set({ projectLogsEntries: msg.entries || [] });
283
+ if (!listEl) return;
284
+ if (filterEl && Array.isArray(msg.categories)) {
285
+ renderFilter(filterEl, msg.categories, store.get('projectLogsCategory'), applyFilter);
286
+ }
287
+ renderList(listEl, msg.entries || [], requestEntry);
288
+ if (store.get('projectLogsView') !== "detail") listEl.scrollTop = store.get('projectLogsListScroll') || 0;
289
+ }
290
+
291
+ export function handleProjectLogEntry(msg) {
292
+ if (msg.requestId && msg.requestId !== store.get('projectLogsReadRequestId')) return;
293
+ if (!msg.entry) return;
294
+ showDetail(msg.entry);
295
+ }
296
+
297
+ export function handleProjectLogCommented(msg) {
298
+ if (msg.requestId && msg.requestId !== store.get('projectLogsCommentRequestId')) return;
299
+ if (!msg.entry) return;
300
+ // The comment is a proposal, not a change, so say so plainly rather than
301
+ // leaving the composer reading "Posting..." forever.
302
+ var pendingStatus = store.get('projectLogsCommentStatusEl');
303
+ if (pendingStatus) pendingStatus.textContent = "Awaiting Project Driver review";
304
+ store.set({ projectLogsCommentStatusEl: null });
305
+ // Re-render the open entry so the new comment and its attribution appear,
306
+ // then refresh the ledger so the comment count is current.
307
+ showDetail(msg.entry);
308
+ requestList();
309
+ }
310
+
311
+ // A canonical revision landed. This marks the ambient cues and never opens,
312
+ // focuses, or scrolls anything.
313
+ export function handleProjectLogUpdated(msg) {
314
+ noteCanonicalUpdate(msg);
315
+ // Refresh the ledger only when it is already on screen, so the row moves to
316
+ // the top the moment its canonical update arrives.
317
+ if (store.get('projectLogsOpen') && store.get('projectLogsView') !== "detail") requestList();
318
+ }
319
+
320
+ export function handleProjectLogsError(msg) {
321
+ var pending = [
322
+ store.get('projectLogsListRequestId'),
323
+ store.get('projectLogsReadRequestId'),
324
+ store.get('projectLogsCommentRequestId')
325
+ ];
326
+ if (msg.requestId && pending.indexOf(msg.requestId) === -1) return;
327
+ showToast(msg.message || "Project Logs could not complete the request.", "error");
328
+ }
@@ -560,6 +560,15 @@ export function handleInstructionsWrite(msg) {
560
560
 
561
561
  // ===== Environment (key-value list) =====
562
562
  var envSaveTimer = null;
563
+ var envStatusTimer = null;
564
+
565
+ function showEnvSaveStatus(elementId, text, duration) {
566
+ var saveStatus = document.getElementById(elementId);
567
+ if (!saveStatus) return;
568
+ if (envStatusTimer) clearTimeout(envStatusTimer);
569
+ saveStatus.textContent = text;
570
+ envStatusTimer = setTimeout(function () { saveStatus.textContent = ""; }, duration);
571
+ }
563
572
 
564
573
  function loadEnvironment() {
565
574
  var saveStatus = document.getElementById("ps-env-save-status");
@@ -571,9 +580,6 @@ function loadEnvironment() {
571
580
  }
572
581
 
573
582
  export function handleProjectEnv(msg) {
574
- var notice = document.getElementById("ps-env-override-notice");
575
- if (notice) notice.classList.toggle("hidden", !msg.hasEnvrc);
576
-
577
583
  // Parse envrc string into key-value pairs
578
584
  var list = document.getElementById("ps-env-list");
579
585
  if (!list) return;
@@ -606,9 +612,10 @@ export function parseEnvString(str) {
606
612
  if (eq === -1) continue;
607
613
  var key = line.substring(0, eq).trim();
608
614
  var val = line.substring(eq + 1).trim();
609
- // Strip surrounding quotes
610
- if ((val.charAt(0) === '"' && val.charAt(val.length - 1) === '"') ||
611
- (val.charAt(0) === "'" && val.charAt(val.length - 1) === "'")) {
615
+ // Saved values are JSON-quoted so shell metacharacters remain data.
616
+ if (val.charAt(0) === '"' && val.charAt(val.length - 1) === '"') {
617
+ try { val = JSON.parse(val); } catch (e) { val = val.substring(1, val.length - 1); }
618
+ } else if (val.charAt(0) === "'" && val.charAt(val.length - 1) === "'") {
612
619
  val = val.substring(1, val.length - 1);
613
620
  }
614
621
  if (key) pairs.push({ key: key, value: val });
@@ -616,6 +623,10 @@ export function parseEnvString(str) {
616
623
  return pairs;
617
624
  }
618
625
 
626
+ export function serializeEnvValue(value) {
627
+ return JSON.stringify(value == null ? "" : String(value));
628
+ }
629
+
619
630
  function buildEnvString() {
620
631
  var list = document.getElementById("ps-env-list");
621
632
  if (!list) return "";
@@ -626,7 +637,7 @@ function buildEnvString() {
626
637
  var valInput = rows[i].querySelector(".ps-env-val");
627
638
  var key = keyInput ? keyInput.value.trim() : "";
628
639
  var val = valInput ? valInput.value : "";
629
- if (key) lines.push("export " + key + "=" + val);
640
+ if (key) lines.push("export " + key + "=" + serializeEnvValue(val));
630
641
  }
631
642
  return lines.join("\n");
632
643
  }
@@ -719,28 +730,30 @@ function autoSaveEnv() {
719
730
  var envrc = buildEnvString();
720
731
  if (ctx.ws && ctx.connected) {
721
732
  ctx.ws.send(JSON.stringify({ type: "set_project_env", slug: currentSlug, envrc: envrc }));
722
- var saveStatus = document.getElementById("ps-env-save-status");
723
- if (saveStatus) {
724
- saveStatus.textContent = "Saved";
725
- setTimeout(function () { saveStatus.textContent = ""; }, 2000);
726
- }
733
+ showEnvSaveStatus("ps-env-save-status", "Saving...", 2000);
727
734
  }
728
735
  }, 800);
729
736
  }
730
737
 
731
738
  export function handleProjectEnvSaved(msg) {
732
- var saveStatus = document.getElementById("ps-env-save-status");
733
- if (!saveStatus) return;
734
739
  if (msg.ok) {
735
- saveStatus.textContent = "Saved";
736
- setTimeout(function () { saveStatus.textContent = ""; }, 2000);
740
+ showEnvSaveStatus("ps-env-save-status", msg.timing || "Saved", 5000);
737
741
  } else {
738
- saveStatus.textContent = "Error: " + (msg.error || "Failed to save");
742
+ showEnvSaveStatus("ps-env-save-status", "Error: " + (msg.error || "Failed to save"), 5000);
739
743
  }
740
744
  }
741
745
 
742
746
  // ===== Shared Environment (via tabs) =====
743
747
  var sharedEnvSaveTimer = null;
748
+ var sharedEnvStatusTimer = null;
749
+
750
+ function showSharedEnvSaveStatus(text, duration) {
751
+ var saveStatus = document.getElementById("ps-shared-env-save-status");
752
+ if (!saveStatus) return;
753
+ if (sharedEnvStatusTimer) clearTimeout(sharedEnvStatusTimer);
754
+ saveStatus.textContent = text;
755
+ sharedEnvStatusTimer = setTimeout(function () { saveStatus.textContent = ""; }, duration);
756
+ }
744
757
 
745
758
  function loadSharedEnv() {
746
759
  var saveStatus = document.getElementById("ps-shared-env-save-status");
@@ -764,13 +777,10 @@ export function handleProjectSharedEnv(msg) {
764
777
  }
765
778
 
766
779
  export function handleProjectSharedEnvSaved(msg) {
767
- var saveStatus = document.getElementById("ps-shared-env-save-status");
768
- if (!saveStatus) return;
769
780
  if (msg.ok) {
770
- saveStatus.textContent = "Saved";
771
- setTimeout(function () { saveStatus.textContent = ""; }, 2000);
781
+ showSharedEnvSaveStatus(msg.timing || "Saved", 5000);
772
782
  } else {
773
- saveStatus.textContent = "Error: " + (msg.error || "Failed to save");
783
+ showSharedEnvSaveStatus("Error: " + (msg.error || "Failed to save"), 5000);
774
784
  }
775
785
  }
776
786
 
@@ -784,7 +794,7 @@ function buildSharedEnvString() {
784
794
  var valInput = rows[i].querySelector(".ps-env-val");
785
795
  var key = keyInput ? keyInput.value.trim() : "";
786
796
  var val = valInput ? valInput.value : "";
787
- if (key) lines.push("export " + key + "=" + val);
797
+ if (key) lines.push("export " + key + "=" + serializeEnvValue(val));
788
798
  }
789
799
  return lines.join("\n");
790
800
  }
@@ -873,11 +883,7 @@ function autoSaveSharedEnv() {
873
883
  var envrc = buildSharedEnvString();
874
884
  if (ctx.ws && ctx.connected) {
875
885
  ctx.ws.send(JSON.stringify({ type: "set_shared_env", envrc: envrc }));
876
- var saveStatus = document.getElementById("ps-shared-env-save-status");
877
- if (saveStatus) {
878
- saveStatus.textContent = "Saved";
879
- setTimeout(function () { saveStatus.textContent = ""; }, 2000);
880
- }
886
+ showSharedEnvSaveStatus("Saving...", 2000);
881
887
  }
882
888
  }, 800);
883
889
  }
@@ -2,7 +2,7 @@
2
2
  //
3
3
  // Two modes share the same overlay:
4
4
  // Cmd/Ctrl+E → projects only (non-Mate), activates via switchProject
5
- // Cmd/Ctrl+M → Mates only, activates via openDm
5
+ // Cmd/Ctrl+M → Mates only, activates in the home conversation pane
6
6
  //
7
7
  // While the modifier is still held, repeating the same shortcut cycles
8
8
  // forward and Shift cycles back. Releasing the modifier commits —
@@ -16,7 +16,8 @@
16
16
  import { store } from './store.js';
17
17
  import { refreshIcons } from './icons.js';
18
18
  import { getCachedProjects, switchProject } from './app-projects.js';
19
- import { openDm } from './app-dm.js';
19
+ import { showHomeHub } from './app-home-hub.js';
20
+ import { openHomeChat } from './home-mate-chat.js';
20
21
  import { mateAvatarUrl } from './avatar.js';
21
22
  import { parseEmojis } from './markdown.js';
22
23
  import { isExternalWorktree, externalWorktreeTooltip, appendExternalWorktreeBadge } from './worktree-location.js';
@@ -33,6 +34,7 @@ var _entries = [];
33
34
 
34
35
  export function openSwitcherForMode(mode) {
35
36
  if (mode !== 'project' && mode !== 'mate') return;
37
+ if (mode === 'mate' && store.get('projectMateDmsEnabled') === false) return;
36
38
  if (_open) closeSwitcher(false);
37
39
  openSwitcher(mode);
38
40
  }
@@ -93,11 +95,11 @@ export function initProjectSwitcher() {
93
95
  function refreshMateHintVisibility() {
94
96
  if (!mateHint) return;
95
97
  var mates = store.get('cachedMatesList') || [];
96
- mateHint.classList.toggle('hidden', mates.length === 0);
98
+ mateHint.classList.toggle('hidden', mates.length === 0 || store.get('projectMateDmsEnabled') === false);
97
99
  }
98
100
  refreshMateHintVisibility();
99
101
  store.subscribe(function (state, prev) {
100
- if (state.cachedMatesList !== prev.cachedMatesList) refreshMateHintVisibility();
102
+ if (state.cachedMatesList !== prev.cachedMatesList || state.projectMateDmsEnabled !== prev.projectMateDmsEnabled) refreshMateHintVisibility();
101
103
  });
102
104
  }
103
105
 
@@ -143,6 +145,7 @@ function handleKeyDown(e) {
143
145
  // same shortcut cycles.
144
146
  if (isMod && (k === 'e' || k === 'm')) {
145
147
  var requestedMode = k === 'e' ? 'project' : 'mate';
148
+ if (requestedMode === 'mate' && store.get('projectMateDmsEnabled') === false) return;
146
149
  e.preventDefault();
147
150
  e.stopPropagation();
148
151
  if (!_open) {
@@ -298,7 +301,10 @@ function closeSwitcher(commit) {
298
301
  var entry = _entries[_highlightedIndex];
299
302
  if (!entry || entry.disabled) return;
300
303
  if (_mode === 'mate') {
301
- if (entry.key !== currentActiveKey()) openDm(entry.key);
304
+ if (entry.key !== currentActiveKey()) {
305
+ showHomeHub();
306
+ openHomeChat(entry.key);
307
+ }
302
308
  } else {
303
309
  if (entry.key !== store.get('currentSlug')) switchProject(entry.key);
304
310
  }
@@ -0,0 +1,23 @@
1
+ import { addToMessages, scrollToBottom } from './app-rendering.js';
2
+ import { createWorkspaceAssignmentCard, showWorkspaceAssignmentError } from './workspace-assignment-card.js';
3
+
4
+ function findCard(assignmentId) {
5
+ var cards = document.querySelectorAll(".workspace-assignment-card:not(.home-workspace-assignment)");
6
+ for (var i = 0; i < cards.length; i++) if (cards[i].dataset.assignmentId === assignmentId) return cards[i];
7
+ return null;
8
+ }
9
+
10
+ export function renderProjectWorkspaceAssignment(msg) {
11
+ var assignment = msg && msg.assignment;
12
+ if (!assignment || !assignment.assignmentId) return false;
13
+ var previous = findCard(assignment.assignmentId);
14
+ var card = createWorkspaceAssignmentCard(assignment, { home: false });
15
+ if (previous && previous.parentNode) previous.parentNode.replaceChild(card, previous);
16
+ else addToMessages(card);
17
+ scrollToBottom();
18
+ return true;
19
+ }
20
+
21
+ export function showProjectWorkspaceAssignmentError(msg) {
22
+ return showWorkspaceAssignmentError(findCard(msg && msg.assignmentId), msg || {});
23
+ }
@@ -125,8 +125,8 @@ export function initScheduler(_ctx) {
125
125
  function ensurePanel() {
126
126
  if (panel) return;
127
127
 
128
- var appEl = document.getElementById("app");
129
- if (!appEl) return;
128
+ var hostEl = document.getElementById("main-area");
129
+ if (!hostEl) return;
130
130
 
131
131
  panel = document.createElement("div");
132
132
  panel.id = "scheduler-panel";
@@ -256,7 +256,7 @@ function ensurePanel() {
256
256
  bodyRow.appendChild(content);
257
257
  panel.appendChild(bodyRow);
258
258
 
259
- appEl.appendChild(panel);
259
+ hostEl.appendChild(panel);
260
260
 
261
261
  // --- Close button (in top bar) ---
262
262
  panel.querySelector("#scheduler-panel-close").addEventListener("click", function () {
@@ -383,6 +383,8 @@ function openScheduler() {
383
383
 
384
384
  var sidebarBtn = document.getElementById("scheduler-btn");
385
385
  if (sidebarBtn) sidebarBtn.classList.add("active");
386
+ var homeBtn = document.getElementById("home-scheduler-btn");
387
+ if (homeBtn) homeBtn.classList.add("active");
386
388
 
387
389
  // Persist scheduler state in URL hash
388
390
  if (location.hash !== "#scheduler") {
@@ -423,6 +425,8 @@ export function closeScheduler() {
423
425
  // Un-mark sidebar button
424
426
  var sidebarBtn = document.getElementById("scheduler-btn");
425
427
  if (sidebarBtn) sidebarBtn.classList.remove("active");
428
+ var homeBtn = document.getElementById("home-scheduler-btn");
429
+ if (homeBtn) homeBtn.classList.remove("active");
426
430
 
427
431
  // Remove scheduler hash from URL
428
432
  if (location.hash === "#scheduler") {
@@ -1470,6 +1474,16 @@ export function openSchedulerToTab(tab) {
1470
1474
  }
1471
1475
  }
1472
1476
 
1477
+ export function openHomeScheduler() {
1478
+ if (!ctx) return;
1479
+ ctx.requireClayRalph(function () {
1480
+ showAllProjects = true;
1481
+ openSchedulerToTab("calendar");
1482
+ renderSidebar();
1483
+ render();
1484
+ });
1485
+ }
1486
+
1473
1487
  export function isSchedulerOpen() {
1474
1488
  return panelOpen;
1475
1489
  }