@zonease/happy 0.6.42 → 0.6.44

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-Ct7Z1lwF.cjs} +65 -5
  2. package/dist/{api-Ew6xr81i.mjs → api-D3j3YL3m.mjs} +65 -5
  3. package/dist/{auth-G9fZpVJ2.cjs → auth-B9kOli6i.cjs} +2 -2
  4. package/dist/{auth-BzYevJo2.mjs → auth-C2ZyAGMu.mjs} +4 -4
  5. package/dist/{auth-Q2STUNI-.mjs → auth-DPe89SQx.mjs} +2 -2
  6. package/dist/{auth-Dor79zTL.cjs → auth-W1aK_a-P.cjs} +4 -4
  7. package/dist/{browser-qDIIAL7S.cjs → browser-BPEhi6te.cjs} +1 -1
  8. package/dist/{browser-BI7s-V8E.mjs → browser-Byx5gq-W.mjs} +1 -1
  9. package/dist/{claudeLocal-Db2tDC4X.mjs → claudeLocal-BtxBYzs7.mjs} +1 -1
  10. package/dist/{claudeLocal-f4uBLJaM.cjs → claudeLocal-CcarNVE9.cjs} +1 -1
  11. package/dist/{config-CHjDs8AS.mjs → config-DJLOlgjr.mjs} +1 -1
  12. package/dist/{config-CSBlEik9.cjs → config-Djr2JMWO.cjs} +1 -1
  13. package/dist/{connect-O_nA5BOC.mjs → connect-PrUuHNLU.mjs} +4 -4
  14. package/dist/{connect-CJoTliii.cjs → connect-rcgRx9US.cjs} +4 -4
  15. package/dist/{controlClient-DoS9JqQC.mjs → controlClient-DSiES8Un.mjs} +1 -1
  16. package/dist/{controlClient-CEz_p1zc.cjs → controlClient-FDsMeGVn.cjs} +1 -1
  17. package/dist/{cursorApprovalHook-DRAHfTXU.cjs → cursorApprovalHook-CTJiiPO6.cjs} +29 -11
  18. package/dist/{cursorApprovalHook-B0v1kGa7.mjs → cursorApprovalHook-DGYeNaLe.mjs} +29 -11
  19. package/dist/{cursorApprovalServer-DzY8yJlI.cjs → cursorApprovalServer-C_wqI7wm.cjs} +109 -8
  20. package/dist/{cursorApprovalServer-JjPkdjGR.mjs → cursorApprovalServer-fjPBXSAb.mjs} +109 -10
  21. package/dist/{deviceKeys-B4aqjnE6.mjs → deviceKeys-BYHzWSyg.mjs} +2 -2
  22. package/dist/{deviceKeys-DyUn0bEx.cjs → deviceKeys-DPut1U07.cjs} +2 -2
  23. package/dist/{doctor-DRnft_5-.mjs → doctor-BA28TuMy.mjs} +2 -2
  24. package/dist/{doctor-BJSuVRUZ.cjs → doctor-B_W7odmC.cjs} +2 -2
  25. package/dist/{downloadImage-Cijmufpr.cjs → downloadImage-B7MKUI0u.cjs} +1 -372
  26. package/dist/downloadImage-DKWzC11M.mjs +485 -0
  27. package/dist/{enroll-CysDL6ee.mjs → enroll-Bc3Lke1H.mjs} +3 -3
  28. package/dist/{enroll-C2gunHdt.cjs → enroll-Blqx_Kg8.cjs} +3 -3
  29. package/dist/{evidence-DPutCuUr.mjs → evidence-D8nvQ8pQ.mjs} +1 -1
  30. package/dist/{evidence-DsQ-h-_W.cjs → evidence-jfqT04hc.cjs} +1 -1
  31. package/dist/{formatImageMessage-6aV9YFWr.mjs → formatImageMessage-BFWnadBR.mjs} +1 -1
  32. package/dist/{formatImageMessage-BPK_Ivfe.cjs → formatImageMessage-CZeSzoFO.cjs} +1 -1
  33. package/dist/index.cjs +50 -46
  34. package/dist/index.mjs +50 -46
  35. package/dist/{install-DBg0xQ3g.mjs → install-BvuadnKW.mjs} +1 -1
  36. package/dist/{install-BsTA2tZn.mjs → install-ChK8Eb58.mjs} +2 -2
  37. package/dist/{install-Tyhs6rUp.cjs → install-DFmp8KKp.cjs} +1 -1
  38. package/dist/{install-DlOppA-e.cjs → install-nJv8rYWA.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-CqMfcEh7.cjs} +2 -2
  44. package/dist/{loadableToolOutput-2dMPAp4N.mjs → loadableToolOutput-D6crYom_.mjs} +2 -2
  45. package/dist/{persistence-D9tMm6bj.mjs → persistence-BXtirP1k.mjs} +1 -1
  46. package/dist/{persistence-BcQmjobZ.cjs → persistence-DOgft4tZ.cjs} +1 -1
  47. package/dist/{push-Dxsugkes.mjs → push-188EjPTP.mjs} +6 -6
  48. package/dist/{push-Cnm6JQMD.cjs → push-DykEVhRR.cjs} +6 -6
  49. package/dist/registerKillSessionHandler-6loIZYD9.mjs +407 -0
  50. package/dist/registerKillSessionHandler-ks8iPY9a.cjs +411 -0
  51. package/dist/{run-CiPWWhXt.cjs → run-DSJ7IIWW.cjs} +10 -8
  52. package/dist/{run-BEflQaQ2.mjs → run-Dp4_De-r.mjs} +10 -8
  53. package/dist/{runClaude-BVXsyCHb.cjs → runClaude-C3be35z0.cjs} +26 -26
  54. package/dist/{runClaude-sPkSqHWY.mjs → runClaude-gj4urJvB.mjs} +21 -21
  55. package/dist/{runCodex-C4rHkIyw.mjs → runCodex-DRZidxNB.mjs} +26 -26
  56. package/dist/{runCodex-BAwhdmSA.cjs → runCodex-p_fToj9T.cjs} +28 -28
  57. package/dist/{runCursor-CD5wC3gA.cjs → runCursor-CJlqkSYR.cjs} +210 -34
  58. package/dist/{runCursor-DiHLWMs8.mjs → runCursor-SFd9G7X_.mjs} +210 -34
  59. package/dist/{runGemini-B-93gOqB.mjs → runGemini-BOsfh6dX.mjs} +17 -17
  60. package/dist/{runGemini-Cto-adir.cjs → runGemini-lxvXq70R.cjs} +19 -19
  61. package/dist/{runOneShot-BfIzE9W4.cjs → runOneShot-BExFkHmA.cjs} +2 -2
  62. package/dist/{runOneShot-DGjlYVhR.mjs → runOneShot-CtzlkF1l.mjs} +2 -2
  63. package/dist/{serve-CqL6NWDr.cjs → serve-Bl1Caf6x.cjs} +6 -6
  64. package/dist/{serve-ttlqF5jn.mjs → serve-DfvSXAD2.mjs} +6 -6
  65. package/dist/{session-BZhGpQUJ.cjs → session--R6FqsxL.cjs} +5 -5
  66. package/dist/{session-Co0REU9e.mjs → session-BSzNJiAb.mjs} +5 -5
  67. package/dist/{sessionKeyCache-BwFUGgMs.cjs → sessionKeyCache-CVkksG68.cjs} +1 -1
  68. package/dist/{sessionKeyCache-pXTjpETo.mjs → sessionKeyCache-DYiCIvYC.mjs} +1 -1
  69. package/dist/{sessionList-fG5uAPzU.cjs → sessionList-C1q9fy5j.cjs} +2 -2
  70. package/dist/{sessionList-DaufR-sA.mjs → sessionList-D3pCseut.mjs} +2 -2
  71. package/dist/{sessionSummary-CjZPt6nr.cjs → sessionSummary-CUZxEBEZ.cjs} +3 -3
  72. package/dist/{sessionSummary-Dv6dPABg.mjs → sessionSummary-WpOlgXe1.mjs} +3 -3
  73. package/dist/{setupOfflineReconnection-DW5il97B.mjs → setupOfflineReconnection-BeWF3SGN.mjs} +5 -3
  74. package/dist/{setupOfflineReconnection-B6coH9QN.cjs → setupOfflineReconnection-CixoiWTS.cjs} +7 -5
  75. package/dist/{spawnHappyCLI-DhUeycct.mjs → spawnHappyCLI-Cyc-69jO.mjs} +1 -1
  76. package/dist/{spawnHappyCLI-Cfu7C1uM.cjs → spawnHappyCLI-hRc7u16s.cjs} +1 -1
  77. package/dist/{ssh-C4YTSHXL.cjs → ssh-B_TYtrys.cjs} +5 -5
  78. package/dist/{ssh-8tPIKk-i.mjs → ssh-BsxAgWgG.mjs} +5 -5
  79. package/dist/{task-DFW-PecP.cjs → task-BInh42e3.cjs} +6 -6
  80. package/dist/{task-CH-oidFy.mjs → task-BS4usaxx.mjs} +6 -6
  81. package/dist/{uninstall-CeioRZNI.mjs → uninstall-BKuDaZKB.mjs} +2 -2
  82. package/dist/{uninstall-yTSL3X6Y.cjs → uninstall-DNFGxNLl.cjs} +2 -2
  83. package/dist/{update-Bju28vNP.cjs → update-B9LoI5l8.cjs} +3 -3
  84. package/dist/{update-Derd2hoX.mjs → update-DC300VOW.mjs} +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
@@ -1,11 +1,13 @@
1
1
  import { randomUUID } from 'node:crypto';
2
2
  import chalk from 'chalk';
3
- import { A as ApiClient } from './api-Ew6xr81i.mjs';
4
- import { l as logger, a as readSettings } from './persistence-D9tMm6bj.mjs';
5
- import { i as initialMachineMetadata } from './run-BEflQaQ2.mjs';
6
- import { B as BasePermissionHandler, c as createSessionMetadata, s as setupOfflineReconnection } from './setupOfflineReconnection-DW5il97B.mjs';
3
+ import { A as ApiClient } from './api-D3j3YL3m.mjs';
4
+ import { l as logger, a as readSettings } from './persistence-BXtirP1k.mjs';
5
+ import { i as initialMachineMetadata } from './run-Dp4_De-r.mjs';
6
+ import { B as BasePermissionHandler, c as createSessionMetadata, s as setupOfflineReconnection } from './setupOfflineReconnection-BeWF3SGN.mjs';
7
+ import { notifyDaemonSessionStarted } from './controlClient-DSiES8Un.mjs';
8
+ import { c as createMcpContext, r as registerKillSessionHandler } from './registerKillSessionHandler-6loIZYD9.mjs';
7
9
  import { spawn } from 'node:child_process';
8
- import { C as CursorApprovalBridge } from './cursorApprovalServer-JjPkdjGR.mjs';
10
+ import { n as normalizeCursorTool, C as CursorApprovalBridge } from './cursorApprovalServer-fjPBXSAb.mjs';
9
11
  import 'axios';
10
12
  import 'node:events';
11
13
  import 'socket.io-client';
@@ -31,31 +33,38 @@ import 'tweetnacl';
31
33
  import '@stablelib/base64';
32
34
  import 'node:readline';
33
35
  import 'expo-server-sdk';
34
- import './sessionKeyCache-pXTjpETo.mjs';
36
+ import './sessionKeyCache-DYiCIvYC.mjs';
35
37
  import 'fs';
36
38
  import 'os';
37
39
  import 'tmp';
38
- import './auth-Q2STUNI-.mjs';
40
+ import './auth-DPe89SQx.mjs';
39
41
  import 'qrcode-terminal';
40
- import './browser-BI7s-V8E.mjs';
42
+ import './browser-Byx5gq-W.mjs';
41
43
  import 'open';
42
44
  import 'react';
43
45
  import 'ink';
44
- import './doctor-DRnft_5-.mjs';
45
- import './controlClient-DoS9JqQC.mjs';
46
+ import './doctor-BA28TuMy.mjs';
46
47
  import './doctor-ON7pN5G5.mjs';
47
48
  import 'ps-list';
48
49
  import 'cross-spawn';
49
- import './spawnHappyCLI-DhUeycct.mjs';
50
- import './deviceKeys-B4aqjnE6.mjs';
51
- import './install-DBg0xQ3g.mjs';
50
+ import './spawnHappyCLI-Cyc-69jO.mjs';
51
+ import './deviceKeys-BYHzWSyg.mjs';
52
+ import './install-BvuadnKW.mjs';
52
53
  import './trimIdent-BAjmv_TG.mjs';
53
54
  import 'fastify';
54
55
  import 'fastify-type-provider-zod';
55
56
  import './common-BdNgghUJ.mjs';
56
- import './gitWorktree-CzJDU2BV.mjs';
57
+ import '@modelcontextprotocol/sdk/server/mcp.js';
57
58
  import 'node:http';
59
+ import '@modelcontextprotocol/sdk/server/streamableHttp.js';
60
+ import '@modelcontextprotocol/sdk/types.js';
61
+ import './prompt-BsAZsPbo.mjs';
62
+ import './mcpToolSchemas-CjeTfrAs.mjs';
63
+ import './types-BoJh4Qfv.mjs';
58
64
 
65
+ function createCursorStreamState() {
66
+ return { thinkingBuffer: "" };
67
+ }
59
68
  function textFromMessageContent(message) {
60
69
  if (!message || typeof message !== "object") return null;
61
70
  const content = message.content;
@@ -74,9 +83,10 @@ function unwrapToolCall(toolCall) {
74
83
  for (const [key, value] of Object.entries(toolCall)) {
75
84
  if (!key.endsWith("ToolCall") || !value || typeof value !== "object") continue;
76
85
  const inner = value;
86
+ const normalized = normalizeCursorTool(key.replace(/ToolCall$/, ""), inner.args);
77
87
  return {
78
- toolName: key.replace(/ToolCall$/, ""),
79
- args: inner.args && typeof inner.args === "object" ? inner.args : {},
88
+ toolName: normalized.name,
89
+ args: normalized.input,
80
90
  result: inner.result ?? null
81
91
  };
82
92
  }
@@ -104,7 +114,7 @@ function parseCursorSessionInit(event) {
104
114
  apiKeySource: str("apiKeySource")
105
115
  };
106
116
  }
107
- function mapCursorStreamEvent(event) {
117
+ function mapCursorStreamEvent(event, state) {
108
118
  switch (event.type) {
109
119
  case "system": {
110
120
  if (event.subtype !== "init") return [];
@@ -120,10 +130,19 @@ function mapCursorStreamEvent(event) {
120
130
  case "user":
121
131
  return [];
122
132
  case "thinking": {
123
- if (event.subtype !== "delta") return [];
124
- const text = typeof event.text === "string" ? event.text : null;
125
- if (!text) return [];
126
- return [{ type: "event", name: "thinking", payload: { textDelta: text } }];
133
+ const text = typeof event.text === "string" ? event.text : "";
134
+ if (event.subtype === "delta") {
135
+ if (!text) return [];
136
+ if (!state) return [{ type: "event", name: "thinking", payload: { textDelta: text } }];
137
+ state.thinkingBuffer += text;
138
+ return [];
139
+ }
140
+ if (event.subtype === "completed" && state?.thinkingBuffer) {
141
+ const full = state.thinkingBuffer;
142
+ state.thinkingBuffer = "";
143
+ return [{ type: "event", name: "thinking", payload: { textDelta: full } }];
144
+ }
145
+ return [];
127
146
  }
128
147
  case "assistant": {
129
148
  const text = textFromMessageContent(event.message);
@@ -192,6 +211,7 @@ function drainJsonLines(buffer) {
192
211
  }
193
212
 
194
213
  const CANCEL_GRACE_MS = 2e3;
214
+ const THINKING_FLUSH_MS = 800;
195
215
  class CursorCliBackend {
196
216
  options;
197
217
  handlers = /* @__PURE__ */ new Set();
@@ -199,12 +219,30 @@ class CursorCliBackend {
199
219
  child = null;
200
220
  /** Cursor's own chat id, used to resume on the next turn. */
201
221
  chatId = null;
222
+ /** Current model id; null leaves the choice to Cursor. */
223
+ model = null;
202
224
  stdoutBuffer = "";
225
+ streamState = createCursorStreamState();
226
+ thinkingFlushTimer = null;
203
227
  turnComplete = Promise.resolve();
204
228
  resolveTurn = null;
205
229
  disposed = false;
206
230
  constructor(options) {
207
231
  this.options = options;
232
+ this.chatId = options.resumeChatId ?? null;
233
+ this.model = options.model ?? null;
234
+ }
235
+ /**
236
+ * The app sends the chosen model with every message, so it can change
237
+ * between turns. Each turn spawns its own process, which is where the new
238
+ * value takes effect.
239
+ */
240
+ setModel(model) {
241
+ this.model = model;
242
+ }
243
+ /** Cursor's own chat id, once it is known. */
244
+ getChatId() {
245
+ return this.chatId;
208
246
  }
209
247
  onMessage(handler) {
210
248
  this.handlers.add(handler);
@@ -243,14 +281,16 @@ class CursorCliBackend {
243
281
  if (this.child) {
244
282
  throw new Error("CursorCliBackend is already running a turn");
245
283
  }
246
- const args = ["-p", prompt, "--output-format", "stream-json", "--trust"];
247
- if (this.options.model) args.push("--model", this.options.model);
284
+ const args = ["-p", prompt, "--output-format", "stream-json", "--trust", "--force"];
285
+ if (this.model) args.push("--model", this.model);
248
286
  if (this.chatId) args.push("--resume", this.chatId);
249
287
  if (this.options.extraArgs?.length) args.push(...this.options.extraArgs);
250
288
  const command = this.options.command ?? "cursor-agent";
251
- logger.debug(`[cursor] spawn ${command} ${this.chatId ? `(resume ${this.chatId})` : "(new chat)"}`);
289
+ logger.debug(`[cursor] spawn ${command} model=${this.model ?? "(Cursor default)"} ${this.chatId ? `(resume ${this.chatId})` : "(new chat)"}`);
252
290
  this.emit({ type: "status", status: "starting" });
253
291
  this.stdoutBuffer = "";
292
+ this.flushThinking();
293
+ this.streamState = createCursorStreamState();
254
294
  this.turnComplete = new Promise((resolve) => {
255
295
  this.resolveTurn = resolve;
256
296
  });
@@ -276,6 +316,7 @@ class CursorCliBackend {
276
316
  });
277
317
  child.on("close", (code) => {
278
318
  this.consumeStdout("\n");
319
+ this.flushThinking();
279
320
  if (code !== 0) {
280
321
  const reason = lastStderr.length > 0 ? `: ${lastStderr}` : "";
281
322
  this.emit({ type: "status", status: "error", detail: `cursor-agent exited with code ${code}${reason}` });
@@ -285,6 +326,17 @@ class CursorCliBackend {
285
326
  });
286
327
  await this.turnComplete;
287
328
  }
329
+ /** Emits whatever reasoning has accumulated, if any. */
330
+ flushThinking() {
331
+ if (this.thinkingFlushTimer) {
332
+ clearTimeout(this.thinkingFlushTimer);
333
+ this.thinkingFlushTimer = null;
334
+ }
335
+ const text = this.streamState.thinkingBuffer;
336
+ if (!text) return;
337
+ this.streamState.thinkingBuffer = "";
338
+ this.emit({ type: "event", name: "thinking", payload: { textDelta: text } });
339
+ }
288
340
  consumeStdout(chunk) {
289
341
  this.stdoutBuffer += chunk;
290
342
  const { events, rest } = drainJsonLines(this.stdoutBuffer);
@@ -295,9 +347,19 @@ class CursorCliBackend {
295
347
  this.chatId = init.sessionId;
296
348
  logger.debug(`[cursor] chat id ${init.sessionId} model ${init.model ?? "(default)"}`);
297
349
  }
298
- for (const message of mapCursorStreamEvent(event)) {
350
+ for (const message of mapCursorStreamEvent(event, this.streamState)) {
351
+ if (message.type === "event" && message.name === "thinking" && this.thinkingFlushTimer) {
352
+ clearTimeout(this.thinkingFlushTimer);
353
+ this.thinkingFlushTimer = null;
354
+ }
299
355
  this.emit(message);
300
356
  }
357
+ if (this.streamState.thinkingBuffer && !this.thinkingFlushTimer) {
358
+ this.thinkingFlushTimer = setTimeout(() => {
359
+ this.thinkingFlushTimer = null;
360
+ this.flushThinking();
361
+ }, THINKING_FLUSH_MS);
362
+ }
301
363
  }
302
364
  }
303
365
  finishTurn() {
@@ -403,9 +465,13 @@ function agentMessageToAcp(message, newId) {
403
465
  }
404
466
 
405
467
  const ALWAYS_ALLOWED_TOOLS = ["change_title", "happy__change_title", "preview_html", "happy__preview_html"];
468
+ const UNATTENDED_MODES = ["yolo", "safe-yolo", "bypassPermissions"];
406
469
  class CursorPermissionHandler extends BasePermissionHandler {
407
- /** Set once the user answers "approve for session" or picks a yolo mode. */
470
+ /** Set by "allow all tools for this session", or by an unattended mode. */
408
471
  approveEverything = false;
472
+ /** Tools the user answered "don't ask again" for. */
473
+ allowedTools = /* @__PURE__ */ new Set();
474
+ currentPermissionMode = null;
409
475
  constructor(session, pushClient) {
410
476
  super(session, pushClient);
411
477
  }
@@ -415,8 +481,21 @@ class CursorPermissionHandler extends BasePermissionHandler {
415
481
  getAgentName() {
416
482
  return "Cursor";
417
483
  }
418
- /** Skip the round trip for tools that are part of Happy's own plumbing. */
484
+ /**
485
+ * The app sends the mode with each message. In an unattended mode asking
486
+ * would contradict what the session badge promises, so tools just run.
487
+ */
488
+ setPermissionMode(mode) {
489
+ if (mode === this.currentPermissionMode) return;
490
+ this.currentPermissionMode = mode;
491
+ logger.debug(`${this.getLogPrefix()} permission mode is now ${mode ?? "default"}`);
492
+ }
493
+ isUnattended() {
494
+ return this.currentPermissionMode !== null && UNATTENDED_MODES.includes(this.currentPermissionMode);
495
+ }
419
496
  shouldAutoApprove(toolName) {
497
+ if (this.approveEverything || this.isUnattended()) return true;
498
+ if (this.allowedTools.has(toolName)) return true;
420
499
  const lower = toolName.toLowerCase();
421
500
  return ALWAYS_ALLOWED_TOOLS.some((name) => lower.includes(name.toLowerCase()));
422
501
  }
@@ -426,7 +505,7 @@ class CursorPermissionHandler extends BasePermissionHandler {
426
505
  * `timeout` we write into hooks.json.
427
506
  */
428
507
  async requestApproval(toolCallId, toolName, input) {
429
- if (this.approveEverything || this.shouldAutoApprove(toolName)) {
508
+ if (this.shouldAutoApprove(toolName)) {
430
509
  logger.debug(`${this.getLogPrefix()} auto-approving ${toolName}`);
431
510
  return true;
432
511
  }
@@ -435,7 +514,10 @@ class CursorPermissionHandler extends BasePermissionHandler {
435
514
  this.addPendingRequestToState(toolCallId, toolName, input);
436
515
  logger.debug(`${this.getLogPrefix()} awaiting approval for ${toolName} (${toolCallId})`);
437
516
  });
438
- if (result.decision === "approved_for_session") {
517
+ for (const allowed of result.allowTools ?? []) {
518
+ this.allowedTools.add(allowed);
519
+ }
520
+ if (result.decision === "approved_for_session" || result.mode && UNATTENDED_MODES.includes(result.mode)) {
439
521
  this.approveEverything = true;
440
522
  }
441
523
  return result.decision === "approved" || result.decision === "approved_for_session";
@@ -472,8 +554,23 @@ async function runCursor(opts) {
472
554
  }
473
555
  });
474
556
  session = initialSession;
557
+ if (response) {
558
+ try {
559
+ const result = await notifyDaemonSessionStarted(response.id, metadata);
560
+ if (result?.error) {
561
+ logger.debug(`[cursor] daemon did not accept the session report: ${result.error}`);
562
+ }
563
+ } catch (error) {
564
+ logger.debug(`[cursor] could not report session to daemon: ${error}`);
565
+ }
566
+ }
567
+ const mcp = await createMcpContext(session);
568
+ const mcpStdio = mcp.configForStdio().happy;
569
+ const mcpUrlArg = mcpStdio?.args?.indexOf("--url") ?? -1;
570
+ const mcpUrl = mcpUrlArg >= 0 ? mcpStdio.args[mcpUrlArg + 1] : null;
571
+ const mcpBridge = mcpStdio ? { command: mcpStdio.command, args: mcpStdio.args ?? [] } : null;
475
572
  const permissionHandler = new CursorPermissionHandler(session, api.push());
476
- const approvalBridge = new CursorApprovalBridge({ cwd: process.cwd(), handler: permissionHandler });
573
+ const approvalBridge = new CursorApprovalBridge({ cwd: process.cwd(), handler: permissionHandler, mcpBridge });
477
574
  let approvalReady = false;
478
575
  try {
479
576
  await approvalBridge.start();
@@ -483,14 +580,51 @@ async function runCursor(opts) {
483
580
  }
484
581
  const backend = new CursorCliBackend({
485
582
  cwd: process.cwd(),
486
- model: opts.model ?? null
583
+ model: opts.model ?? null,
584
+ resumeChatId: opts.resumeChatId ?? null,
585
+ // Cursor asks before loading an MCP server; nobody is at the terminal.
586
+ extraArgs: mcpBridge ? ["--approve-mcps"] : void 0
487
587
  });
588
+ let publishedChatId = opts.resumeChatId ?? null;
589
+ if (publishedChatId) {
590
+ session.updateMetadata((current) => ({ ...current, cursorSessionId: publishedChatId }));
591
+ }
488
592
  const queue = [];
489
593
  let draining = false;
490
594
  let thinking = false;
595
+ let reportedModel = null;
491
596
  backend.onMessage((message) => {
597
+ const chatId = backend.getChatId();
598
+ if (chatId && chatId !== publishedChatId) {
599
+ publishedChatId = chatId;
600
+ session.updateMetadata((current) => ({ ...current, cursorSessionId: chatId }));
601
+ void approvalBridge.registerChat(chatId);
602
+ }
603
+ if (message.type === "token-count") {
604
+ const usage = message;
605
+ const num = (key) => typeof usage[key] === "number" ? usage[key] : 0;
606
+ const input = num("inputTokens");
607
+ const output = num("outputTokens");
608
+ const cacheRead = num("cacheReadTokens");
609
+ const cacheWrite = num("cacheWriteTokens");
610
+ const total = input + output + cacheRead + cacheWrite;
611
+ if (total > 0) {
612
+ session.sendUsageReport({
613
+ key: "cursor-session",
614
+ tokens: { total, input, output, cache_read: cacheRead, cache_creation: cacheWrite },
615
+ // Cursor bills against its own subscription and reports no
616
+ // per-request price, so there is nothing honest to put here.
617
+ cost: { total: 0 }
618
+ });
619
+ }
620
+ }
492
621
  if (message.type === "status") {
493
622
  thinking = message.status === "starting" || message.status === "running";
623
+ const named = message.detail?.match(/^cursor-agent ready \((.+)\)$/)?.[1];
624
+ if (named && named !== reportedModel) {
625
+ reportedModel = named;
626
+ session.updateMetadata((current) => ({ ...current, model: named }));
627
+ }
494
628
  }
495
629
  for (const acp of agentMessageToAcp(message, () => randomUUID())) {
496
630
  session.sendAgentMessage("cursor", acp);
@@ -516,11 +650,28 @@ async function runCursor(opts) {
516
650
  thinking = false;
517
651
  }
518
652
  };
653
+ const handleAbort = async () => {
654
+ logger.debug(`[cursor] abort requested (${queue.length} queued)`);
655
+ queue.length = 0;
656
+ try {
657
+ await backend.cancel("");
658
+ } catch (error) {
659
+ logger.debug(`[cursor] abort failed: ${error}`);
660
+ }
661
+ permissionHandler.reset();
662
+ thinking = false;
663
+ session.sendSessionEvent({ type: "message", message: "Aborted by user" });
664
+ };
519
665
  session.onUserMessage((message) => {
520
666
  const text = message.content?.text;
521
667
  if (typeof text !== "string" || text.trim().length === 0) return;
668
+ if (message.meta?.permissionMode) {
669
+ permissionHandler.setPermissionMode(message.meta.permissionMode);
670
+ }
522
671
  if (message.meta?.hasOwnProperty("model")) {
523
- logger.debug("[cursor] per-message model override is not wired yet, ignoring");
672
+ const chosen = message.meta.model || null;
673
+ backend.setModel(chosen);
674
+ logger.debug(`[cursor] model set to ${chosen ?? "(Cursor default)"}`);
524
675
  }
525
676
  queue.push(text);
526
677
  void drain();
@@ -537,6 +688,11 @@ async function runCursor(opts) {
537
688
  }
538
689
  console.log(chalk.gray(` cwd ${process.cwd()}`));
539
690
  console.log(chalk.gray(` model ${opts.model ?? "(Cursor default)"}`));
691
+ if (mcpUrl) {
692
+ console.log(chalk.gray(` tools Happy MCP on ${mcpUrl}`));
693
+ } else {
694
+ console.log(chalk.yellow(" tools Happy MCP unavailable \u2014 change_title, preview_html and device tools are missing"));
695
+ }
540
696
  if (approvalReady) {
541
697
  console.log(chalk.gray(" approval tool calls are sent to your phone before they run"));
542
698
  } else {
@@ -547,14 +703,26 @@ async function runCursor(opts) {
547
703
  void drain();
548
704
  }
549
705
  let shuttingDown = false;
550
- const shutdown = async () => {
706
+ const shutdown = async (archived = false) => {
551
707
  if (shuttingDown) return;
552
708
  shuttingDown = true;
553
709
  clearInterval(keepAlive);
554
- logger.debug("[cursor] shutting down");
710
+ thinking = false;
711
+ logger.debug(`[cursor] shutting down${archived ? " (archived)" : ""}`);
712
+ if (archived) {
713
+ session.updateMetadata((current) => ({
714
+ ...current,
715
+ lifecycleState: "archived",
716
+ lifecycleStateSince: Date.now(),
717
+ archivedBy: "cli",
718
+ archiveReason: "User terminated"
719
+ }));
720
+ }
555
721
  try {
722
+ permissionHandler.reset();
556
723
  await backend.dispose();
557
724
  await approvalBridge.stop();
725
+ mcp.stop();
558
726
  } finally {
559
727
  session.sendSessionDeath();
560
728
  await session.flush();
@@ -569,6 +737,14 @@ async function runCursor(opts) {
569
737
  };
570
738
  process.on("SIGINT", shutdownAndExit);
571
739
  process.on("SIGTERM", shutdownAndExit);
740
+ session.rpcHandlerManager.registerHandler("abort", handleAbort);
741
+ registerKillSessionHandler(session.rpcHandlerManager, async () => {
742
+ await Promise.race([
743
+ shutdown(true),
744
+ new Promise((resolve) => setTimeout(resolve, SHUTDOWN_TIMEOUT_MS))
745
+ ]);
746
+ process.exit(0);
747
+ });
572
748
  await new Promise(() => {
573
749
  });
574
750
  }
@@ -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-D3j3YL3m.mjs';
5
+ import { l as logger, p as packageJson, i as isDebug, a as readSettings } from './persistence-BXtirP1k.mjs';
6
+ import { B as BasePermissionHandler, c as createSessionMetadata, s as setupOfflineReconnection } from './setupOfflineReconnection-BeWF3SGN.mjs';
7
+ import { i as initialMachineMetadata, s as stopCaffeinate } from './run-Dp4_De-r.mjs';
8
+ import { M as MessageQueue2, h as hashObject, a as MessageBuffer, p as parseClear } from './downloadImage-DKWzC11M.mjs';
9
+ import { r as registerKillSessionHandler, c as createMcpContext } from './registerKillSessionHandler-6loIZYD9.mjs';
10
+ import { notifyDaemonSessionStarted } from './controlClient-DSiES8Un.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-D6crYom_.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-DJLOlgjr.mjs';
18
+ import { f as formatMessageForGemini } from './formatImageMessage-BFWnadBR.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-DYiCIvYC.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-DPe89SQx.mjs';
47
47
  import 'qrcode-terminal';
48
- import './browser-BI7s-V8E.mjs';
48
+ import './browser-Byx5gq-W.mjs';
49
49
  import 'open';
50
- import './doctor-DRnft_5-.mjs';
50
+ import './doctor-BA28TuMy.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-Cyc-69jO.mjs';
55
+ import './deviceKeys-BYHzWSyg.mjs';
56
+ import './install-BvuadnKW.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-Ct7Z1lwF.cjs');
7
+ var logger = require('./persistence-DOgft4tZ.cjs');
8
+ var setupOfflineReconnection = require('./setupOfflineReconnection-CixoiWTS.cjs');
9
+ var run = require('./run-DSJ7IIWW.cjs');
10
+ var downloadImage = require('./downloadImage-B7MKUI0u.cjs');
11
+ var registerKillSessionHandler = require('./registerKillSessionHandler-ks8iPY9a.cjs');
12
+ var controlClient = require('./controlClient-FDsMeGVn.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-CqMfcEh7.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-Djr2JMWO.cjs');
20
+ var formatImageMessage = require('./formatImageMessage-CZeSzoFO.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-CVkksG68.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-B9kOli6i.cjs');
49
49
  require('qrcode-terminal');
50
- require('./browser-qDIIAL7S.cjs');
50
+ require('./browser-BPEhi6te.cjs');
51
51
  require('open');
52
- require('./doctor-BJSuVRUZ.cjs');
52
+ require('./doctor-B_W7odmC.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-hRc7u16s.cjs');
57
+ require('./deviceKeys-DPut1U07.cjs');
58
+ require('./install-DFmp8KKp.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-CcarNVE9.cjs');
6
+ var logger = require('./persistence-DOgft4tZ.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-BtxBYzs7.mjs';
4
+ import { l as logger } from './persistence-BXtirP1k.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-DOgft4tZ.cjs');
6
+ var sessionList = require('./sessionList-C1q9fy5j.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-CUZxEBEZ.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-B9kOli6i.cjs');
19
19
  require('qrcode-terminal');
20
20
  require('./time-Bj2HRkKH.cjs');
21
- require('./browser-qDIIAL7S.cjs');
21
+ require('./browser-BPEhi6te.cjs');
22
22
  require('open');
23
23
  require('react');
24
24
  require('ink');
25
- require('./sessionKeyCache-BwFUGgMs.cjs');
25
+ require('./sessionKeyCache-CVkksG68.cjs');
26
26
 
27
27
  const PREVIEW_LIMIT = 200;
28
28
  const TAIL_MESSAGES = 5;