very-happy-cli 0.2.104 → 0.2.106

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 (33) hide show
  1. package/dist/{AcpBackend-DDGfk3HJ.cjs → AcpBackend-DMmvLNNM.cjs} +1 -1
  2. package/dist/{AcpBackend-De4iQ_JG.mjs → AcpBackend-Ux5FD_6F.mjs} +1 -1
  3. package/dist/{AcpSessionManager-DRzAgHtT.mjs → AcpSessionManager-C1hoU_rK.mjs} +1 -1
  4. package/dist/{AcpSessionManager-Cdv6Xgwy.cjs → AcpSessionManager-RYkxYWDn.cjs} +1 -1
  5. package/dist/{config-E3KdWyaQ.mjs → config-DAb5saOC.mjs} +2 -2
  6. package/dist/{config-CqM5UxVd.cjs → config-Dkn5KcXp.cjs} +2 -2
  7. package/dist/{index-CX_3uUem.cjs → index-BLW-smJu.cjs} +209 -71
  8. package/dist/{index--TiLl7-A.mjs → index-CgWdwkhB.mjs} +205 -67
  9. package/dist/{index-C_dmtMJ6.mjs → index-D2ZzPm58.mjs} +5 -5
  10. package/dist/{index-De6H3U0N.cjs → index-D48mWUTN.cjs} +2 -2
  11. package/dist/{index-Bzj1XdPx.mjs → index-DCpgRGWy.mjs} +2 -2
  12. package/dist/{index-fuavV8uc.cjs → index-Do3JnAi7.cjs} +4 -4
  13. package/dist/index.cjs +2 -2
  14. package/dist/index.mjs +2 -2
  15. package/dist/{installTerminalHooks-BXMBwrvq.cjs → installTerminalHooks-9rn15uP8.cjs} +2 -2
  16. package/dist/{installTerminalHooks-DV0e8-V3.mjs → installTerminalHooks-l1B-nbWa.mjs} +2 -2
  17. package/dist/lib.cjs +1 -1
  18. package/dist/lib.d.cts +6 -0
  19. package/dist/lib.d.mts +6 -0
  20. package/dist/lib.mjs +1 -1
  21. package/dist/{mcp-Cq3On_Qo.cjs → mcp-C3zpbx-m.cjs} +2 -2
  22. package/dist/{mcp-DFS9Zohz.mjs → mcp-ZvHzv_lJ.mjs} +2 -2
  23. package/dist/{runGemini-BFjtlkZI.mjs → runGemini-0QqaFN1l.mjs} +4 -4
  24. package/dist/{runGemini-DFvjh8cO.cjs → runGemini-q20IdxQV.cjs} +4 -4
  25. package/dist/{runOpenClaw-BFYj1F4u.mjs → runOpenClaw-C6Z6MLHq.mjs} +3 -3
  26. package/dist/{runOpenClaw-B5Og1FED.cjs → runOpenClaw-CtWkyvDJ.cjs} +3 -3
  27. package/dist/{send-Bog0YPxo.cjs → send-B8YQyV3D.cjs} +2 -2
  28. package/dist/{send-B666yos0.mjs → send-DEhUWg8v.mjs} +2 -2
  29. package/dist/{spawn-DrqvVoNF.mjs → spawn-BzbPyTHU.mjs} +2 -2
  30. package/dist/{spawn-BSdMkMPX.cjs → spawn-ChmlP1Z_.cjs} +2 -2
  31. package/dist/{types-14LATr9X.cjs → types-C8-WODFE.cjs} +24 -10
  32. package/dist/{types-D9LyZitb.mjs → types-lSeHEzPR.mjs} +23 -9
  33. package/package.json +7 -7
@@ -1,7 +1,7 @@
1
1
  import{createRequire as _pkgrollCR}from"node:module";const require=_pkgrollCR(import.meta.url);import chalk from 'chalk';
2
2
  import os, { homedir, userInfo } from 'node:os';
3
3
  import { randomUUID, randomBytes, createHash as createHash$1, timingSafeEqual } from 'node:crypto';
4
- import { n as readDaemonState, l as logger, c as configuration, o as clearDaemonState, q as getProjectPath, p as projectPath, s as initializeSandbox, t as wrapCommand, d as contentLogMetadata, I as InvalidateSync, R as RawJSONLinesSchema, g as delay, u as AsyncLock, v as ensurePrivateDirectory, M as MAX_CHAT_ATTACHMENT_SOURCE_BYTES, x as hardenPrivateFile, f as packageJson, i as readSettings, y as readCredentials, z as credentialRelayProblem, B as deriveLocalCliUpdateSummary, j as encodeBase64, C as updateSettings, D as encodeBase64Url, E as decodeBase64, F as writeCredentialsLegacy, G as writeCredentialsDataKey, H as readCredentialsForConfiguredRelay, J as encrypt, r as readPersistedSessions, K as isClaudeSessionId, L as getLocalHappyAgentCredentialPath, N as readLocalHappyAgentCredentials, O as decryptWithDataKey, P as decryptLegacy, Q as extractCompleteLines, S as decideBackfill, T as parseTerminalHookPayload, U as tmuxArgs, V as scrubTmuxClientEnv, W as decideMirrorBinding, X as MIRROR_BACKFILL_LINES_DEFAULT, Y as persistSession, Z as mirrorLineKey, _ as mirrorLocalId, w as writePrivateFileSync, $ as detectResumeSupport, a0 as detectCLIAvailability, a1 as acquireDaemonLock, a2 as summarizeSpawnSessionForLog, a3 as deletePersistedSessions, a4 as shellescape, a5 as decrypt, a6 as writeDaemonState, A as ApiClient, a7 as writeSettings, a8 as fetchCliUpdateState, a9 as resolveCliUpdateCheckInterval, aa as DEFAULT_CLI_UPDATE_CHECK_INTERVAL_MS, ab as releaseDaemonLock, ac as isValidSessionId, ad as listVhTerminals, ae as isValidTerminalId, af as readVhTerminal, ag as sendToVhTerminal, ah as TITLE_PROMPT_PREFIX, ai as BOARD_ANALYZER_PROMPT_PREFIX, h as connectionState, aj as startOfflineReconnection, ak as clearCredentials, al as clearMachineId, k as ensurePrivateDirectorySync, am as SandboxConfigSchema, b as createEnvelope, an as CodexAppServerClient, ao as getLatestDaemonLog } from './types-D9LyZitb.mjs';
4
+ import { n as readDaemonState, l as logger, c as configuration, o as clearDaemonState, q as getProjectPath, p as projectPath, s as initializeSandbox, t as wrapCommand, d as contentLogMetadata, I as InvalidateSync, R as RawJSONLinesSchema, g as delay, u as AsyncLock, v as ensurePrivateDirectory, M as MAX_CHAT_ATTACHMENT_SOURCE_BYTES, x as hardenPrivateFile, f as packageJson, i as readSettings, y as readCredentials, z as credentialRelayProblem, B as deriveLocalCliUpdateSummary, j as encodeBase64, C as updateSettings, D as encodeBase64Url, E as decodeBase64, F as writeCredentialsLegacy, G as writeCredentialsDataKey, H as readCredentialsForConfiguredRelay, J as encrypt, r as readPersistedSessions, K as isClaudeSessionId, L as getLocalHappyAgentCredentialPath, N as readLocalHappyAgentCredentials, O as decryptWithDataKey, P as decryptLegacy, Q as extractCompleteLines, S as decideBackfill, T as parseTerminalHookPayload, U as tmuxArgs, V as scrubTmuxClientEnv, W as decideMirrorBinding, X as MIRROR_BACKFILL_LINES_DEFAULT, Y as persistSession, Z as mirrorLineKey, _ as mirrorLocalId, w as writePrivateFileSync, $ as detectResumeSupport, a0 as detectCLIAvailability, a1 as acquireDaemonLock, a2 as summarizeSpawnSessionForLog, a3 as deletePersistedSessions, a4 as shellescape, a5 as decrypt, a6 as writeDaemonState, A as ApiClient, a7 as writeSettings, a8 as fetchCliUpdateState, a9 as resolveCliUpdateCheckInterval, aa as DEFAULT_CLI_UPDATE_CHECK_INTERVAL_MS, ab as releaseDaemonLock, ac as isValidSessionId, ad as listVhTerminals, ae as isValidTerminalId, af as readVhTerminal, ag as sendToVhTerminal, ah as TITLE_PROMPT_PREFIX, ai as BOARD_ANALYZER_PROMPT_PREFIX, h as connectionState, aj as startOfflineReconnection, ak as clearCredentials, al as clearMachineId, k as ensurePrivateDirectorySync, am as SandboxConfigSchema, b as createEnvelope, an as CodexAppServerClient, ao as getLatestDaemonLog } from './types-lSeHEzPR.mjs';
5
5
  import spawn$1, { spawn } from 'cross-spawn';
6
6
  import path, { join, resolve, basename, delimiter, isAbsolute, normalize, sep, dirname } from 'node:path';
7
7
  import { createInterface } from 'node:readline';
@@ -1907,6 +1907,18 @@ async function awaitFileExist(file, timeout = 1e4) {
1907
1907
  return false;
1908
1908
  }
1909
1909
 
1910
+ function modelTarget(model) {
1911
+ return model == null || model === "" ? void 0 : model;
1912
+ }
1913
+ function needsModelSwitch(current, next) {
1914
+ return modelTarget(current) !== modelTarget(next);
1915
+ }
1916
+ function modelSwitchFailureNotice(model, error) {
1917
+ const name = modelTarget(model) ?? "default";
1918
+ const detail = error instanceof Error ? error.message : String(error);
1919
+ return `\u26A0\uFE0F Could not switch to model "${name}" \u2014 continuing with the previous model. ${detail}`;
1920
+ }
1921
+
1910
1922
  async function claudeRemote(opts) {
1911
1923
  let startFrom = opts.sessionId;
1912
1924
  if (opts.sessionId && !claudeCheckSession(opts.sessionId, opts.path)) {
@@ -2013,11 +2025,12 @@ async function claudeRemote(opts) {
2013
2025
  if (opts.onQueryReady) {
2014
2026
  opts.onQueryReady({
2015
2027
  setPermissionMode: (mode2) => response.setPermissionMode(mode2),
2028
+ setModel: (model) => response.setModel(modelTarget(model)),
2016
2029
  interrupt: async () => {
2017
2030
  await response.interrupt();
2018
2031
  },
2019
2032
  steer: (message, nextMode) => {
2020
- mode = nextMode;
2033
+ mode = { ...nextMode, model: mode.model };
2021
2034
  messages.push({
2022
2035
  type: "user",
2023
2036
  parent_tool_use_id: null,
@@ -2049,7 +2062,10 @@ async function claudeRemote(opts) {
2049
2062
  mcpServers: systemInit.mcp_servers?.map((s) => ({ name: s.name, status: s.status })),
2050
2063
  skills: systemInit.skills,
2051
2064
  model: systemInit.model,
2052
- modelIsDefault: initial.mode.model == null,
2065
+ // `mode`, not `initial.mode`: the model can move mid-Query
2066
+ // via setModel, and init is re-emitted every turn with the
2067
+ // model actually in force.
2068
+ modelIsDefault: modelTarget(mode.model) === void 0,
2053
2069
  // The SDK's own verdict on the mode it will enforce —
2054
2070
  // settings (permissions.deny/ask/defaultMode/
2055
2071
  // disableBypassPermissionsMode) can override what we
@@ -2094,19 +2110,29 @@ async function claudeRemote(opts) {
2094
2110
  messages.end();
2095
2111
  continue;
2096
2112
  }
2097
- opts.nextMessage().then((next) => {
2113
+ opts.nextMessage().then(async (next) => {
2098
2114
  if (!next) {
2099
2115
  messages.end();
2100
- } else {
2101
- mode = next.mode;
2102
- updateThinking(true);
2103
- messages.push({
2104
- type: "user",
2105
- parent_tool_use_id: null,
2106
- origin: { kind: "human" },
2107
- message: { role: "user", content: next.message }
2108
- });
2116
+ return;
2117
+ }
2118
+ if (needsModelSwitch(mode.model, next.mode.model)) {
2119
+ try {
2120
+ await response.setModel(modelTarget(next.mode.model));
2121
+ logger.debug(`[claudeRemote] Model switched to ${modelTarget(next.mode.model) ?? "default"}`);
2122
+ } catch (e) {
2123
+ logger.debug(`[claudeRemote] setModel failed: ${e instanceof Error ? e.message : String(e)}`);
2124
+ opts.onCompletionEvent?.(modelSwitchFailureNotice(next.mode.model, e));
2125
+ next = { ...next, mode: { ...next.mode, model: mode.model } };
2126
+ }
2109
2127
  }
2128
+ mode = next.mode;
2129
+ updateThinking(true);
2130
+ messages.push({
2131
+ type: "user",
2132
+ parent_tool_use_id: null,
2133
+ origin: { kind: "human" },
2134
+ message: { role: "user", content: next.message }
2135
+ });
2110
2136
  }).catch(() => {
2111
2137
  messages.end();
2112
2138
  });
@@ -3387,6 +3413,12 @@ function applyClaudeSdkMetadata(current, update) {
3387
3413
  skills: update.skills,
3388
3414
  attachmentKinds: [...CLAUDE_ATTACHMENT_KINDS],
3389
3415
  queueCancellation: true,
3416
+ // The model Claude Code reports it is ACTUALLY running. system/init is
3417
+ // re-emitted at every turn boundary (verified against the pinned SDK,
3418
+ // 2026-09-03), including after a live setModel, so this is the web's
3419
+ // only ground truth for "did my switch take effect" — the picker
3420
+ // otherwise renders nothing but the client's own optimistic intent.
3421
+ ...update.model ? { currentModelCode: update.model } : {},
3390
3422
  ...update.modelIsDefault && update.model ? { defaultModelCode: update.model } : {}
3391
3423
  };
3392
3424
  }
@@ -3421,6 +3453,102 @@ function createTurnSteeringController() {
3421
3453
  };
3422
3454
  }
3423
3455
 
3456
+ class LaunchModeGate {
3457
+ constructor(hasher) {
3458
+ this.hasher = hasher;
3459
+ }
3460
+ currentMode = null;
3461
+ currentHash = null;
3462
+ parked = null;
3463
+ /** The mode this launch is running, or null before the first message. */
3464
+ get mode() {
3465
+ return this.currentMode;
3466
+ }
3467
+ /** Hash of {@link mode}; null means "not armed yet — accept anything". */
3468
+ get hash() {
3469
+ return this.currentHash;
3470
+ }
3471
+ /** Whether a message has been adopted into this launch yet. */
3472
+ get armed() {
3473
+ return this.currentHash !== null;
3474
+ }
3475
+ /** Bind the launch to `mode`. Idempotent; safe to call with the same mode. */
3476
+ adopt(mode) {
3477
+ this.currentMode = mode;
3478
+ this.currentHash = this.hasher(mode);
3479
+ }
3480
+ /**
3481
+ * Re-adopt the current mode with one field replaced. No-op before the first
3482
+ * adopt: there is nothing to derive from, and arming here would make the
3483
+ * first real message look like a mode change.
3484
+ */
3485
+ amend(patch) {
3486
+ if (this.currentMode === null) return null;
3487
+ this.adopt({ ...this.currentMode, ...patch });
3488
+ return this.currentMode;
3489
+ }
3490
+ /**
3491
+ * True when `incomingHash` cannot run on this launch's Query and must be
3492
+ * parked for a fresh one. An un-armed gate accepts anything — that is how a
3493
+ * new launch takes its first message, including a replayed parked one.
3494
+ */
3495
+ requiresRelaunch(incomingHash, isolate) {
3496
+ if (isolate) return true;
3497
+ return this.armed && incomingHash !== this.currentHash;
3498
+ }
3499
+ /** True when `mode` is exactly what this launch adopted (Steer's precondition). */
3500
+ matches(mode) {
3501
+ return this.armed && this.hasher(mode) === this.currentHash;
3502
+ }
3503
+ /**
3504
+ * Hold a message that cannot run on this launch until its successor picks
3505
+ * it up. Survives {@link reset} — the parked message is precisely what the
3506
+ * next launch exists to run.
3507
+ */
3508
+ park(message) {
3509
+ this.parked = message;
3510
+ }
3511
+ /** Whether a parked message is waiting for the next launch. */
3512
+ get hasParked() {
3513
+ return this.parked !== null;
3514
+ }
3515
+ /**
3516
+ * Take the parked message AND adopt its mode in one step.
3517
+ *
3518
+ * This pairing is the whole point of the class. When parking and adopting
3519
+ * were separate — a `pending` variable in the launcher next to a `modeHash`
3520
+ * one — the replay path took the message and forgot to adopt, so the gate
3521
+ * stayed un-armed and swallowed the NEXT mode change into a Query built for
3522
+ * the previous one. Nothing structural tied the two, and no unit test could
3523
+ * reach the wiring. Here they cannot come apart.
3524
+ */
3525
+ takeParked() {
3526
+ const message = this.parked;
3527
+ if (!message) return null;
3528
+ this.parked = null;
3529
+ this.adopt(message.mode);
3530
+ return message;
3531
+ }
3532
+ /**
3533
+ * Re-adopt the parked message with a patched mode. Used when an explicit
3534
+ * mode switch lands while a message is parked: the switch is newer than the
3535
+ * snapshot the message carried when it was queued.
3536
+ */
3537
+ amendParked(patch) {
3538
+ if (!this.parked) return;
3539
+ this.parked = { ...this.parked, mode: { ...this.parked.mode, ...patch } };
3540
+ }
3541
+ /**
3542
+ * Drop back to un-armed. Called when a launch ends. Deliberately does NOT
3543
+ * clear a parked message: parking happens in the launch that is ending, and
3544
+ * the message belongs to the one about to start.
3545
+ */
3546
+ reset() {
3547
+ this.currentMode = null;
3548
+ this.currentHash = null;
3549
+ }
3550
+ }
3551
+
3424
3552
  async function claudeRemoteLauncher(session, onPermissionModeChange, onEffectivePermissionMode) {
3425
3553
  logger.debug("[claudeRemoteLauncher] Starting remote launcher");
3426
3554
  const hasTTY = process.stdout.isTTY && process.stdin.isTTY;
@@ -3628,7 +3756,7 @@ async function claudeRemoteLauncher(session, onPermissionModeChange, onEffective
3628
3756
  }
3629
3757
  }
3630
3758
  try {
3631
- let pending = null;
3759
+ const modeGate = new LaunchModeGate(session.queue.modeHasher);
3632
3760
  let previousSessionId = null;
3633
3761
  while (!exitReason) {
3634
3762
  logger.debug("[remote]: launch");
@@ -3647,18 +3775,11 @@ async function claudeRemoteLauncher(session, onPermissionModeChange, onEffective
3647
3775
  const controller = new AbortController();
3648
3776
  abortController = controller;
3649
3777
  abortFuture = new Future();
3650
- let modeHash = null;
3651
- let mode = null;
3652
3778
  const commitPermissionMode = (nextMode) => {
3653
3779
  permissionHandler.handleModeChange(nextMode);
3654
- if (mode) {
3655
- mode = { ...mode, permissionMode: nextMode };
3656
- modeHash = session.queue.modeHasher(mode);
3657
- }
3780
+ modeGate.amend({ permissionMode: nextMode });
3658
3781
  rewriteQueuedPermissionMode(session.queue.queue, session.queue.modeHasher, nextMode);
3659
- if (pending) {
3660
- pending = { ...pending, mode: { ...pending.mode, permissionMode: nextMode } };
3661
- }
3782
+ modeGate.amendParked({ permissionMode: nextMode });
3662
3783
  onPermissionModeChange?.(nextMode);
3663
3784
  return nextMode;
3664
3785
  };
@@ -3684,22 +3805,28 @@ async function claudeRemoteLauncher(session, onPermissionModeChange, onEffective
3684
3805
  return permissionHandler.isAborted(toolCallId);
3685
3806
  },
3686
3807
  nextMessage: async () => {
3687
- if (pending) {
3688
- let p = pending;
3689
- pending = null;
3690
- permissionHandler.handleModeChange(p.mode.permissionMode);
3691
- return p;
3808
+ const parked = modeGate.takeParked();
3809
+ if (parked) {
3810
+ permissionHandler.handleModeChange(parked.mode.permissionMode);
3811
+ return parked;
3692
3812
  }
3693
3813
  let msg = await session.queue.waitForMessagesAndGetAsString(controller.signal);
3694
3814
  if (msg) {
3695
- if (modeHash && msg.hash !== modeHash || msg.isolate) {
3815
+ if (modeGate.requiresRelaunch(msg.hash, msg.isolate)) {
3696
3816
  logger.debug("[remote]: mode has changed, pending message");
3697
- pending = msg;
3817
+ const parkedAttachments = msg.attachments ?? [];
3818
+ const parkedStaged = parkedAttachments.length > 0 ? await stageClaudeAttachments(parkedAttachments, {
3819
+ happyHomeDir: configuration.happyHomeDir,
3820
+ sessionId: session.client.sessionId
3821
+ }) : [];
3822
+ modeGate.park({
3823
+ message: parkedStaged.length > 0 ? appendStagedAttachmentsToPrompt(msg.message, parkedStaged) : msg.message,
3824
+ mode: msg.mode
3825
+ });
3698
3826
  return null;
3699
3827
  }
3700
- modeHash = msg.hash;
3701
- mode = msg.mode;
3702
- permissionHandler.handleModeChange(mode.permissionMode);
3828
+ modeGate.adopt(msg.mode);
3829
+ permissionHandler.handleModeChange(msg.mode.permissionMode);
3703
3830
  const attachments = msg.attachments ?? [];
3704
3831
  if (attachments.length > 0) {
3705
3832
  const staged = await stageClaudeAttachments(attachments, {
@@ -3736,8 +3863,7 @@ async function claudeRemoteLauncher(session, onPermissionModeChange, onEffective
3736
3863
  onQueryReady: (q) => {
3737
3864
  turnSteering.setInterrupt(q.interrupt);
3738
3865
  session.setSteerHandler(async (input) => {
3739
- const expectedHash = session.queue.modeHasher(input.mode);
3740
- if (!modeHash || expectedHash !== modeHash) return false;
3866
+ if (!modeGate.matches(input.mode)) return false;
3741
3867
  let message = input.message;
3742
3868
  if (input.attachments?.length) {
3743
3869
  const staged = await stageClaudeAttachments(input.attachments, {
@@ -3746,19 +3872,16 @@ async function claudeRemoteLauncher(session, onPermissionModeChange, onEffective
3746
3872
  });
3747
3873
  message = appendStagedAttachmentsToPrompt(input.message, staged);
3748
3874
  }
3749
- if (!session.thinking || modeHash !== expectedHash) return false;
3875
+ if (!session.thinking || !modeGate.matches(input.mode)) return false;
3750
3876
  q.steer(message, input.mode);
3751
3877
  return true;
3752
3878
  });
3753
- permissionHandler.setPermissionModeUpdater(async (mode2) => {
3754
- await q.setPermissionMode(mode2);
3879
+ permissionHandler.setPermissionModeUpdater(async (mode) => {
3880
+ await q.setPermissionMode(mode);
3755
3881
  });
3756
3882
  livePermissionModeHandler = async (nextMode) => {
3757
3883
  await permissionHandler.setLivePermissionMode(nextMode);
3758
- if (mode) {
3759
- mode = { ...mode, permissionMode: nextMode };
3760
- modeHash = session.queue.modeHasher(mode);
3761
- }
3884
+ modeGate.amend({ permissionMode: nextMode });
3762
3885
  onPermissionModeChange?.(nextMode);
3763
3886
  return nextMode;
3764
3887
  };
@@ -3789,7 +3912,7 @@ async function claudeRemoteLauncher(session, onPermissionModeChange, onEffective
3789
3912
  closeFailed: (error) => session.client.closeClaudeSessionTurn("failed", { error }),
3790
3913
  onFailed: (error) => session.onSessionError(error),
3791
3914
  onCompleted: () => {
3792
- const idle = !pending && session.queue.size() === 0;
3915
+ const idle = !modeGate.hasParked && session.queue.size() === 0;
3793
3916
  session.onTurnEnd(idle);
3794
3917
  if (idle) {
3795
3918
  session.api.push().sendSessionNotification({
@@ -3848,8 +3971,7 @@ async function claudeRemoteLauncher(session, onPermissionModeChange, onEffective
3848
3971
  abortFuture = null;
3849
3972
  logger.debug("[remote]: launch done");
3850
3973
  permissionHandler.reset();
3851
- modeHash = null;
3852
- mode = null;
3974
+ modeGate.reset();
3853
3975
  }
3854
3976
  }
3855
3977
  } finally {
@@ -4146,6 +4268,7 @@ class MessageQueue2 {
4146
4268
  while (this.queue.length > 0 && this.queue[0].modeHash === targetModeHash && !this.queue[0].isolate) {
4147
4269
  const item = this.queue.shift();
4148
4270
  sameModeMessages.push(item.message);
4271
+ mode = item.mode;
4149
4272
  if (item.attachments) collectedAttachments.push(...item.attachments);
4150
4273
  }
4151
4274
  logger.debug(`[MessageQueue2] Collected batch of ${sameModeMessages.length} messages with mode hash: ${targetModeHash}`);
@@ -4287,6 +4410,18 @@ function hashObject(obj, options, encoding = "hex") {
4287
4410
  return createHash("sha256").update(jsonString).digest(encoding);
4288
4411
  }
4289
4412
 
4413
+ function claudeModeHash(mode) {
4414
+ return hashObject({
4415
+ isPlan: mode.permissionMode === "plan",
4416
+ fallbackModel: mode.fallbackModel,
4417
+ customSystemPrompt: mode.customSystemPrompt,
4418
+ appendSystemPrompt: mode.appendSystemPrompt,
4419
+ allowedTools: mode.allowedTools,
4420
+ disallowedTools: mode.disallowedTools,
4421
+ effort: mode.effort
4422
+ });
4423
+ }
4424
+
4290
4425
  function assistantSpawnTags(env = process.env) {
4291
4426
  if (env.HAPPY_SPAWNED_BY !== "assistant") return void 0;
4292
4427
  if (env.HAPPY_SESSION_VARIANT === "assistant") return void 0;
@@ -7836,6 +7971,12 @@ async function claimSessionOrExit(happySessionId, options) {
7836
7971
  }
7837
7972
 
7838
7973
  const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
7974
+ function sanitizeImportTitle(value) {
7975
+ if (typeof value !== "string") return null;
7976
+ const collapsed = value.replace(/[\u0000-\u001f\u007f]/g, " ").replace(/\s+/g, " ").trim();
7977
+ if (collapsed.length === 0) return null;
7978
+ return collapsed.length > 200 ? `${collapsed.slice(0, 199)}\u2026` : collapsed;
7979
+ }
7839
7980
  const hostSuffix = process.env.HAPPY_VARIANT === "dev" ? "-dev" : "";
7840
7981
  const initialMachineMetadata = {
7841
7982
  host: os$1.hostname() + hostSuffix,
@@ -8174,6 +8315,10 @@ async function startDaemon() {
8174
8315
  if (options.importedFromClaudeSessionId && UUID_RE.test(options.importedFromClaudeSessionId)) {
8175
8316
  extraEnv.HAPPY_IMPORTED_FROM_CLAUDE_SESSION_ID = options.importedFromClaudeSessionId;
8176
8317
  }
8318
+ const importTitle = sanitizeImportTitle(options.importTitle);
8319
+ if (importTitle) {
8320
+ extraEnv.HAPPY_IMPORT_TITLE = importTitle;
8321
+ }
8177
8322
  if (options.resumeCodexThreadId) {
8178
8323
  extraEnv.HAPPY_FORK_CODEX_THREAD_ID = options.resumeCodexThreadId;
8179
8324
  }
@@ -10035,7 +10180,7 @@ function registerSideQuestionHandler(rpc, deps) {
10035
10180
  const retainMs = deps.retainMs ?? SIDE_QUESTION_RETAIN_MS;
10036
10181
  const maxRunMs = deps.maxRunMs ?? SIDE_QUESTION_MAX_RUN_MS;
10037
10182
  const run = deps.run ?? (async (input) => {
10038
- const { query } = await import('./index-Bzj1XdPx.mjs');
10183
+ const { query } = await import('./index-DCpgRGWy.mjs');
10039
10184
  return runSideQuestion(query, input);
10040
10185
  });
10041
10186
  const slots = /* @__PURE__ */ new Map();
@@ -10574,6 +10719,7 @@ async function runClaude(credentials, options = {}) {
10574
10719
  const forkedFromSessionId = process.env.HAPPY_FORKED_FROM_SESSION_ID;
10575
10720
  const forkedFromMessageId = process.env.HAPPY_FORKED_FROM_MESSAGE_ID;
10576
10721
  const importedFromClaudeSessionId = process.env.HAPPY_IMPORTED_FROM_CLAUDE_SESSION_ID;
10722
+ const importTitle = process.env.HAPPY_IMPORT_TITLE;
10577
10723
  let metadata = {
10578
10724
  path: workingDirectory,
10579
10725
  host: os.hostname(),
@@ -10604,6 +10750,7 @@ async function runClaude(credentials, options = {}) {
10604
10750
  ...forkedFromSessionId ? { parentSessionId: forkedFromSessionId } : {},
10605
10751
  ...forkedFromMessageId ? { forkedFromMessageId } : {},
10606
10752
  ...importedFromClaudeSessionId ? { importedFromClaudeSessionId } : {},
10753
+ ...importTitle ? { summary: { text: importTitle, updatedAt: Date.now() } } : {},
10607
10754
  ...isAssistantVariant ? { variant: "assistant" } : {},
10608
10755
  // B-091: sessions the assistant dispatched (daemon spawn RPC exports
10609
10756
  // HAPPY_SPAWNED_BY, B-069) are born with the 'assistant' tag so every
@@ -10834,16 +10981,7 @@ async function runClaude(credentials, options = {}) {
10834
10981
  ...currentState,
10835
10982
  controlledByUser: options.startingMode !== "remote"
10836
10983
  }));
10837
- const messageQueue = new MessageQueue2((mode) => hashObject({
10838
- isPlan: mode.permissionMode === "plan",
10839
- model: mode.model,
10840
- fallbackModel: mode.fallbackModel,
10841
- customSystemPrompt: mode.customSystemPrompt,
10842
- appendSystemPrompt: mode.appendSystemPrompt,
10843
- allowedTools: mode.allowedTools,
10844
- disallowedTools: mode.disallowedTools,
10845
- effort: mode.effort
10846
- }));
10984
+ const messageQueue = new MessageQueue2(claudeModeHash);
10847
10985
  session.rpcHandlerManager.registerHandler("cancelQueuedMessage", async (params) => ({
10848
10986
  removed: messageQueue.cancelPending(
10849
10987
  typeof params?.localKey === "string" ? params.localKey : void 0,
@@ -14970,7 +15108,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
14970
15108
  process.exit(0);
14971
15109
  } else if (subcommand === "install-terminal-hooks") {
14972
15110
  try {
14973
- const { installTerminalHooks, parseTerminalHooksArgs, TERMINAL_HOOKS_HELP } = await import('./installTerminalHooks-DV0e8-V3.mjs');
15111
+ const { installTerminalHooks, parseTerminalHooksArgs, TERMINAL_HOOKS_HELP } = await import('./installTerminalHooks-l1B-nbWa.mjs');
14974
15112
  const command = parseTerminalHooksArgs(args.slice(1));
14975
15113
  if (command.action === "help") {
14976
15114
  console.log(TERMINAL_HOOKS_HELP);
@@ -14987,7 +15125,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
14987
15125
  }
14988
15126
  } else if (subcommand === "spawn") {
14989
15127
  try {
14990
- const { handleSpawnCommand } = await import('./spawn-DrqvVoNF.mjs');
15128
+ const { handleSpawnCommand } = await import('./spawn-BzbPyTHU.mjs');
14991
15129
  await handleSpawnCommand(args.slice(1));
14992
15130
  } catch (error) {
14993
15131
  console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
@@ -14999,7 +15137,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
14999
15137
  return;
15000
15138
  } else if (subcommand === "send") {
15001
15139
  try {
15002
- const { handleSendCommand } = await import('./send-B666yos0.mjs');
15140
+ const { handleSendCommand } = await import('./send-DEhUWg8v.mjs');
15003
15141
  await handleSendCommand(args.slice(1));
15004
15142
  } catch (error) {
15005
15143
  console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
@@ -15105,9 +15243,9 @@ Conversation history is preserved on the server, but in-flight tool calls are in
15105
15243
  if (geminiSubcommand === "project" && args[2] === "set" && args[3]) {
15106
15244
  const projectId = args[3];
15107
15245
  try {
15108
- const { saveGoogleCloudProjectToConfig } = await import('./config-E3KdWyaQ.mjs');
15109
- const { readCredentialsForConfiguredRelay: readCredentialsForConfiguredRelay2 } = await import('./types-D9LyZitb.mjs').then(function (n) { return n.ap; });
15110
- const { ApiClient: ApiClient2 } = await import('./types-D9LyZitb.mjs').then(function (n) { return n.aq; });
15246
+ const { saveGoogleCloudProjectToConfig } = await import('./config-DAb5saOC.mjs');
15247
+ const { readCredentialsForConfiguredRelay: readCredentialsForConfiguredRelay2 } = await import('./types-lSeHEzPR.mjs').then(function (n) { return n.ap; });
15248
+ const { ApiClient: ApiClient2 } = await import('./types-lSeHEzPR.mjs').then(function (n) { return n.aq; });
15111
15249
  let userEmail = void 0;
15112
15250
  try {
15113
15251
  const credentials = await readCredentialsForConfiguredRelay2();
@@ -15138,7 +15276,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
15138
15276
  }
15139
15277
  if (geminiSubcommand === "project" && args[2] === "get") {
15140
15278
  try {
15141
- const { readGeminiLocalConfig } = await import('./config-E3KdWyaQ.mjs');
15279
+ const { readGeminiLocalConfig } = await import('./config-DAb5saOC.mjs');
15142
15280
  const config = readGeminiLocalConfig();
15143
15281
  if (config.googleCloudProject) {
15144
15282
  console.log(`Current Google Cloud Project: ${config.googleCloudProject}`);
@@ -15178,7 +15316,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
15178
15316
  process.exit(0);
15179
15317
  }
15180
15318
  try {
15181
- const { runGemini } = await import('./runGemini-BFjtlkZI.mjs');
15319
+ const { runGemini } = await import('./runGemini-0QqaFN1l.mjs');
15182
15320
  let startedBy = void 0;
15183
15321
  for (let i = 1; i < args.length; i++) {
15184
15322
  if (args[i] === "--started-by") {
@@ -15200,7 +15338,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
15200
15338
  return;
15201
15339
  } else if (subcommand === "acp") {
15202
15340
  try {
15203
- const { runAcp, resolveAcpAgentConfig } = await import('./index-C_dmtMJ6.mjs');
15341
+ const { runAcp, resolveAcpAgentConfig } = await import('./index-D2ZzPm58.mjs');
15204
15342
  let startedBy = void 0;
15205
15343
  let verbose = false;
15206
15344
  const acpArgs = [];
@@ -15240,7 +15378,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
15240
15378
  return;
15241
15379
  } else if (subcommand === "openclaw") {
15242
15380
  try {
15243
- const { runOpenClaw } = await import('./runOpenClaw-BFYj1F4u.mjs');
15381
+ const { runOpenClaw } = await import('./runOpenClaw-C6Z6MLHq.mjs');
15244
15382
  let startedBy = void 0;
15245
15383
  let verbose = false;
15246
15384
  let gatewayUrl;
@@ -15291,7 +15429,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
15291
15429
  return;
15292
15430
  } else if (subcommand === "mcp" && args.length === 1) {
15293
15431
  try {
15294
- const { handleMcpCommand } = await import('./mcp-DFS9Zohz.mjs');
15432
+ const { handleMcpCommand } = await import('./mcp-ZvHzv_lJ.mjs');
15295
15433
  await handleMcpCommand();
15296
15434
  } catch (error) {
15297
15435
  process.stderr.write(`[very-happy mcp] Fatal: ${error instanceof Error ? error.message : String(error)}
@@ -1,10 +1,10 @@
1
- import { D as DefaultTransport, A as AcpBackend } from './AcpBackend-De4iQ_JG.mjs';
2
- export { a as DEFAULT_IDLE_TIMEOUT_MS, b as DEFAULT_TOOL_CALL_TIMEOUT_MS, e as extractErrorDetail, f as formatDuration, c as formatDurationMinutes, h as handleAgentMessageChunk, d as handleAgentThoughtChunk, g as handleLegacyMessageChunk, i as handlePlanUpdate, j as handleThinkingUpdate, k as handleToolCall, l as handleToolCallUpdate, p as parseArgsFromContent } from './AcpBackend-De4iQ_JG.mjs';
3
- import { h as connectionState, A as ApiClient, i as readSettings, j as encodeBase64, l as logger, p as projectPath } from './types-D9LyZitb.mjs';
4
- import { A as AcpSessionManager } from './AcpSessionManager-DRzAgHtT.mjs';
1
+ import { D as DefaultTransport, A as AcpBackend } from './AcpBackend-Ux5FD_6F.mjs';
2
+ export { a as DEFAULT_IDLE_TIMEOUT_MS, b as DEFAULT_TOOL_CALL_TIMEOUT_MS, e as extractErrorDetail, f as formatDuration, c as formatDurationMinutes, h as handleAgentMessageChunk, d as handleAgentThoughtChunk, g as handleLegacyMessageChunk, i as handlePlanUpdate, j as handleThinkingUpdate, k as handleToolCall, l as handleToolCallUpdate, p as parseArgsFromContent } from './AcpBackend-Ux5FD_6F.mjs';
3
+ import { h as connectionState, A as ApiClient, i as readSettings, j as encodeBase64, l as logger, p as projectPath } from './types-lSeHEzPR.mjs';
4
+ import { A as AcpSessionManager } from './AcpSessionManager-C1hoU_rK.mjs';
5
5
  import { randomUUID } from 'node:crypto';
6
6
  import { join } from 'node:path';
7
- import { i as initialMachineMetadata, d as createSessionMetadata, e as setupOfflineReconnection, n as notifyDaemonSessionStarted, M as MessageQueue2, h as hashObject, f as startHappyServer, r as registerKillSessionHandler, B as BasePermissionHandler } from './index--TiLl7-A.mjs';
7
+ import { i as initialMachineMetadata, d as createSessionMetadata, e as setupOfflineReconnection, n as notifyDaemonSessionStarted, M as MessageQueue2, h as hashObject, f as startHappyServer, r as registerKillSessionHandler, B as BasePermissionHandler } from './index-CgWdwkhB.mjs';
8
8
  import 'node:child_process';
9
9
  import '@agentclientprotocol/sdk';
10
10
  import 'axios';
@@ -1,11 +1,11 @@
1
1
  'use strict';
2
2
 
3
- var index = require('./index-CX_3uUem.cjs');
3
+ var index = require('./index-BLW-smJu.cjs');
4
4
  var claudeAgentSdk = require('@anthropic-ai/claude-agent-sdk');
5
5
  require('chalk');
6
6
  require('node:os');
7
7
  require('node:crypto');
8
- require('./types-14LATr9X.cjs');
8
+ require('./types-C8-WODFE.cjs');
9
9
  require('axios');
10
10
  require('node:util');
11
11
  require('node:fs');
@@ -1,9 +1,9 @@
1
- import { q as query } from './index--TiLl7-A.mjs';
1
+ import { q as query } from './index-CgWdwkhB.mjs';
2
2
  export { AbortError } from '@anthropic-ai/claude-agent-sdk';
3
3
  import 'chalk';
4
4
  import 'node:os';
5
5
  import 'node:crypto';
6
- import './types-D9LyZitb.mjs';
6
+ import './types-lSeHEzPR.mjs';
7
7
  import 'axios';
8
8
  import 'node:util';
9
9
  import 'node:fs';
@@ -1,11 +1,11 @@
1
1
  'use strict';
2
2
 
3
- var AcpBackend = require('./AcpBackend-DDGfk3HJ.cjs');
4
- var persistence = require('./types-14LATr9X.cjs');
5
- var AcpSessionManager = require('./AcpSessionManager-Cdv6Xgwy.cjs');
3
+ var AcpBackend = require('./AcpBackend-DMmvLNNM.cjs');
4
+ var persistence = require('./types-C8-WODFE.cjs');
5
+ var AcpSessionManager = require('./AcpSessionManager-RYkxYWDn.cjs');
6
6
  var node_crypto = require('node:crypto');
7
7
  var path = require('node:path');
8
- var index = require('./index-CX_3uUem.cjs');
8
+ var index = require('./index-BLW-smJu.cjs');
9
9
  require('node:child_process');
10
10
  require('@agentclientprotocol/sdk');
11
11
  require('axios');
package/dist/index.cjs CHANGED
@@ -1,8 +1,8 @@
1
1
  'use strict';
2
2
 
3
3
  require('chalk');
4
- require('./index-CX_3uUem.cjs');
5
- require('./types-14LATr9X.cjs');
4
+ require('./index-BLW-smJu.cjs');
5
+ require('./types-C8-WODFE.cjs');
6
6
  require('zod');
7
7
  require('node:child_process');
8
8
  require('node:os');
package/dist/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import 'chalk';
2
- import './index--TiLl7-A.mjs';
3
- import './types-D9LyZitb.mjs';
2
+ import './index-CgWdwkhB.mjs';
3
+ import './types-lSeHEzPR.mjs';
4
4
  import 'zod';
5
5
  import 'node:child_process';
6
6
  import 'node:os';
@@ -5,8 +5,8 @@ var path = require('node:path');
5
5
  var os = require('node:os');
6
6
  var node_child_process = require('node:child_process');
7
7
  var chalk = require('chalk');
8
- var persistence = require('./types-14LATr9X.cjs');
9
- var index = require('./index-CX_3uUem.cjs');
8
+ var persistence = require('./types-C8-WODFE.cjs');
9
+ var index = require('./index-BLW-smJu.cjs');
10
10
  require('axios');
11
11
  require('node:util');
12
12
  require('node:fs/promises');
@@ -3,8 +3,8 @@ import { join, resolve } from 'node:path';
3
3
  import { homedir } from 'node:os';
4
4
  import { spawnSync } from 'node:child_process';
5
5
  import chalk from 'chalk';
6
- import { p as projectPath } from './types-D9LyZitb.mjs';
7
- import { t as tmuxSupportsSessionEnv } from './index--TiLl7-A.mjs';
6
+ import { p as projectPath } from './types-lSeHEzPR.mjs';
7
+ import { t as tmuxSupportsSessionEnv } from './index-CgWdwkhB.mjs';
8
8
  import 'axios';
9
9
  import 'node:util';
10
10
  import 'node:fs/promises';
package/dist/lib.cjs CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var persistence = require('./types-14LATr9X.cjs');
3
+ var persistence = require('./types-C8-WODFE.cjs');
4
4
  require('axios');
5
5
  require('chalk');
6
6
  require('node:util');
package/dist/lib.d.cts CHANGED
@@ -444,6 +444,7 @@ declare const UserMessageSchema: z$1.ZodObject<{
444
444
  appendSystemPrompt: z$1.ZodOptional<z$1.ZodNullable<z$1.ZodString>>;
445
445
  allowedTools: z$1.ZodOptional<z$1.ZodNullable<z$1.ZodArray<z$1.ZodString>>>;
446
446
  disallowedTools: z$1.ZodOptional<z$1.ZodNullable<z$1.ZodArray<z$1.ZodString>>>;
447
+ effort: z$1.ZodOptional<z$1.ZodNullable<z$1.ZodString>>;
447
448
  delivery: z$1.ZodOptional<z$1.ZodEnum<{
448
449
  queue: "queue";
449
450
  steer: "steer";
@@ -1001,6 +1002,11 @@ interface SpawnSessionOptions {
1001
1002
  * metadata so the import picker hides it. Old daemons ignore the field.
1002
1003
  */
1003
1004
  importedFromClaudeSessionId?: string;
1005
+ /**
1006
+ * B-294: title to stamp on the imported session's metadata (the source
1007
+ * conversation's summary / first prompt). Sanitized daemon-side.
1008
+ */
1009
+ importTitle?: string;
1004
1010
  /**
1005
1011
  * B-051: spawn the machine's assistant (meta-agent) session. The daemon
1006
1012
  * forces cwd to ~/.happy/assistant (the passed `directory` is ignored),