clay-server 4.1.0-beta.5 → 4.1.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 (155) hide show
  1. package/LICENSE +661 -21
  2. package/LICENSES/MIT-legacy.txt +21 -0
  3. package/LICENSES/README.md +7 -0
  4. package/README.md +2 -2
  5. package/bin/cli.js +0 -2
  6. package/lib/autonomous-run-record.js +32 -0
  7. package/lib/autonomous-run-scheduler.js +92 -0
  8. package/lib/daemon.js +0 -2
  9. package/lib/default-ai-runtime.js +117 -0
  10. package/lib/durable-scheduler-record.js +61 -0
  11. package/lib/durable-scheduler-store.js +185 -0
  12. package/lib/durable-scheduler.js +495 -0
  13. package/lib/git-cli.js +1 -0
  14. package/lib/issue-work-session.js +10 -0
  15. package/lib/issues-comments.js +40 -0
  16. package/lib/issues-commit-verification.js +30 -0
  17. package/lib/issues-mcp-server.js +27 -0
  18. package/lib/issues-schema.js +119 -0
  19. package/lib/issues-service.js +339 -0
  20. package/lib/issues-store.js +210 -0
  21. package/lib/loop-cron.js +47 -0
  22. package/lib/loop-durable-schedule.js +159 -0
  23. package/lib/loop-guidance.js +35 -0
  24. package/lib/loop-registry-occurrence.js +76 -0
  25. package/lib/model-entry.js +15 -0
  26. package/lib/project-autonomous-run.js +498 -0
  27. package/lib/project-capsule-turn.js +1 -0
  28. package/lib/project-connection.js +6 -1
  29. package/lib/project-delegated-session.js +1 -0
  30. package/lib/project-filesystem.js +10 -10
  31. package/lib/project-full-access.js +86 -0
  32. package/lib/project-http.js +3 -1
  33. package/lib/project-issue-launch.js +154 -0
  34. package/lib/project-issues.js +58 -0
  35. package/lib/project-logs-mcp-server.js +8 -12
  36. package/lib/project-loop-interview.js +163 -0
  37. package/lib/project-loop.js +150 -64
  38. package/lib/project-message-delivery.js +4 -2
  39. package/lib/project-models.js +71 -3
  40. package/lib/project-pair-autonomous-stop.js +16 -0
  41. package/lib/project-pair-owned-stop.js +18 -0
  42. package/lib/project-pair-task-control.js +14 -1
  43. package/lib/project-pending-message-queue.js +264 -0
  44. package/lib/project-scheduled-messages.js +316 -0
  45. package/lib/project-scheduled-task-execution.js +236 -0
  46. package/lib/project-scheduled-tasks.js +481 -0
  47. package/lib/project-session-pair.js +16 -19
  48. package/lib/project-sessions.js +114 -31
  49. package/lib/project-tool-llm-runtime.js +48 -0
  50. package/lib/project-user-message.js +202 -60
  51. package/lib/project-workspace-query.js +25 -0
  52. package/lib/project.js +361 -69
  53. package/lib/public/app.js +85 -22
  54. package/lib/public/css/default-ai.css +33 -0
  55. package/lib/public/css/home-hub.css +15 -2
  56. package/lib/public/css/home-sidebar.css +50 -2
  57. package/lib/public/css/input.css +160 -5
  58. package/lib/public/css/issues.css +101 -0
  59. package/lib/public/css/messages.css +19 -0
  60. package/lib/public/css/pane.css +58 -7
  61. package/lib/public/css/pending-message-queue.css +187 -0
  62. package/lib/public/css/rewind.css +11 -2
  63. package/lib/public/css/scheduled-tasks.css +122 -0
  64. package/lib/public/index.html +42 -150
  65. package/lib/public/modules/app-connection.js +3 -0
  66. package/lib/public/modules/app-cursors.js +29 -12
  67. package/lib/public/modules/app-loop-ui.js +4 -8
  68. package/lib/public/modules/app-message-router.js +8 -0
  69. package/lib/public/modules/app-messages.js +34 -9
  70. package/lib/public/modules/app-projects.js +2 -0
  71. package/lib/public/modules/app-rate-limit.js +8 -5
  72. package/lib/public/modules/autonomous-run.js +285 -0
  73. package/lib/public/modules/clay-file-links.js +111 -0
  74. package/lib/public/modules/clay-issue-links.js +131 -0
  75. package/lib/public/modules/clay-log-links.js +2 -0
  76. package/lib/public/modules/cursor-sharing-preference.js +38 -0
  77. package/lib/public/modules/default-ai.js +377 -0
  78. package/lib/public/modules/filebrowser.js +43 -2
  79. package/lib/public/modules/home-sidebar.js +16 -8
  80. package/lib/public/modules/home-surface.js +5 -0
  81. package/lib/public/modules/input.js +11 -20
  82. package/lib/public/modules/issues-context.js +117 -0
  83. package/lib/public/modules/issues-render.js +29 -0
  84. package/lib/public/modules/issues.js +270 -0
  85. package/lib/public/modules/loop-interview.js +151 -0
  86. package/lib/public/modules/markdown.js +4 -0
  87. package/lib/public/modules/message-delivery.js +5 -3
  88. package/lib/public/modules/pending-message-image-editor.js +143 -0
  89. package/lib/public/modules/pending-message-queue-model.js +22 -0
  90. package/lib/public/modules/pending-message-queue.js +449 -0
  91. package/lib/public/modules/project-logs.js +2 -2
  92. package/lib/public/modules/scheduled-message-state.js +46 -0
  93. package/lib/public/modules/scheduled-task-edit-state.js +7 -0
  94. package/lib/public/modules/scheduled-task-engine.js +48 -0
  95. package/lib/public/modules/scheduled-task-form.js +122 -0
  96. package/lib/public/modules/scheduled-task-recurrence.js +46 -0
  97. package/lib/public/modules/scheduled-task-schedule-rule.js +115 -0
  98. package/lib/public/modules/scheduled-tasks.js +459 -0
  99. package/lib/public/modules/scheduler-config.js +6 -52
  100. package/lib/public/modules/scheduler.js +9 -21
  101. package/lib/public/modules/sidebar-mobile.js +3 -0
  102. package/lib/public/modules/sidebar.js +0 -11
  103. package/lib/public/modules/split-view.js +2 -0
  104. package/lib/public/modules/sticky-note-markdown.js +1 -0
  105. package/lib/public/modules/terminal.js +2 -0
  106. package/lib/public/modules/tool-palette-order.js +3 -3
  107. package/lib/public/modules/tools.js +5 -2
  108. package/lib/public/style.css +3 -0
  109. package/lib/record-revision.js +11 -0
  110. package/lib/schedule-rule.js +151 -0
  111. package/lib/schedule-validation.js +124 -0
  112. package/lib/scheduled-run-state.js +87 -0
  113. package/lib/scheduled-task-records.js +127 -0
  114. package/lib/scheduler.js +179 -196
  115. package/lib/sdk-bridge.js +157 -22
  116. package/lib/sdk-message-processor.js +3 -0
  117. package/lib/server-cursor-sharing.js +65 -0
  118. package/lib/server-default-ai.js +265 -0
  119. package/lib/server-durable-scheduler.js +26 -0
  120. package/lib/server-home-chat.js +29 -9
  121. package/lib/server-home-clay-entry.js +4 -1
  122. package/lib/server-home-debate-planning.js +32 -14
  123. package/lib/server-home-mate-creation.js +30 -11
  124. package/lib/server-home-models.js +14 -6
  125. package/lib/server-issue-links.js +18 -0
  126. package/lib/server.js +63 -0
  127. package/lib/session-notes-mcp-server.js +1 -1
  128. package/lib/session-pair-factory.js +42 -4
  129. package/lib/session-pair-query-start.js +22 -0
  130. package/lib/session-query-tool-bridge.js +52 -0
  131. package/lib/session-search.js +4 -1
  132. package/lib/session-split-groups.js +35 -1
  133. package/lib/sessions.js +51 -3
  134. package/lib/tool-llm.js +1 -0
  135. package/lib/users-default-ai-preferences.js +80 -0
  136. package/lib/users-home-surface-preferences.js +2 -1
  137. package/lib/users-preferences.js +35 -0
  138. package/lib/users-scheduled-task-preferences.js +44 -0
  139. package/lib/users.js +16 -0
  140. package/lib/workspace-query-projection.js +29 -0
  141. package/lib/workspace-query-service.js +19 -29
  142. package/lib/ws-schema.js +61 -1
  143. package/lib/yoke/adapters/antigravity.js +1 -0
  144. package/lib/yoke/adapters/claude-worker.js +11 -6
  145. package/lib/yoke/adapters/claude.js +35 -1
  146. package/lib/yoke/adapters/codex.js +3 -0
  147. package/lib/yoke/claude-user-input.js +2 -0
  148. package/lib/yoke/index.js +2 -0
  149. package/lib/yoke/mcp-bridge-server.js +7 -1
  150. package/lib/yoke/package.json +1 -1
  151. package/lib/yoke/session-tool-transport.js +53 -0
  152. package/lib/yoke/user-input.js +21 -7
  153. package/package.json +5 -6
  154. package/bin/claude-relay.js +0 -6
  155. package/lib/public/modules/app-loop-wizard.js +0 -439
@@ -0,0 +1,498 @@
1
+ var crypto = require("crypto");
2
+ var driverEligibility = require("./session-driver-eligibility");
3
+ var attachWakeScheduler = require("./autonomous-run-scheduler").attachAutonomousRunScheduler;
4
+ var runRecord = require("./autonomous-run-record");
5
+
6
+ var DEFAULT_CONTINUATIONS = 10;
7
+ var DEFAULT_MINUTES = 60;
8
+ var ACTIVE_STATES = ["armed", "running", "reviewing", "waiting-worker", "waiting-user", "paused"];
9
+ var TERMINAL_STATES = ["completed", "stopped", "limit", "error"];
10
+ function cleanText(value, max) {
11
+ return typeof value === "string" ? value.trim().substring(0, max) : "";
12
+ }
13
+ function boundedInt(value, fallback, max) {
14
+ var number = Number(value);
15
+ return Number.isFinite(number) ? Math.max(1, Math.min(max, Math.floor(number))) : fallback;
16
+ }
17
+ function cleanCriteria(value) {
18
+ var criteria = Array.isArray(value) ? value : [];
19
+ return criteria.slice(0, 10).map(function (item) { return cleanText(item, 1000); }).filter(Boolean);
20
+ }
21
+ var project = runRecord.project;
22
+ var restoreStoredRun = runRecord.restore;
23
+ function attachAutonomousRun(ctx) {
24
+ var sm = ctx.sm;
25
+ var sendTo = ctx.sendTo;
26
+ var fullAccess = ctx.fullAccess;
27
+ var timers = new Map();
28
+ var actions = new Map();
29
+ var wakeScheduler = null;
30
+
31
+ function serialize(session, action) {
32
+ if (!session) return Promise.reject(new Error("No session was bound to this request."));
33
+ var prior = actions.get(session.localId) || Promise.resolve();
34
+ var next = prior.catch(function () {}).then(action);
35
+ actions.set(session.localId, next);
36
+ return next.finally(function () { if (actions.get(session.localId) === next) actions.delete(session.localId); });
37
+ }
38
+ function broadcast(session) {
39
+ sm.sendToSession(session, { type: "autonomous_run_state", sessionId: session.localId, run: project(session.autonomousRun) });
40
+ sm.broadcastSessionList();
41
+ }
42
+ function persist(session) { sm.saveSessionFile(session); broadcast(session); }
43
+ function clearTimer(session) {
44
+ if (wakeScheduler && session && session.autonomousRun) {
45
+ try { wakeScheduler.cancel(session, session.autonomousRun); }
46
+ catch (error) { session.autonomousRun.schedulerError = cleanText(error.message || String(error), 1000); }
47
+ }
48
+ var timer = timers.get(session.localId);
49
+ if (timer) clearTimeout(timer);
50
+ timers.delete(session.localId);
51
+ }
52
+
53
+ function scheduleLimit(session) {
54
+ var run = session.autonomousRun;
55
+ if (!run || TERMINAL_STATES.indexOf(run.state) !== -1 || run.state === "paused" || !run.deadlineAt) return;
56
+ var remaining = run.deadlineAt - Date.now();
57
+ if (remaining <= 0) { finish(session, "limit", "The wall-clock limit was reached."); return; }
58
+ if (wakeScheduler) { wakeScheduler.schedule(session, run, "deadline", run.deadlineAt); return; }
59
+ clearTimer(session);
60
+ timers.set(session.localId, setTimeout(function () { finish(session, "limit", "The wall-clock limit was reached."); }, Math.min(remaining, 2147483647)));
61
+ }
62
+
63
+ function eligible(session) {
64
+ return !ctx.isMate && !!session && (session.runtimeMode || session.mode) === "gui" && driverEligibility.isEligibleDriverSession(session);
65
+ }
66
+
67
+ function owns(ws, session, msg) {
68
+ if (!session || !msg || Number(msg.sessionId) !== Number(session.localId)) return false;
69
+ return !ctx.canAccess || ctx.canAccess(ws, session);
70
+ }
71
+
72
+ function ownsRun(session, msg) {
73
+ return !!(session && session.autonomousRun && cleanText(msg && msg.runId, 200) === session.autonomousRun.id);
74
+ }
75
+
76
+ function removeRunOwnedPending(session, runId) {
77
+ if (!Array.isArray(session.pendingPush)) return;
78
+ session.pendingPush = session.pendingPush.filter(function (item) { return item.autonomousRunId !== runId; });
79
+ }
80
+
81
+ async function finishNow(session, state, reason, outcome) {
82
+ var run = session && session.autonomousRun;
83
+ if (!run || TERMINAL_STATES.indexOf(run.state) !== -1) return false;
84
+ run.cancellationEpoch = (run.cancellationEpoch || 0) + 1;
85
+ run.continuationPending = false;
86
+ run.completionRequested = false;
87
+ run.state = state;
88
+ run.terminalReason = cleanText(reason, 2000);
89
+ run.finishedAt = Date.now();
90
+ if (outcome) run.outcome = outcome;
91
+ clearTimer(session);
92
+ removeRunOwnedPending(session, run.id);
93
+ if (typeof ctx.stopPair === "function") ctx.stopPair(session);
94
+ if (state !== "completed" && (session.isProcessing || session._queryStarting)) {
95
+ session.taskStopRequested = true;
96
+ if (session.abortController) session.abortController.abort();
97
+ }
98
+ try {
99
+ await fullAccess.restore(session, run.permissionSnapshot);
100
+ } catch (error) {
101
+ run.permissionRestoreError = cleanText(error.message || String(error), 1000);
102
+ run.state = "error";
103
+ run.terminalReason = "The run ended, but Skip Permissions could not be restored: " + run.permissionRestoreError;
104
+ }
105
+ persist(session);
106
+ return true;
107
+ }
108
+
109
+ function finish(session, state, reason, outcome) {
110
+ return serialize(session, function () { return finishNow(session, state, reason, outcome); });
111
+ }
112
+
113
+ function finishOwned(session, runId, state, reason, outcome) {
114
+ return serialize(session, function () {
115
+ if (!session.autonomousRun || session.autonomousRun.id !== runId) return false;
116
+ return finishNow(session, state, reason, outcome);
117
+ });
118
+ }
119
+
120
+ async function armNow(ws, session, msg, options) {
121
+ if (!owns(ws, session, msg)) throw new Error("The requested session is not authorized.");
122
+ if (!eligible(session)) throw new Error("Until complete is only available in a project GUI Driver session.");
123
+ if (session.isProcessing || session._queryStarting) throw new Error("Wait for the current task to finish before selecting Until complete.");
124
+ var existing = session.autonomousRun;
125
+ if (existing && ACTIVE_STATES.indexOf(existing.state) !== -1) throw new Error("This session already has an active Until complete run.");
126
+ var permissionSnapshot = fullAccess.snapshot(session);
127
+ await fullAccess.setEnabled(session, true);
128
+ try {
129
+ var run = {
130
+ version: 1, id: crypto.randomUUID(), armToken: crypto.randomUUID(), state: "armed",
131
+ objective: "", successCriteria: cleanCriteria(msg.successCriteria), continuationCount: 0,
132
+ maxContinuations: boundedInt(msg.maxContinuations, DEFAULT_CONTINUATIONS, 100),
133
+ maxMinutes: boundedInt(msg.maxMinutes, DEFAULT_MINUTES, 1440),
134
+ permissionSnapshot: permissionSnapshot, createdAt: Date.now(), cancellationEpoch: 0,
135
+ continuationPending: false, wakeSequence: 0,
136
+ };
137
+ if (run.successCriteria.length === 0) run.successCriteria = ["The requested outcome is implemented and meaningfully verified."];
138
+ session.autonomousRun = run;
139
+ persist(session);
140
+ if (!options || !options.deferResult) sendTo(ws, { type: "autonomous_run_arm_result", ok: true, sessionId: session.localId, requestId: cleanText(msg.requestId, 200), runId: run.id, armToken: run.armToken, run: project(run) });
141
+ } catch (error) {
142
+ session.autonomousRun = null;
143
+ await fullAccess.restore(session, permissionSnapshot);
144
+ throw error;
145
+ }
146
+ }
147
+
148
+ async function disarmNow(ws, session, msg) {
149
+ if (!owns(ws, session, msg)) throw new Error("The requested session is not authorized.");
150
+ var run = session.autonomousRun;
151
+ if (!run || run.state !== "armed") throw new Error("There is no armed Until complete task to deselect.");
152
+ run.cancellationEpoch = (run.cancellationEpoch || 0) + 1;
153
+ await fullAccess.restore(session, run.permissionSnapshot);
154
+ session.autonomousRun = null;
155
+ persist(session);
156
+ }
157
+
158
+ function consume(session, msg) {
159
+ var run = session && session.autonomousRun;
160
+ if (!run || session._autonomousLifecyclePending || !eligible(session) || run.state !== "armed" || msg.autonomousRunToken !== run.armToken) return false;
161
+ run.state = "running";
162
+ run.objective = cleanText(msg.text, 20000) || "Complete the submitted task.";
163
+ if (run.successCriteria.length === 0) run.successCriteria = ["The requested outcome is implemented and meaningfully verified."];
164
+ run.startedAt = Date.now();
165
+ run.deadlineAt = run.startedAt + run.maxMinutes * 60000;
166
+ delete run.armToken;
167
+ persist(session);
168
+ try { scheduleLimit(session); } catch (error) { finish(session, "error", "The run deadline could not be stored: " + (error.message || error)); return false; }
169
+ return true;
170
+ }
171
+
172
+ function startApprovedBrief(ws, session, brief, requestId, isStillApproved) {
173
+ return serialize(session, async function () {
174
+ if (!owns(ws, session, { sessionId: session && session.localId }) || !eligible(session)) throw new Error("The requested session is not authorized.");
175
+ if (session.loopInterviewHandoff && session.loopInterviewHandoff.requestId === cleanText(requestId, 200) && session.autonomousRun) {
176
+ sendTo(ws, { type: "loop_interview_start_run_result", ok: true, duplicate: true, sessionId: session.localId, requestId: cleanText(requestId, 200), run: project(session.autonomousRun) });
177
+ return true;
178
+ }
179
+ if (session.isProcessing || session._queryStarting) throw new Error("Wait for the current task to finish before starting the Loop run.");
180
+ if (session.autonomousRun && ACTIVE_STATES.indexOf(session.autonomousRun.state) !== -1) throw new Error("This session already has an active Until complete run.");
181
+ session.loopInterviewHandoff = { version: 1, proposalId: brief.id, proposalVersion: brief.version, requestId: cleanText(requestId, 200), state: "starting", createdAt: Date.now() };
182
+ sm.saveSessionFile(session);
183
+ try {
184
+ await armNow(ws, session, { sessionId: session.localId, requestId: requestId, successCriteria: brief.successCriteria, maxContinuations: brief.maxContinuations, maxMinutes: brief.maxMinutes }, { deferResult: true });
185
+ if (!owns(ws, session, { sessionId: session.localId }) || !eligible(session) || session.isProcessing || session._queryStarting) throw new Error("The session changed while permissions were being prepared.");
186
+ if (typeof isStillApproved === "function" && !isStillApproved()) throw new Error("This Loop brief was changed or cancelled while permissions were being prepared.");
187
+ var run = session.autonomousRun;
188
+ if (!run || !run.armToken || !consume(session, { text: brief.objective, autonomousRunToken: run.armToken })) throw new Error("The approved Loop brief could not be consumed.");
189
+ var entry = { type: "user_message", text: "[Approved Loop run started]\n\n" + brief.objective, source: "loop_interview_run", proposalId: brief.id, proposalVersion: brief.version, _ts: Date.now() };
190
+ session.history.push(entry);
191
+ sm.appendToSessionFile(session, entry);
192
+ sm.sendToSession(session, entry);
193
+ session.isProcessing = true;
194
+ ctx.onProcessingChanged();
195
+ sm.sendToSession(session, { type: "status", status: "processing" });
196
+ var sdk = ctx.getSdk();
197
+ session.loopInterviewHandoff.state = "running";
198
+ sm.saveSessionFile(session);
199
+ var started;
200
+ try { started = sdk.startQuery(session, brief.objective, null, ctx.ensureProjectAccessForSession(session)); }
201
+ catch (error) {
202
+ session.isProcessing = false;
203
+ session._queryStarting = false;
204
+ ctx.onProcessingChanged();
205
+ sm.sendToSession(session, { type: "status", status: "idle" });
206
+ await finishNow(session, "error", "The approved Loop run could not start: " + (error.message || error));
207
+ session.loopInterviewHandoff = null;
208
+ sm.saveSessionFile(session);
209
+ sendTo(ws, { type: "loop_interview_start_run_result", ok: false, sessionId: session.localId, requestId: cleanText(requestId, 200), error: "The approved Loop run could not start: " + (error.message || error) });
210
+ return false;
211
+ }
212
+ sendTo(ws, { type: "loop_interview_start_run_result", ok: true, sessionId: session.localId, requestId: cleanText(requestId, 200), run: project(session.autonomousRun) });
213
+ var approvedRunId = session.autonomousRun.id;
214
+ Promise.resolve(started).catch(function (error) {
215
+ if (session.autonomousRun && session.autonomousRun.id === approvedRunId && TERMINAL_STATES.indexOf(session.autonomousRun.state) === -1) {
216
+ session.isProcessing = false;
217
+ session._queryStarting = false;
218
+ ctx.onProcessingChanged();
219
+ sm.sendToSession(session, { type: "status", status: "idle" });
220
+ finishOwned(session, approvedRunId, "error", "The approved Loop run could not start: " + (error.message || error));
221
+ }
222
+ });
223
+ return true;
224
+ } catch (error) {
225
+ if (session.autonomousRun && ACTIVE_STATES.indexOf(session.autonomousRun.state) !== -1) await finishNow(session, "stopped", "The approved Loop handoff failed before execution could start.");
226
+ session.loopInterviewHandoff = null;
227
+ sm.saveSessionFile(session);
228
+ sendTo(ws, { type: "loop_interview_start_run_result", ok: false, sessionId: session.localId, requestId: cleanText(requestId, 200), error: error.message || String(error) });
229
+ return false;
230
+ }
231
+ });
232
+ }
233
+
234
+ function onHumanMessage(session) {
235
+ var run = session && session.autonomousRun;
236
+ if (!run || run.state === "armed" || run.state === "paused" || TERMINAL_STATES.indexOf(run.state) !== -1) return;
237
+ run.completionRequested = false;
238
+ run.outcome = null;
239
+ run.state = "running";
240
+ run.waitingReason = "";
241
+ persist(session);
242
+ }
243
+
244
+ function continuationPrompt(run) {
245
+ return "[Until complete continuation: " + run.id + "]\nContinue the single active task. Review the objective and success criteria, inspect actual evidence, and do the next useful work. Do not claim independent verification merely from your own prose. Before ending this turn, call report_until_complete_outcome with this runId and a structured status, summary, and concrete evidence. If a Worker or human response is required, report the corresponding waiting status and do not spin.";
246
+ }
247
+
248
+ function dispatchContinuation(session, runId, epoch) {
249
+ var run = session.autonomousRun;
250
+ if (!run || run.id !== runId || run.cancellationEpoch !== epoch || !run.continuationPending || TERMINAL_STATES.indexOf(run.state) !== -1) return;
251
+ if (Date.now() >= run.deadlineAt) { finish(session, "limit", "The wall-clock limit was reached."); return; }
252
+ if (run.rateLimitUntil && run.rateLimitUntil > Date.now()) { scheduleRateLimitWake(session, run); return; }
253
+ if (typeof ctx.stopBarrier === "function" && ctx.stopBarrier(session)) { finish(session, "stopped", "A cancellation barrier prevented a stale continuation."); return; }
254
+ if (session.isProcessing || session._queryStarting) {
255
+ run.continuationPending = false;
256
+ run.state = "reviewing";
257
+ persist(session);
258
+ return;
259
+ }
260
+ run.continuationPending = false;
261
+ run.continuationCount += 1;
262
+ run.state = "running";
263
+ session.isProcessing = true;
264
+ session.taskStopRequested = false;
265
+ ctx.onProcessingChanged();
266
+ sm.sendToSession(session, { type: "status", status: "processing" });
267
+ var text = continuationPrompt(run);
268
+ var sdk = ctx.getSdk();
269
+ try {
270
+ if (!sdk.pushMessage(session, text, null, { autonomousRunId: run.id })) {
271
+ var started = sdk.startQuery(session, text, null, ctx.ensureProjectAccessForSession(session));
272
+ var queryOwner = { session: session, runId: run.id, epoch: epoch, generation: Number(session._queryGeneration || 0) };
273
+ session._autonomousQueryOwner = queryOwner;
274
+ Promise.resolve(started).then(function () {
275
+ if (session._autonomousQueryOwner === queryOwner) delete session._autonomousQueryOwner;
276
+ }, function (error) {
277
+ var current = sm.sessions.get(session.localId);
278
+ var currentRun = session.autonomousRun;
279
+ if (current !== session || session._autonomousQueryOwner !== queryOwner || !currentRun || currentRun.id !== queryOwner.runId ||
280
+ Number(currentRun.cancellationEpoch || 0) !== queryOwner.epoch || Number(session._queryGeneration || 0) !== queryOwner.generation) return;
281
+ delete session._autonomousQueryOwner;
282
+ finish(session, "error", "Failed to continue the run: " + (error.message || error));
283
+ });
284
+ }
285
+ } catch (error) {
286
+ finish(session, "error", "Failed to continue the run: " + (error.message || error));
287
+ return;
288
+ }
289
+ persist(session);
290
+ }
291
+
292
+ function scheduleRateLimitWake(session, run) {
293
+ var wakeAt = Math.min(run.rateLimitUntil, run.deadlineAt);
294
+ var runId = run.id;
295
+ var epoch = run.cancellationEpoch;
296
+ if (wakeScheduler) {
297
+ wakeScheduler.cancel(session, run);
298
+ wakeScheduler.schedule(session, run, "deadline", run.deadlineAt);
299
+ wakeScheduler.schedule(session, run, "rate-limit", wakeAt);
300
+ return;
301
+ }
302
+ clearTimer(session);
303
+ timers.set(session.localId, setTimeout(function () {
304
+ var current = session.autonomousRun;
305
+ if (!current || current.id !== runId || current.cancellationEpoch !== epoch || TERMINAL_STATES.indexOf(current.state) !== -1) return;
306
+ if (Date.now() >= current.deadlineAt) { finish(session, "limit", "The wall-clock limit was reached."); return; }
307
+ current.rateLimitUntil = null;
308
+ current.waitingReason = "";
309
+ scheduleLimit(session);
310
+ continueRun(session, current);
311
+ }, Math.max(0, wakeAt - Date.now())));
312
+ }
313
+
314
+ function continueRun(session, run) {
315
+ if (run.continuationPending || session.isProcessing || session._queryStarting) return;
316
+ if (Date.now() >= run.deadlineAt) { finish(session, "limit", "The wall-clock limit was reached."); return; }
317
+ if (run.rateLimitUntil && run.rateLimitUntil > Date.now()) { scheduleRateLimitWake(session, run); return; }
318
+ if (run.continuationCount >= run.maxContinuations) { finish(session, "limit", "The automatic continuation limit was reached."); return; }
319
+ run.continuationPending = true;
320
+ run.state = "reviewing";
321
+ var epoch = run.cancellationEpoch;
322
+ persist(session);
323
+ if (wakeScheduler) {
324
+ try { wakeScheduler.schedule(session, run, "continuation", Date.now()); }
325
+ catch (error) { finish(session, "error", "The continuation could not be stored: " + (error.message || error)); }
326
+ } else setTimeout(function () { dispatchContinuation(session, run.id, epoch); }, 0);
327
+ }
328
+
329
+ function onTurnDone(session) {
330
+ var run = session && session.autonomousRun;
331
+ if (!run || run.state === "armed" || run.state === "paused" || TERMINAL_STATES.indexOf(run.state) !== -1) return;
332
+ if (run.completionRequested) { finish(session, "completed", "The Driver assessed the success criteria as complete.", run.outcome); return; }
333
+ if (run.state === "waiting-user" || run.state === "waiting-worker") return;
334
+ continueRun(session, run);
335
+ }
336
+
337
+ function report(args, session) {
338
+ var run = session && session.autonomousRun;
339
+ if (!session || sm.sessions.get(session.localId) !== session || !run || args.runId !== run.id || run.state === "armed" || run.state === "paused" || TERMINAL_STATES.indexOf(run.state) !== -1) return Promise.resolve({ content: [{ type: "text", text: JSON.stringify({ status: "rejected", reason: "This callback does not belong to a running active run." }) }] });
340
+ var statuses = ["completed", "continue", "waiting_user", "waiting_worker", "error"];
341
+ if (statuses.indexOf(args.status) === -1) return Promise.resolve({ content: [{ type: "text", text: JSON.stringify({ status: "rejected", reason: "Invalid outcome status." }) }] });
342
+ var evidence = Array.isArray(args.evidence) ? args.evidence.slice(0, 20).map(function (item) { return cleanText(item, 2000); }).filter(Boolean) : [];
343
+ var criteria = Array.isArray(args.criteria) ? args.criteria.slice(0, 10).map(function (item) {
344
+ if (!item || typeof item !== "object") return null;
345
+ return { criterion: cleanText(item.criterion, 1000), met: item.met === true, evidence: cleanText(item.evidence, 2000) };
346
+ }).filter(function (item) { return item && item.criterion && item.evidence; }) : [];
347
+ var outcome = { assessment: args.status, summary: cleanText(args.summary, 4000), evidence: evidence, criteria: criteria, reportedAt: Date.now() };
348
+ var criteriaComplete = (run.successCriteria || []).every(function (criterion) {
349
+ return criteria.some(function (item) { return item.met && item.criterion === criterion; });
350
+ });
351
+ if (args.status === "completed" && (!outcome.summary || evidence.length === 0 || !run.successCriteria || run.successCriteria.length === 0 || !criteriaComplete)) return Promise.resolve({ content: [{ type: "text", text: JSON.stringify({ status: "rejected", reason: "Completion requires a summary, concrete evidence, and a met assessment for every success criterion." }) }] });
352
+ run.completionRequested = false;
353
+ run.outcome = outcome;
354
+ if (args.status === "completed") { run.completionRequested = true; run.state = "reviewing"; persist(session); }
355
+ else if (args.status === "error") finish(session, "error", outcome.summary || "The Driver reported an error.", outcome);
356
+ else if (args.status === "waiting_user") { run.state = "waiting-user"; run.waitingReason = outcome.summary || "Waiting for human input."; persist(session); }
357
+ else if (args.status === "waiting_worker") { run.state = "waiting-worker"; run.waitingReason = outcome.summary || "Waiting for the Split Worker."; persist(session); }
358
+ else { run.state = "reviewing"; run.waitingReason = ""; persist(session); }
359
+ return Promise.resolve({ content: [{ type: "text", text: JSON.stringify({ status: "recorded", run: project(run) }) }] });
360
+ }
361
+
362
+ function getToolDefs(session) {
363
+ var run = session && session.autonomousRun;
364
+ if (!run || run.state === "armed" || run.state === "paused" || TERMINAL_STATES.indexOf(run.state) !== -1) return [];
365
+ return [{ name: "report_until_complete_outcome", description: "Report the Driver's structured assessment for the exact active Until complete run.", inputSchema: { type: "object", properties: { runId: { type: "string" }, status: { type: "string", enum: ["completed", "continue", "waiting_user", "waiting_worker", "error"] }, summary: { type: "string" }, evidence: { type: "array", items: { type: "string" } }, criteria: { type: "array", items: { type: "object", properties: { criterion: { type: "string" }, met: { type: "boolean" }, evidence: { type: "string" } }, required: ["criterion", "met", "evidence"], additionalProperties: false } } }, required: ["runId", "status", "summary", "evidence", "criteria"], additionalProperties: false }, handler: function (args) { return report(args || {}, session); } }];
366
+ }
367
+
368
+ function getSystemPrompt(session) {
369
+ var run = session && session.autonomousRun;
370
+ if (!run || run.state === "armed" || TERMINAL_STATES.indexOf(run.state) !== -1) return "";
371
+ return "An Until complete run is active. Run ID: " + run.id + ". Objective: " + run.objective + ". Success criteria: " + run.successCriteria.join("; ") + ". You are the Driver and may use a Split Worker when useful. Base completion only on inspected evidence, not your own prose. Before ending every turn, call report_until_complete_outcome with a criterion-by-criterion assessment. Report waiting_user or waiting_worker when blocked and wait rather than spinning.";
372
+ }
373
+
374
+ function stopForPermissionChange(session) { return finish(session, "stopped", "Skip Permissions was turned off."); }
375
+
376
+ function handleMessage(ws, msg) {
377
+ if (msg.type !== "autonomous_run_arm" && msg.type !== "autonomous_run_disarm" && msg.type !== "autonomous_run_stop" && msg.type !== "autonomous_run_resume") return false;
378
+ var session = ctx.getSessionForWs(ws);
379
+ var requestId = cleanText(msg.requestId, 200);
380
+ var runId = cleanText(msg.runId, 200) || (session && session.autonomousRun && session.autonomousRun.id) || "";
381
+ if (!session) {
382
+ sendTo(ws, { type: msg.type === "autonomous_run_arm" ? "autonomous_run_arm_result" : "autonomous_run_action_result", ok: false, sessionId: null, requestId: requestId, runId: runId, error: "No session was bound to this request." });
383
+ return true;
384
+ }
385
+ var lifecycle = null;
386
+ if (msg.type !== "autonomous_run_arm" && owns(ws, session, msg) && ownsRun(session, msg)) {
387
+ lifecycle = { id: crypto.randomUUID(), type: msg.type, runId: runId };
388
+ session._autonomousLifecyclePending = lifecycle;
389
+ }
390
+ serialize(session, async function () {
391
+ try {
392
+ if (msg.type === "autonomous_run_arm") await armNow(ws, session, msg);
393
+ else if (!owns(ws, session, msg) || !ownsRun(session, msg)) throw new Error("The requested run is stale or is not authorized.");
394
+ else if (msg.type === "autonomous_run_disarm") await disarmNow(ws, session, msg);
395
+ else if (msg.type === "autonomous_run_stop") await finishNow(session, "stopped", "Stopped by the user.");
396
+ else {
397
+ var run = session.autonomousRun;
398
+ if (!run || run.state !== "paused") throw new Error("There is no paused run to resume.");
399
+ if (Date.now() >= run.deadlineAt) { await finishNow(session, "limit", "The wall-clock limit was reached."); return; }
400
+ await fullAccess.setEnabled(session, true);
401
+ run.state = "reviewing";
402
+ run.pausedAfterRestart = false;
403
+ run.waitingReason = "";
404
+ persist(session);
405
+ try { scheduleLimit(session); }
406
+ catch (error) {
407
+ await finishNow(session, "error", "The run deadline could not be stored: " + (error.message || error));
408
+ throw error;
409
+ }
410
+ continueRun(session, run);
411
+ }
412
+ if (msg.type !== "autonomous_run_arm") sendTo(ws, { type: "autonomous_run_action_result", ok: true, action: msg.type, sessionId: session.localId, requestId: requestId, runId: runId });
413
+ } catch (error) {
414
+ sendTo(ws, { type: msg.type === "autonomous_run_arm" ? "autonomous_run_arm_result" : "autonomous_run_action_result", ok: false, action: msg.type, sessionId: session.localId, requestId: requestId, runId: runId, error: error.message || String(error) });
415
+ }
416
+ }).catch(function () {}).finally(function () {
417
+ if (lifecycle && session._autonomousLifecyclePending === lifecycle) delete session._autonomousLifecyclePending;
418
+ });
419
+ return true;
420
+ }
421
+
422
+ function recover() {
423
+ sm.sessions.forEach(function (session) {
424
+ var run = session.autonomousRun;
425
+ if (!run || TERMINAL_STATES.indexOf(run.state) !== -1) return;
426
+ if (wakeScheduler) {
427
+ try { wakeScheduler.cancel(session, run); }
428
+ catch (error) { run.schedulerError = cleanText(error.message || String(error), 1000); }
429
+ }
430
+ if (run.state === "armed") {
431
+ serialize(session, function () {
432
+ return fullAccess.restore(session, run.permissionSnapshot).then(function () { session.autonomousRun = null; session.loopInterviewHandoff = null; persist(session); }, function (error) {
433
+ run.state = "error"; run.terminalReason = "Skip Permissions could not be restored after restart: " + cleanText(error.message || String(error), 1000); persist(session);
434
+ });
435
+ }).catch(function () {});
436
+ return;
437
+ }
438
+ run.state = "paused";
439
+ run.pausedAfterRestart = true;
440
+ run.waitingReason = "Paused after daemon restart. Resume explicitly to continue.";
441
+ run.continuationPending = false;
442
+ run.cancellationEpoch = (run.cancellationEpoch || 0) + 1;
443
+ serialize(session, function () {
444
+ return fullAccess.restore(session, run.permissionSnapshot).catch(function (error) {
445
+ run.permissionRestoreError = cleanText(error.message || String(error), 1000);
446
+ run.state = "error"; run.terminalReason = "Skip Permissions could not be restored after restart: " + run.permissionRestoreError;
447
+ }).then(function () { persist(session); });
448
+ }).catch(function () {});
449
+ });
450
+ }
451
+
452
+ function onPartnerResult(session) {
453
+ var run = session && session.autonomousRun;
454
+ if (!run || run.state !== "waiting-worker") return;
455
+ run.completionRequested = false; run.outcome = null;
456
+ run.state = "running"; run.waitingReason = ""; persist(session);
457
+ }
458
+
459
+ function onUserAnswer(session) {
460
+ var run = session && session.autonomousRun;
461
+ if (!run || run.state !== "waiting-user") return;
462
+ run.completionRequested = false; run.outcome = null;
463
+ run.state = "running"; run.waitingReason = ""; persist(session);
464
+ }
465
+
466
+ function onRateLimit(session, resetsAt) {
467
+ var run = session && session.autonomousRun;
468
+ if (!run || TERMINAL_STATES.indexOf(run.state) !== -1 || run.state === "armed" || run.state === "paused") return;
469
+ run.cancellationEpoch = (run.cancellationEpoch || 0) + 1;
470
+ run.continuationPending = false;
471
+ run.rateLimitUntil = Math.max(Date.now(), Math.min(Number(resetsAt) || Date.now(), run.deadlineAt));
472
+ run.state = "reviewing";
473
+ run.waitingReason = "Waiting for the rate limit to reset.";
474
+ persist(session);
475
+ try { scheduleRateLimitWake(session, run); }
476
+ catch (error) { finish(session, "error", "The rate-limit wake could not be stored: " + (error.message || error)); }
477
+ }
478
+
479
+ if (ctx.durableScheduler) {
480
+ wakeScheduler = attachWakeScheduler({
481
+ scheduler: ctx.durableScheduler, projectId: ctx.projectSlug, sm: sm, isMultiUser: ctx.isMultiUser,
482
+ authorize: ctx.authorizeSession,
483
+ onWake: function (kind, session, run) {
484
+ if (kind === "deadline") return finishOwned(session, run.id, "limit", "The wall-clock limit was reached.");
485
+ if (kind === "rate-limit") {
486
+ run.rateLimitUntil = null; run.waitingReason = ""; persist(session); scheduleLimit(session); continueRun(session, run); return true;
487
+ }
488
+ dispatchContinuation(session, run.id, run.cancellationEpoch); return true;
489
+ },
490
+ });
491
+ } else if (ctx.allowInMemoryTimers !== true) throw new Error("Until complete requires the durable scheduler.");
492
+ recover();
493
+ if (wakeScheduler) wakeScheduler.start();
494
+ return { consume: consume, finish: finish, startApprovedBrief: startApprovedBrief, getSystemPrompt: getSystemPrompt, getToolDefs: getToolDefs, handleMessage: handleMessage, onHumanMessage: onHumanMessage, onPartnerResult: onPartnerResult, onRateLimit: onRateLimit, onTurnDone: onTurnDone, onUserAnswer: onUserAnswer, project: project, stopForPermissionChange: stopForPermissionChange,
495
+ shutdown: function () { if (wakeScheduler) wakeScheduler.shutdown(); } };
496
+ }
497
+
498
+ module.exports = { attachAutonomousRun: attachAutonomousRun, projectAutonomousRun: project, restoreStoredRun: restoreStoredRun };
@@ -41,6 +41,7 @@ function attachCapsuleTurn(ctx) {
41
41
  ownerId: principal.userId || null,
42
42
  vendor: selection.vendor,
43
43
  model: selection.model,
44
+ effort: selection.effort || null,
44
45
  });
45
46
  session.title = (options.toolName || options.toolId) + " game";
46
47
  session.capsuleGame = { toolId: options.toolId };
@@ -95,6 +95,8 @@ function attachConnection(ctx) {
95
95
  var getTitle = ctx.getTitle;
96
96
  var getProject = ctx.getProject;
97
97
  var warmup = ctx.warmup;
98
+ var scheduledMessages = ctx.scheduledMessages;
99
+ var sendCursorSharingState = ctx.sendCursorSharingState;
98
100
 
99
101
  // Adapters are initialized lazily: the first websocket connection into
100
102
  // this project triggers warmup. Without this guard we would either keep
@@ -177,6 +179,7 @@ function attachConnection(ctx) {
177
179
  }
178
180
  var initialModels = (sm.modelsByVendor && sm.modelsByVendor[initialVendor]) || sm.availableModels || [];
179
181
  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 });
182
+ if (typeof sendCursorSharingState === "function") sendCursorSharingState(ws);
180
183
  // Update notifications are pushed on a scheduled interval (see
181
184
  // scheduleUpdateBroadcast). We no longer push on connect to avoid
182
185
  // re-triggering the banner on every page refresh.
@@ -262,7 +265,8 @@ function attachConnection(ctx) {
262
265
  try { resolveSessionForView(active, ws); } catch (e) {}
263
266
  }
264
267
  var _vendorCaps = (sm.capabilitiesByVendor && sm.capabilitiesByVendor[active.vendor || sm.defaultVendor || "claude"]) || {};
265
- sendTo(ws, { type: "session_switched", id: active.localId, cliSessionId: active.cliSessionId || null, loop: active.loop || null, vendor: active.vendor || null, model: active.model || null, effort: active.effort || initialEffort || null, hasHistory: (active.history && active.history.length > 0), capabilities: _vendorCaps, mode: active.mode || "gui", terminalId: typeof active.terminalId === "number" ? active.terminalId : null, runtimeMode: active.runtimeMode || null, runtimeTerminalId: typeof active.runtimeTerminalId === "number" ? active.runtimeTerminalId : null, tuiSuspended: !!active.tuiSuspended });
268
+ sendTo(ws, { type: "session_switched", id: active.localId, cliSessionId: active.cliSessionId || null, loop: active.loop || null, vendor: active.vendor || null, model: active.model || null, effort: active.effort || initialEffort || null, hasHistory: (active.history && active.history.length > 0), capabilities: _vendorCaps, mode: active.mode || "gui", terminalId: typeof active.terminalId === "number" ? active.terminalId : null, runtimeMode: active.runtimeMode || null, runtimeTerminalId: typeof active.runtimeTerminalId === "number" ? active.runtimeTerminalId : null, tuiSuspended: !!active.tuiSuspended, permissionMode: active.permissionMode || null });
269
+ if (ctx.autonomousRun) sendTo(ws, { type: "autonomous_run_state", sessionId: active.localId, run: ctx.autonomousRun.project(active.autonomousRun) });
266
270
  // Send per-session context sources
267
271
  var sessionSources = loadContextSources(slug, active.localId);
268
272
  sendTo(ws, { type: "context_sources_state", active: sessionSources });
@@ -288,6 +292,7 @@ function attachConnection(ctx) {
288
292
  }
289
293
  }
290
294
  sendTo(ws, { type: "history_done", lastUsage: _lastUsage, lastModelUsage: _lastModelUsage, lastCost: _lastCost, lastStreamInputTokens: _lastStreamInputTokens, contextUsage: active.lastContextUsage || null });
295
+ if (scheduledMessages) scheduledMessages.sendState(active, ws);
291
296
 
292
297
  if (active.isProcessing) {
293
298
  sendTo(ws, { type: "status", status: "processing" });
@@ -15,6 +15,7 @@ function attachDelegatedSession(ctx) {
15
15
  sessionVisibility: "private",
16
16
  vendor: selection.vendor,
17
17
  model: selection.model,
18
+ effort: selection.effort || null,
18
19
  });
19
20
  session.title = metadata.title;
20
21
  session.assignment = {
@@ -43,11 +43,11 @@ function attachFilesystem(ctx) {
43
43
  // Unsubscription must remain available after permission revocation.
44
44
  if (/^fs_/.test(msg.type) && msg.type !== "fs_unwatch") {
45
45
  if (!access.canUseFiles(ws)) {
46
- sendTo(ws, { type: msg.type + "_result", error: "File browser access is not permitted" });
46
+ sendTo(ws, { type: msg.type + "_result", path: msg.path, requestId: msg.requestId, projectSlug: msg.projectSlug, sessionId: msg.sessionId, accountId: msg.accountId, error: "File browser access is not permitted" });
47
47
  return true;
48
48
  }
49
49
  try { access.osIdentity(ws); } catch (e) {
50
- sendTo(ws, { type: msg.type + "_result", error: e.message });
50
+ sendTo(ws, { type: msg.type + "_result", path: msg.path, requestId: msg.requestId, projectSlug: msg.projectSlug, sessionId: msg.sessionId, accountId: msg.accountId, error: e.message });
51
51
  return true;
52
52
  }
53
53
  }
@@ -152,7 +152,7 @@ function attachFilesystem(ctx) {
152
152
  fsFile = safeAbsPath(msg.path);
153
153
  }
154
154
  if (!fsFile) {
155
- sendTo(ws, { type: "fs_read_result", path: msg.path, error: "Access denied" });
155
+ sendTo(ws, { type: "fs_read_result", path: msg.path, requestId: msg.requestId, projectSlug: msg.projectSlug, sessionId: msg.sessionId, accountId: msg.accountId, error: "Access denied" });
156
156
  return true;
157
157
  }
158
158
  try {
@@ -162,34 +162,34 @@ function attachFilesystem(ctx) {
162
162
  // Run stat and read as target OS user
163
163
  var statResult = fsAsUser("stat", { file: fsFile }, fsReadUserInfo);
164
164
  if (statResult.size > FS_MAX_SIZE) {
165
- sendTo(ws, { type: "fs_read_result", path: msg.path, binary: true, size: statResult.size, error: "File too large (" + (statResult.size / 1024 / 1024).toFixed(1) + " MB)" });
165
+ sendTo(ws, { type: "fs_read_result", path: msg.path, requestId: msg.requestId, projectSlug: msg.projectSlug, sessionId: msg.sessionId, accountId: msg.accountId, binary: true, size: statResult.size, error: "File too large (" + (statResult.size / 1024 / 1024).toFixed(1) + " MB)" });
166
166
  return true;
167
167
  }
168
168
  if (BINARY_EXTS.has(ext)) {
169
- var result = { type: "fs_read_result", path: msg.path, binary: true, size: statResult.size };
169
+ var result = { type: "fs_read_result", path: msg.path, requestId: msg.requestId, projectSlug: msg.projectSlug, sessionId: msg.sessionId, accountId: msg.accountId, binary: true, size: statResult.size };
170
170
  if (IMAGE_EXTS.has(ext)) result.imageUrl = "api/file?path=" + encodeURIComponent(msg.path);
171
171
  sendTo(ws, result);
172
172
  return true;
173
173
  }
174
174
  var readResult = fsAsUser("read", { file: fsFile, readContent: true }, fsReadUserInfo);
175
- sendTo(ws, { type: "fs_read_result", path: msg.path, content: readResult.content, size: statResult.size });
175
+ sendTo(ws, { type: "fs_read_result", path: msg.path, requestId: msg.requestId, projectSlug: msg.projectSlug, sessionId: msg.sessionId, accountId: msg.accountId, content: readResult.content, size: statResult.size });
176
176
  } else {
177
177
  var stat = fs.statSync(fsFile);
178
178
  if (stat.size > FS_MAX_SIZE) {
179
- sendTo(ws, { type: "fs_read_result", path: msg.path, binary: true, size: stat.size, error: "File too large (" + (stat.size / 1024 / 1024).toFixed(1) + " MB)" });
179
+ sendTo(ws, { type: "fs_read_result", path: msg.path, requestId: msg.requestId, projectSlug: msg.projectSlug, sessionId: msg.sessionId, accountId: msg.accountId, binary: true, size: stat.size, error: "File too large (" + (stat.size / 1024 / 1024).toFixed(1) + " MB)" });
180
180
  return true;
181
181
  }
182
182
  if (BINARY_EXTS.has(ext)) {
183
- var result = { type: "fs_read_result", path: msg.path, binary: true, size: stat.size };
183
+ var result = { type: "fs_read_result", path: msg.path, requestId: msg.requestId, projectSlug: msg.projectSlug, sessionId: msg.sessionId, accountId: msg.accountId, binary: true, size: stat.size };
184
184
  if (IMAGE_EXTS.has(ext)) result.imageUrl = "api/file?path=" + encodeURIComponent(msg.path);
185
185
  sendTo(ws, result);
186
186
  return true;
187
187
  }
188
188
  var content = fs.readFileSync(fsFile, "utf8");
189
- sendTo(ws, { type: "fs_read_result", path: msg.path, content: content, size: stat.size });
189
+ sendTo(ws, { type: "fs_read_result", path: msg.path, requestId: msg.requestId, projectSlug: msg.projectSlug, sessionId: msg.sessionId, accountId: msg.accountId, content: content, size: stat.size });
190
190
  }
191
191
  } catch (e) {
192
- sendTo(ws, { type: "fs_read_result", path: msg.path, error: e.message });
192
+ sendTo(ws, { type: "fs_read_result", path: msg.path, requestId: msg.requestId, projectSlug: msg.projectSlug, sessionId: msg.sessionId, accountId: msg.accountId, error: e.message });
193
193
  }
194
194
  return true;
195
195
  }