@zonease/happy 0.6.42 → 0.6.43

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 (88) hide show
  1. package/dist/{api-CIE4_cWd.cjs → api-B-PlVQY6.cjs} +49 -3
  2. package/dist/{api-Ew6xr81i.mjs → api-BUCtd3cB.mjs} +49 -3
  3. package/dist/{auth-G9fZpVJ2.cjs → auth-BqGkUXvq.cjs} +2 -2
  4. package/dist/{auth-BzYevJo2.mjs → auth-DQH6q_q-.mjs} +4 -4
  5. package/dist/{auth-Dor79zTL.cjs → auth-OL3nNcDX.cjs} +4 -4
  6. package/dist/{auth-Q2STUNI-.mjs → auth-qM2eojWx.mjs} +2 -2
  7. package/dist/{browser-qDIIAL7S.cjs → browser-CY7VNU0C.cjs} +1 -1
  8. package/dist/{browser-BI7s-V8E.mjs → browser-D5RN_Axa.mjs} +1 -1
  9. package/dist/{claudeLocal-Db2tDC4X.mjs → claudeLocal-BXK_vQXS.mjs} +1 -1
  10. package/dist/{claudeLocal-f4uBLJaM.cjs → claudeLocal-HAXk0deK.cjs} +1 -1
  11. package/dist/{config-CHjDs8AS.mjs → config-2wMjzcHl.mjs} +1 -1
  12. package/dist/{config-CSBlEik9.cjs → config-CaKxWGVe.cjs} +1 -1
  13. package/dist/{connect-O_nA5BOC.mjs → connect-BAunPyp_.mjs} +4 -4
  14. package/dist/{connect-CJoTliii.cjs → connect-Dt1vY1BC.cjs} +4 -4
  15. package/dist/{controlClient-DoS9JqQC.mjs → controlClient-BzzTNSfl.mjs} +1 -1
  16. package/dist/{controlClient-CEz_p1zc.cjs → controlClient-yTkpwcOq.cjs} +1 -1
  17. package/dist/{cursorApprovalHook-DRAHfTXU.cjs → cursorApprovalHook-B2chnVni.cjs} +29 -11
  18. package/dist/{cursorApprovalHook-B0v1kGa7.mjs → cursorApprovalHook-BDLPzI5l.mjs} +29 -11
  19. package/dist/{cursorApprovalServer-JjPkdjGR.mjs → cursorApprovalServer-Ba1yOUCA.mjs} +109 -10
  20. package/dist/{cursorApprovalServer-DzY8yJlI.cjs → cursorApprovalServer-xwxVGL3u.cjs} +109 -8
  21. package/dist/{deviceKeys-B4aqjnE6.mjs → deviceKeys-CQyFFPtt.mjs} +2 -2
  22. package/dist/{deviceKeys-DyUn0bEx.cjs → deviceKeys-CdW-HPnz.cjs} +2 -2
  23. package/dist/{doctor-BJSuVRUZ.cjs → doctor-D4xWXiE7.cjs} +2 -2
  24. package/dist/{doctor-DRnft_5-.mjs → doctor-DDT2TXHH.mjs} +2 -2
  25. package/dist/{downloadImage-Cijmufpr.cjs → downloadImage-3A6zY5AX.cjs} +1 -372
  26. package/dist/downloadImage-BqNp0l7y.mjs +485 -0
  27. package/dist/{enroll-CysDL6ee.mjs → enroll-BlZ0giJI.mjs} +3 -3
  28. package/dist/{enroll-C2gunHdt.cjs → enroll-DeYs89Ic.cjs} +3 -3
  29. package/dist/{evidence-DsQ-h-_W.cjs → evidence-Bn1JXtdz.cjs} +1 -1
  30. package/dist/{evidence-DPutCuUr.mjs → evidence-PUnN9yPK.mjs} +1 -1
  31. package/dist/{formatImageMessage-6aV9YFWr.mjs → formatImageMessage-BRHeLf0-.mjs} +1 -1
  32. package/dist/{formatImageMessage-BPK_Ivfe.cjs → formatImageMessage-bVFGFDJl.cjs} +1 -1
  33. package/dist/index.cjs +50 -46
  34. package/dist/index.mjs +50 -46
  35. package/dist/{install-BsTA2tZn.mjs → install-BLPNuSeM.mjs} +2 -2
  36. package/dist/{install-DBg0xQ3g.mjs → install-BOIcR_Wr.mjs} +1 -1
  37. package/dist/{install-Tyhs6rUp.cjs → install-Jrs6YUu8.cjs} +1 -1
  38. package/dist/{install-DlOppA-e.cjs → install-Q-YMwwwg.cjs} +2 -2
  39. package/dist/lib.cjs +3 -3
  40. package/dist/lib.d.cts +1 -0
  41. package/dist/lib.d.mts +1 -0
  42. package/dist/lib.mjs +3 -3
  43. package/dist/{loadableToolOutput-DPGTtKJM.cjs → loadableToolOutput-B3qpMqXT.cjs} +2 -2
  44. package/dist/{loadableToolOutput-2dMPAp4N.mjs → loadableToolOutput-DzReWITb.mjs} +2 -2
  45. package/dist/{persistence-BcQmjobZ.cjs → persistence-D7zNFI-d.cjs} +1 -1
  46. package/dist/{persistence-D9tMm6bj.mjs → persistence-DbLMvLvk.mjs} +1 -1
  47. package/dist/{push-Cnm6JQMD.cjs → push-RT6Y0Hr3.cjs} +6 -6
  48. package/dist/{push-Dxsugkes.mjs → push-z5wQ4Ki_.mjs} +6 -6
  49. package/dist/registerKillSessionHandler-DmfnJ0zq.mjs +407 -0
  50. package/dist/registerKillSessionHandler-MoLdSQxM.cjs +411 -0
  51. package/dist/{run-BEflQaQ2.mjs → run-BFnSUK1o.mjs} +10 -8
  52. package/dist/{run-CiPWWhXt.cjs → run-C5StYRIB.cjs} +10 -8
  53. package/dist/{runClaude-BVXsyCHb.cjs → runClaude-ChRp6f5G.cjs} +26 -26
  54. package/dist/{runClaude-sPkSqHWY.mjs → runClaude-CjENBdrX.mjs} +21 -21
  55. package/dist/{runCodex-C4rHkIyw.mjs → runCodex-BzQWlRoj.mjs} +26 -26
  56. package/dist/{runCodex-BAwhdmSA.cjs → runCodex-DcQGyotH.cjs} +28 -28
  57. package/dist/{runCursor-DiHLWMs8.mjs → runCursor-B73z6MCN.mjs} +210 -34
  58. package/dist/{runCursor-CD5wC3gA.cjs → runCursor-BlPAxaTy.cjs} +210 -34
  59. package/dist/{runGemini-B-93gOqB.mjs → runGemini-BPv4UzuF.mjs} +17 -17
  60. package/dist/{runGemini-Cto-adir.cjs → runGemini-JFqaIC3u.cjs} +19 -19
  61. package/dist/{runOneShot-BfIzE9W4.cjs → runOneShot-2iV11e9L.cjs} +2 -2
  62. package/dist/{runOneShot-DGjlYVhR.mjs → runOneShot-DTukim2A.mjs} +2 -2
  63. package/dist/{serve-CqL6NWDr.cjs → serve-BDwuAIK4.cjs} +6 -6
  64. package/dist/{serve-ttlqF5jn.mjs → serve-BnlYj5ek.mjs} +6 -6
  65. package/dist/{session-BZhGpQUJ.cjs → session-Cp9LEESM.cjs} +5 -5
  66. package/dist/{session-Co0REU9e.mjs → session-gfeFU8CA.mjs} +5 -5
  67. package/dist/{sessionKeyCache-BwFUGgMs.cjs → sessionKeyCache-3Pme04yL.cjs} +1 -1
  68. package/dist/{sessionKeyCache-pXTjpETo.mjs → sessionKeyCache-DtBS_Sb3.mjs} +1 -1
  69. package/dist/{sessionList-DaufR-sA.mjs → sessionList-B1w4BUdv.mjs} +2 -2
  70. package/dist/{sessionList-fG5uAPzU.cjs → sessionList-BR_5UDZJ.cjs} +2 -2
  71. package/dist/{sessionSummary-CjZPt6nr.cjs → sessionSummary-DLweYgHO.cjs} +3 -3
  72. package/dist/{sessionSummary-Dv6dPABg.mjs → sessionSummary-E-a2bg8W.mjs} +3 -3
  73. package/dist/{setupOfflineReconnection-DW5il97B.mjs → setupOfflineReconnection-DHwthFtd.mjs} +5 -3
  74. package/dist/{setupOfflineReconnection-B6coH9QN.cjs → setupOfflineReconnection-Dkp00dC9.cjs} +7 -5
  75. package/dist/{spawnHappyCLI-Cfu7C1uM.cjs → spawnHappyCLI-C2uY9WV-.cjs} +1 -1
  76. package/dist/{spawnHappyCLI-DhUeycct.mjs → spawnHappyCLI-Dt7fbmbk.mjs} +1 -1
  77. package/dist/{ssh-8tPIKk-i.mjs → ssh-C-nEZiGP.mjs} +5 -5
  78. package/dist/{ssh-C4YTSHXL.cjs → ssh-CsnS89Lr.cjs} +5 -5
  79. package/dist/{task-CH-oidFy.mjs → task-B-8_QGGX.mjs} +6 -6
  80. package/dist/{task-DFW-PecP.cjs → task-lrLb4FCL.cjs} +6 -6
  81. package/dist/{uninstall-CeioRZNI.mjs → uninstall-CirMt_WC.mjs} +2 -2
  82. package/dist/{uninstall-yTSL3X6Y.cjs → uninstall-DMjUlEXT.cjs} +2 -2
  83. package/dist/{update-Derd2hoX.mjs → update-CfXbYshs.mjs} +3 -3
  84. package/dist/{update-Bju28vNP.cjs → update-mx2p28IP.cjs} +3 -3
  85. package/package.json +1 -1
  86. package/dist/downloadImage-BpVF7A79.mjs +0 -854
  87. package/dist/gitWorktree-C6jLoaMw.cjs +0 -39
  88. package/dist/gitWorktree-CzJDU2BV.mjs +0 -37
@@ -2,12 +2,14 @@
2
2
 
3
3
  var node_crypto = require('node:crypto');
4
4
  var chalk = require('chalk');
5
- var api = require('./api-CIE4_cWd.cjs');
6
- var logger = require('./persistence-BcQmjobZ.cjs');
7
- var run = require('./run-CiPWWhXt.cjs');
8
- var setupOfflineReconnection = require('./setupOfflineReconnection-B6coH9QN.cjs');
5
+ var api = require('./api-B-PlVQY6.cjs');
6
+ var logger = require('./persistence-D7zNFI-d.cjs');
7
+ var run = require('./run-C5StYRIB.cjs');
8
+ var setupOfflineReconnection = require('./setupOfflineReconnection-Dkp00dC9.cjs');
9
+ var controlClient = require('./controlClient-yTkpwcOq.cjs');
10
+ var registerKillSessionHandler = require('./registerKillSessionHandler-MoLdSQxM.cjs');
9
11
  var node_child_process = require('node:child_process');
10
- var cursorApprovalServer = require('./cursorApprovalServer-DzY8yJlI.cjs');
12
+ var cursorApprovalServer = require('./cursorApprovalServer-xwxVGL3u.cjs');
11
13
  require('axios');
12
14
  require('node:events');
13
15
  require('socket.io-client');
@@ -33,31 +35,38 @@ require('tweetnacl');
33
35
  require('@stablelib/base64');
34
36
  require('node:readline');
35
37
  require('expo-server-sdk');
36
- require('./sessionKeyCache-BwFUGgMs.cjs');
38
+ require('./sessionKeyCache-3Pme04yL.cjs');
37
39
  require('fs');
38
40
  require('os');
39
41
  require('tmp');
40
- require('./auth-G9fZpVJ2.cjs');
42
+ require('./auth-BqGkUXvq.cjs');
41
43
  require('qrcode-terminal');
42
- require('./browser-qDIIAL7S.cjs');
44
+ require('./browser-CY7VNU0C.cjs');
43
45
  require('open');
44
46
  require('react');
45
47
  require('ink');
46
- require('./doctor-BJSuVRUZ.cjs');
47
- require('./controlClient-CEz_p1zc.cjs');
48
+ require('./doctor-D4xWXiE7.cjs');
48
49
  require('./doctor-C08m5Z9f.cjs');
49
50
  require('ps-list');
50
51
  require('cross-spawn');
51
- require('./spawnHappyCLI-Cfu7C1uM.cjs');
52
- require('./deviceKeys-DyUn0bEx.cjs');
53
- require('./install-Tyhs6rUp.cjs');
52
+ require('./spawnHappyCLI-C2uY9WV-.cjs');
53
+ require('./deviceKeys-CdW-HPnz.cjs');
54
+ require('./install-Jrs6YUu8.cjs');
54
55
  require('./trimIdent-4gvk9Wk6.cjs');
55
56
  require('fastify');
56
57
  require('fastify-type-provider-zod');
57
58
  require('./common-CZvSRU4p.cjs');
58
- require('./gitWorktree-C6jLoaMw.cjs');
59
+ require('@modelcontextprotocol/sdk/server/mcp.js');
59
60
  require('node:http');
61
+ require('@modelcontextprotocol/sdk/server/streamableHttp.js');
62
+ require('@modelcontextprotocol/sdk/types.js');
63
+ require('./prompt-BTWDZZO0.cjs');
64
+ require('./mcpToolSchemas-DK42yoZ_.cjs');
65
+ require('./types-DWeB7aqh.cjs');
60
66
 
67
+ function createCursorStreamState() {
68
+ return { thinkingBuffer: "" };
69
+ }
61
70
  function textFromMessageContent(message) {
62
71
  if (!message || typeof message !== "object") return null;
63
72
  const content = message.content;
@@ -76,9 +85,10 @@ function unwrapToolCall(toolCall) {
76
85
  for (const [key, value] of Object.entries(toolCall)) {
77
86
  if (!key.endsWith("ToolCall") || !value || typeof value !== "object") continue;
78
87
  const inner = value;
88
+ const normalized = cursorApprovalServer.normalizeCursorTool(key.replace(/ToolCall$/, ""), inner.args);
79
89
  return {
80
- toolName: key.replace(/ToolCall$/, ""),
81
- args: inner.args && typeof inner.args === "object" ? inner.args : {},
90
+ toolName: normalized.name,
91
+ args: normalized.input,
82
92
  result: inner.result ?? null
83
93
  };
84
94
  }
@@ -106,7 +116,7 @@ function parseCursorSessionInit(event) {
106
116
  apiKeySource: str("apiKeySource")
107
117
  };
108
118
  }
109
- function mapCursorStreamEvent(event) {
119
+ function mapCursorStreamEvent(event, state) {
110
120
  switch (event.type) {
111
121
  case "system": {
112
122
  if (event.subtype !== "init") return [];
@@ -122,10 +132,19 @@ function mapCursorStreamEvent(event) {
122
132
  case "user":
123
133
  return [];
124
134
  case "thinking": {
125
- if (event.subtype !== "delta") return [];
126
- const text = typeof event.text === "string" ? event.text : null;
127
- if (!text) return [];
128
- return [{ type: "event", name: "thinking", payload: { textDelta: text } }];
135
+ const text = typeof event.text === "string" ? event.text : "";
136
+ if (event.subtype === "delta") {
137
+ if (!text) return [];
138
+ if (!state) return [{ type: "event", name: "thinking", payload: { textDelta: text } }];
139
+ state.thinkingBuffer += text;
140
+ return [];
141
+ }
142
+ if (event.subtype === "completed" && state?.thinkingBuffer) {
143
+ const full = state.thinkingBuffer;
144
+ state.thinkingBuffer = "";
145
+ return [{ type: "event", name: "thinking", payload: { textDelta: full } }];
146
+ }
147
+ return [];
129
148
  }
130
149
  case "assistant": {
131
150
  const text = textFromMessageContent(event.message);
@@ -194,6 +213,7 @@ function drainJsonLines(buffer) {
194
213
  }
195
214
 
196
215
  const CANCEL_GRACE_MS = 2e3;
216
+ const THINKING_FLUSH_MS = 800;
197
217
  class CursorCliBackend {
198
218
  options;
199
219
  handlers = /* @__PURE__ */ new Set();
@@ -201,12 +221,30 @@ class CursorCliBackend {
201
221
  child = null;
202
222
  /** Cursor's own chat id, used to resume on the next turn. */
203
223
  chatId = null;
224
+ /** Current model id; null leaves the choice to Cursor. */
225
+ model = null;
204
226
  stdoutBuffer = "";
227
+ streamState = createCursorStreamState();
228
+ thinkingFlushTimer = null;
205
229
  turnComplete = Promise.resolve();
206
230
  resolveTurn = null;
207
231
  disposed = false;
208
232
  constructor(options) {
209
233
  this.options = options;
234
+ this.chatId = options.resumeChatId ?? null;
235
+ this.model = options.model ?? null;
236
+ }
237
+ /**
238
+ * The app sends the chosen model with every message, so it can change
239
+ * between turns. Each turn spawns its own process, which is where the new
240
+ * value takes effect.
241
+ */
242
+ setModel(model) {
243
+ this.model = model;
244
+ }
245
+ /** Cursor's own chat id, once it is known. */
246
+ getChatId() {
247
+ return this.chatId;
210
248
  }
211
249
  onMessage(handler) {
212
250
  this.handlers.add(handler);
@@ -245,14 +283,16 @@ class CursorCliBackend {
245
283
  if (this.child) {
246
284
  throw new Error("CursorCliBackend is already running a turn");
247
285
  }
248
- const args = ["-p", prompt, "--output-format", "stream-json", "--trust"];
249
- if (this.options.model) args.push("--model", this.options.model);
286
+ const args = ["-p", prompt, "--output-format", "stream-json", "--trust", "--force"];
287
+ if (this.model) args.push("--model", this.model);
250
288
  if (this.chatId) args.push("--resume", this.chatId);
251
289
  if (this.options.extraArgs?.length) args.push(...this.options.extraArgs);
252
290
  const command = this.options.command ?? "cursor-agent";
253
- logger.logger.debug(`[cursor] spawn ${command} ${this.chatId ? `(resume ${this.chatId})` : "(new chat)"}`);
291
+ logger.logger.debug(`[cursor] spawn ${command} model=${this.model ?? "(Cursor default)"} ${this.chatId ? `(resume ${this.chatId})` : "(new chat)"}`);
254
292
  this.emit({ type: "status", status: "starting" });
255
293
  this.stdoutBuffer = "";
294
+ this.flushThinking();
295
+ this.streamState = createCursorStreamState();
256
296
  this.turnComplete = new Promise((resolve) => {
257
297
  this.resolveTurn = resolve;
258
298
  });
@@ -278,6 +318,7 @@ class CursorCliBackend {
278
318
  });
279
319
  child.on("close", (code) => {
280
320
  this.consumeStdout("\n");
321
+ this.flushThinking();
281
322
  if (code !== 0) {
282
323
  const reason = lastStderr.length > 0 ? `: ${lastStderr}` : "";
283
324
  this.emit({ type: "status", status: "error", detail: `cursor-agent exited with code ${code}${reason}` });
@@ -287,6 +328,17 @@ class CursorCliBackend {
287
328
  });
288
329
  await this.turnComplete;
289
330
  }
331
+ /** Emits whatever reasoning has accumulated, if any. */
332
+ flushThinking() {
333
+ if (this.thinkingFlushTimer) {
334
+ clearTimeout(this.thinkingFlushTimer);
335
+ this.thinkingFlushTimer = null;
336
+ }
337
+ const text = this.streamState.thinkingBuffer;
338
+ if (!text) return;
339
+ this.streamState.thinkingBuffer = "";
340
+ this.emit({ type: "event", name: "thinking", payload: { textDelta: text } });
341
+ }
290
342
  consumeStdout(chunk) {
291
343
  this.stdoutBuffer += chunk;
292
344
  const { events, rest } = drainJsonLines(this.stdoutBuffer);
@@ -297,9 +349,19 @@ class CursorCliBackend {
297
349
  this.chatId = init.sessionId;
298
350
  logger.logger.debug(`[cursor] chat id ${init.sessionId} model ${init.model ?? "(default)"}`);
299
351
  }
300
- for (const message of mapCursorStreamEvent(event)) {
352
+ for (const message of mapCursorStreamEvent(event, this.streamState)) {
353
+ if (message.type === "event" && message.name === "thinking" && this.thinkingFlushTimer) {
354
+ clearTimeout(this.thinkingFlushTimer);
355
+ this.thinkingFlushTimer = null;
356
+ }
301
357
  this.emit(message);
302
358
  }
359
+ if (this.streamState.thinkingBuffer && !this.thinkingFlushTimer) {
360
+ this.thinkingFlushTimer = setTimeout(() => {
361
+ this.thinkingFlushTimer = null;
362
+ this.flushThinking();
363
+ }, THINKING_FLUSH_MS);
364
+ }
303
365
  }
304
366
  }
305
367
  finishTurn() {
@@ -405,9 +467,13 @@ function agentMessageToAcp(message, newId) {
405
467
  }
406
468
 
407
469
  const ALWAYS_ALLOWED_TOOLS = ["change_title", "happy__change_title", "preview_html", "happy__preview_html"];
470
+ const UNATTENDED_MODES = ["yolo", "safe-yolo", "bypassPermissions"];
408
471
  class CursorPermissionHandler extends setupOfflineReconnection.BasePermissionHandler {
409
- /** Set once the user answers "approve for session" or picks a yolo mode. */
472
+ /** Set by "allow all tools for this session", or by an unattended mode. */
410
473
  approveEverything = false;
474
+ /** Tools the user answered "don't ask again" for. */
475
+ allowedTools = /* @__PURE__ */ new Set();
476
+ currentPermissionMode = null;
411
477
  constructor(session, pushClient) {
412
478
  super(session, pushClient);
413
479
  }
@@ -417,8 +483,21 @@ class CursorPermissionHandler extends setupOfflineReconnection.BasePermissionHan
417
483
  getAgentName() {
418
484
  return "Cursor";
419
485
  }
420
- /** Skip the round trip for tools that are part of Happy's own plumbing. */
486
+ /**
487
+ * The app sends the mode with each message. In an unattended mode asking
488
+ * would contradict what the session badge promises, so tools just run.
489
+ */
490
+ setPermissionMode(mode) {
491
+ if (mode === this.currentPermissionMode) return;
492
+ this.currentPermissionMode = mode;
493
+ logger.logger.debug(`${this.getLogPrefix()} permission mode is now ${mode ?? "default"}`);
494
+ }
495
+ isUnattended() {
496
+ return this.currentPermissionMode !== null && UNATTENDED_MODES.includes(this.currentPermissionMode);
497
+ }
421
498
  shouldAutoApprove(toolName) {
499
+ if (this.approveEverything || this.isUnattended()) return true;
500
+ if (this.allowedTools.has(toolName)) return true;
422
501
  const lower = toolName.toLowerCase();
423
502
  return ALWAYS_ALLOWED_TOOLS.some((name) => lower.includes(name.toLowerCase()));
424
503
  }
@@ -428,7 +507,7 @@ class CursorPermissionHandler extends setupOfflineReconnection.BasePermissionHan
428
507
  * `timeout` we write into hooks.json.
429
508
  */
430
509
  async requestApproval(toolCallId, toolName, input) {
431
- if (this.approveEverything || this.shouldAutoApprove(toolName)) {
510
+ if (this.shouldAutoApprove(toolName)) {
432
511
  logger.logger.debug(`${this.getLogPrefix()} auto-approving ${toolName}`);
433
512
  return true;
434
513
  }
@@ -437,7 +516,10 @@ class CursorPermissionHandler extends setupOfflineReconnection.BasePermissionHan
437
516
  this.addPendingRequestToState(toolCallId, toolName, input);
438
517
  logger.logger.debug(`${this.getLogPrefix()} awaiting approval for ${toolName} (${toolCallId})`);
439
518
  });
440
- if (result.decision === "approved_for_session") {
519
+ for (const allowed of result.allowTools ?? []) {
520
+ this.allowedTools.add(allowed);
521
+ }
522
+ if (result.decision === "approved_for_session" || result.mode && UNATTENDED_MODES.includes(result.mode)) {
441
523
  this.approveEverything = true;
442
524
  }
443
525
  return result.decision === "approved" || result.decision === "approved_for_session";
@@ -474,8 +556,23 @@ async function runCursor(opts) {
474
556
  }
475
557
  });
476
558
  session = initialSession;
559
+ if (response) {
560
+ try {
561
+ const result = await controlClient.notifyDaemonSessionStarted(response.id, metadata);
562
+ if (result?.error) {
563
+ logger.logger.debug(`[cursor] daemon did not accept the session report: ${result.error}`);
564
+ }
565
+ } catch (error) {
566
+ logger.logger.debug(`[cursor] could not report session to daemon: ${error}`);
567
+ }
568
+ }
569
+ const mcp = await registerKillSessionHandler.createMcpContext(session);
570
+ const mcpStdio = mcp.configForStdio().happy;
571
+ const mcpUrlArg = mcpStdio?.args?.indexOf("--url") ?? -1;
572
+ const mcpUrl = mcpUrlArg >= 0 ? mcpStdio.args[mcpUrlArg + 1] : null;
573
+ const mcpBridge = mcpStdio ? { command: mcpStdio.command, args: mcpStdio.args ?? [] } : null;
477
574
  const permissionHandler = new CursorPermissionHandler(session, api$1.push());
478
- const approvalBridge = new cursorApprovalServer.CursorApprovalBridge({ cwd: process.cwd(), handler: permissionHandler });
575
+ const approvalBridge = new cursorApprovalServer.CursorApprovalBridge({ cwd: process.cwd(), handler: permissionHandler, mcpBridge });
479
576
  let approvalReady = false;
480
577
  try {
481
578
  await approvalBridge.start();
@@ -485,14 +582,51 @@ async function runCursor(opts) {
485
582
  }
486
583
  const backend = new CursorCliBackend({
487
584
  cwd: process.cwd(),
488
- model: opts.model ?? null
585
+ model: opts.model ?? null,
586
+ resumeChatId: opts.resumeChatId ?? null,
587
+ // Cursor asks before loading an MCP server; nobody is at the terminal.
588
+ extraArgs: mcpBridge ? ["--approve-mcps"] : void 0
489
589
  });
590
+ let publishedChatId = opts.resumeChatId ?? null;
591
+ if (publishedChatId) {
592
+ session.updateMetadata((current) => ({ ...current, cursorSessionId: publishedChatId }));
593
+ }
490
594
  const queue = [];
491
595
  let draining = false;
492
596
  let thinking = false;
597
+ let reportedModel = null;
493
598
  backend.onMessage((message) => {
599
+ const chatId = backend.getChatId();
600
+ if (chatId && chatId !== publishedChatId) {
601
+ publishedChatId = chatId;
602
+ session.updateMetadata((current) => ({ ...current, cursorSessionId: chatId }));
603
+ void approvalBridge.registerChat(chatId);
604
+ }
605
+ if (message.type === "token-count") {
606
+ const usage = message;
607
+ const num = (key) => typeof usage[key] === "number" ? usage[key] : 0;
608
+ const input = num("inputTokens");
609
+ const output = num("outputTokens");
610
+ const cacheRead = num("cacheReadTokens");
611
+ const cacheWrite = num("cacheWriteTokens");
612
+ const total = input + output + cacheRead + cacheWrite;
613
+ if (total > 0) {
614
+ session.sendUsageReport({
615
+ key: "cursor-session",
616
+ tokens: { total, input, output, cache_read: cacheRead, cache_creation: cacheWrite },
617
+ // Cursor bills against its own subscription and reports no
618
+ // per-request price, so there is nothing honest to put here.
619
+ cost: { total: 0 }
620
+ });
621
+ }
622
+ }
494
623
  if (message.type === "status") {
495
624
  thinking = message.status === "starting" || message.status === "running";
625
+ const named = message.detail?.match(/^cursor-agent ready \((.+)\)$/)?.[1];
626
+ if (named && named !== reportedModel) {
627
+ reportedModel = named;
628
+ session.updateMetadata((current) => ({ ...current, model: named }));
629
+ }
496
630
  }
497
631
  for (const acp of agentMessageToAcp(message, () => node_crypto.randomUUID())) {
498
632
  session.sendAgentMessage("cursor", acp);
@@ -518,11 +652,28 @@ async function runCursor(opts) {
518
652
  thinking = false;
519
653
  }
520
654
  };
655
+ const handleAbort = async () => {
656
+ logger.logger.debug(`[cursor] abort requested (${queue.length} queued)`);
657
+ queue.length = 0;
658
+ try {
659
+ await backend.cancel("");
660
+ } catch (error) {
661
+ logger.logger.debug(`[cursor] abort failed: ${error}`);
662
+ }
663
+ permissionHandler.reset();
664
+ thinking = false;
665
+ session.sendSessionEvent({ type: "message", message: "Aborted by user" });
666
+ };
521
667
  session.onUserMessage((message) => {
522
668
  const text = message.content?.text;
523
669
  if (typeof text !== "string" || text.trim().length === 0) return;
670
+ if (message.meta?.permissionMode) {
671
+ permissionHandler.setPermissionMode(message.meta.permissionMode);
672
+ }
524
673
  if (message.meta?.hasOwnProperty("model")) {
525
- logger.logger.debug("[cursor] per-message model override is not wired yet, ignoring");
674
+ const chosen = message.meta.model || null;
675
+ backend.setModel(chosen);
676
+ logger.logger.debug(`[cursor] model set to ${chosen ?? "(Cursor default)"}`);
526
677
  }
527
678
  queue.push(text);
528
679
  void drain();
@@ -539,6 +690,11 @@ async function runCursor(opts) {
539
690
  }
540
691
  console.log(chalk.gray(` cwd ${process.cwd()}`));
541
692
  console.log(chalk.gray(` model ${opts.model ?? "(Cursor default)"}`));
693
+ if (mcpUrl) {
694
+ console.log(chalk.gray(` tools Happy MCP on ${mcpUrl}`));
695
+ } else {
696
+ console.log(chalk.yellow(" tools Happy MCP unavailable \u2014 change_title, preview_html and device tools are missing"));
697
+ }
542
698
  if (approvalReady) {
543
699
  console.log(chalk.gray(" approval tool calls are sent to your phone before they run"));
544
700
  } else {
@@ -549,14 +705,26 @@ async function runCursor(opts) {
549
705
  void drain();
550
706
  }
551
707
  let shuttingDown = false;
552
- const shutdown = async () => {
708
+ const shutdown = async (archived = false) => {
553
709
  if (shuttingDown) return;
554
710
  shuttingDown = true;
555
711
  clearInterval(keepAlive);
556
- logger.logger.debug("[cursor] shutting down");
712
+ thinking = false;
713
+ logger.logger.debug(`[cursor] shutting down${archived ? " (archived)" : ""}`);
714
+ if (archived) {
715
+ session.updateMetadata((current) => ({
716
+ ...current,
717
+ lifecycleState: "archived",
718
+ lifecycleStateSince: Date.now(),
719
+ archivedBy: "cli",
720
+ archiveReason: "User terminated"
721
+ }));
722
+ }
557
723
  try {
724
+ permissionHandler.reset();
558
725
  await backend.dispose();
559
726
  await approvalBridge.stop();
727
+ mcp.stop();
560
728
  } finally {
561
729
  session.sendSessionDeath();
562
730
  await session.flush();
@@ -571,6 +739,14 @@ async function runCursor(opts) {
571
739
  };
572
740
  process.on("SIGINT", shutdownAndExit);
573
741
  process.on("SIGTERM", shutdownAndExit);
742
+ session.rpcHandlerManager.registerHandler("abort", handleAbort);
743
+ registerKillSessionHandler.registerKillSessionHandler(session.rpcHandlerManager, async () => {
744
+ await Promise.race([
745
+ shutdown(true),
746
+ new Promise((resolve) => setTimeout(resolve, SHUTDOWN_TIMEOUT_MS))
747
+ ]);
748
+ process.exit(0);
749
+ });
574
750
  await new Promise(() => {
575
751
  });
576
752
  }
@@ -1,20 +1,21 @@
1
1
  import { useStdout, useInput, Box, Text, render } from 'ink';
2
2
  import React, { useState, useRef, useEffect, useCallback } from 'react';
3
3
  import { randomUUID } from 'node:crypto';
4
- import { c as saveDiffRecords, h as readGeminiSessionLog, d as connectionState, A as ApiClient, G as GeminiSessionWriter, j as buildResumeContextFromSessionLog } from './api-Ew6xr81i.mjs';
5
- import { l as logger, p as packageJson, i as isDebug, a as readSettings } from './persistence-D9tMm6bj.mjs';
6
- import { B as BasePermissionHandler, c as createSessionMetadata, s as setupOfflineReconnection } from './setupOfflineReconnection-DW5il97B.mjs';
7
- import { i as initialMachineMetadata, s as stopCaffeinate } from './run-BEflQaQ2.mjs';
8
- import { M as MessageQueue2, h as hashObject, a as MessageBuffer, r as registerKillSessionHandler, c as createMcpContext, p as parseClear } from './downloadImage-BpVF7A79.mjs';
9
- import { notifyDaemonSessionStarted } from './controlClient-DoS9JqQC.mjs';
4
+ import { c as saveDiffRecords, h as readGeminiSessionLog, d as connectionState, A as ApiClient, G as GeminiSessionWriter, j as buildResumeContextFromSessionLog } from './api-BUCtd3cB.mjs';
5
+ import { l as logger, p as packageJson, i as isDebug, a as readSettings } from './persistence-DbLMvLvk.mjs';
6
+ import { B as BasePermissionHandler, c as createSessionMetadata, s as setupOfflineReconnection } from './setupOfflineReconnection-DHwthFtd.mjs';
7
+ import { i as initialMachineMetadata, s as stopCaffeinate } from './run-BFnSUK1o.mjs';
8
+ import { M as MessageQueue2, h as hashObject, a as MessageBuffer, p as parseClear } from './downloadImage-BqNp0l7y.mjs';
9
+ import { r as registerKillSessionHandler, c as createMcpContext } from './registerKillSessionHandler-DmfnJ0zq.mjs';
10
+ import { notifyDaemonSessionStarted } from './controlClient-BzzTNSfl.mjs';
10
11
  import { spawn } from 'node:child_process';
11
12
  import { ndJsonStream, ClientSideConnection } from '@agentclientprotocol/sdk';
12
13
  import { d as delay } from './time-DgsBLJA_.mjs';
13
14
  import { G as GEMINI_MODEL_MODES } from './index-DTV7drqs.mjs';
14
- import { a as handleAgentMessageChunk, b as handleToolCallUpdate, c as handleAgentThoughtChunk, d as handleToolCall, e as handleLegacyMessageChunk, f as handlePlanUpdate, g as handleThinkingUpdate, B as BaseReasoningProcessor, s as summarizeBashToolOutput, h as handleConfigMetadataEvent } from './loadableToolOutput-2dMPAp4N.mjs';
15
+ import { a as handleAgentMessageChunk, b as handleToolCallUpdate, c as handleAgentThoughtChunk, d as handleToolCall, e as handleLegacyMessageChunk, f as handlePlanUpdate, g as handleThinkingUpdate, B as BaseReasoningProcessor, s as summarizeBashToolOutput, h as handleConfigMetadataEvent } from './loadableToolOutput-DzReWITb.mjs';
15
16
  import { a as GEMINI_API_KEY_ENV, b as GOOGLE_API_KEY_ENV, G as GEMINI_MODEL_ENV, g as getFirstTurnInstruction } from './constants-pmXZGibX.mjs';
16
- import { readGeminiLocalConfig, determineGeminiModel, getGeminiModelSource, getInitialGeminiModel, saveGeminiModelToConfig } from './config-CHjDs8AS.mjs';
17
- import { f as formatMessageForGemini } from './formatImageMessage-6aV9YFWr.mjs';
17
+ import { readGeminiLocalConfig, determineGeminiModel, getGeminiModelSource, getInitialGeminiModel, saveGeminiModelToConfig } from './config-2wMjzcHl.mjs';
18
+ import { f as formatMessageForGemini } from './formatImageMessage-BRHeLf0-.mjs';
18
19
  import 'axios';
19
20
  import 'node:events';
20
21
  import 'socket.io-client';
@@ -37,23 +38,22 @@ import 'tweetnacl';
37
38
  import '@stablelib/base64';
38
39
  import 'node:readline';
39
40
  import 'expo-server-sdk';
40
- import './sessionKeyCache-pXTjpETo.mjs';
41
+ import './sessionKeyCache-DtBS_Sb3.mjs';
41
42
  import 'chalk';
42
43
  import 'fs';
43
- import './gitWorktree-CzJDU2BV.mjs';
44
44
  import 'os';
45
45
  import 'tmp';
46
- import './auth-Q2STUNI-.mjs';
46
+ import './auth-qM2eojWx.mjs';
47
47
  import 'qrcode-terminal';
48
- import './browser-BI7s-V8E.mjs';
48
+ import './browser-D5RN_Axa.mjs';
49
49
  import 'open';
50
- import './doctor-DRnft_5-.mjs';
50
+ import './doctor-DDT2TXHH.mjs';
51
51
  import './doctor-ON7pN5G5.mjs';
52
52
  import 'ps-list';
53
53
  import 'cross-spawn';
54
- import './spawnHappyCLI-DhUeycct.mjs';
55
- import './deviceKeys-B4aqjnE6.mjs';
56
- import './install-DBg0xQ3g.mjs';
54
+ import './spawnHappyCLI-Dt7fbmbk.mjs';
55
+ import './deviceKeys-CQyFFPtt.mjs';
56
+ import './install-BOIcR_Wr.mjs';
57
57
  import './trimIdent-BAjmv_TG.mjs';
58
58
  import 'fastify';
59
59
  import 'fastify-type-provider-zod';
@@ -3,20 +3,21 @@
3
3
  var ink = require('ink');
4
4
  var React = require('react');
5
5
  var node_crypto = require('node:crypto');
6
- var api = require('./api-CIE4_cWd.cjs');
7
- var logger = require('./persistence-BcQmjobZ.cjs');
8
- var setupOfflineReconnection = require('./setupOfflineReconnection-B6coH9QN.cjs');
9
- var run = require('./run-CiPWWhXt.cjs');
10
- var downloadImage = require('./downloadImage-Cijmufpr.cjs');
11
- var controlClient = require('./controlClient-CEz_p1zc.cjs');
6
+ var api = require('./api-B-PlVQY6.cjs');
7
+ var logger = require('./persistence-D7zNFI-d.cjs');
8
+ var setupOfflineReconnection = require('./setupOfflineReconnection-Dkp00dC9.cjs');
9
+ var run = require('./run-C5StYRIB.cjs');
10
+ var downloadImage = require('./downloadImage-3A6zY5AX.cjs');
11
+ var registerKillSessionHandler = require('./registerKillSessionHandler-MoLdSQxM.cjs');
12
+ var controlClient = require('./controlClient-yTkpwcOq.cjs');
12
13
  var node_child_process = require('node:child_process');
13
14
  var sdk = require('@agentclientprotocol/sdk');
14
15
  var time = require('./time-Bj2HRkKH.cjs');
15
16
  var index = require('./index-BeKrqPYR.cjs');
16
- var loadableToolOutput = require('./loadableToolOutput-DPGTtKJM.cjs');
17
+ var loadableToolOutput = require('./loadableToolOutput-B3qpMqXT.cjs');
17
18
  var constants = require('./constants-E_UWYTo3.cjs');
18
- var config = require('./config-CSBlEik9.cjs');
19
- var formatImageMessage = require('./formatImageMessage-BPK_Ivfe.cjs');
19
+ var config = require('./config-CaKxWGVe.cjs');
20
+ var formatImageMessage = require('./formatImageMessage-bVFGFDJl.cjs');
20
21
  require('axios');
21
22
  require('node:events');
22
23
  require('socket.io-client');
@@ -39,23 +40,22 @@ require('tweetnacl');
39
40
  require('@stablelib/base64');
40
41
  require('node:readline');
41
42
  require('expo-server-sdk');
42
- require('./sessionKeyCache-BwFUGgMs.cjs');
43
+ require('./sessionKeyCache-3Pme04yL.cjs');
43
44
  require('chalk');
44
45
  require('fs');
45
- require('./gitWorktree-C6jLoaMw.cjs');
46
46
  require('os');
47
47
  require('tmp');
48
- require('./auth-G9fZpVJ2.cjs');
48
+ require('./auth-BqGkUXvq.cjs');
49
49
  require('qrcode-terminal');
50
- require('./browser-qDIIAL7S.cjs');
50
+ require('./browser-CY7VNU0C.cjs');
51
51
  require('open');
52
- require('./doctor-BJSuVRUZ.cjs');
52
+ require('./doctor-D4xWXiE7.cjs');
53
53
  require('./doctor-C08m5Z9f.cjs');
54
54
  require('ps-list');
55
55
  require('cross-spawn');
56
- require('./spawnHappyCLI-Cfu7C1uM.cjs');
57
- require('./deviceKeys-DyUn0bEx.cjs');
58
- require('./install-Tyhs6rUp.cjs');
56
+ require('./spawnHappyCLI-C2uY9WV-.cjs');
57
+ require('./deviceKeys-CdW-HPnz.cjs');
58
+ require('./install-Jrs6YUu8.cjs');
59
59
  require('./trimIdent-4gvk9Wk6.cjs');
60
60
  require('fastify');
61
61
  require('fastify-type-provider-zod');
@@ -2397,7 +2397,7 @@ async function runGemini(opts) {
2397
2397
  }
2398
2398
  };
2399
2399
  session.rpcHandlerManager.registerHandler("abort", handleAbort);
2400
- downloadImage.registerKillSessionHandler(session.rpcHandlerManager, handleKillSession);
2400
+ registerKillSessionHandler.registerKillSessionHandler(session.rpcHandlerManager, handleKillSession);
2401
2401
  const messageBuffer = new downloadImage.MessageBuffer();
2402
2402
  const hasTTY = process.stdout.isTTY && process.stdin.isTTY;
2403
2403
  let inkInstance = null;
@@ -2453,7 +2453,7 @@ async function runGemini(opts) {
2453
2453
  }
2454
2454
  process.stdin.setEncoding("utf8");
2455
2455
  }
2456
- const mcp = await downloadImage.createMcpContext(session);
2456
+ const mcp = await registerKillSessionHandler.createMcpContext(session);
2457
2457
  const mcpServers = mcp.configForHttp();
2458
2458
  permissionHandler = new GeminiPermissionHandler(session, api$1.push());
2459
2459
  const reasoningProcessor = new GeminiReasoningProcessor((message) => {
@@ -2,8 +2,8 @@
2
2
 
3
3
  var node_child_process = require('node:child_process');
4
4
  var fs = require('node:fs');
5
- var claudeLocal = require('./claudeLocal-f4uBLJaM.cjs');
6
- var logger = require('./persistence-BcQmjobZ.cjs');
5
+ var claudeLocal = require('./claudeLocal-HAXk0deK.cjs');
6
+ var logger = require('./persistence-D7zNFI-d.cjs');
7
7
  var index = require('./index-BeKrqPYR.cjs');
8
8
  var common = require('./common-CZvSRU4p.cjs');
9
9
  require('node:path');
@@ -1,7 +1,7 @@
1
1
  import { spawn } from 'node:child_process';
2
2
  import { existsSync } from 'node:fs';
3
- import { c as claudeCliPath } from './claudeLocal-Db2tDC4X.mjs';
4
- import { l as logger } from './persistence-D9tMm6bj.mjs';
3
+ import { c as claudeCliPath } from './claudeLocal-BXK_vQXS.mjs';
4
+ import { l as logger } from './persistence-DbLMvLvk.mjs';
5
5
  import { i as isModelModeForAgent, M as MODEL_MODE_DEFAULT, p as parseCodexModelMode, a as parseClaudeModelMode } from './index-DTV7drqs.mjs';
6
6
  import { i as isOrchestratorProvider, O as ORCHESTRATOR_ENV_KEYS, d as decodePromptFromBase64 } from './common-BdNgghUJ.mjs';
7
7
  import 'node:path';
@@ -2,11 +2,11 @@
2
2
 
3
3
  var mcp_js = require('@modelcontextprotocol/sdk/server/mcp.js');
4
4
  var stdio_js = require('@modelcontextprotocol/sdk/server/stdio.js');
5
- var logger = require('./persistence-BcQmjobZ.cjs');
6
- var sessionList = require('./sessionList-fG5uAPzU.cjs');
5
+ var logger = require('./persistence-D7zNFI-d.cjs');
6
+ var sessionList = require('./sessionList-BR_5UDZJ.cjs');
7
7
  var axios = require('axios');
8
8
  var z = require('zod');
9
- var sessionSummary = require('./sessionSummary-CjZPt6nr.cjs');
9
+ var sessionSummary = require('./sessionSummary-DLweYgHO.cjs');
10
10
  var node_crypto = require('node:crypto');
11
11
  require('node:fs/promises');
12
12
  require('node:fs');
@@ -15,14 +15,14 @@ require('node:path');
15
15
  require('tweetnacl');
16
16
  require('chalk');
17
17
  require('fs');
18
- require('./auth-G9fZpVJ2.cjs');
18
+ require('./auth-BqGkUXvq.cjs');
19
19
  require('qrcode-terminal');
20
20
  require('./time-Bj2HRkKH.cjs');
21
- require('./browser-qDIIAL7S.cjs');
21
+ require('./browser-CY7VNU0C.cjs');
22
22
  require('open');
23
23
  require('react');
24
24
  require('ink');
25
- require('./sessionKeyCache-BwFUGgMs.cjs');
25
+ require('./sessionKeyCache-3Pme04yL.cjs');
26
26
 
27
27
  const PREVIEW_LIMIT = 200;
28
28
  const TAIL_MESSAGES = 5;