very-happy-cli 0.2.88 → 0.2.90

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-C_simUP7.mjs → AcpBackend-BV4wSnJ3.mjs} +1 -1
  2. package/dist/{AcpBackend-CBZNjcYO.cjs → AcpBackend-CnYZFdd0.cjs} +1 -1
  3. package/dist/{AcpSessionManager-B9mJWEzL.cjs → AcpSessionManager-BPBdrrME.cjs} +1 -1
  4. package/dist/{AcpSessionManager-G8w8Q6tt.mjs → AcpSessionManager-Cyne6Rmg.mjs} +1 -1
  5. package/dist/{config-CFSdirvc.mjs → config-BEdyyciP.mjs} +2 -2
  6. package/dist/{config-BPPY98U1.cjs → config-CgXitj0f.cjs} +2 -2
  7. package/dist/{index-FxD6Abgx.cjs → index-4YmVbx7j.cjs} +199 -46
  8. package/dist/{index-Bp5ZSCRm.cjs → index-B1wTVXjt.cjs} +4 -4
  9. package/dist/{index-Cn3VqAah.mjs → index-Dfu5uvjk.mjs} +5 -5
  10. package/dist/{index-j2LYx2KW.mjs → index-an09rejO.mjs} +196 -43
  11. package/dist/index.cjs +2 -2
  12. package/dist/index.mjs +2 -2
  13. package/dist/{installTerminalHooks-DsqU3Dxc.mjs → installTerminalHooks-C-OgZr5k.mjs} +2 -2
  14. package/dist/{installTerminalHooks-BL25f9BN.cjs → installTerminalHooks-DDpVxk8-.cjs} +2 -2
  15. package/dist/lib.cjs +1 -1
  16. package/dist/lib.d.cts +8 -0
  17. package/dist/lib.d.mts +8 -0
  18. package/dist/lib.mjs +1 -1
  19. package/dist/{mcp-BVoo0PQS.cjs → mcp-B6Nq-RUL.cjs} +2 -2
  20. package/dist/{mcp-DV49L5g2.mjs → mcp-Du-N0qlK.mjs} +2 -2
  21. package/dist/{runGemini-t_Oikq3z.cjs → runGemini-ChG7cGMC.cjs} +4 -4
  22. package/dist/{runGemini-cm1mKuxv.mjs → runGemini-DBr_TiTL.mjs} +4 -4
  23. package/dist/{runOpenClaw-CCwYAKyZ.mjs → runOpenClaw-Cbde8t92.mjs} +3 -3
  24. package/dist/{runOpenClaw-CCGywWGD.cjs → runOpenClaw-D2jtVgpC.cjs} +3 -3
  25. package/dist/{send-ifKBw-7j.mjs → send-BHliX59m.mjs} +2 -2
  26. package/dist/{send-CqPpRb7v.cjs → send-Bjen1TUE.cjs} +2 -2
  27. package/dist/{spawn-28RbN4P6.cjs → spawn-CH1Rq-LW.cjs} +2 -2
  28. package/dist/{spawn-DkG_GIXM.mjs → spawn-JxaPHAoP.mjs} +2 -2
  29. package/dist/{types-DE03map-.cjs → types-BNGeiP1C.cjs} +62 -44
  30. package/dist/{types-AO-TVSTa.mjs → types-DS3i_5LV.mjs} +60 -44
  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-AO-TVSTa.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-DS3i_5LV.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';
@@ -1506,7 +1506,7 @@ function query(params) {
1506
1506
  fallbackModel: opts?.fallbackModel,
1507
1507
  maxTurns: opts?.maxTurns,
1508
1508
  permissionMode: opts?.permissionMode,
1509
- allowDangerouslySkipPermissions: opts?.permissionMode === "bypassPermissions",
1509
+ allowDangerouslySkipPermissions: opts?.allowDangerouslySkipPermissions ?? opts?.permissionMode === "bypassPermissions",
1510
1510
  allowedTools: opts?.allowedTools,
1511
1511
  disallowedTools: opts?.disallowedTools,
1512
1512
  mcpServers: opts?.mcpServers,
@@ -1581,6 +1581,9 @@ const VALID_PERMISSION_MODES = [
1581
1581
  function isPermissionMode(value) {
1582
1582
  return !!value && VALID_PERMISSION_MODES.includes(value);
1583
1583
  }
1584
+ function parseClaudePermissionMode(value) {
1585
+ return typeof value === "string" && isPermissionMode(value) ? mapToClaudeMode(value) : null;
1586
+ }
1584
1587
  function extractPermissionModeFromClaudeArgs(claudeArgs) {
1585
1588
  if (!claudeArgs || claudeArgs.length === 0) {
1586
1589
  return void 0;
@@ -1881,6 +1884,10 @@ async function claudeRemote(opts) {
1881
1884
  resume: startFrom ?? void 0,
1882
1885
  mcpServers: opts.mcpServers,
1883
1886
  permissionMode: mapToClaudeMode(initial.mode.permissionMode),
1887
+ // This is only the SDK safety opt-in; permissionMode/canUseTool still
1888
+ // enforce the selected policy. It must be enabled at Query creation so
1889
+ // a later explicit live switch to bypassPermissions can succeed.
1890
+ allowDangerouslySkipPermissions: true,
1884
1891
  model: initial.mode.model,
1885
1892
  fallbackModel: initial.mode.fallbackModel,
1886
1893
  customSystemPrompt: initial.mode.customSystemPrompt ? initial.mode.customSystemPrompt + "\n\n" + systemPrompt : void 0,
@@ -2056,6 +2063,10 @@ class PermissionHandler {
2056
2063
  onPermissionRequestCallback;
2057
2064
  /** Callback to change permission mode on the active query (set by claudeRemote) */
2058
2065
  setPermissionModeCallback;
2066
+ modeChangeQueue = Promise.resolve();
2067
+ deferredPermissionMode;
2068
+ /** Fired when an approval response (plan approval / approve-with-mode) moved the mode. */
2069
+ onModeChangedCallback;
2059
2070
  constructor(session) {
2060
2071
  this.session = session;
2061
2072
  this.setupClientHandler();
@@ -2067,7 +2078,19 @@ class PermissionHandler {
2067
2078
  this.onPermissionRequestCallback = callback;
2068
2079
  }
2069
2080
  handleModeChange(mode) {
2070
- this.permissionMode = mode;
2081
+ this.permissionMode = mapToClaudeMode(mode);
2082
+ }
2083
+ /** Current locally enforced mode (what canUseTool applies right now). */
2084
+ getPermissionMode() {
2085
+ return this.permissionMode;
2086
+ }
2087
+ /**
2088
+ * Observe mode transitions that originate inside the approval flow
2089
+ * (ExitPlanMode approval, approve-with-mode). Live/idle RPC changes and
2090
+ * queue-driven changes are owned by the launcher and do not fire this.
2091
+ */
2092
+ setOnModeChanged(callback) {
2093
+ this.onModeChangedCallback = callback;
2071
2094
  }
2072
2095
  /**
2073
2096
  * Set callback to dynamically change permission mode on the active query.
@@ -2076,6 +2099,74 @@ class PermissionHandler {
2076
2099
  setPermissionModeUpdater(callback) {
2077
2100
  this.setPermissionModeCallback = callback;
2078
2101
  }
2102
+ /**
2103
+ * Apply a user-requested mode to the active SDK Query. Mode changes are
2104
+ * serialized so rapid menu selections cannot finish out of order.
2105
+ *
2106
+ * A live Query can be blocked inside canUseTool. When the requested mode
2107
+ * would have auto-allowed that ordinary tool, finish the callback first;
2108
+ * interactive prompts and ExitPlanMode deliberately remain pending.
2109
+ */
2110
+ setLivePermissionMode(mode) {
2111
+ const task = this.modeChangeQueue.then(() => this.applyLivePermissionMode(mode));
2112
+ this.modeChangeQueue = task.catch(() => void 0);
2113
+ return task;
2114
+ }
2115
+ async applyLivePermissionMode(mode) {
2116
+ if (!this.setPermissionModeCallback) {
2117
+ throw new Error("active Claude query is not ready");
2118
+ }
2119
+ const previousMode = this.permissionMode;
2120
+ this.permissionMode = mode;
2121
+ this.deferredPermissionMode = void 0;
2122
+ try {
2123
+ const autoApproved = [...this.pendingRequests.entries()].filter(([, pending]) => pending.kind === "tool" && this.modeAutoAllowsTool(mode, pending.toolName));
2124
+ for (const [id, pending] of autoApproved) {
2125
+ if (pending.kind !== "tool" || !this.pendingRequests.delete(id)) continue;
2126
+ const response = {
2127
+ id,
2128
+ approved: true,
2129
+ decision: "approved",
2130
+ reason: `Approved by live permission mode ${mode}`
2131
+ };
2132
+ const effectiveResponse = await this.handlePermissionResponse(response, pending);
2133
+ this.responses.set(id, { ...effectiveResponse, receivedAt: Date.now() });
2134
+ this.recordCompletedRequest(id, effectiveResponse);
2135
+ }
2136
+ if (this.pendingRequests.size > 0) {
2137
+ this.deferredPermissionMode = mode;
2138
+ return;
2139
+ }
2140
+ await new Promise((resolve) => setImmediate(resolve));
2141
+ await this.setPermissionModeCallback(mode);
2142
+ } catch (error) {
2143
+ this.permissionMode = previousMode;
2144
+ if (this.deferredPermissionMode === mode) this.deferredPermissionMode = void 0;
2145
+ throw error;
2146
+ }
2147
+ }
2148
+ scheduleDeferredPermissionModeUpdate() {
2149
+ const mode = this.deferredPermissionMode;
2150
+ if (!mode || this.pendingRequests.size > 0) return;
2151
+ this.deferredPermissionMode = void 0;
2152
+ setImmediate(() => {
2153
+ const task = this.modeChangeQueue.then(async () => {
2154
+ if (!this.setPermissionModeCallback || this.permissionMode !== mode) return;
2155
+ await this.setPermissionModeCallback(mode);
2156
+ });
2157
+ this.modeChangeQueue = task.catch((error) => {
2158
+ logger.debug("[PermissionHandler] Deferred SDK permission mode update failed", error);
2159
+ });
2160
+ });
2161
+ }
2162
+ modeAutoAllowsTool(mode, toolName) {
2163
+ if (toolName === "AskUserQuestion") return false;
2164
+ const descriptor = getToolDescriptor(toolName);
2165
+ if (descriptor.exitPlan) return false;
2166
+ if (mode === "bypassPermissions") return true;
2167
+ if (mode === "acceptEdits") return descriptor.edit;
2168
+ return mode === "plan" && !descriptor.dangerous;
2169
+ }
2079
2170
  /**
2080
2171
  * Handler response
2081
2172
  */
@@ -2089,9 +2180,10 @@ class PermissionHandler {
2089
2180
  updatedInput: contentLogMetadata(response.updatedInput)
2090
2181
  });
2091
2182
  if (response.approved) {
2092
- const newMode = response.mode && ["default", "acceptEdits", "bypassPermissions"].includes(response.mode) ? response.mode : "default";
2183
+ const newMode = response.mode && ["default", "acceptEdits", "bypassPermissions"].includes(response.mode) ? response.mode : "bypassPermissions";
2093
2184
  logger.debug(`Plan approved - allowing ExitPlanMode with ${newMode} as the local follow-up mode`);
2094
2185
  this.permissionMode = newMode;
2186
+ this.onModeChangedCallback?.(newMode);
2095
2187
  pending.resolve({ behavior: "allow", updatedInput: pending.input || {} });
2096
2188
  } else {
2097
2189
  pending.resolve({ behavior: "deny", message: response.reason || "Plan rejected" });
@@ -2102,6 +2194,7 @@ class PermissionHandler {
2102
2194
  if (!this.setPermissionModeCallback) throw new Error("permission mode updater unavailable");
2103
2195
  await this.setPermissionModeCallback(response.mode);
2104
2196
  this.permissionMode = response.mode;
2197
+ this.onModeChangedCallback?.(response.mode);
2105
2198
  } catch (err) {
2106
2199
  const reason = `Failed to switch permission mode: ${err instanceof Error ? err.message : String(err)}`;
2107
2200
  pending.resolve({ behavior: "deny", message: reason });
@@ -2320,6 +2413,30 @@ class PermissionHandler {
2320
2413
  };
2321
2414
  });
2322
2415
  }
2416
+ recordCompletedRequest(id, response) {
2417
+ this.session.client.updateAgentState((currentState) => {
2418
+ const request = currentState.requests?.[id];
2419
+ if (!request) return currentState;
2420
+ const requests = { ...currentState.requests };
2421
+ delete requests[id];
2422
+ return {
2423
+ ...currentState,
2424
+ requests,
2425
+ completedRequests: {
2426
+ ...currentState.completedRequests,
2427
+ [id]: {
2428
+ ...request,
2429
+ completedAt: Date.now(),
2430
+ status: response.approved ? "approved" : "denied",
2431
+ reason: response.reason,
2432
+ mode: response.mode,
2433
+ decision: response.decision,
2434
+ allowedTools: response.allowTools
2435
+ }
2436
+ }
2437
+ };
2438
+ });
2439
+ }
2323
2440
  /**
2324
2441
  * Parses Bash permission strings into literal and prefix sets
2325
2442
  */
@@ -2358,6 +2475,7 @@ class PermissionHandler {
2358
2475
  this.allowedBashLiterals.clear();
2359
2476
  this.allowedBashPrefixes.clear();
2360
2477
  this.permissionMode = "default";
2478
+ this.deferredPermissionMode = void 0;
2361
2479
  for (const [, pending] of this.pendingRequests.entries()) {
2362
2480
  pending.reject(new Error("Session reset"));
2363
2481
  }
@@ -2410,28 +2528,8 @@ class PermissionHandler {
2410
2528
  } else {
2411
2529
  pending.resolve(message.approved ? { behavior: "completed", result: message.updatedInput?.result ?? "retry_fallback" } : { behavior: "cancelled" });
2412
2530
  }
2413
- this.session.client.updateAgentState((currentState) => {
2414
- const request = currentState.requests?.[id];
2415
- if (!request) return currentState;
2416
- let r = { ...currentState.requests };
2417
- delete r[id];
2418
- return {
2419
- ...currentState,
2420
- requests: r,
2421
- completedRequests: {
2422
- ...currentState.completedRequests,
2423
- [id]: {
2424
- ...request,
2425
- completedAt: Date.now(),
2426
- status: effectiveResponse.approved ? "approved" : "denied",
2427
- reason: effectiveResponse.reason,
2428
- mode: effectiveResponse.mode,
2429
- decision: effectiveResponse.decision,
2430
- allowedTools: effectiveResponse.allowTools
2431
- }
2432
- }
2433
- };
2434
- });
2531
+ this.recordCompletedRequest(id, effectiveResponse);
2532
+ this.scheduleDeferredPermissionModeUpdate();
2435
2533
  });
2436
2534
  }
2437
2535
  /**
@@ -3207,7 +3305,7 @@ function createTurnSteeringController() {
3207
3305
  };
3208
3306
  }
3209
3307
 
3210
- async function claudeRemoteLauncher(session) {
3308
+ async function claudeRemoteLauncher(session, onPermissionModeChange) {
3211
3309
  logger.debug("[claudeRemoteLauncher] Starting remote launcher");
3212
3310
  const hasTTY = process.stdout.isTTY && process.stdin.isTTY;
3213
3311
  logger.debug(`[claudeRemoteLauncher] TTY available: ${hasTTY}`);
@@ -3268,7 +3366,24 @@ async function claudeRemoteLauncher(session) {
3268
3366
  await turnSteering.steer();
3269
3367
  });
3270
3368
  session.client.rpcHandlerManager.registerHandler("switch", doSwitch);
3369
+ let livePermissionModeHandler = null;
3370
+ let idlePermissionModeHandler = null;
3371
+ session.client.rpcHandlerManager.registerHandler(
3372
+ "set-permission-mode",
3373
+ async (request) => {
3374
+ const requestedMode = parseClaudePermissionMode(request?.mode);
3375
+ if (!requestedMode) throw new Error("Invalid Claude permission mode");
3376
+ if (livePermissionModeHandler) return { mode: await livePermissionModeHandler(requestedMode) };
3377
+ if (idlePermissionModeHandler) return { mode: idlePermissionModeHandler(requestedMode) };
3378
+ throw new Error("No active Claude query");
3379
+ }
3380
+ );
3271
3381
  const permissionHandler = new PermissionHandler(session);
3382
+ idlePermissionModeHandler = (nextMode) => {
3383
+ permissionHandler.handleModeChange(nextMode);
3384
+ onPermissionModeChange?.(nextMode);
3385
+ return nextMode;
3386
+ };
3272
3387
  permissionHandler.reset("Previous CLI process exited before responding");
3273
3388
  const messageQueue = new OutgoingMessageQueue(
3274
3389
  (logMessage) => session.client.sendClaudeSessionMessage(logMessage)
@@ -3418,6 +3533,19 @@ async function claudeRemoteLauncher(session) {
3418
3533
  abortFuture = new Future();
3419
3534
  let modeHash = null;
3420
3535
  let mode = null;
3536
+ const commitPermissionMode = (nextMode) => {
3537
+ permissionHandler.handleModeChange(nextMode);
3538
+ if (mode) {
3539
+ mode = { ...mode, permissionMode: nextMode };
3540
+ modeHash = session.queue.modeHasher(mode);
3541
+ }
3542
+ onPermissionModeChange?.(nextMode);
3543
+ return nextMode;
3544
+ };
3545
+ idlePermissionModeHandler = commitPermissionMode;
3546
+ permissionHandler.setOnModeChanged((nextMode) => {
3547
+ commitPermissionMode(nextMode);
3548
+ });
3421
3549
  try {
3422
3550
  const attachmentDirectory = chatAttachmentDirectory(configuration.happyHomeDir, session.client.sessionId);
3423
3551
  await ensurePrivateDirectory(attachmentDirectory);
@@ -3504,6 +3632,15 @@ async function claudeRemoteLauncher(session) {
3504
3632
  permissionHandler.setPermissionModeUpdater(async (mode2) => {
3505
3633
  await q.setPermissionMode(mode2);
3506
3634
  });
3635
+ livePermissionModeHandler = async (nextMode) => {
3636
+ await permissionHandler.setLivePermissionMode(nextMode);
3637
+ if (mode) {
3638
+ mode = { ...mode, permissionMode: nextMode };
3639
+ modeHash = session.queue.modeHasher(mode);
3640
+ }
3641
+ onPermissionModeChange?.(nextMode);
3642
+ return nextMode;
3643
+ };
3507
3644
  },
3508
3645
  onThinkingChange: session.onThinkingChange,
3509
3646
  claudeEnvVars: session.claudeEnvVars,
@@ -3565,6 +3702,8 @@ async function claudeRemoteLauncher(session) {
3565
3702
  } finally {
3566
3703
  turnSteering.reset();
3567
3704
  session.setSteerHandler(null);
3705
+ livePermissionModeHandler = null;
3706
+ permissionHandler.setPermissionModeUpdater(void 0);
3568
3707
  sdkToLogConverter.resetTransientState();
3569
3708
  logger.debug("[remote]: launch finally");
3570
3709
  for (let [toolCallId, { parentToolCallId }] of ongoingToolCalls) {
@@ -3649,7 +3788,7 @@ async function loop(opts) {
3649
3788
  break;
3650
3789
  }
3651
3790
  case "remote": {
3652
- const reason = await claudeRemoteLauncher(session);
3791
+ const reason = await claudeRemoteLauncher(session, opts.onPermissionModeChange);
3653
3792
  switch (reason) {
3654
3793
  case "exit":
3655
3794
  return 0;
@@ -6511,7 +6650,7 @@ function createMirrorManager(deps) {
6511
6650
  let chain = Promise.resolve();
6512
6651
  const isTerminalAlive = deps.isTerminalAlive ?? ((terminalId) => {
6513
6652
  try {
6514
- return spawnSync("tmux", ["has-session", "-t", `=vh-${terminalId}:`], { stdio: "ignore", timeout: 3e3 }).status === 0;
6653
+ return spawnSync("tmux", tmuxArgs(["has-session", "-t", `=vh-${terminalId}:`]), { stdio: "ignore", timeout: 3e3, env: scrubTmuxClientEnv({ ...process.env }) }).status === 0;
6515
6654
  } catch {
6516
6655
  return false;
6517
6656
  }
@@ -9139,7 +9278,10 @@ async function runClaude(credentials, options = {}) {
9139
9278
  // a brand-new session cannot pick a PDF until after sending once.
9140
9279
  attachmentKinds: [...CLAUDE_ATTACHMENT_KINDS],
9141
9280
  queueCancellation: true,
9142
- capabilities: ["claude-steer-v1"],
9281
+ capabilities: ["claude-steer-v1", "claude-live-permission-v1", "claude-live-permission-v2"],
9282
+ // Effective mode this process enforces. Kept current by
9283
+ // publishPermissionMode below; the web renders it instead of guessing.
9284
+ permissionMode: mapToClaudeMode(initialPermissionMode ?? "default"),
9143
9285
  sandbox: sandboxConfig?.enabled ? sandboxConfig : null,
9144
9286
  dangerouslySkipPermissions,
9145
9287
  ...forkedFromSessionId ? { parentSessionId: forkedFromSessionId } : {},
@@ -9376,6 +9518,14 @@ async function runClaude(credentials, options = {}) {
9376
9518
  )
9377
9519
  }));
9378
9520
  let currentPermissionMode = initialPermissionMode;
9521
+ let publishedPermissionMode = mapToClaudeMode(initialPermissionMode ?? "default");
9522
+ const publishPermissionMode = (mode) => {
9523
+ currentPermissionMode = mode;
9524
+ const published = mapToClaudeMode(mode ?? "default");
9525
+ if (published === publishedPermissionMode) return;
9526
+ publishedPermissionMode = published;
9527
+ session.updateMetadata((meta) => ({ ...meta, permissionMode: published }));
9528
+ };
9379
9529
  let currentModel = options.model ?? DEFAULT_CLAUDE_MODEL;
9380
9530
  let currentFallbackModel = void 0;
9381
9531
  let currentCustomSystemPrompt = void 0;
@@ -9384,7 +9534,6 @@ async function runClaude(credentials, options = {}) {
9384
9534
  let currentDisallowedTools = withAssistantDenylist(void 0, isAssistantVariant);
9385
9535
  let currentEffort = DEFAULT_CLAUDE_EFFORT;
9386
9536
  const resetCurrentModeDefaults = () => {
9387
- currentPermissionMode = initialPermissionMode;
9388
9537
  currentModel = options.model ?? DEFAULT_CLAUDE_MODEL;
9389
9538
  currentFallbackModel = void 0;
9390
9539
  currentCustomSystemPrompt = void 0;
@@ -9428,7 +9577,7 @@ async function runClaude(credentials, options = {}) {
9428
9577
  message.meta.permissionMode,
9429
9578
  sandboxEnabled
9430
9579
  );
9431
- currentPermissionMode = messagePermissionMode;
9580
+ publishPermissionMode(messagePermissionMode);
9432
9581
  const ignoredDefaultDowngrade = (previousPermissionMode === "bypassPermissions" || previousPermissionMode === "yolo") && message.meta.permissionMode === "default" && currentPermissionMode === previousPermissionMode;
9433
9582
  if (ignoredDefaultDowngrade) {
9434
9583
  logger.debug(`[loop] Ignoring permission mode downgrade from ${previousPermissionMode} to default`);
@@ -9684,6 +9833,10 @@ async function runClaude(credentials, options = {}) {
9684
9833
  currentSession = sessionInstance;
9685
9834
  },
9686
9835
  onAbort: resetCurrentModeDefaults,
9836
+ onPermissionModeChange: (mode) => {
9837
+ publishPermissionMode(mode);
9838
+ logger.debug(`[loop] Permission mode updated from session RPC / approval to: ${mode}`);
9839
+ },
9687
9840
  mcpServers: {
9688
9841
  "happy": {
9689
9842
  type: "http",
@@ -13440,7 +13593,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
13440
13593
  process.exit(0);
13441
13594
  } else if (subcommand === "install-terminal-hooks") {
13442
13595
  try {
13443
- const { installTerminalHooks, parseTerminalHooksArgs, TERMINAL_HOOKS_HELP } = await import('./installTerminalHooks-DsqU3Dxc.mjs');
13596
+ const { installTerminalHooks, parseTerminalHooksArgs, TERMINAL_HOOKS_HELP } = await import('./installTerminalHooks-C-OgZr5k.mjs');
13444
13597
  const command = parseTerminalHooksArgs(args.slice(1));
13445
13598
  if (command.action === "help") {
13446
13599
  console.log(TERMINAL_HOOKS_HELP);
@@ -13457,7 +13610,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
13457
13610
  }
13458
13611
  } else if (subcommand === "spawn") {
13459
13612
  try {
13460
- const { handleSpawnCommand } = await import('./spawn-DkG_GIXM.mjs');
13613
+ const { handleSpawnCommand } = await import('./spawn-JxaPHAoP.mjs');
13461
13614
  await handleSpawnCommand(args.slice(1));
13462
13615
  } catch (error) {
13463
13616
  console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
@@ -13469,7 +13622,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
13469
13622
  return;
13470
13623
  } else if (subcommand === "send") {
13471
13624
  try {
13472
- const { handleSendCommand } = await import('./send-ifKBw-7j.mjs');
13625
+ const { handleSendCommand } = await import('./send-BHliX59m.mjs');
13473
13626
  await handleSendCommand(args.slice(1));
13474
13627
  } catch (error) {
13475
13628
  console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
@@ -13575,9 +13728,9 @@ Conversation history is preserved on the server, but in-flight tool calls are in
13575
13728
  if (geminiSubcommand === "project" && args[2] === "set" && args[3]) {
13576
13729
  const projectId = args[3];
13577
13730
  try {
13578
- const { saveGoogleCloudProjectToConfig } = await import('./config-CFSdirvc.mjs');
13579
- const { readCredentialsForConfiguredRelay: readCredentialsForConfiguredRelay2 } = await import('./types-AO-TVSTa.mjs').then(function (n) { return n.ai; });
13580
- const { ApiClient: ApiClient2 } = await import('./types-AO-TVSTa.mjs').then(function (n) { return n.aj; });
13731
+ const { saveGoogleCloudProjectToConfig } = await import('./config-BEdyyciP.mjs');
13732
+ const { readCredentialsForConfiguredRelay: readCredentialsForConfiguredRelay2 } = await import('./types-DS3i_5LV.mjs').then(function (n) { return n.ak; });
13733
+ const { ApiClient: ApiClient2 } = await import('./types-DS3i_5LV.mjs').then(function (n) { return n.al; });
13581
13734
  let userEmail = void 0;
13582
13735
  try {
13583
13736
  const credentials = await readCredentialsForConfiguredRelay2();
@@ -13608,7 +13761,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
13608
13761
  }
13609
13762
  if (geminiSubcommand === "project" && args[2] === "get") {
13610
13763
  try {
13611
- const { readGeminiLocalConfig } = await import('./config-CFSdirvc.mjs');
13764
+ const { readGeminiLocalConfig } = await import('./config-BEdyyciP.mjs');
13612
13765
  const config = readGeminiLocalConfig();
13613
13766
  if (config.googleCloudProject) {
13614
13767
  console.log(`Current Google Cloud Project: ${config.googleCloudProject}`);
@@ -13648,7 +13801,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
13648
13801
  process.exit(0);
13649
13802
  }
13650
13803
  try {
13651
- const { runGemini } = await import('./runGemini-cm1mKuxv.mjs');
13804
+ const { runGemini } = await import('./runGemini-DBr_TiTL.mjs');
13652
13805
  let startedBy = void 0;
13653
13806
  for (let i = 1; i < args.length; i++) {
13654
13807
  if (args[i] === "--started-by") {
@@ -13670,7 +13823,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
13670
13823
  return;
13671
13824
  } else if (subcommand === "acp") {
13672
13825
  try {
13673
- const { runAcp, resolveAcpAgentConfig } = await import('./index-Cn3VqAah.mjs');
13826
+ const { runAcp, resolveAcpAgentConfig } = await import('./index-Dfu5uvjk.mjs');
13674
13827
  let startedBy = void 0;
13675
13828
  let verbose = false;
13676
13829
  const acpArgs = [];
@@ -13710,7 +13863,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
13710
13863
  return;
13711
13864
  } else if (subcommand === "openclaw") {
13712
13865
  try {
13713
- const { runOpenClaw } = await import('./runOpenClaw-CCwYAKyZ.mjs');
13866
+ const { runOpenClaw } = await import('./runOpenClaw-Cbde8t92.mjs');
13714
13867
  let startedBy = void 0;
13715
13868
  let verbose = false;
13716
13869
  let gatewayUrl;
@@ -13761,7 +13914,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
13761
13914
  return;
13762
13915
  } else if (subcommand === "mcp" && args.length === 1) {
13763
13916
  try {
13764
- const { handleMcpCommand } = await import('./mcp-DV49L5g2.mjs');
13917
+ const { handleMcpCommand } = await import('./mcp-Du-N0qlK.mjs');
13765
13918
  await handleMcpCommand();
13766
13919
  } catch (error) {
13767
13920
  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-FxD6Abgx.cjs');
5
- require('./types-DE03map-.cjs');
4
+ require('./index-4YmVbx7j.cjs');
5
+ require('./types-BNGeiP1C.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-j2LYx2KW.mjs';
3
- import './types-AO-TVSTa.mjs';
2
+ import './index-an09rejO.mjs';
3
+ import './types-DS3i_5LV.mjs';
4
4
  import 'zod';
5
5
  import 'node:child_process';
6
6
  import 'node:os';
@@ -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-AO-TVSTa.mjs';
7
- import { t as tmuxSupportsSessionEnv } from './index-j2LYx2KW.mjs';
6
+ import { p as projectPath } from './types-DS3i_5LV.mjs';
7
+ import { t as tmuxSupportsSessionEnv } from './index-an09rejO.mjs';
8
8
  import 'axios';
9
9
  import 'node:util';
10
10
  import 'node:fs/promises';
@@ -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-DE03map-.cjs');
9
- var index = require('./index-FxD6Abgx.cjs');
8
+ var persistence = require('./types-BNGeiP1C.cjs');
9
+ var index = require('./index-4YmVbx7j.cjs');
10
10
  require('axios');
11
11
  require('node:util');
12
12
  require('node:fs/promises');
package/dist/lib.cjs CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var persistence = require('./types-DE03map-.cjs');
3
+ var persistence = require('./types-BNGeiP1C.cjs');
4
4
  require('axios');
5
5
  require('chalk');
6
6
  require('node:util');
package/dist/lib.d.cts CHANGED
@@ -451,6 +451,14 @@ type Metadata = {
451
451
  flavor?: string;
452
452
  sandbox?: SandboxConfig | null;
453
453
  dangerouslySkipPermissions?: boolean | null;
454
+ /**
455
+ * Effective permission mode the running Claude process enforces right now
456
+ * (SDK vocabulary: default | acceptEdits | bypassPermissions | plan). The
457
+ * CLI is the single source of truth: it writes this at start, on every
458
+ * message/RPC/plan-approval change. Web displays it verbatim; old clients
459
+ * ignore it and old CLIs never write it (capability claude-live-permission-v2).
460
+ */
461
+ permissionMode?: string;
454
462
  /** Lineage for sessions created via the fork / duplicate flow. */
455
463
  parentSessionId?: string;
456
464
  forkedFromMessageId?: string;
package/dist/lib.d.mts CHANGED
@@ -451,6 +451,14 @@ type Metadata = {
451
451
  flavor?: string;
452
452
  sandbox?: SandboxConfig | null;
453
453
  dangerouslySkipPermissions?: boolean | null;
454
+ /**
455
+ * Effective permission mode the running Claude process enforces right now
456
+ * (SDK vocabulary: default | acceptEdits | bypassPermissions | plan). The
457
+ * CLI is the single source of truth: it writes this at start, on every
458
+ * message/RPC/plan-approval change. Web displays it verbatim; old clients
459
+ * ignore it and old CLIs never write it (capability claude-live-permission-v2).
460
+ */
461
+ permissionMode?: string;
454
462
  /** Lineage for sessions created via the fork / duplicate flow. */
455
463
  parentSessionId?: string;
456
464
  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-AO-TVSTa.mjs';
1
+ export { A as ApiClient, a as ApiSessionClient, R as RawJSONLinesSchema, c as configuration, l as logger } from './types-DS3i_5LV.mjs';
2
2
  import 'axios';
3
3
  import 'chalk';
4
4
  import 'node:util';
@@ -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-FxD6Abgx.cjs');
6
+ var index = require('./index-4YmVbx7j.cjs');
7
7
  var limits = require('./limits-KdWKmwtH.cjs');
8
- var persistence = require('./types-DE03map-.cjs');
8
+ var persistence = require('./types-BNGeiP1C.cjs');
9
9
  require('chalk');
10
10
  require('node:os');
11
11
  require('node:crypto');
@@ -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-j2LYx2KW.mjs';
4
+ import { p as pushClipboardViaDaemon } from './index-an09rejO.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-AO-TVSTa.mjs';
6
+ import { l as logger } from './types-DS3i_5LV.mjs';
7
7
  import 'chalk';
8
8
  import 'node:os';
9
9
  import 'node:crypto';
@@ -4,10 +4,10 @@ var ink = require('ink');
4
4
  var React = require('react');
5
5
  var node_crypto = require('node:crypto');
6
6
  var path = require('node:path');
7
- var persistence = require('./types-DE03map-.cjs');
8
- var index = require('./index-FxD6Abgx.cjs');
9
- var AcpBackend = require('./AcpBackend-CBZNjcYO.cjs');
10
- var config = require('./config-BPPY98U1.cjs');
7
+ var persistence = require('./types-BNGeiP1C.cjs');
8
+ var index = require('./index-4YmVbx7j.cjs');
9
+ var AcpBackend = require('./AcpBackend-CnYZFdd0.cjs');
10
+ var config = require('./config-CgXitj0f.cjs');
11
11
  require('axios');
12
12
  require('chalk');
13
13
  require('node:util');
@@ -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-AO-TVSTa.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-j2LYx2KW.mjs';
7
- import { A as AcpBackend } from './AcpBackend-C_simUP7.mjs';
8
- import { readGeminiLocalConfig, determineGeminiModel, getGeminiModelSource, getInitialGeminiModel, saveGeminiModelToConfig } from './config-CFSdirvc.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-DS3i_5LV.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-an09rejO.mjs';
7
+ import { A as AcpBackend } from './AcpBackend-BV4wSnJ3.mjs';
8
+ import { readGeminiLocalConfig, determineGeminiModel, getGeminiModelSource, getInitialGeminiModel, saveGeminiModelToConfig } from './config-BEdyyciP.mjs';
9
9
  import 'axios';
10
10
  import 'chalk';
11
11
  import 'node:util';
@@ -3,9 +3,9 @@ import { execFileSync } from 'node:child_process';
3
3
  import os from 'node:os';
4
4
  import { existsSync, readFileSync } from 'node:fs';
5
5
  import { join } from 'node:path';
6
- import { k as ensurePrivateDirectorySync, m as hardenPrivateFileSync, w as writePrivateFileSync, e as errorLogMetadata, d as contentLogMetadata, h as connectionState, A as ApiClient, i as readSettings, j as encodeBase64, l as logger, c as configuration } from './types-AO-TVSTa.mjs';
7
- import { A as AcpSessionManager } from './AcpSessionManager-G8w8Q6tt.mjs';
8
- import { i as initialMachineMetadata, d as createSessionMetadata, e as setupOfflineReconnection, n as notifyDaemonSessionStarted, M as MessageQueue2, r as registerKillSessionHandler } from './index-j2LYx2KW.mjs';
6
+ import { k as ensurePrivateDirectorySync, m as hardenPrivateFileSync, w as writePrivateFileSync, e as errorLogMetadata, d as contentLogMetadata, h as connectionState, A as ApiClient, i as readSettings, j as encodeBase64, l as logger, c as configuration } from './types-DS3i_5LV.mjs';
7
+ import { A as AcpSessionManager } from './AcpSessionManager-Cyne6Rmg.mjs';
8
+ import { i as initialMachineMetadata, d as createSessionMetadata, e as setupOfflineReconnection, n as notifyDaemonSessionStarted, M as MessageQueue2, r as registerKillSessionHandler } from './index-an09rejO.mjs';
9
9
  import WebSocket from 'ws';
10
10
  import * as ed from '@noble/ed25519';
11
11
  import { sha512 } from '@noble/hashes/sha2.js';
@@ -5,9 +5,9 @@ var node_child_process = require('node:child_process');
5
5
  var os = require('node:os');
6
6
  var node_fs = require('node:fs');
7
7
  var path = require('node:path');
8
- var persistence = require('./types-DE03map-.cjs');
9
- var AcpSessionManager = require('./AcpSessionManager-B9mJWEzL.cjs');
10
- var index = require('./index-FxD6Abgx.cjs');
8
+ var persistence = require('./types-BNGeiP1C.cjs');
9
+ var AcpSessionManager = require('./AcpSessionManager-BPBdrrME.cjs');
10
+ var index = require('./index-4YmVbx7j.cjs');
11
11
  var WebSocket = require('ws');
12
12
  var ed = require('@noble/ed25519');
13
13
  var sha2_js = require('@noble/hashes/sha2.js');