very-happy-cli 0.2.89 → 0.2.91

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 (31) hide show
  1. package/dist/{AcpBackend-DuxcHPyn.mjs → AcpBackend-B5RRlzLU.mjs} +1 -1
  2. package/dist/{AcpBackend-ClEvE-A-.cjs → AcpBackend-GqveWg8u.cjs} +1 -1
  3. package/dist/{AcpSessionManager-CGrA8bTm.mjs → AcpSessionManager-Ij1pI5Ex.mjs} +1 -1
  4. package/dist/{AcpSessionManager-DgbEf8FX.cjs → AcpSessionManager-YcrSZwvq.cjs} +1 -1
  5. package/dist/{config-BZNnPK1L.cjs → config-C4NCw7Dz.cjs} +2 -2
  6. package/dist/{config-CdoGLla8.mjs → config-ra8iKxdU.mjs} +2 -2
  7. package/dist/{index-Bx-UTV3I.cjs → index-C1HIm7NY.cjs} +4 -4
  8. package/dist/{index-BP2wylez.mjs → index-CB5H7YI6.mjs} +5 -5
  9. package/dist/{index-DYDAFqAN.cjs → index-FkXJNwiy.cjs} +141 -39
  10. package/dist/{index-vCgb7QnZ.mjs → index-UCYD5Wk3.mjs} +138 -36
  11. package/dist/index.cjs +2 -2
  12. package/dist/index.mjs +2 -2
  13. package/dist/{installTerminalHooks-T4NE5fmC.cjs → installTerminalHooks-CoK5wNcA.cjs} +2 -2
  14. package/dist/{installTerminalHooks-C7JS6CBM.mjs → installTerminalHooks-TkkGzzbI.mjs} +2 -2
  15. package/dist/lib.cjs +1 -1
  16. package/dist/lib.d.cts +42 -0
  17. package/dist/lib.d.mts +42 -0
  18. package/dist/lib.mjs +1 -1
  19. package/dist/{mcp-B3EgenZh.mjs → mcp-C3MiqObi.mjs} +2 -2
  20. package/dist/{mcp-DgPg5mKa.cjs → mcp-DcRvswVI.cjs} +2 -2
  21. package/dist/{runGemini-B3iv1QRL.mjs → runGemini-BbC0uwUj.mjs} +4 -4
  22. package/dist/{runGemini-xZnGj7jx.cjs → runGemini-Dnvd2gWq.cjs} +4 -4
  23. package/dist/{runOpenClaw-CmM4ZLYi.mjs → runOpenClaw--nbqTW3K.mjs} +3 -3
  24. package/dist/{runOpenClaw-6ZLPeYYZ.cjs → runOpenClaw-CnuTBNQW.cjs} +3 -3
  25. package/dist/{send-M2ax7UGy.mjs → send-9nJxn09Y.mjs} +2 -2
  26. package/dist/{send-S9ibMiAt.cjs → send-DkKOPatx.cjs} +2 -2
  27. package/dist/{spawn-Dx_xnLlg.mjs → spawn-CRW4ioms.mjs} +2 -2
  28. package/dist/{spawn-ie_kfwQe.cjs → spawn-DE7WhS8Y.cjs} +2 -2
  29. package/dist/{types-d_104eQF.mjs → types-BKyjG23_.mjs} +304 -52
  30. package/dist/{types-DLI6sss2.cjs → types-ClDADuxP.cjs} +306 -52
  31. 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 } 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 getLocalHappyAgentCredentialPath, L as readLocalHappyAgentCredentials, N as decryptWithDataKey, O as decryptLegacy, P as extractCompleteLines, Q as decideBackfill, S as parseTerminalHookPayload, T as decideMirrorBinding, U as persistSession, V as MIRROR_BACKFILL_LINES_DEFAULT, W as mirrorLineKey, X as mirrorLocalId, Y as detectResumeSupport, Z as detectCLIAvailability, _ as acquireDaemonLock, $ as summarizeSpawnSessionForLog, a0 as deletePersistedSessions, a1 as decrypt, a2 as writeDaemonState, A as ApiClient, a3 as fetchCliUpdateState, a4 as resolveCliUpdateCheckInterval, a5 as DEFAULT_CLI_UPDATE_CHECK_INTERVAL_MS, a6 as releaseDaemonLock, a7 as isValidSessionId, a8 as listVhTerminals, a9 as isValidTerminalId, aa as readVhTerminal, ab as sendToVhTerminal, h as connectionState, ac as startOfflineReconnection, ad as clearCredentials, ae as clearMachineId, k as ensurePrivateDirectorySync, w as writePrivateFileSync, af as SandboxConfigSchema, b as createEnvelope, ag as CodexAppServerClient, ah as getLatestDaemonLog } from './types-d_104eQF.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 getLocalHappyAgentCredentialPath, L as readLocalHappyAgentCredentials, N as decryptWithDataKey, O as decryptLegacy, P as extractCompleteLines, Q as decideBackfill, S as parseTerminalHookPayload, T as tmuxArgs, U as scrubTmuxClientEnv, V as decideMirrorBinding, W as persistSession, X as MIRROR_BACKFILL_LINES_DEFAULT, Y as mirrorLineKey, Z as mirrorLocalId, _ as detectResumeSupport, $ as detectCLIAvailability, a0 as acquireDaemonLock, a1 as summarizeSpawnSessionForLog, a2 as deletePersistedSessions, a3 as decrypt, a4 as writeDaemonState, A as ApiClient, a5 as fetchCliUpdateState, a6 as resolveCliUpdateCheckInterval, a7 as DEFAULT_CLI_UPDATE_CHECK_INTERVAL_MS, a8 as releaseDaemonLock, a9 as isValidSessionId, aa as listVhTerminals, ab as isValidTerminalId, ac as readVhTerminal, ad as sendToVhTerminal, h as connectionState, ae as startOfflineReconnection, af as clearCredentials, ag as clearMachineId, k as ensurePrivateDirectorySync, w as writePrivateFileSync, ah as SandboxConfigSchema, b as createEnvelope, ai as CodexAppServerClient, aj as getLatestDaemonLog } from './types-BKyjG23_.mjs';
5
5
  import spawn$1, { spawn } from 'cross-spawn';
6
6
  import path, { join, resolve, basename, isAbsolute, normalize, sep, dirname } from 'node:path';
7
7
  import { createInterface } from 'node:readline';
@@ -1633,6 +1633,12 @@ function resolveRemoteClaudePermissionMode(currentMode, incomingMode, sandboxEna
1633
1633
  }
1634
1634
  return nextMode;
1635
1635
  }
1636
+ function reconcilePublishedPermissionMode(input) {
1637
+ if (!input.effective) return null;
1638
+ const mismatch = input.effective !== input.intent;
1639
+ if (input.effective === input.published) return mismatch ? { publish: input.effective, mismatch } : null;
1640
+ return { publish: input.effective, mismatch };
1641
+ }
1636
1642
 
1637
1643
  function parseCompact(message) {
1638
1644
  const trimmed = message.trim();
@@ -1962,7 +1968,12 @@ async function claudeRemote(opts) {
1962
1968
  mcpServers: systemInit.mcp_servers?.map((s) => ({ name: s.name, status: s.status })),
1963
1969
  skills: systemInit.skills,
1964
1970
  model: systemInit.model,
1965
- modelIsDefault: initial.mode.model == null
1971
+ modelIsDefault: initial.mode.model == null,
1972
+ // The SDK's own verdict on the mode it will enforce —
1973
+ // settings (permissions.deny/ask/defaultMode/
1974
+ // disableBypassPermissionsMode) can override what we
1975
+ // asked for. runClaude publishes THIS, not our intent.
1976
+ permissionMode: systemInit.permissionMode
1966
1977
  });
1967
1978
  }
1968
1979
  if (systemInit.session_id) {
@@ -1980,6 +1991,10 @@ async function claudeRemote(opts) {
1980
1991
  if (message.type === "result") {
1981
1992
  updateThinking(false);
1982
1993
  logger.debug("[claudeRemote] Result received");
1994
+ const denials = message.permission_denials;
1995
+ if (denials && denials.length > 0) {
1996
+ logger.warn(`[claudeRemote] ${denials.length} tool call(s) auto-denied by Claude Code policy: ${denials.map((d) => d.tool_name ?? "?").join(", ")}`);
1997
+ }
1983
1998
  if (isCompactCommand) {
1984
1999
  const compactSucceeded = message.subtype === "success";
1985
2000
  logger.debug(`[claudeRemote] Compaction ${compactSucceeded ? "completed" : "failed"}`);
@@ -2065,6 +2080,8 @@ class PermissionHandler {
2065
2080
  setPermissionModeCallback;
2066
2081
  modeChangeQueue = Promise.resolve();
2067
2082
  deferredPermissionMode;
2083
+ /** Fired when an approval response (plan approval / approve-with-mode) moved the mode. */
2084
+ onModeChangedCallback;
2068
2085
  constructor(session) {
2069
2086
  this.session = session;
2070
2087
  this.setupClientHandler();
@@ -2078,6 +2095,18 @@ class PermissionHandler {
2078
2095
  handleModeChange(mode) {
2079
2096
  this.permissionMode = mapToClaudeMode(mode);
2080
2097
  }
2098
+ /** Current locally enforced mode (what canUseTool applies right now). */
2099
+ getPermissionMode() {
2100
+ return this.permissionMode;
2101
+ }
2102
+ /**
2103
+ * Observe mode transitions that originate inside the approval flow
2104
+ * (ExitPlanMode approval, approve-with-mode). Live/idle RPC changes and
2105
+ * queue-driven changes are owned by the launcher and do not fire this.
2106
+ */
2107
+ setOnModeChanged(callback) {
2108
+ this.onModeChangedCallback = callback;
2109
+ }
2081
2110
  /**
2082
2111
  * Set callback to dynamically change permission mode on the active query.
2083
2112
  * Called by claudeRemote after the Query object is created.
@@ -2166,24 +2195,19 @@ class PermissionHandler {
2166
2195
  updatedInput: contentLogMetadata(response.updatedInput)
2167
2196
  });
2168
2197
  if (response.approved) {
2169
- const newMode = response.mode && ["default", "acceptEdits", "bypassPermissions"].includes(response.mode) ? response.mode : "default";
2198
+ const newMode = response.mode && ["default", "acceptEdits", "bypassPermissions"].includes(response.mode) ? response.mode : "bypassPermissions";
2170
2199
  logger.debug(`Plan approved - allowing ExitPlanMode with ${newMode} as the local follow-up mode`);
2171
2200
  this.permissionMode = newMode;
2201
+ this.onModeChangedCallback?.(newMode);
2172
2202
  pending.resolve({ behavior: "allow", updatedInput: pending.input || {} });
2173
2203
  } else {
2174
2204
  pending.resolve({ behavior: "deny", message: response.reason || "Plan rejected" });
2175
2205
  }
2176
2206
  } else {
2177
2207
  if (response.approved && response.mode) {
2178
- try {
2179
- if (!this.setPermissionModeCallback) throw new Error("permission mode updater unavailable");
2180
- await this.setPermissionModeCallback(response.mode);
2181
- this.permissionMode = response.mode;
2182
- } catch (err) {
2183
- const reason = `Failed to switch permission mode: ${err instanceof Error ? err.message : String(err)}`;
2184
- pending.resolve({ behavior: "deny", message: reason });
2185
- return { ...response, approved: false, reason };
2186
- }
2208
+ this.permissionMode = response.mode;
2209
+ this.deferredPermissionMode = response.mode;
2210
+ this.onModeChangedCallback?.(response.mode);
2187
2211
  }
2188
2212
  if (response.approved && !pending.suggestions?.length && response.allowTools?.length) {
2189
2213
  response.allowTools.forEach((tool) => {
@@ -2524,6 +2548,17 @@ class PermissionHandler {
2524
2548
  }
2525
2549
  }
2526
2550
 
2551
+ function rewriteQueuedPermissionMode(queue, hasher, permissionMode) {
2552
+ let rewritten = 0;
2553
+ for (const item of queue) {
2554
+ if (item.mode.permissionMode === permissionMode) continue;
2555
+ item.mode = { ...item.mode, permissionMode };
2556
+ item.modeHash = hasher(item.mode);
2557
+ rewritten += 1;
2558
+ }
2559
+ return rewritten;
2560
+ }
2561
+
2527
2562
  function formatClaudeMessageForInk(message, messageBuffer, onAssistantResult) {
2528
2563
  logger.debugLargeJson("[CLAUDE INK] Message from remote mode:", message);
2529
2564
  switch (message.type) {
@@ -2778,7 +2813,13 @@ class SDKToLogConverter {
2778
2813
  type: "user",
2779
2814
  message: userMsg.message,
2780
2815
  ...userMsg.parent_tool_use_id ? { parent_tool_use_id: userMsg.parent_tool_use_id } : {},
2781
- ...meta ? { isMeta: true } : {}
2816
+ ...meta ? { isMeta: true } : {},
2817
+ // B-260-P2: structured tool output (Agent/Task final report +
2818
+ // run totals, or the `async_launched` stub marker) and the
2819
+ // message origin (task-notification). Both are consumed by
2820
+ // sessionProtocolMapper; the raw-line schema is passthrough.
2821
+ ...userMsg.tool_use_result !== void 0 ? { tool_use_result: userMsg.tool_use_result } : {},
2822
+ ...userMsg.origin ? { origin: userMsg.origin } : {}
2782
2823
  };
2783
2824
  if (Array.isArray(userMsg.message.content)) {
2784
2825
  for (const content of userMsg.message.content) {
@@ -3289,7 +3330,7 @@ function createTurnSteeringController() {
3289
3330
  };
3290
3331
  }
3291
3332
 
3292
- async function claudeRemoteLauncher(session, onPermissionModeChange) {
3333
+ async function claudeRemoteLauncher(session, onPermissionModeChange, onEffectivePermissionMode) {
3293
3334
  logger.debug("[claudeRemoteLauncher] Starting remote launcher");
3294
3335
  const hasTTY = process.stdout.isTTY && process.stdin.isTTY;
3295
3336
  logger.debug(`[claudeRemoteLauncher] TTY available: ${hasTTY}`);
@@ -3351,16 +3392,23 @@ async function claudeRemoteLauncher(session, onPermissionModeChange) {
3351
3392
  });
3352
3393
  session.client.rpcHandlerManager.registerHandler("switch", doSwitch);
3353
3394
  let livePermissionModeHandler = null;
3395
+ let idlePermissionModeHandler = null;
3354
3396
  session.client.rpcHandlerManager.registerHandler(
3355
3397
  "set-permission-mode",
3356
3398
  async (request) => {
3357
3399
  const requestedMode = parseClaudePermissionMode(request?.mode);
3358
3400
  if (!requestedMode) throw new Error("Invalid Claude permission mode");
3359
- if (!livePermissionModeHandler) throw new Error("No active Claude query");
3360
- return { mode: await livePermissionModeHandler(requestedMode) };
3401
+ if (livePermissionModeHandler) return { mode: await livePermissionModeHandler(requestedMode) };
3402
+ if (idlePermissionModeHandler) return { mode: idlePermissionModeHandler(requestedMode) };
3403
+ throw new Error("No active Claude query");
3361
3404
  }
3362
3405
  );
3363
3406
  const permissionHandler = new PermissionHandler(session);
3407
+ idlePermissionModeHandler = (nextMode) => {
3408
+ permissionHandler.handleModeChange(nextMode);
3409
+ onPermissionModeChange?.(nextMode);
3410
+ return nextMode;
3411
+ };
3364
3412
  permissionHandler.reset("Previous CLI process exited before responding");
3365
3413
  const messageQueue = new OutgoingMessageQueue(
3366
3414
  (logMessage) => session.client.sendClaudeSessionMessage(logMessage)
@@ -3510,6 +3558,23 @@ async function claudeRemoteLauncher(session, onPermissionModeChange) {
3510
3558
  abortFuture = new Future();
3511
3559
  let modeHash = null;
3512
3560
  let mode = null;
3561
+ const commitPermissionMode = (nextMode) => {
3562
+ permissionHandler.handleModeChange(nextMode);
3563
+ if (mode) {
3564
+ mode = { ...mode, permissionMode: nextMode };
3565
+ modeHash = session.queue.modeHasher(mode);
3566
+ }
3567
+ rewriteQueuedPermissionMode(session.queue.queue, session.queue.modeHasher, nextMode);
3568
+ if (pending) {
3569
+ pending = { ...pending, mode: { ...pending.mode, permissionMode: nextMode } };
3570
+ }
3571
+ onPermissionModeChange?.(nextMode);
3572
+ return nextMode;
3573
+ };
3574
+ idlePermissionModeHandler = commitPermissionMode;
3575
+ permissionHandler.setOnModeChanged((nextMode) => {
3576
+ commitPermissionMode(nextMode);
3577
+ });
3513
3578
  try {
3514
3579
  const attachmentDirectory = chatAttachmentDirectory(configuration.happyHomeDir, session.client.sessionId);
3515
3580
  await ensurePrivateDirectory(attachmentDirectory);
@@ -3575,6 +3640,7 @@ async function claudeRemoteLauncher(session, onPermissionModeChange) {
3575
3640
  skillCount: metadata.skills?.length ?? 0
3576
3641
  });
3577
3642
  session.client.updateMetadata((currentMetadata) => applyClaudeSdkMetadata(currentMetadata, metadata));
3643
+ if (metadata.permissionMode) onEffectivePermissionMode?.(metadata.permissionMode);
3578
3644
  },
3579
3645
  onQueryReady: (q) => {
3580
3646
  turnSteering.setInterrupt(q.interrupt);
@@ -3752,7 +3818,7 @@ async function loop(opts) {
3752
3818
  break;
3753
3819
  }
3754
3820
  case "remote": {
3755
- const reason = await claudeRemoteLauncher(session, opts.onPermissionModeChange);
3821
+ const reason = await claudeRemoteLauncher(session, opts.onPermissionModeChange, opts.onEffectivePermissionMode);
3756
3822
  switch (reason) {
3757
3823
  case "exit":
3758
3824
  return 0;
@@ -6614,7 +6680,7 @@ function createMirrorManager(deps) {
6614
6680
  let chain = Promise.resolve();
6615
6681
  const isTerminalAlive = deps.isTerminalAlive ?? ((terminalId) => {
6616
6682
  try {
6617
- return spawnSync("tmux", ["has-session", "-t", `=vh-${terminalId}:`], { stdio: "ignore", timeout: 3e3 }).status === 0;
6683
+ return spawnSync("tmux", tmuxArgs(["has-session", "-t", `=vh-${terminalId}:`]), { stdio: "ignore", timeout: 3e3, env: scrubTmuxClientEnv({ ...process.env }) }).status === 0;
6618
6684
  } catch {
6619
6685
  return false;
6620
6686
  }
@@ -9242,7 +9308,10 @@ async function runClaude(credentials, options = {}) {
9242
9308
  // a brand-new session cannot pick a PDF until after sending once.
9243
9309
  attachmentKinds: [...CLAUDE_ATTACHMENT_KINDS],
9244
9310
  queueCancellation: true,
9245
- capabilities: ["claude-steer-v1", "claude-live-permission-v1"],
9311
+ capabilities: ["claude-steer-v1", "claude-live-permission-v1", "claude-live-permission-v2"],
9312
+ // Effective mode this process enforces. Kept current by
9313
+ // publishPermissionMode below; the web renders it instead of guessing.
9314
+ permissionMode: mapToClaudeMode(initialPermissionMode ?? "default"),
9246
9315
  sandbox: sandboxConfig?.enabled ? sandboxConfig : null,
9247
9316
  dangerouslySkipPermissions,
9248
9317
  ...forkedFromSessionId ? { parentSessionId: forkedFromSessionId } : {},
@@ -9347,7 +9416,11 @@ async function runClaude(credentials, options = {}) {
9347
9416
  ...meta,
9348
9417
  lifecycleState: "running",
9349
9418
  archivedBy: void 0,
9350
- queueCancellation: true
9419
+ queueCancellation: true,
9420
+ // B-262 batch 2 (B5): a reconnected process re-publishes the mode
9421
+ // IT enforces; the server copy may still carry the previous
9422
+ // process's value.
9423
+ permissionMode: mapToClaudeMode(initialPermissionMode ?? "default")
9351
9424
  }));
9352
9425
  }
9353
9426
  const forkClaudeSessionId = process.env.HAPPY_FORK_CLAUDE_SESSION_ID;
@@ -9479,6 +9552,30 @@ async function runClaude(credentials, options = {}) {
9479
9552
  )
9480
9553
  }));
9481
9554
  let currentPermissionMode = initialPermissionMode;
9555
+ let publishedPermissionMode = mapToClaudeMode(initialPermissionMode ?? "default");
9556
+ const publishPermissionMode = (mode) => {
9557
+ currentPermissionMode = mode;
9558
+ const published = mapToClaudeMode(mode ?? "default");
9559
+ if (published === publishedPermissionMode) return;
9560
+ publishedPermissionMode = published;
9561
+ session.updateMetadata((meta) => ({ ...meta, permissionMode: published }));
9562
+ };
9563
+ const publishEffectivePermissionMode = (effective) => {
9564
+ const decision = reconcilePublishedPermissionMode({
9565
+ intent: mapToClaudeMode(currentPermissionMode ?? "default"),
9566
+ published: publishedPermissionMode,
9567
+ effective
9568
+ });
9569
+ if (!decision) return;
9570
+ if (decision.mismatch) {
9571
+ logger.warn(`[loop] Claude Code enforces permission mode '${effective}' but this process asked for '${mapToClaudeMode(currentPermissionMode ?? "default")}' \u2014 check ~/.claude settings (permissions.deny/ask/defaultMode/disableBypassPermissionsMode). Publishing the effective mode.`);
9572
+ }
9573
+ if (decision.publish !== publishedPermissionMode) {
9574
+ publishedPermissionMode = decision.publish;
9575
+ session.updateMetadata((meta) => ({ ...meta, permissionMode: decision.publish }));
9576
+ }
9577
+ };
9578
+ let lastExplicitModeSwitchAt = 0;
9482
9579
  let currentModel = options.model ?? DEFAULT_CLAUDE_MODEL;
9483
9580
  let currentFallbackModel = void 0;
9484
9581
  let currentCustomSystemPrompt = void 0;
@@ -9487,7 +9584,6 @@ async function runClaude(credentials, options = {}) {
9487
9584
  let currentDisallowedTools = withAssistantDenylist(void 0, isAssistantVariant);
9488
9585
  let currentEffort = DEFAULT_CLAUDE_EFFORT;
9489
9586
  const resetCurrentModeDefaults = () => {
9490
- currentPermissionMode = initialPermissionMode;
9491
9587
  currentModel = options.model ?? DEFAULT_CLAUDE_MODEL;
9492
9588
  currentFallbackModel = void 0;
9493
9589
  currentCustomSystemPrompt = void 0;
@@ -9524,14 +9620,18 @@ async function runClaude(credentials, options = {}) {
9524
9620
  }
9525
9621
  const attachmentsForThisMessage = await session.drainAttachmentsForUserMessage();
9526
9622
  let messagePermissionMode = currentPermissionMode;
9527
- if (message.meta?.permissionMode) {
9623
+ const messageCreatedAt = message.createdAt;
9624
+ const staleModeSnapshot = message.meta?.permissionMode !== void 0 && typeof messageCreatedAt === "number" && messageCreatedAt < lastExplicitModeSwitchAt;
9625
+ if (staleModeSnapshot) {
9626
+ logger.debug(`[loop] Ignoring stale meta.permissionMode=${message.meta?.permissionMode} (message ${messageCreatedAt} predates explicit switch ${lastExplicitModeSwitchAt}); keeping ${currentPermissionMode}`);
9627
+ } else if (message.meta?.permissionMode) {
9528
9628
  const previousPermissionMode = currentPermissionMode;
9529
9629
  messagePermissionMode = resolveRemoteClaudePermissionMode(
9530
9630
  currentPermissionMode,
9531
9631
  message.meta.permissionMode,
9532
9632
  sandboxEnabled
9533
9633
  );
9534
- currentPermissionMode = messagePermissionMode;
9634
+ publishPermissionMode(messagePermissionMode);
9535
9635
  const ignoredDefaultDowngrade = (previousPermissionMode === "bypassPermissions" || previousPermissionMode === "yolo") && message.meta.permissionMode === "default" && currentPermissionMode === previousPermissionMode;
9536
9636
  if (ignoredDefaultDowngrade) {
9537
9637
  logger.debug(`[loop] Ignoring permission mode downgrade from ${previousPermissionMode} to default`);
@@ -9788,9 +9888,11 @@ async function runClaude(credentials, options = {}) {
9788
9888
  },
9789
9889
  onAbort: resetCurrentModeDefaults,
9790
9890
  onPermissionModeChange: (mode) => {
9791
- currentPermissionMode = mode;
9792
- logger.debug(`[loop] Permission mode updated from live session RPC to: ${mode}`);
9891
+ lastExplicitModeSwitchAt = Date.now();
9892
+ publishPermissionMode(mode);
9893
+ logger.debug(`[loop] Permission mode updated from session RPC / approval to: ${mode}`);
9793
9894
  },
9895
+ onEffectivePermissionMode: (mode) => publishEffectivePermissionMode(mode),
9794
9896
  mcpServers: {
9795
9897
  "happy": {
9796
9898
  type: "http",
@@ -13547,7 +13649,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
13547
13649
  process.exit(0);
13548
13650
  } else if (subcommand === "install-terminal-hooks") {
13549
13651
  try {
13550
- const { installTerminalHooks, parseTerminalHooksArgs, TERMINAL_HOOKS_HELP } = await import('./installTerminalHooks-C7JS6CBM.mjs');
13652
+ const { installTerminalHooks, parseTerminalHooksArgs, TERMINAL_HOOKS_HELP } = await import('./installTerminalHooks-TkkGzzbI.mjs');
13551
13653
  const command = parseTerminalHooksArgs(args.slice(1));
13552
13654
  if (command.action === "help") {
13553
13655
  console.log(TERMINAL_HOOKS_HELP);
@@ -13564,7 +13666,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
13564
13666
  }
13565
13667
  } else if (subcommand === "spawn") {
13566
13668
  try {
13567
- const { handleSpawnCommand } = await import('./spawn-Dx_xnLlg.mjs');
13669
+ const { handleSpawnCommand } = await import('./spawn-CRW4ioms.mjs');
13568
13670
  await handleSpawnCommand(args.slice(1));
13569
13671
  } catch (error) {
13570
13672
  console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
@@ -13576,7 +13678,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
13576
13678
  return;
13577
13679
  } else if (subcommand === "send") {
13578
13680
  try {
13579
- const { handleSendCommand } = await import('./send-M2ax7UGy.mjs');
13681
+ const { handleSendCommand } = await import('./send-9nJxn09Y.mjs');
13580
13682
  await handleSendCommand(args.slice(1));
13581
13683
  } catch (error) {
13582
13684
  console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
@@ -13682,9 +13784,9 @@ Conversation history is preserved on the server, but in-flight tool calls are in
13682
13784
  if (geminiSubcommand === "project" && args[2] === "set" && args[3]) {
13683
13785
  const projectId = args[3];
13684
13786
  try {
13685
- const { saveGoogleCloudProjectToConfig } = await import('./config-CdoGLla8.mjs');
13686
- const { readCredentialsForConfiguredRelay: readCredentialsForConfiguredRelay2 } = await import('./types-d_104eQF.mjs').then(function (n) { return n.ai; });
13687
- const { ApiClient: ApiClient2 } = await import('./types-d_104eQF.mjs').then(function (n) { return n.aj; });
13787
+ const { saveGoogleCloudProjectToConfig } = await import('./config-ra8iKxdU.mjs');
13788
+ const { readCredentialsForConfiguredRelay: readCredentialsForConfiguredRelay2 } = await import('./types-BKyjG23_.mjs').then(function (n) { return n.ak; });
13789
+ const { ApiClient: ApiClient2 } = await import('./types-BKyjG23_.mjs').then(function (n) { return n.al; });
13688
13790
  let userEmail = void 0;
13689
13791
  try {
13690
13792
  const credentials = await readCredentialsForConfiguredRelay2();
@@ -13715,7 +13817,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
13715
13817
  }
13716
13818
  if (geminiSubcommand === "project" && args[2] === "get") {
13717
13819
  try {
13718
- const { readGeminiLocalConfig } = await import('./config-CdoGLla8.mjs');
13820
+ const { readGeminiLocalConfig } = await import('./config-ra8iKxdU.mjs');
13719
13821
  const config = readGeminiLocalConfig();
13720
13822
  if (config.googleCloudProject) {
13721
13823
  console.log(`Current Google Cloud Project: ${config.googleCloudProject}`);
@@ -13755,7 +13857,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
13755
13857
  process.exit(0);
13756
13858
  }
13757
13859
  try {
13758
- const { runGemini } = await import('./runGemini-B3iv1QRL.mjs');
13860
+ const { runGemini } = await import('./runGemini-BbC0uwUj.mjs');
13759
13861
  let startedBy = void 0;
13760
13862
  for (let i = 1; i < args.length; i++) {
13761
13863
  if (args[i] === "--started-by") {
@@ -13777,7 +13879,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
13777
13879
  return;
13778
13880
  } else if (subcommand === "acp") {
13779
13881
  try {
13780
- const { runAcp, resolveAcpAgentConfig } = await import('./index-BP2wylez.mjs');
13882
+ const { runAcp, resolveAcpAgentConfig } = await import('./index-CB5H7YI6.mjs');
13781
13883
  let startedBy = void 0;
13782
13884
  let verbose = false;
13783
13885
  const acpArgs = [];
@@ -13817,7 +13919,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
13817
13919
  return;
13818
13920
  } else if (subcommand === "openclaw") {
13819
13921
  try {
13820
- const { runOpenClaw } = await import('./runOpenClaw-CmM4ZLYi.mjs');
13922
+ const { runOpenClaw } = await import('./runOpenClaw--nbqTW3K.mjs');
13821
13923
  let startedBy = void 0;
13822
13924
  let verbose = false;
13823
13925
  let gatewayUrl;
@@ -13868,7 +13970,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
13868
13970
  return;
13869
13971
  } else if (subcommand === "mcp" && args.length === 1) {
13870
13972
  try {
13871
- const { handleMcpCommand } = await import('./mcp-B3EgenZh.mjs');
13973
+ const { handleMcpCommand } = await import('./mcp-C3MiqObi.mjs');
13872
13974
  await handleMcpCommand();
13873
13975
  } catch (error) {
13874
13976
  process.stderr.write(`[very-happy mcp] Fatal: ${error instanceof Error ? error.message : String(error)}
package/dist/index.cjs CHANGED
@@ -1,8 +1,8 @@
1
1
  'use strict';
2
2
 
3
3
  require('chalk');
4
- require('./index-DYDAFqAN.cjs');
5
- require('./types-DLI6sss2.cjs');
4
+ require('./index-FkXJNwiy.cjs');
5
+ require('./types-ClDADuxP.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-vCgb7QnZ.mjs';
3
- import './types-d_104eQF.mjs';
2
+ import './index-UCYD5Wk3.mjs';
3
+ import './types-BKyjG23_.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-DLI6sss2.cjs');
9
- var index = require('./index-DYDAFqAN.cjs');
8
+ var persistence = require('./types-ClDADuxP.cjs');
9
+ var index = require('./index-FkXJNwiy.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-d_104eQF.mjs';
7
- import { t as tmuxSupportsSessionEnv } from './index-vCgb7QnZ.mjs';
6
+ import { p as projectPath } from './types-BKyjG23_.mjs';
7
+ import { t as tmuxSupportsSessionEnv } from './index-UCYD5Wk3.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-DLI6sss2.cjs');
3
+ var persistence = require('./types-ClDADuxP.cjs');
4
4
  require('axios');
5
5
  require('chalk');
6
6
  require('node:util');
package/dist/lib.d.cts CHANGED
@@ -45,6 +45,17 @@ declare const sessionEnvelopeSchema: z.ZodObject<{
45
45
  }, z.core.$strip>, z.ZodObject<{
46
46
  t: z.ZodLiteral<"tool-call-end">;
47
47
  call: z.ZodString;
48
+ result: z.ZodOptional<z.ZodObject<{
49
+ text: z.ZodString;
50
+ truncated: z.ZodOptional<z.ZodBoolean>;
51
+ isError: z.ZodOptional<z.ZodBoolean>;
52
+ stats: z.ZodOptional<z.ZodObject<{
53
+ toolUses: z.ZodOptional<z.ZodNumber>;
54
+ totalTokens: z.ZodOptional<z.ZodNumber>;
55
+ durationMs: z.ZodOptional<z.ZodNumber>;
56
+ toolStats: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
57
+ }, z.core.$strip>>;
58
+ }, z.core.$strip>>;
48
59
  }, z.core.$strip>, z.ZodObject<{
49
60
  t: z.ZodLiteral<"file">;
50
61
  ref: z.ZodString;
@@ -61,6 +72,8 @@ declare const sessionEnvelopeSchema: z.ZodObject<{
61
72
  }, z.core.$strip>, z.ZodObject<{
62
73
  t: z.ZodLiteral<"start">;
63
74
  title: z.ZodOptional<z.ZodString>;
75
+ description: z.ZodOptional<z.ZodString>;
76
+ subagentType: z.ZodOptional<z.ZodString>;
64
77
  }, z.core.$strip>, z.ZodObject<{
65
78
  t: z.ZodLiteral<"turn-end">;
66
79
  status: z.ZodEnum<{
@@ -80,6 +93,27 @@ declare const sessionEnvelopeSchema: z.ZodObject<{
80
93
  }, z.core.$strip>>;
81
94
  }, z.core.$strip>, z.ZodObject<{
82
95
  t: z.ZodLiteral<"stop">;
96
+ status: z.ZodOptional<z.ZodEnum<{
97
+ completed: "completed";
98
+ failed: "failed";
99
+ stopped: "stopped";
100
+ }>>;
101
+ result: z.ZodOptional<z.ZodObject<{
102
+ text: z.ZodString;
103
+ truncated: z.ZodOptional<z.ZodBoolean>;
104
+ }, z.core.$strip>>;
105
+ usage: z.ZodOptional<z.ZodObject<{
106
+ toolUses: z.ZodOptional<z.ZodNumber>;
107
+ totalTokens: z.ZodOptional<z.ZodNumber>;
108
+ durationMs: z.ZodOptional<z.ZodNumber>;
109
+ }, z.core.$strip>>;
110
+ }, z.core.$strip>, z.ZodObject<{
111
+ t: z.ZodLiteral<"progress">;
112
+ toolUses: z.ZodNumber;
113
+ lastTool: z.ZodOptional<z.ZodString>;
114
+ totalTokens: z.ZodOptional<z.ZodNumber>;
115
+ durationMs: z.ZodOptional<z.ZodNumber>;
116
+ summary: z.ZodOptional<z.ZodString>;
83
117
  }, z.core.$strip>], "t">;
84
118
  }, z.core.$strip>;
85
119
  type SessionEnvelope = z.infer<typeof sessionEnvelopeSchema>;
@@ -451,6 +485,14 @@ type Metadata = {
451
485
  flavor?: string;
452
486
  sandbox?: SandboxConfig | null;
453
487
  dangerouslySkipPermissions?: boolean | null;
488
+ /**
489
+ * Effective permission mode the running Claude process enforces right now
490
+ * (SDK vocabulary: default | acceptEdits | bypassPermissions | plan). The
491
+ * CLI is the single source of truth: it writes this at start, on every
492
+ * message/RPC/plan-approval change. Web displays it verbatim; old clients
493
+ * ignore it and old CLIs never write it (capability claude-live-permission-v2).
494
+ */
495
+ permissionMode?: string;
454
496
  /** Lineage for sessions created via the fork / duplicate flow. */
455
497
  parentSessionId?: string;
456
498
  forkedFromMessageId?: string;
package/dist/lib.d.mts CHANGED
@@ -45,6 +45,17 @@ declare const sessionEnvelopeSchema: z.ZodObject<{
45
45
  }, z.core.$strip>, z.ZodObject<{
46
46
  t: z.ZodLiteral<"tool-call-end">;
47
47
  call: z.ZodString;
48
+ result: z.ZodOptional<z.ZodObject<{
49
+ text: z.ZodString;
50
+ truncated: z.ZodOptional<z.ZodBoolean>;
51
+ isError: z.ZodOptional<z.ZodBoolean>;
52
+ stats: z.ZodOptional<z.ZodObject<{
53
+ toolUses: z.ZodOptional<z.ZodNumber>;
54
+ totalTokens: z.ZodOptional<z.ZodNumber>;
55
+ durationMs: z.ZodOptional<z.ZodNumber>;
56
+ toolStats: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
57
+ }, z.core.$strip>>;
58
+ }, z.core.$strip>>;
48
59
  }, z.core.$strip>, z.ZodObject<{
49
60
  t: z.ZodLiteral<"file">;
50
61
  ref: z.ZodString;
@@ -61,6 +72,8 @@ declare const sessionEnvelopeSchema: z.ZodObject<{
61
72
  }, z.core.$strip>, z.ZodObject<{
62
73
  t: z.ZodLiteral<"start">;
63
74
  title: z.ZodOptional<z.ZodString>;
75
+ description: z.ZodOptional<z.ZodString>;
76
+ subagentType: z.ZodOptional<z.ZodString>;
64
77
  }, z.core.$strip>, z.ZodObject<{
65
78
  t: z.ZodLiteral<"turn-end">;
66
79
  status: z.ZodEnum<{
@@ -80,6 +93,27 @@ declare const sessionEnvelopeSchema: z.ZodObject<{
80
93
  }, z.core.$strip>>;
81
94
  }, z.core.$strip>, z.ZodObject<{
82
95
  t: z.ZodLiteral<"stop">;
96
+ status: z.ZodOptional<z.ZodEnum<{
97
+ completed: "completed";
98
+ failed: "failed";
99
+ stopped: "stopped";
100
+ }>>;
101
+ result: z.ZodOptional<z.ZodObject<{
102
+ text: z.ZodString;
103
+ truncated: z.ZodOptional<z.ZodBoolean>;
104
+ }, z.core.$strip>>;
105
+ usage: z.ZodOptional<z.ZodObject<{
106
+ toolUses: z.ZodOptional<z.ZodNumber>;
107
+ totalTokens: z.ZodOptional<z.ZodNumber>;
108
+ durationMs: z.ZodOptional<z.ZodNumber>;
109
+ }, z.core.$strip>>;
110
+ }, z.core.$strip>, z.ZodObject<{
111
+ t: z.ZodLiteral<"progress">;
112
+ toolUses: z.ZodNumber;
113
+ lastTool: z.ZodOptional<z.ZodString>;
114
+ totalTokens: z.ZodOptional<z.ZodNumber>;
115
+ durationMs: z.ZodOptional<z.ZodNumber>;
116
+ summary: z.ZodOptional<z.ZodString>;
83
117
  }, z.core.$strip>], "t">;
84
118
  }, z.core.$strip>;
85
119
  type SessionEnvelope = z.infer<typeof sessionEnvelopeSchema>;
@@ -451,6 +485,14 @@ type Metadata = {
451
485
  flavor?: string;
452
486
  sandbox?: SandboxConfig | null;
453
487
  dangerouslySkipPermissions?: boolean | null;
488
+ /**
489
+ * Effective permission mode the running Claude process enforces right now
490
+ * (SDK vocabulary: default | acceptEdits | bypassPermissions | plan). The
491
+ * CLI is the single source of truth: it writes this at start, on every
492
+ * message/RPC/plan-approval change. Web displays it verbatim; old clients
493
+ * ignore it and old CLIs never write it (capability claude-live-permission-v2).
494
+ */
495
+ permissionMode?: string;
454
496
  /** Lineage for sessions created via the fork / duplicate flow. */
455
497
  parentSessionId?: string;
456
498
  forkedFromMessageId?: string;
package/dist/lib.mjs CHANGED
@@ -1,4 +1,4 @@
1
- export { A as ApiClient, a as ApiSessionClient, R as RawJSONLinesSchema, c as configuration, l as logger } from './types-d_104eQF.mjs';
1
+ export { A as ApiClient, a as ApiSessionClient, R as RawJSONLinesSchema, c as configuration, l as logger } from './types-BKyjG23_.mjs';
2
2
  import 'axios';
3
3
  import 'chalk';
4
4
  import 'node:util';
@@ -1,9 +1,9 @@
1
1
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
2
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
3
  import { z } from 'zod';
4
- import { p as pushClipboardViaDaemon } from './index-vCgb7QnZ.mjs';
4
+ import { p as pushClipboardViaDaemon } from './index-UCYD5Wk3.mjs';
5
5
  import { C as CLIPBOARD_TOOL_NAME, a as CLIPBOARD_TOOL_TITLE, b as CLIPBOARD_TOOL_DESCRIPTION, c as CLIPBOARD_MAX_BYTES } from './limits-CwFb5S2b.mjs';
6
- import { l as logger } from './types-d_104eQF.mjs';
6
+ import { l as logger } from './types-BKyjG23_.mjs';
7
7
  import 'chalk';
8
8
  import 'node:os';
9
9
  import 'node:crypto';
@@ -3,9 +3,9 @@
3
3
  var mcp_js = require('@modelcontextprotocol/sdk/server/mcp.js');
4
4
  var stdio_js = require('@modelcontextprotocol/sdk/server/stdio.js');
5
5
  var z = require('zod');
6
- var index = require('./index-DYDAFqAN.cjs');
6
+ var index = require('./index-FkXJNwiy.cjs');
7
7
  var limits = require('./limits-KdWKmwtH.cjs');
8
- var persistence = require('./types-DLI6sss2.cjs');
8
+ var persistence = require('./types-ClDADuxP.cjs');
9
9
  require('chalk');
10
10
  require('node:os');
11
11
  require('node:crypto');
@@ -2,10 +2,10 @@ import { useStdout, useInput, Box, Text, render } from 'ink';
2
2
  import React, { useState, useRef, useEffect, useCallback } from 'react';
3
3
  import { randomUUID } from 'node:crypto';
4
4
  import { join } from 'node:path';
5
- import { l as logger, h as connectionState, A as ApiClient, i as readSettings, e as errorLogMetadata, j as encodeBase64, p as projectPath, d as contentLogMetadata } from './types-d_104eQF.mjs';
6
- import { g as GEMINI_API_KEY_ENV, j as GOOGLE_API_KEY_ENV, G as GEMINI_MODEL_ENV, B as BasePermissionHandler, k as BaseReasoningProcessor, i as initialMachineMetadata, d as createSessionMetadata, e as setupOfflineReconnection, n as notifyDaemonSessionStarted, M as MessageQueue2, h as hashObject, l as MessageBuffer, C as CHANGE_TITLE_INSTRUCTION, r as registerKillSessionHandler, f as startHappyServer } from './index-vCgb7QnZ.mjs';
7
- import { A as AcpBackend } from './AcpBackend-DuxcHPyn.mjs';
8
- import { readGeminiLocalConfig, determineGeminiModel, getGeminiModelSource, getInitialGeminiModel, saveGeminiModelToConfig } from './config-CdoGLla8.mjs';
5
+ import { l as logger, h as connectionState, A as ApiClient, i as readSettings, e as errorLogMetadata, j as encodeBase64, p as projectPath, d as contentLogMetadata } from './types-BKyjG23_.mjs';
6
+ import { g as GEMINI_API_KEY_ENV, j as GOOGLE_API_KEY_ENV, G as GEMINI_MODEL_ENV, B as BasePermissionHandler, k as BaseReasoningProcessor, i as initialMachineMetadata, d as createSessionMetadata, e as setupOfflineReconnection, n as notifyDaemonSessionStarted, M as MessageQueue2, h as hashObject, l as MessageBuffer, C as CHANGE_TITLE_INSTRUCTION, r as registerKillSessionHandler, f as startHappyServer } from './index-UCYD5Wk3.mjs';
7
+ import { A as AcpBackend } from './AcpBackend-B5RRlzLU.mjs';
8
+ import { readGeminiLocalConfig, determineGeminiModel, getGeminiModelSource, getInitialGeminiModel, saveGeminiModelToConfig } from './config-ra8iKxdU.mjs';
9
9
  import 'axios';
10
10
  import 'chalk';
11
11
  import 'node:util';