very-happy-cli 0.2.155 → 0.2.157

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 (55) hide show
  1. package/dist/{AcpBackend-DmFcRh54.cjs → AcpBackend-BymWX-LI.cjs} +2 -2
  2. package/dist/{AcpBackend-DVB0nzgR.mjs → AcpBackend-Cy18U7yw.mjs} +2 -2
  3. package/dist/{AcpSessionManager-CWaSYTnT.cjs → AcpSessionManager-BF7zBGAi.cjs} +1 -1
  4. package/dist/{AcpSessionManager-dGDgNb-p.mjs → AcpSessionManager-Ds8rcp95.mjs} +1 -1
  5. package/dist/{agentGuidance-BWxFC6UL.mjs → agentGuidance-CU6YD_Ms.mjs} +560 -29
  6. package/dist/{agentGuidance-un7YPryB.cjs → agentGuidance-Db76O1kw.cjs} +579 -28
  7. package/dist/{agentHome-aucT8BHw.cjs → agentHome-B0CLy0Qx.cjs} +2 -2
  8. package/dist/{agentHome-ByLb2HFg.mjs → agentHome-BaJ9xtxB.mjs} +2 -2
  9. package/dist/{auto-BQnD6gVx.mjs → auto-B1YAwaeU.mjs} +6 -6
  10. package/dist/{auto-P9RoJaY0.cjs → auto-DqKGFq8U.cjs} +6 -6
  11. package/dist/codex/happyMcpStdioBridge.cjs +2 -2
  12. package/dist/codex/happyMcpStdioBridge.mjs +2 -2
  13. package/dist/{config-BoePh9PX.cjs → config-CzkBjZnV.cjs} +4 -4
  14. package/dist/{config-BPZ2Y6o8.mjs → config-aAbhJllb.mjs} +4 -4
  15. package/dist/{index-BjzdXb9x.mjs → index-BVu8Cufk.mjs} +8 -8
  16. package/dist/{index-B4oXnbYw.mjs → index-BYU1e81k.mjs} +372 -65
  17. package/dist/{index-D_fYUxVg.cjs → index-C9QzZsUQ.cjs} +375 -68
  18. package/dist/{index-DRrLaljt.cjs → index-CTbGqVFY.cjs} +7 -7
  19. package/dist/{index--KCpVs1c.cjs → index-Cz-uqnJk.cjs} +4 -4
  20. package/dist/{index-DVVRqaVG.mjs → index-DQcRkI9r.mjs} +4 -4
  21. package/dist/index.cjs +4 -4
  22. package/dist/index.mjs +4 -4
  23. package/dist/{install-CGigiocs.cjs → install-BMlV0Tnr.cjs} +1 -1
  24. package/dist/{install-DP5aPNEX.mjs → install-BuYnqtVa.mjs} +1 -1
  25. package/dist/{installTerminalHooks-XrcPvfBs.cjs → installTerminalHooks-BVVOEGhG.cjs} +4 -4
  26. package/dist/{installTerminalHooks-tIg7mANc.mjs → installTerminalHooks-ELDoqh0A.mjs} +4 -4
  27. package/dist/lib.cjs +2 -2
  28. package/dist/lib.d.cts +101 -9
  29. package/dist/lib.d.mts +101 -9
  30. package/dist/lib.mjs +2 -2
  31. package/dist/{limits-C_DW0M0q.mjs → limits-A2c-3M-_.mjs} +162 -11
  32. package/dist/{limits-DkfHJ3nS.cjs → limits-CyuIt-Vl.cjs} +169 -10
  33. package/dist/{mcp-o7QglAQM.mjs → mcp-Bo934Kpy.mjs} +4 -4
  34. package/dist/{mcp-BqlXl10F.cjs → mcp-CXkvnztT.cjs} +4 -4
  35. package/dist/{piRuntime-BxP4B_zY.cjs → piRuntime-CJjA26oD.cjs} +2 -2
  36. package/dist/{piRuntime-MjiJI-nV.mjs → piRuntime-DdEY2mE9.mjs} +2 -2
  37. package/dist/{piTerminal-DYzRcwgb.cjs → piTerminal-CpU3hzrX.cjs} +6 -6
  38. package/dist/{piTerminal-Dpq6yqKU.mjs → piTerminal-DwWmgumi.mjs} +6 -6
  39. package/dist/{runGemini-CDlcQ-OA.mjs → runGemini-Cj6oJQ-I.mjs} +6 -6
  40. package/dist/{runGemini-8Pxh94Fm.cjs → runGemini-DS-0qm0L.cjs} +6 -6
  41. package/dist/{runOpenClaw-B5FSZIbK.cjs → runOpenClaw-CvSm8oTc.cjs} +5 -5
  42. package/dist/{runOpenClaw-YkhsgPs0.mjs → runOpenClaw-DUCQxkI-.mjs} +5 -5
  43. package/dist/{send-CwrYXTPr.cjs → send-CHxobaoI.cjs} +43 -31
  44. package/dist/{send-DJStmLBv.mjs → send-D5CyRyZ_.mjs} +43 -31
  45. package/dist/{sessions-MX21NJXd.cjs → sessions-BFlIrKCY.cjs} +133 -49
  46. package/dist/{sessions-JaVXlSqj.mjs → sessions-DsE01xKc.mjs} +133 -49
  47. package/dist/{spawn-BaPnjClT.mjs → spawn-CdihhwHs.mjs} +4 -4
  48. package/dist/{spawn-cIoglKVf.cjs → spawn-HV345RN-.cjs} +4 -4
  49. package/dist/{teams-DXV5Pwwt.mjs → teams-BE8nMB6O.mjs} +5 -5
  50. package/dist/{teams-Dqk7F3D-.cjs → teams-BahinMnH.cjs} +5 -5
  51. package/dist/{todo-Buuqn9gm.cjs → todo-CMMQ6yhU.cjs} +2 -2
  52. package/dist/{todo-2Z1REhfx.mjs → todo-CuQAkSp2.mjs} +2 -2
  53. package/dist/{types-a2xsH2UI.cjs → types-B0sSi6eM.cjs} +125 -13
  54. package/dist/{types-CkbfdRzq.mjs → types-BrGJ7PJz.mjs} +125 -13
  55. package/package.json +7 -7
@@ -3,14 +3,14 @@
3
3
  var chalk = require('chalk');
4
4
  var os = require('node:os');
5
5
  var node_crypto = require('node:crypto');
6
- var index = require('./types-a2xsH2UI.cjs');
7
- var persistence = require('./limits-DkfHJ3nS.cjs');
6
+ var index = require('./types-B0sSi6eM.cjs');
7
+ var persistence = require('./limits-CyuIt-Vl.cjs');
8
8
  var spawn = require('cross-spawn');
9
9
  var path = require('node:path');
10
10
  var node_readline = require('node:readline');
11
11
  var node_fs = require('node:fs');
12
12
  var terminalTitleSuggest = require('./terminalTitleSuggest-Dji8I0Jy.cjs');
13
- var agentGuidance = require('./agentGuidance-un7YPryB.cjs');
13
+ var agentGuidance = require('./agentGuidance-Db76O1kw.cjs');
14
14
  var promises = require('node:fs/promises');
15
15
  var fs = require('fs/promises');
16
16
  var ink = require('ink');
@@ -137,6 +137,11 @@ class Session {
137
137
  sessionId;
138
138
  mode = "local";
139
139
  thinking = false;
140
+ /** B-507: background tasks still in flight (async sub-agents, backgrounded
141
+ * commands, monitors) — published with every keepAlive. Always an array
142
+ * for the Claude runner: an empty one says "nothing running", which is a
143
+ * different statement from a runner that has no such notion (undefined). */
144
+ backgroundTasks = [];
140
145
  /**
141
146
  * Window after a user-initiated abort during which stray promise rejections
142
147
  * from tearing down the SDK query (MCP transports, in-flight tool calls,
@@ -198,11 +203,21 @@ class Session {
198
203
  persistence.logger.debug("[Session] Failed to create notification producer:", err);
199
204
  this.notificationProducer = null;
200
205
  }
201
- this.client.keepAlive(this.thinking, this.mode);
206
+ this.client.keepAlive(this.thinking, this.mode, this.backgroundTasks);
202
207
  this.keepAliveInterval = setInterval(() => {
203
- this.client.keepAlive(this.thinking, this.mode);
208
+ this.client.keepAlive(this.thinking, this.mode, this.backgroundTasks);
204
209
  }, 2e3);
205
210
  }
211
+ /**
212
+ * B-507: the tracker's current set (`claude/backgroundTasks.ts`). A change
213
+ * beats at once so the daemon, agentState and the web see the edge without
214
+ * waiting for the 2s tick; an unchanged set is left to the interval.
215
+ */
216
+ setBackgroundTasks = (tasks) => {
217
+ if (persistence.sameBackgroundTasks(this.backgroundTasks, tasks)) return;
218
+ this.backgroundTasks = tasks;
219
+ this.client.keepAlive(this.thinking, this.mode, this.backgroundTasks);
220
+ };
206
221
  /**
207
222
  * Cleanup resources (call when session is no longer needed)
208
223
  */
@@ -237,7 +252,7 @@ class Session {
237
252
  this.lastAssistantSnippet = void 0;
238
253
  }
239
254
  this.thinking = thinking;
240
- this.client.keepAlive(thinking, this.mode);
255
+ this.client.keepAlive(thinking, this.mode, this.backgroundTasks);
241
256
  };
242
257
  /**
243
258
  * Record that Claude emitted an assistant text reply during this turn.
@@ -304,7 +319,7 @@ class Session {
304
319
  };
305
320
  onModeChange = (mode) => {
306
321
  this.mode = mode;
307
- this.client.keepAlive(this.thinking, mode);
322
+ this.client.keepAlive(this.thinking, mode, this.backgroundTasks);
308
323
  this._onModeChange(mode);
309
324
  };
310
325
  onAbort = () => {
@@ -3948,6 +3963,7 @@ async function claudeRemoteLauncher(session, onPermissionModeChange, onEffective
3948
3963
  const turnSteering = createTurnSteeringController();
3949
3964
  const runtimeControls = createRuntimeControls(() => session.thinking);
3950
3965
  session.client.rpcHandlerManager.registerHandler("claude-runtime-control", async (input) => runtimeControls.request(input));
3966
+ const backgroundTasks = persistence.createBackgroundTaskTracker();
3951
3967
  async function abort() {
3952
3968
  if (abortController && !abortController.signal.aborted) {
3953
3969
  abortController.abort();
@@ -4033,6 +4049,8 @@ async function claudeRemoteLauncher(session, onPermissionModeChange, onEffective
4033
4049
  let notifiedQuestionToolCalls = /* @__PURE__ */ new Set();
4034
4050
  function onMessage(message) {
4035
4051
  runtimeControls.observe(message);
4052
+ backgroundTasks.observe(message);
4053
+ session.setBackgroundTasks(backgroundTasks.list());
4036
4054
  if (isRuntimeCheckpointReplay(message)) return;
4037
4055
  if (message.type === "user" && isClaudeInterruptSentinelContent(message.message.content)) {
4038
4056
  sdkToLogConverter.convert(message);
@@ -4257,6 +4275,8 @@ async function claudeRemoteLauncher(session, onPermissionModeChange, onEffective
4257
4275
  },
4258
4276
  onQueryReady: (q) => {
4259
4277
  runtimeControls.setQuery(q);
4278
+ backgroundTasks.reset();
4279
+ session.setBackgroundTasks([]);
4260
4280
  turnSteering.setInterrupt(q.interrupt);
4261
4281
  session.setSideQuestionLive({ ask: q.sideQuestion, canControl: q.canControl });
4262
4282
  session.setSteerHandler(async (input) => {
@@ -4353,6 +4373,8 @@ async function claudeRemoteLauncher(session, onPermissionModeChange, onEffective
4353
4373
  }
4354
4374
  } finally {
4355
4375
  runtimeControls.setQuery(null);
4376
+ backgroundTasks.reset();
4377
+ session.setBackgroundTasks([]);
4356
4378
  turnSteering.reset();
4357
4379
  session.setSteerHandler(null);
4358
4380
  session.setSideQuestionLive(null);
@@ -5402,8 +5424,8 @@ ${typeLabels[type] || type}:`));
5402
5424
  }
5403
5425
  console.log(chalk.gray(" An external claude command is only needed for native terminal/mirror use."));
5404
5426
  try {
5405
- const { resolveAgentHomesForCli } = await Promise.resolve().then(function () { return require('./agentHome-aucT8BHw.cjs'); });
5406
- const { describeAgentHome } = await Promise.resolve().then(function () { return require('./types-a2xsH2UI.cjs'); }).then(function (n) { return n.index; });
5427
+ const { resolveAgentHomesForCli } = await Promise.resolve().then(function () { return require('./agentHome-B0CLy0Qx.cjs'); });
5428
+ const { describeAgentHome } = await Promise.resolve().then(function () { return require('./types-B0sSi6eM.cjs'); }).then(function (n) { return n.index; });
5407
5429
  const homes = await resolveAgentHomesForCli();
5408
5430
  console.log(chalk.green(`\u2713 Claude config dir: ${describeAgentHome(homes.claudeConfigDir)}`));
5409
5431
  console.log(chalk.green(`\u2713 Codex home: ${describeAgentHome(homes.codexHome)}`));
@@ -6226,6 +6248,18 @@ function checkPreviewPath(input, opts) {
6226
6248
  return { resolved, deniedReason: null };
6227
6249
  }
6228
6250
 
6251
+ const BackgroundTaskInfoSchema = z.z.object({
6252
+ id: z.z.string().min(1).max(200),
6253
+ type: z.z.string().max(64),
6254
+ description: z.z.string().max(persistence.BACKGROUND_TASK_DESCRIPTION_MAX + 1),
6255
+ startedAt: z.z.number()
6256
+ });
6257
+ const BackgroundTasksReportSchema = z.z.object({
6258
+ count: z.z.number().int().nonnegative(),
6259
+ tasks: z.z.array(BackgroundTaskInfoSchema),
6260
+ reportedAt: z.z.number().optional(),
6261
+ stale: z.z.boolean().optional()
6262
+ });
6229
6263
  function startDaemonControlServer({
6230
6264
  controlToken,
6231
6265
  getChildren,
@@ -6237,12 +6271,15 @@ function startDaemonControlServer({
6237
6271
  onSessionStateEvent,
6238
6272
  onClaudeAuthFailed,
6239
6273
  onSessionTurnEvent,
6274
+ onSessionBackgroundTasks,
6275
+ getBackgroundTasks,
6240
6276
  pushClipboard,
6241
6277
  pushFilePreview,
6242
6278
  onTerminalHook,
6243
6279
  setTerminalTitle,
6244
6280
  onSessionEdit,
6245
- listPeers
6281
+ listPeers,
6282
+ isTurnActive
6246
6283
  }) {
6247
6284
  return new Promise((resolve) => {
6248
6285
  const app = fastify({
@@ -6296,8 +6333,10 @@ function startDaemonControlServer({
6296
6333
  schema: {
6297
6334
  body: z.z.object({
6298
6335
  sessionId: z.z.string(),
6299
- event: z.z.enum(["completed", "needs_input", "auth_failed", "turn_started", "turn_ended"]),
6300
- spawnedBy: z.z.string().optional()
6336
+ event: z.z.enum(["completed", "needs_input", "auth_failed", "turn_started", "turn_ended", "background_tasks"]),
6337
+ spawnedBy: z.z.string().optional(),
6338
+ // B-507: only with event 'background_tasks' — the wrapper's full set.
6339
+ backgroundTasks: z.z.array(BackgroundTaskInfoSchema).max(persistence.BACKGROUND_TASKS_MAX).optional()
6301
6340
  }),
6302
6341
  response: {
6303
6342
  200: z.z.object({
@@ -6308,6 +6347,10 @@ function startDaemonControlServer({
6308
6347
  }, async (request) => {
6309
6348
  const { sessionId, event, spawnedBy } = request.body;
6310
6349
  persistence.logger.debug(`[CONTROL SERVER] Session event: ${sessionId} ${event} (spawnedBy=${spawnedBy ?? "unset"})`);
6350
+ if (event === "background_tasks") {
6351
+ onSessionBackgroundTasks?.(sessionId, request.body.backgroundTasks ?? []);
6352
+ return { status: "ok" };
6353
+ }
6311
6354
  if (event === "auth_failed") {
6312
6355
  onClaudeAuthFailed?.(sessionId);
6313
6356
  return { status: "ok" };
@@ -6326,7 +6369,10 @@ function startDaemonControlServer({
6326
6369
  children: z.z.array(z.z.object({
6327
6370
  startedBy: z.z.string(),
6328
6371
  happySessionId: z.z.string(),
6329
- pid: z.z.number()
6372
+ pid: z.z.number(),
6373
+ turnActive: z.z.boolean(),
6374
+ // B-507: in-flight background tasks per the wrapper's last report (lease-expired = count 0).
6375
+ backgroundTasks: BackgroundTasksReportSchema.optional()
6330
6376
  }))
6331
6377
  })
6332
6378
  }
@@ -6338,7 +6384,9 @@ function startDaemonControlServer({
6338
6384
  children: children.filter((child) => child.happySessionId !== void 0).map((child) => ({
6339
6385
  startedBy: child.startedBy,
6340
6386
  happySessionId: child.happySessionId,
6341
- pid: child.pid
6387
+ pid: child.pid,
6388
+ turnActive: isTurnActive ? isTurnActive(child.happySessionId) : false,
6389
+ ...getBackgroundTasks ? { backgroundTasks: getBackgroundTasks(child.happySessionId) } : {}
6342
6390
  }))
6343
6391
  };
6344
6392
  });
@@ -6906,6 +6954,170 @@ function createPeerCoordinator(deps) {
6906
6954
  };
6907
6955
  }
6908
6956
 
6957
+ const WRITE_METHODS = /* @__PURE__ */ new Set(["sessions.send", "sessions.message"]);
6958
+ const REMOTE_CLIENT_TAG = "cli-send-remote";
6959
+ function defaultDeps(machineId) {
6960
+ return {
6961
+ machineId,
6962
+ host: os.hostname(),
6963
+ enabled: async () => (await persistence.readSettings()).remoteSessionOps !== "off",
6964
+ readPersisted: persistence.readPersistedSessions,
6965
+ listLocal: (options) => agentGuidance.listSessions(options),
6966
+ listAccount: (options) => agentGuidance.listAccountSessions(options),
6967
+ readTranscript: (sessionId, limit, options) => agentGuidance.readSessionTranscript(sessionId, limit, options),
6968
+ deliver: (sessionId, persisted, text, client, options, deps) => agentGuidance.deliverToSession(sessionId, persisted, text, client, options, deps),
6969
+ listPeers: (context, scope) => agentGuidance.listPeerSessions(context, scope),
6970
+ sendPeer: (context, args) => agentGuidance.sendPeerMessage(context, args),
6971
+ log: (line) => persistence.logger.info(line),
6972
+ now: () => Date.now()
6973
+ };
6974
+ }
6975
+ function createRemoteSessionOpsHandlers(machineId, overrides = {}) {
6976
+ const deps = { ...defaultDeps(machineId), ...overrides };
6977
+ const readBudget = deps.budget ?? new agentGuidance.RemoteCallBudget(void 0, deps.now);
6978
+ const writeBudget = deps.writeBudget ?? new agentGuidance.RemoteCallBudget(agentGuidance.REMOTE_WRITE_CALLS_PER_MINUTE, deps.now);
6979
+ const deadlineMs = deps.deadlineMs ?? agentGuidance.REMOTE_OP_DEADLINE_MS;
6980
+ const sendCommitDeadlineMs = deps.sendCommitDeadlineMs ?? agentGuidance.REMOTE_SEND_COMMIT_DEADLINE_MS;
6981
+ const resumeWaitMs = deps.resumeWaitMs ?? agentGuidance.REMOTE_RESUME_WAIT_MS;
6982
+ const run = (method, op, targetOf) => async (params) => {
6983
+ const started = deps.now();
6984
+ let from = null;
6985
+ let sessionId;
6986
+ const finish = (response) => {
6987
+ const outcome = response.ok ? "ok" : response.error.code;
6988
+ deps.log(agentGuidance.formatRemoteAuditLine({ method, from, sessionId, outcome, durationMs: deps.now() - started }));
6989
+ return response;
6990
+ };
6991
+ let enabled = false;
6992
+ try {
6993
+ enabled = await deps.enabled();
6994
+ } catch {
6995
+ enabled = false;
6996
+ }
6997
+ const guard = agentGuidance.guardRemoteSessionOpsRequest(method, params, { enabled });
6998
+ if (!guard.ok) return finish(guard);
6999
+ from = guard.request.from;
7000
+ sessionId = targetOf(guard.request.args);
7001
+ const budget = WRITE_METHODS.has(method) ? writeBudget : readBudget;
7002
+ const token = budget.take();
7003
+ if (!token.ok) {
7004
+ return finish(agentGuidance.remoteError("rate_limited", `This machine accepts at most ${budget.perMinute} remote ${WRITE_METHODS.has(method) ? "send/message" : "list/read/peers"} calls per minute; retry in ${Math.ceil(token.retryAfterMs / 1e3)}s`));
7005
+ }
7006
+ let timer;
7007
+ const deadline = new Promise((_, reject) => {
7008
+ timer = setTimeout(() => reject(new RemoteOpFailure("timeout", `${method} did not finish within ${Math.round(deadlineMs / 1e3)}s on ${deps.host}`)), deadlineMs);
7009
+ timer.unref?.();
7010
+ });
7011
+ try {
7012
+ return finish({ ok: true, result: await Promise.race([op(guard.request.args, from, started), deadline]) });
7013
+ } catch (error) {
7014
+ const message = error instanceof Error ? error.message : "Remote session operation failed";
7015
+ if (error instanceof RemoteOpFailure) return finish(agentGuidance.remoteError(error.code, message));
7016
+ return finish(agentGuidance.remoteError("internal", message));
7017
+ } finally {
7018
+ if (timer) clearTimeout(timer);
7019
+ }
7020
+ };
7021
+ const requirePersisted = (id) => {
7022
+ const persisted = deps.readPersisted()[id];
7023
+ if (!persisted) throw new RemoteOpFailure("no_local_key", `Session ${id} is not on ${deps.host} (no local key there either)`);
7024
+ return persisted;
7025
+ };
7026
+ return {
7027
+ "sessions.list": run("sessions.list", async (args) => {
7028
+ const wanted = args.ids ? new Set(args.ids) : null;
7029
+ let sessions;
7030
+ if (args.all) {
7031
+ sessions = (await deps.listAccount({ tag: args.tag, recentLimit: wanted ? 1e4 : args.limit, includeArchived: true })).filter((row) => row.decryptable);
7032
+ } else if (wanted) {
7033
+ const persisted = deps.readPersisted();
7034
+ const live = await deps.listLocal({ recentLimit: 1e4 });
7035
+ const liveById = new Map(live.map((row) => [row.id, row]));
7036
+ sessions = [...wanted].filter((id) => persisted[id] !== void 0 || liveById.has(id)).map((id) => liveById.get(id) ?? summaryFromPersisted(id, persisted[id]));
7037
+ } else {
7038
+ sessions = await deps.listLocal({ tag: args.tag, recentLimit: args.limit });
7039
+ }
7040
+ if (wanted) sessions = sessions.filter((row) => wanted.has(row.id));
7041
+ const result = { machineId: deps.machineId, host: deps.host, sessions };
7042
+ return result;
7043
+ }, () => void 0),
7044
+ "sessions.read": run("sessions.read", async (args) => {
7045
+ requirePersisted(args.sessionId);
7046
+ const limit = Math.max(1, Math.min(agentGuidance.MAX_READ_LIMIT, args.limit ?? 20));
7047
+ const transcript = await deps.readTranscript(args.sessionId, limit, { full: args.full === true });
7048
+ const capped = agentGuidance.capRemoteTranscript(transcript.transcript);
7049
+ const result = {
7050
+ ...transcript,
7051
+ summary: {
7052
+ ...transcript.summary,
7053
+ ...transcript.summary.title !== void 0 ? { title: agentGuidance.capRemoteText(transcript.summary.title, agentGuidance.REMOTE_TITLE_MAX_BYTES) } : {}
7054
+ },
7055
+ turn: { ...transcript.turn, answer: agentGuidance.capRemoteText(transcript.turn.answer, agentGuidance.REMOTE_ANSWER_MAX_BYTES, "tail") },
7056
+ transcript: capped.transcript,
7057
+ ...capped.truncated ? { truncated: true } : {},
7058
+ machineId: deps.machineId,
7059
+ host: deps.host
7060
+ };
7061
+ return result;
7062
+ }, (args) => args.sessionId),
7063
+ "sessions.send": run("sessions.send", async (args, _from, started) => {
7064
+ const persisted = requirePersisted(args.sessionId);
7065
+ const send = (sessionId, key, text, client, options) => {
7066
+ if (deps.now() - started > sendCommitDeadlineMs) {
7067
+ throw new RemoteOpFailure("timeout", `Session ${sessionId} was not live within ${Math.round(sendCommitDeadlineMs / 1e3)}s on ${deps.host}; nothing was sent \u2014 retry (the same localId cannot double-deliver)`);
7068
+ }
7069
+ return agentGuidance.sendUserMessage(sessionId, key, text, client, { ...options, ...args.localId ? { localId: args.localId } : {} });
7070
+ };
7071
+ return deps.deliver(args.sessionId, persisted, args.text, REMOTE_CLIENT_TAG, {
7072
+ resume: args.resume === true,
7073
+ waitMs: resumeWaitMs,
7074
+ pollMs: 1e3,
7075
+ ...args.model !== void 0 ? { model: args.model } : {},
7076
+ ...args.sentFrom !== void 0 ? { sentFrom: args.sentFrom } : {}
7077
+ }, { send });
7078
+ }, (args) => args.sessionId),
7079
+ "sessions.peers": run("sessions.peers", async (args, from) => {
7080
+ const scope = args.cwd ? args.scope ?? "machine" : "machine";
7081
+ const context = {
7082
+ self: () => ({ sessionId: from.sessionId ?? agentGuidance.CLI_PEER_SENDER_ID, cwd: args.cwd ?? "/", machine: from.host }),
7083
+ readPersisted: deps.readPersisted
7084
+ };
7085
+ const listing = await deps.listPeers(context, scope);
7086
+ return { ...listing, machineId: deps.machineId, host: deps.host };
7087
+ }, () => void 0),
7088
+ "sessions.message": run("sessions.message", async (args) => {
7089
+ requirePersisted(args.to);
7090
+ const context = {
7091
+ self: () => ({ ...args.from, cwd: args.from.cwd ?? "/" }),
7092
+ readPersisted: deps.readPersisted,
7093
+ // The target must be HERE; never bounce a remote request onward.
7094
+ remoteMessage: null
7095
+ };
7096
+ return deps.sendPeer(context, { to: args.to, body: args.body, ...args.replyTo ? { replyTo: args.replyTo } : {} });
7097
+ }, (args) => args.to)
7098
+ };
7099
+ }
7100
+ class RemoteOpFailure extends Error {
7101
+ constructor(code, message) {
7102
+ super(message);
7103
+ this.code = code;
7104
+ }
7105
+ }
7106
+ function summaryFromPersisted(id, persisted) {
7107
+ const meta = persisted?.metadata;
7108
+ return {
7109
+ id,
7110
+ live: false,
7111
+ ...meta?.summary?.text ? { title: meta.summary.text } : {},
7112
+ ...meta?.path ? { cwd: meta.path } : {},
7113
+ ...meta?.flavor ? { flavor: meta.flavor } : {},
7114
+ ...meta?.tags?.length ? { tags: [...meta.tags] } : {},
7115
+ ...meta?.variant ? { variant: meta.variant } : {},
7116
+ ...persisted?.savedAt !== void 0 ? { savedAt: persisted.savedAt } : {},
7117
+ url: agentGuidance.sessionWebUrl(id)
7118
+ };
7119
+ }
7120
+
6909
7121
  const ASSISTANT_CLAUDE_MD = `# very-happy \u8C03\u5EA6\u4E2D\u5FC3\uFF08\u8BED\u97F3\u52A9\u624B\uFF09
6910
7122
 
6911
7123
  \u4F60\u662F very-happy \u7684\u8C03\u5EA6\u4E2D\u5FC3\uFF0C\u8DD1\u5728\u7528\u6237\u81EA\u5DF1\u7684\u673A\u5668\u4E0A\u3002\u7528\u6237\u4E3B\u8981\u901A\u8FC7\u8BED\u97F3\u8DDF\u4F60\u8BF4\u8BDD\uFF0C
@@ -8442,7 +8654,7 @@ function parseJsonObject(text) {
8442
8654
  return null;
8443
8655
  }
8444
8656
  }
8445
- function resolveSdkClaudeBinary(platform = process.platform, arch = process.arch, resolver = (s) => node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index-D_fYUxVg.cjs', document.baseURI).href))).resolve(s)) {
8657
+ function resolveSdkClaudeBinary(platform = process.platform, arch = process.arch, resolver = (s) => node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index-C9QzZsUQ.cjs', document.baseURI).href))).resolve(s)) {
8446
8658
  const platformArch = `${platform}-${arch}`;
8447
8659
  const candidates = [
8448
8660
  `@anthropic-ai/claude-agent-sdk-${platformArch}/claude`,
@@ -9132,9 +9344,9 @@ function createMirrorManager(deps) {
9132
9344
  archiveReason: reason
9133
9345
  });
9134
9346
  try {
9135
- await deps.api.deactivateSession(binding.happySessionId);
9347
+ await deps.api.archiveSession(binding.happySessionId);
9136
9348
  } catch (error) {
9137
- persistence.logger.debug(`[MIRROR] deactivateSession failed for ${binding.happySessionId}:`, error);
9349
+ persistence.logger.debug(`[MIRROR] archiveSession failed for ${binding.happySessionId}:`, error);
9138
9350
  }
9139
9351
  deps.onBindingsChanged?.();
9140
9352
  };
@@ -10614,6 +10826,13 @@ ${action.prompt}`, { localId: `automation-${run.id}`, ...meta });
10614
10826
  if (edgeFell) item.sawTurn = false;
10615
10827
  const turn = await readTurn(sessionId, item.runId).catch(() => null);
10616
10828
  if (!turn?.ended) continue;
10829
+ const background = deps.backgroundTasks?.(sessionId) ?? 0;
10830
+ if (background > 0 && turn.status !== "failed" && turn.status !== "cancelled") {
10831
+ if (!item.waitingOnBackground) deps.log(`Automation run ${item.runId}: turn ended but ${background} background task(s) still running; not done yet`);
10832
+ item.waitingOnBackground = true;
10833
+ continue;
10834
+ }
10835
+ item.waitingOnBackground = false;
10617
10836
  if (turn.status === "failed" || turn.status === "cancelled") await finish(item, { claimId: item.claimId, status: "failed", sessionId, error: turn.error ?? `Turn ${turn.status}`, needsAttention: true, attentionReason: `turn_${turn.status}` });
10618
10837
  else await finish(item, { claimId: item.claimId, status: "done", sessionId, summary: clipText(turn.answer, persistence.AUTOMATION_SUMMARY_MAX_CHARS) });
10619
10838
  }
@@ -10743,6 +10962,16 @@ ${action.prompt}`, { localId: `automation-${run.id}`, ...meta });
10743
10962
  function daemonExitCode(source) {
10744
10963
  return source === "exception" ? 1 : 0;
10745
10964
  }
10965
+ const HANDOVER_EXIT_CODE = 75;
10966
+
10967
+ function detectDaemonSupervisor(env, platform = process.platform) {
10968
+ const explicit = env.HAPPY_DAEMON_SUPERVISOR?.trim().toLowerCase();
10969
+ if (explicit === "systemd") return "systemd";
10970
+ if (explicit === "none") return "none";
10971
+ if (platform === "linux" && typeof env.INVOCATION_ID === "string" && env.INVOCATION_ID.trim() !== "") return "systemd";
10972
+ return "none";
10973
+ }
10974
+ const SYSTEMD_RESTART_HINT = "systemctl --user restart very-happy-daemon";
10746
10975
 
10747
10976
  const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
10748
10977
  function sanitizeImportTitle(value) {
@@ -10898,6 +11127,7 @@ async function startDaemon() {
10898
11127
  persistence.logger.debug("[DAEMON RUN] Auth and machine setup complete");
10899
11128
  const pidToTrackedSession = /* @__PURE__ */ new Map();
10900
11129
  const turnActivity = new index.TurnActivityTracker();
11130
+ const backgroundTasks = new persistence.BackgroundTaskTracker();
10901
11131
  const peerCoordinator = createPeerCoordinator({ getChildren: () => Array.from(pidToTrackedSession.values()) });
10902
11132
  const sessionIdToFinishedSession = /* @__PURE__ */ new Map();
10903
11133
  let teamWorker;
@@ -11758,6 +11988,7 @@ async function startDaemon() {
11758
11988
  if (session?.happySessionId && (session.spawnedBy === "teams" || teamWorker?.hasSession(session.happySessionId))) teamWorker?.report(session.happySessionId, "exited");
11759
11989
  if (session?.happySessionId && automationRunner?.hasSession(session.happySessionId)) automationRunner.report(session.happySessionId, "exited");
11760
11990
  if (session?.happySessionId) turnActivity.forget(session.happySessionId);
11991
+ if (session?.happySessionId) backgroundTasks.forget(session.happySessionId);
11761
11992
  if (session?.happySessionId) peerCoordinator.forget(session.happySessionId);
11762
11993
  if (session?.happySessionId && session.encryption) {
11763
11994
  sessionIdToFinishedSession.set(session.happySessionId, session);
@@ -11844,10 +12075,15 @@ async function startDaemon() {
11844
12075
  onSessionStateEvent,
11845
12076
  onClaudeAuthFailed: (sessionId) => claudeAuthServiceRef?.signalAuthFailed(sessionId),
11846
12077
  onSessionTurnEvent: (sessionId, event) => turnActivity.apply(sessionId, event),
12078
+ onSessionBackgroundTasks: (sessionId, tasks) => backgroundTasks.apply(sessionId, tasks),
12079
+ getBackgroundTasks: (sessionId) => backgroundTasks.report(sessionId),
11847
12080
  onSessionEdit: (edit) => {
11848
12081
  peerCoordinator.onEdit(edit);
11849
12082
  },
11850
12083
  listPeers: () => peerCoordinator.list(),
12084
+ // B-505: `daemon list` shows which sessions have a turn in flight, so an
12085
+ // operator can check the WHOLE machine before a supervised restart.
12086
+ isTurnActive: (sessionId) => turnActivity.activeSessions().includes(sessionId),
11851
12087
  pushClipboard: (text, terminalId) => {
11852
12088
  if (!apiMachineRef) {
11853
12089
  return { delivered: false, truncated: false, totalBytes: 0, error: "daemon is still starting up" };
@@ -11867,6 +12103,8 @@ async function startDaemon() {
11867
12103
  }
11868
12104
  });
11869
12105
  const daemonClaudeCredentials = resolveClaudeCredentialReadiness();
12106
+ const daemonSupervisor = detectDaemonSupervisor(process.env);
12107
+ persistence.logger.debug(`[DAEMON RUN] Supervisor: ${daemonSupervisor}`);
11870
12108
  let fileState = {
11871
12109
  pid: process.pid,
11872
12110
  httpPort: controlPort,
@@ -11876,7 +12114,8 @@ async function startDaemon() {
11876
12114
  serverUrl: persistence.configuration.serverUrl,
11877
12115
  webappUrl: persistence.configuration.webappUrl,
11878
12116
  daemonLogPath: persistence.logger.logFilePath,
11879
- claudeCredentialSource: daemonClaudeCredentials.source
12117
+ claudeCredentialSource: daemonClaudeCredentials.source,
12118
+ supervisor: daemonSupervisor
11880
12119
  };
11881
12120
  persistence.writeDaemonState(fileState);
11882
12121
  persistence.logger.debug("[DAEMON RUN] Daemon state written");
@@ -11937,6 +12176,7 @@ async function startDaemon() {
11937
12176
  listTrackedSessionIds: () => [...pidToTrackedSession.values()].map((session) => session.happySessionId).filter((sessionId) => typeof sessionId === "string"),
11938
12177
  requestShutdown: () => requestShutdown("happy-app")
11939
12178
  });
12179
+ apiMachine.setRemoteSessionOpsHandlers(createRemoteSessionOpsHandlers(machineId));
11940
12180
  const claudeAuthService = new ClaudeAuthService({
11941
12181
  happyHomeDir: persistence.configuration.happyHomeDir,
11942
12182
  happyLibDir: terminalTitleSuggest.projectPath(),
@@ -11969,8 +12209,9 @@ async function startDaemon() {
11969
12209
  enabled: async () => ((await persistence.readSettings()).cliAutoUpdate ?? "idle") !== "off",
11970
12210
  // B-466: "idle" = no agent turn in flight. Idle wrappers survive a
11971
12211
  // handover (they talk to the server themselves) and web terminals live
11972
- // in tmux, so neither is a reason to hold an update.
11973
- idle: () => !turnActivity.hasActiveTurn(),
12212
+ // in tmux, so neither is a reason to hold an update. B-507: a session
12213
+ // whose turn ended but still has background tasks is busy too.
12214
+ idle: () => !turnActivity.hasActiveTurn() && !backgroundTasks.hasAny(),
11974
12215
  install: installCliSafely,
11975
12216
  publish: (state) => {
11976
12217
  cliUpdateStateRef = state;
@@ -12025,7 +12266,7 @@ async function startDaemon() {
12025
12266
  } catch {
12026
12267
  }
12027
12268
  }
12028
- if (bundleReplaced && !teamWorker?.busy && !automationRunner?.busy && !updateController.isRunning() && !turnActivity.hasActiveTurn()) {
12269
+ if (bundleReplaced && !teamWorker?.busy && !automationRunner?.busy && !updateController.isRunning() && !turnActivity.hasActiveTurn() && !backgroundTasks.hasAny()) {
12029
12270
  const preflight = await preflightNewBundle(bundlePath);
12030
12271
  if (preflight.action === "hold") {
12031
12272
  if (lastHandoverHold !== preflight.reason) {
@@ -12035,7 +12276,7 @@ async function startDaemon() {
12035
12276
  updateController.holdHandover(preflight.reason);
12036
12277
  return;
12037
12278
  }
12038
- if (teamWorker?.busy || automationRunner?.busy || updateController.isRunning() || turnActivity.hasActiveTurn()) return;
12279
+ if (teamWorker?.busy || automationRunner?.busy || updateController.isRunning() || turnActivity.hasActiveTurn() || backgroundTasks.hasAny()) return;
12039
12280
  lastHandoverHold = null;
12040
12281
  persistence.logger.debug("[DAEMON RUN] Daemon bundle replaced on disk and verified, handing off to new daemon");
12041
12282
  teamWorker?.stop();
@@ -12049,6 +12290,10 @@ async function startDaemon() {
12049
12290
  await persistence.cleanupDaemonState();
12050
12291
  await persistence.releaseDaemonLock(daemonLockHandle);
12051
12292
  await stopCaffeinate();
12293
+ if (daemonSupervisor === "systemd") {
12294
+ persistence.logger.debug(`[DAEMON RUN] systemd-supervised: exiting ${HANDOVER_EXIT_CODE} for the unit to restart on the new bundle`);
12295
+ process.exit(HANDOVER_EXIT_CODE);
12296
+ }
12052
12297
  try {
12053
12298
  spawnHappyCLI(["daemon", "start"], {
12054
12299
  detached: true,
@@ -12097,6 +12342,7 @@ async function startDaemon() {
12097
12342
  spawn: spawnSession,
12098
12343
  live: (id) => [...pidToTrackedSession.values()].some((s) => s.happySessionId === id && isPidAlive(s.pid)),
12099
12344
  turnActive: (id) => turnActivity.activeSessions().includes(id),
12345
+ backgroundTasks: (id) => backgroundTasks.count(id),
12100
12346
  log: (message) => persistence.logger.debug(`[DAEMON RUN] ${message}`)
12101
12347
  });
12102
12348
  void apiMachine.updateMachineMetadata((current) => ({
@@ -12390,36 +12636,54 @@ ${sessions.map(describeSummary).join("\n")}`);
12390
12636
  }
12391
12637
  });
12392
12638
  mcp.registerTool("session_read", {
12393
- description: "Read the latest messages of a session as a compact role-tagged transcript (user / assistant / tool summaries; large payloads truncated). Only works for sessions spawned by this machine (their keys live in ~/.happy/sessions.json).",
12639
+ description: "Read the latest messages of a session as a compact role-tagged transcript (user / assistant / tool summaries; large payloads truncated). Sessions spawned by this machine are read directly; a session spawned by another machine of this account is read through that machine's daemon (it must be online).",
12394
12640
  title: "Read Session Transcript",
12395
12641
  inputSchema: {
12396
12642
  sessionId: z.z.string().describe("The Happy session id to read"),
12397
- limit: z.z.number().optional().describe("How many recent messages to fetch (default 20, max 100)")
12643
+ limit: z.z.number().optional().describe("How many recent messages to fetch (default 20, max 100)"),
12644
+ machineId: z.z.string().optional().describe("Machine id the session runs on, when known (skips the lookup across the account's machines)")
12398
12645
  }
12399
12646
  }, async (args) => {
12400
12647
  if (!persistence.isValidSessionId(args.sessionId)) return fail("Invalid sessionId");
12401
12648
  try {
12402
- const result = await agentGuidance.readSessionTranscript(args.sessionId, args.limit ?? 20);
12649
+ const local = persistence.readPersistedSessions()[args.sessionId];
12650
+ if (local && !args.machineId) {
12651
+ const result2 = await agentGuidance.readSessionTranscript(args.sessionId, args.limit ?? 20);
12652
+ const body2 = result2.transcript.length > 0 ? result2.transcript : "(no readable conversation content in this range)";
12653
+ return ok(`${describeSummary(result2.summary)}
12654
+ --- last ${result2.messageCount} message(s) ---
12655
+ ${body2}`);
12656
+ }
12657
+ const result = await agentGuidance.readRemoteTranscript(args.sessionId, { limit: args.limit ?? 20, ...args.machineId ? { machineId: args.machineId } : {} });
12403
12658
  const body = result.transcript.length > 0 ? result.transcript : "(no readable conversation content in this range)";
12404
- return ok(`${describeSummary(result.summary)}
12405
- --- last ${result.messageCount} message(s) ---
12659
+ return ok(`${describeSummary(result.summary)} machine=${result.host} (${result.machineId})
12660
+ --- last ${result.messageCount} message(s)${result.truncated ? ", truncated to fit" : ""} ---
12406
12661
  ${body}`);
12407
12662
  } catch (error) {
12408
12663
  return fail(`Failed to read session: ${error instanceof Error ? error.message : String(error)}`);
12409
12664
  }
12410
12665
  });
12411
12666
  mcp.registerTool("session_send", {
12412
- description: "Send a user message into an existing session on this machine. Only delivers when a live wrapper will read it: an archived / offline session is refused (the server would store the message unread) unless resume:true brings it back on this machine first. Returns immediately after delivery \u2014 it does NOT wait for the session to respond; use session_read later to check progress.",
12667
+ description: "Send a user message into an existing session. Only delivers when a live wrapper will read it: an archived / offline session is refused (the server would store the message unread) unless resume:true brings it back on its machine first. A session spawned by another machine of this account is delivered through that machine's daemon (it must be online). Returns immediately after delivery \u2014 it does NOT wait for the session to respond; use session_read later to check progress.",
12413
12668
  title: "Send Message to Session",
12414
12669
  inputSchema: {
12415
12670
  sessionId: z.z.string().describe("The Happy session id to message"),
12416
12671
  text: z.z.string().describe("The message text to send"),
12417
- resume: z.z.boolean().optional().describe("If the session is archived or offline, resume it on this machine (like the web Restore button) and wait until it is live before sending")
12672
+ resume: z.z.boolean().optional().describe("If the session is archived or offline, resume it on its machine (like the web Restore button) and wait until it is live before sending"),
12673
+ machineId: z.z.string().optional().describe("Machine id the session runs on, when known (skips the lookup across the account's machines)")
12418
12674
  }
12419
12675
  }, async (args) => {
12420
12676
  if (!persistence.isValidSessionId(args.sessionId)) return fail("Invalid sessionId");
12421
12677
  if (typeof args.text !== "string" || args.text.trim().length === 0) return fail("text must be non-empty");
12422
12678
  try {
12679
+ if (!persistence.readPersistedSessions()[args.sessionId] || args.machineId) {
12680
+ const remote = await agentGuidance.sendRemoteMessage(args.sessionId, args.text, { resume: args.resume === true, sentFrom: "assistant", ...args.machineId ? { machineId: args.machineId } : {} });
12681
+ if (!remote.delivered) {
12682
+ const detail = remote.error ?? `session is ${remote.status}`;
12683
+ return fail(remote.stored ? `Not delivered (via ${remote.machine.host}): ${detail}` : `Session ${args.sessionId} on ${remote.machine.host} is ${remote.status}: ${detail}`);
12684
+ }
12685
+ return ok(`Message delivered${remote.resumed ? ` (session resumed on ${remote.machine.host})` : ""} to ${args.sessionId} on machine ${remote.machine.host} (${remote.machine.id}) ${agentGuidance.sessionWebUrl(args.sessionId)}`);
12686
+ }
12423
12687
  const persisted = await agentGuidance.waitForSessionKey(args.sessionId, 0);
12424
12688
  const result = await agentGuidance.deliverToSession(args.sessionId, persisted, args.text, MCP_CLIENT_TAG, {
12425
12689
  resume: args.resume === true,
@@ -12431,6 +12695,7 @@ ${body}`);
12431
12695
  }
12432
12696
  return ok(`Message delivered${result.resumed ? " (session resumed on this machine)" : ""} to ${describeSession(args.sessionId, persisted, { live: true })}`);
12433
12697
  } catch (error) {
12698
+ if (error instanceof agentGuidance.RemoteSessionOpsError) return fail(`Failed to send (${error.code}): ${error.message}`);
12434
12699
  return fail(`Failed to send: ${error instanceof Error ? error.message : String(error)}`);
12435
12700
  }
12436
12701
  });
@@ -13028,6 +13293,40 @@ function registerKillSessionHandler(rpcHandlerManager, killThisHappy, archiveEve
13028
13293
  };
13029
13294
  }
13030
13295
 
13296
+ async function settleSessionOnExit(sessionId, intent, deps) {
13297
+ if (intent === "archive") {
13298
+ deps.updateMetadata((currentMetadata) => ({
13299
+ ...currentMetadata,
13300
+ lifecycleState: "archived",
13301
+ lifecycleStateSince: Date.now(),
13302
+ archivedBy: "cli",
13303
+ archiveReason: "User terminated"
13304
+ }));
13305
+ }
13306
+ deps.sendSessionDeath();
13307
+ try {
13308
+ if (intent === "archive") await deps.archiveSession(sessionId);
13309
+ else await deps.deactivateSession(sessionId);
13310
+ } catch (error) {
13311
+ deps.log?.(`[START] ${intent === "archive" ? "archiveSession" : "deactivateSession"} during cleanup failed:`, error);
13312
+ }
13313
+ }
13314
+ class SessionExitGate {
13315
+ decided = null;
13316
+ /** Returns the intent to act on, or null when cleanup already started. */
13317
+ claim(intent) {
13318
+ if (this.decided !== null) return null;
13319
+ this.decided = intent;
13320
+ return intent;
13321
+ }
13322
+ get current() {
13323
+ return this.decided;
13324
+ }
13325
+ }
13326
+ function exitIntentFromArchiveFlag(archive) {
13327
+ return archive ?? true ? "archive" : "offline";
13328
+ }
13329
+
13031
13330
  function isUnsupportedSideQuestionError(error) {
13032
13331
  const message = error instanceof Error ? error.message : String(error);
13033
13332
  return /unknown|unsupported|not supported|unrecognized|invalid.*subtype/i.test(message);
@@ -13204,7 +13503,7 @@ function registerSideQuestionHandler(rpc, deps) {
13204
13503
  const retainMs = deps.retainMs ?? SIDE_QUESTION_RETAIN_MS;
13205
13504
  const maxRunMs = deps.maxRunMs ?? SIDE_QUESTION_MAX_RUN_MS;
13206
13505
  const fork = deps.run ?? (async (input) => {
13207
- const { query } = await Promise.resolve().then(function () { return require('./index--KCpVs1c.cjs'); });
13506
+ const { query } = await Promise.resolve().then(function () { return require('./index-Cz-uqnJk.cjs'); });
13208
13507
  return runSideQuestion(query, input);
13209
13508
  });
13210
13509
  const run = async (input, live) => {
@@ -13323,7 +13622,7 @@ const MAX_TITLE_CHARS = 60;
13323
13622
  const GENERATION_TIMEOUT_MS = 3e4;
13324
13623
  function resolveClaudeBinary() {
13325
13624
  try {
13326
- const require$1 = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index-D_fYUxVg.cjs', document.baseURI).href)));
13625
+ const require$1 = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index-C9QzZsUQ.cjs', document.baseURI).href)));
13327
13626
  const utilsPath = path.resolve(path.join(terminalTitleSuggest.projectPath(), "scripts", "claude_version_utils.cjs"));
13328
13627
  const { getClaudeCliPath } = require$1(utilsPath);
13329
13628
  const path$1 = getClaudeCliPath();
@@ -14341,26 +14640,24 @@ async function runClaude(credentials, options = {}) {
14341
14640
  hasMetadata: Boolean(message.meta)
14342
14641
  });
14343
14642
  });
14643
+ const exitGate = new SessionExitGate();
14344
14644
  const cleanup = async (opts = { archive: true }) => {
14345
- persistence.logger.debug(`[START] Received termination signal, cleaning up (archive=${opts.archive ?? true})...`);
14645
+ const intent = exitGate.claim(exitIntentFromArchiveFlag(opts.archive));
14646
+ if (intent === null) {
14647
+ persistence.logger.debug(`[START] Termination (archive=${opts.archive ?? true}) ignored: cleanup already running as ${exitGate.current}`);
14648
+ return;
14649
+ }
14650
+ persistence.logger.debug(`[START] Received termination signal, cleaning up (archive=${intent === "archive"})...`);
14346
14651
  try {
14347
14652
  if (session) {
14348
- if (opts.archive ?? true) {
14349
- session.updateMetadata((currentMetadata) => ({
14350
- ...currentMetadata,
14351
- lifecycleState: "archived",
14352
- lifecycleStateSince: Date.now(),
14353
- archivedBy: "cli",
14354
- archiveReason: "User terminated"
14355
- }));
14356
- }
14357
14653
  currentSession?.cleanup();
14358
- session.sendSessionDeath();
14359
- try {
14360
- await api.deactivateSession(session.sessionId);
14361
- } catch (err) {
14362
- persistence.logger.debug("[START] deactivateSession during cleanup failed:", err);
14363
- }
14654
+ await settleSessionOnExit(session.sessionId, intent, {
14655
+ updateMetadata: (handler) => session.updateMetadata(handler),
14656
+ sendSessionDeath: () => session.sendSessionDeath(),
14657
+ archiveSession: (id) => api.archiveSession(id),
14658
+ deactivateSession: (id) => api.deactivateSession(id),
14659
+ log: (message, ...args) => persistence.logger.debug(message, ...args)
14660
+ });
14364
14661
  await session.flush();
14365
14662
  await session.close();
14366
14663
  }
@@ -15569,9 +15866,9 @@ function resolveLocalSignupBootstrap(configuredMode, configuredInviteCodes, gene
15569
15866
  };
15570
15867
  }
15571
15868
 
15572
- const __filename$1 = node_url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index-D_fYUxVg.cjs', document.baseURI).href)));
15869
+ const __filename$1 = node_url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index-C9QzZsUQ.cjs', document.baseURI).href)));
15573
15870
  const __dirname$1 = path.dirname(__filename$1);
15574
- const require_ = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index-D_fYUxVg.cjs', document.baseURI).href)));
15871
+ const require_ = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index-C9QzZsUQ.cjs', document.baseURI).href)));
15575
15872
  const PRISMA_QUERY_ENGINE_FILES = {
15576
15873
  "arm64-darwin": "libquery_engine-darwin-arm64.dylib.node",
15577
15874
  "x64-darwin": "libquery_engine-darwin.dylib.node",
@@ -18514,7 +18811,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
18514
18811
  return;
18515
18812
  } else if (subcommand === "install-terminal-hooks") {
18516
18813
  try {
18517
- const { installTerminalHooks, parseTerminalHooksArgs, TERMINAL_HOOKS_HELP } = await Promise.resolve().then(function () { return require('./installTerminalHooks-XrcPvfBs.cjs'); });
18814
+ const { installTerminalHooks, parseTerminalHooksArgs, TERMINAL_HOOKS_HELP } = await Promise.resolve().then(function () { return require('./installTerminalHooks-BVVOEGhG.cjs'); });
18518
18815
  const command = parseTerminalHooksArgs(args.slice(1));
18519
18816
  if (command.action === "help") {
18520
18817
  console.log(TERMINAL_HOOKS_HELP);
@@ -18531,7 +18828,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
18531
18828
  }
18532
18829
  } else if (subcommand === "agent-home") {
18533
18830
  try {
18534
- const { parseAgentHomeArgs, runAgentHomeCommand } = await Promise.resolve().then(function () { return require('./agentHome-aucT8BHw.cjs'); });
18831
+ const { parseAgentHomeArgs, runAgentHomeCommand } = await Promise.resolve().then(function () { return require('./agentHome-B0CLy0Qx.cjs'); });
18535
18832
  await runAgentHomeCommand(parseAgentHomeArgs(args.slice(1)));
18536
18833
  process.exit(0);
18537
18834
  } catch (error) {
@@ -18540,7 +18837,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
18540
18837
  }
18541
18838
  } else if (subcommand === "spawn") {
18542
18839
  try {
18543
- const { handleSpawnCommand } = await Promise.resolve().then(function () { return require('./spawn-cIoglKVf.cjs'); });
18840
+ const { handleSpawnCommand } = await Promise.resolve().then(function () { return require('./spawn-HV345RN-.cjs'); });
18544
18841
  await handleSpawnCommand(args.slice(1));
18545
18842
  } catch (error) {
18546
18843
  console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
@@ -18552,7 +18849,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
18552
18849
  return;
18553
18850
  } else if (subcommand === "sessions") {
18554
18851
  try {
18555
- const { handleSessionsCommand } = await Promise.resolve().then(function () { return require('./sessions-MX21NJXd.cjs'); });
18852
+ const { handleSessionsCommand } = await Promise.resolve().then(function () { return require('./sessions-BFlIrKCY.cjs'); });
18556
18853
  await handleSessionsCommand(args.slice(1));
18557
18854
  } catch (error) {
18558
18855
  console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
@@ -18564,7 +18861,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
18564
18861
  return;
18565
18862
  } else if (subcommand === "send") {
18566
18863
  try {
18567
- const { handleSendCommand } = await Promise.resolve().then(function () { return require('./send-CwrYXTPr.cjs'); });
18864
+ const { handleSendCommand } = await Promise.resolve().then(function () { return require('./send-CHxobaoI.cjs'); });
18568
18865
  await handleSendCommand(args.slice(1));
18569
18866
  } catch (error) {
18570
18867
  console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
@@ -18670,9 +18967,9 @@ Conversation history is preserved on the server, but in-flight tool calls are in
18670
18967
  if (geminiSubcommand === "project" && args[2] === "set" && args[3]) {
18671
18968
  const projectId = args[3];
18672
18969
  try {
18673
- const { saveGoogleCloudProjectToConfig } = await Promise.resolve().then(function () { return require('./config-BoePh9PX.cjs'); });
18674
- const { readCredentialsForConfiguredRelay: readCredentialsForConfiguredRelay2 } = await Promise.resolve().then(function () { return require('./limits-DkfHJ3nS.cjs'); }).then(function (n) { return n.persistence; });
18675
- const { ApiClient: ApiClient2 } = await Promise.resolve().then(function () { return require('./types-a2xsH2UI.cjs'); }).then(function (n) { return n.api; });
18970
+ const { saveGoogleCloudProjectToConfig } = await Promise.resolve().then(function () { return require('./config-CzkBjZnV.cjs'); });
18971
+ const { readCredentialsForConfiguredRelay: readCredentialsForConfiguredRelay2 } = await Promise.resolve().then(function () { return require('./limits-CyuIt-Vl.cjs'); }).then(function (n) { return n.persistence; });
18972
+ const { ApiClient: ApiClient2 } = await Promise.resolve().then(function () { return require('./types-B0sSi6eM.cjs'); }).then(function (n) { return n.api; });
18676
18973
  let userEmail = void 0;
18677
18974
  try {
18678
18975
  const credentials = await readCredentialsForConfiguredRelay2();
@@ -18703,7 +19000,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
18703
19000
  }
18704
19001
  if (geminiSubcommand === "project" && args[2] === "get") {
18705
19002
  try {
18706
- const { readGeminiLocalConfig } = await Promise.resolve().then(function () { return require('./config-BoePh9PX.cjs'); });
19003
+ const { readGeminiLocalConfig } = await Promise.resolve().then(function () { return require('./config-CzkBjZnV.cjs'); });
18707
19004
  const config = readGeminiLocalConfig();
18708
19005
  if (config.googleCloudProject) {
18709
19006
  console.log(`Current Google Cloud Project: ${config.googleCloudProject}`);
@@ -18743,7 +19040,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
18743
19040
  process.exit(0);
18744
19041
  }
18745
19042
  try {
18746
- const { runGemini } = await Promise.resolve().then(function () { return require('./runGemini-8Pxh94Fm.cjs'); });
19043
+ const { runGemini } = await Promise.resolve().then(function () { return require('./runGemini-DS-0qm0L.cjs'); });
18747
19044
  let startedBy = void 0;
18748
19045
  for (let i = 1; i < args.length; i++) {
18749
19046
  if (args[i] === "--started-by") {
@@ -18766,7 +19063,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
18766
19063
  } else if (subcommand === "pi") {
18767
19064
  if (args[1] === "--terminal") {
18768
19065
  try {
18769
- const { runPiTerminal } = await Promise.resolve().then(function () { return require('./piTerminal-DYzRcwgb.cjs'); });
19066
+ const { runPiTerminal } = await Promise.resolve().then(function () { return require('./piTerminal-CpU3hzrX.cjs'); });
18770
19067
  process.exitCode = await runPiTerminal(args.slice(2));
18771
19068
  } catch (error) {
18772
19069
  console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
@@ -18783,7 +19080,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
18783
19080
  }
18784
19081
  };
18785
19082
  try {
18786
- const { runAcp, resolveAcpAgentConfig } = await Promise.resolve().then(function () { return require('./index-DRrLaljt.cjs'); });
19083
+ const { runAcp, resolveAcpAgentConfig } = await Promise.resolve().then(function () { return require('./index-CTbGqVFY.cjs'); });
18787
19084
  const parsed = parsePiRunnerArgs(args.slice(1));
18788
19085
  const resolved = resolveAcpAgentConfig(["pi", ...parsed.passthrough]);
18789
19086
  const { credentials } = await authAndSetupMachineIfNeeded();
@@ -18811,7 +19108,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
18811
19108
  return;
18812
19109
  } else if (subcommand === "acp") {
18813
19110
  try {
18814
- const { runAcp, resolveAcpAgentConfig } = await Promise.resolve().then(function () { return require('./index-DRrLaljt.cjs'); });
19111
+ const { runAcp, resolveAcpAgentConfig } = await Promise.resolve().then(function () { return require('./index-CTbGqVFY.cjs'); });
18815
19112
  let startedBy = void 0;
18816
19113
  let verbose = false;
18817
19114
  const acpArgs = [];
@@ -18851,7 +19148,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
18851
19148
  return;
18852
19149
  } else if (subcommand === "openclaw") {
18853
19150
  try {
18854
- const { runOpenClaw } = await Promise.resolve().then(function () { return require('./runOpenClaw-B5FSZIbK.cjs'); });
19151
+ const { runOpenClaw } = await Promise.resolve().then(function () { return require('./runOpenClaw-CvSm8oTc.cjs'); });
18855
19152
  let startedBy = void 0;
18856
19153
  let verbose = false;
18857
19154
  let gatewayUrl;
@@ -18902,7 +19199,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
18902
19199
  return;
18903
19200
  } else if (subcommand === "todo") {
18904
19201
  try {
18905
- const { handleTodoCommand } = await Promise.resolve().then(function () { return require('./todo-Buuqn9gm.cjs'); });
19202
+ const { handleTodoCommand } = await Promise.resolve().then(function () { return require('./todo-CMMQ6yhU.cjs'); });
18906
19203
  await handleTodoCommand(args.slice(1));
18907
19204
  } catch (error) {
18908
19205
  console.error(JSON.stringify({ error: error instanceof Error ? error.message : "Todo command failed" }));
@@ -18911,7 +19208,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
18911
19208
  return;
18912
19209
  } else if (subcommand === "teams") {
18913
19210
  try {
18914
- const { handleTeamsCommand } = await Promise.resolve().then(function () { return require('./teams-Dqk7F3D-.cjs'); });
19211
+ const { handleTeamsCommand } = await Promise.resolve().then(function () { return require('./teams-BahinMnH.cjs'); });
18915
19212
  await handleTeamsCommand(args.slice(1));
18916
19213
  } catch (error) {
18917
19214
  console.error(error instanceof Error ? error.message : "Teams command failed");
@@ -18920,7 +19217,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
18920
19217
  return;
18921
19218
  } else if (subcommand === "auto") {
18922
19219
  try {
18923
- const { handleAutoCommand } = await Promise.resolve().then(function () { return require('./auto-P9RoJaY0.cjs'); });
19220
+ const { handleAutoCommand } = await Promise.resolve().then(function () { return require('./auto-DqKGFq8U.cjs'); });
18924
19221
  await handleAutoCommand(args.slice(1));
18925
19222
  } catch (error) {
18926
19223
  console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Automation command failed");
@@ -18932,7 +19229,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
18932
19229
  return;
18933
19230
  } else if (subcommand === "mcp" && (args.length === 1 || args.length === 2 && args[1] === "--terminal-tools")) {
18934
19231
  try {
18935
- const { handleMcpCommand } = await Promise.resolve().then(function () { return require('./mcp-BqlXl10F.cjs'); });
19232
+ const { handleMcpCommand } = await Promise.resolve().then(function () { return require('./mcp-CXkvnztT.cjs'); });
18936
19233
  await handleMcpCommand(args[1] === "--terminal-tools");
18937
19234
  } catch (error) {
18938
19235
  process.stderr.write(`[very-happy mcp] Fatal: ${error instanceof Error ? error.message : String(error)}
@@ -18961,6 +19258,10 @@ Conversation history is preserved on the server, but in-flight tool calls are in
18961
19258
  } else {
18962
19259
  console.log("Active sessions:");
18963
19260
  console.log(JSON.stringify(sessions, null, 2));
19261
+ const busy = sessions.filter((s) => s.turnActive === true);
19262
+ console.log(busy.length === 0 ? "No turn in flight on this machine (wrappers older than 0.2.139 do not report turns)." : `${busy.length} session(s) with a turn in flight: ${busy.map((s) => s.happySessionId).join(", ")}`);
19263
+ const background = sessions.filter((s) => (s.backgroundTasks?.count ?? 0) > 0);
19264
+ console.log(background.length === 0 ? "No background tasks in flight (wrappers older than 0.2.157 do not report them)." : `${background.length} session(s) with background tasks in flight: ${background.map((s) => `${s.happySessionId} (${s.backgroundTasks?.count})`).join(", ")}`);
18964
19265
  }
18965
19266
  } catch (error) {
18966
19267
  console.log("No daemon running");
@@ -18989,13 +19290,19 @@ Conversation history is preserved on the server, but in-flight tool calls are in
18989
19290
  persistence.configuration.webappUrl
18990
19291
  );
18991
19292
  if (running && !currentDaemonMatches) {
19293
+ if (currentState?.supervisor === "systemd") {
19294
+ console.error(`The running daemon is supervised by systemd; hand over through the unit instead:
19295
+ ${SYSTEMD_RESTART_HINT}`);
19296
+ process.exit(1);
19297
+ }
18992
19298
  console.log("Configured relay or CLI version changed; restarting the existing daemon");
18993
19299
  await persistence.stopDaemon();
18994
19300
  }
19301
+ const { HAPPY_DAEMON_SUPERVISOR: _supervisor, INVOCATION_ID: _invocation, ...detachedEnv } = process.env;
18995
19302
  const child = spawnHappyCLI(["daemon", "start-sync"], {
18996
19303
  detached: true,
18997
19304
  stdio: "ignore",
18998
- env: process.env
19305
+ env: detachedEnv
18999
19306
  });
19000
19307
  child.unref();
19001
19308
  let started = false;