very-happy-cli 0.2.119 → 0.2.121

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 (35) hide show
  1. package/dist/{AcpBackend-DpsLyp2y.mjs → AcpBackend-BxvQ6O4k.mjs} +1 -1
  2. package/dist/{AcpBackend-BpwOAcgu.cjs → AcpBackend-Cdj56ZfU.cjs} +1 -1
  3. package/dist/{AcpSessionManager-Ck6heffK.cjs → AcpSessionManager-DahCU78z.cjs} +51 -11
  4. package/dist/{AcpSessionManager-CM0PcgwO.mjs → AcpSessionManager-DlWlQVhw.mjs} +51 -11
  5. package/dist/{config-CtQJKe8h.cjs → config-DxOhdFEx.cjs} +2 -2
  6. package/dist/{config-DpE-aSHW.mjs → config-osO37dR1.mjs} +2 -2
  7. package/dist/{index-BITXLEZo.mjs → index-BcwAnM9k.mjs} +25 -11
  8. package/dist/{index-CK-yge8m.cjs → index-Bv53Zygv.cjs} +24 -10
  9. package/dist/{index-CRhOD3CQ.cjs → index-DObePs_K.cjs} +176 -54
  10. package/dist/{index-CGpkGfDj.mjs → index-DtZFY8aS.mjs} +2 -2
  11. package/dist/{index-DNXCQz_A.mjs → index-Iz-M3qbz.mjs} +172 -51
  12. package/dist/{index-DY7g3UWq.cjs → index-Vjo1jIJH.cjs} +2 -2
  13. package/dist/index.cjs +2 -2
  14. package/dist/index.mjs +2 -2
  15. package/dist/{installTerminalHooks-Dmr3JUJR.mjs → installTerminalHooks-Bq11vOqF.mjs} +2 -2
  16. package/dist/{installTerminalHooks-BOOg7NNs.cjs → installTerminalHooks-DehsXLmV.cjs} +2 -2
  17. package/dist/lib.cjs +1 -1
  18. package/dist/lib.d.cts +25 -1
  19. package/dist/lib.d.mts +25 -1
  20. package/dist/lib.mjs +1 -1
  21. package/dist/{mcp-D4N7yn98.mjs → mcp-Bx1B3uny.mjs} +2 -2
  22. package/dist/{mcp-CxIk4Mes.cjs → mcp-Cpx9Qlch.cjs} +2 -2
  23. package/dist/{runGemini-Bexbg8pA.mjs → runGemini-CMgudbJs.mjs} +9 -5
  24. package/dist/{runGemini-TbGYRy0V.cjs → runGemini-DI8DipUx.cjs} +9 -5
  25. package/dist/{runOpenClaw-B-TV4EkO.cjs → runOpenClaw-CetdjTl-.cjs} +3 -3
  26. package/dist/{runOpenClaw-BpoIQWbe.mjs → runOpenClaw-DR3fLtVf.mjs} +3 -3
  27. package/dist/{send-U9UJvqNA.cjs → send-Cq0NZCn8.cjs} +2 -2
  28. package/dist/{send-z5SeDtKA.mjs → send-M4b_hD7t.mjs} +2 -2
  29. package/dist/{sessions-DNALvNe8.mjs → sessions-CelkWhqX.mjs} +11 -4
  30. package/dist/{sessions-BAZGALtl.cjs → sessions-DW4lrLOp.cjs} +11 -4
  31. package/dist/{spawn-CsV0SKkw.mjs → spawn-C4fF0q3Z.mjs} +2 -2
  32. package/dist/{spawn-dBxu-RAv.cjs → spawn-D3cwBlWI.cjs} +2 -2
  33. package/dist/{types-Bw_6p8NP.cjs → types-COOYpI8_.cjs} +176 -33
  34. package/dist/{types-DdyNuB7D.mjs → types-P2cJWD0E.mjs} +174 -33
  35. 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, tmpdir } from 'node:os';
3
3
  import { randomUUID, randomBytes, createHash as createHash$1, timingSafeEqual } from 'node:crypto';
4
- import { o as readDaemonState, l as logger, c as configuration, q as clearDaemonState, s as getProjectPath, p as projectPath, t as initializeSandbox, u as wrapCommand, g as contentLogMetadata, I as InvalidateSync, R as RawJSONLinesSchema, k as delay, v as AsyncLock, S as STREAM_DELTA_MAX_CHARS, x as ensurePrivateDirectory, M as MAX_CHAT_ATTACHMENT_SOURCE_BYTES, y as hardenPrivateFile, j as packageJson, z as commandOnPath, e as readSettings, B as readCredentials, C as credentialRelayProblem, D as deriveLocalCliUpdateSummary, f as encodeBase64, E as updateSettings, F as encodeBase64Url, G as decodeBase64, H as writeCredentialsLegacy, J as writeCredentialsDataKey, K as readCredentialsForConfiguredRelay, L as encrypt, r as readPersistedSessions, N as isClaudeSessionId, w as writePrivateFileSync, O as getLocalHappyAgentCredentialPath, P as readLocalHappyAgentCredentials, Q as decryptWithDataKey, T as decryptLegacy, U as compareExactVersions, V as extractCompleteLines, W as decideBackfill, X as parseTerminalHookPayload, Y as tmuxArgs, Z as scrubTmuxClientEnv, _ as decideMirrorBinding, $ as MIRROR_BACKFILL_LINES_DEFAULT, a0 as persistSession, a1 as mirrorLineKey, a2 as mirrorLocalId, a3 as detectCLIAvailability, a4 as detectResumeSupport, a5 as acquireDaemonLock, a6 as summarizeSpawnSessionForLog, a7 as deletePersistedSessions, a8 as shellescape, a9 as decrypt, aa as writeDaemonState, A as ApiClient, ab as writeSettings, ac as fetchCliUpdateState, ad as resolveCliUpdateCheckInterval, ae as DEFAULT_CLI_UPDATE_CHECK_INTERVAL_MS, af as releaseDaemonLock, i as isValidSessionId, ag as listVhTerminals, ah as isValidTerminalId, ai as readVhTerminal, aj as sendToVhTerminal, ak as TITLE_PROMPT_PREFIX, al as BOARD_ANALYZER_PROMPT_PREFIX, d as connectionState, am as startOfflineReconnection, an as clearCredentials, ao as clearMachineId, m as ensurePrivateDirectorySync, ap as SandboxConfigSchema, b as createEnvelope, aq as CodexAppServerClient, ar as getLatestDaemonLog } from './types-DdyNuB7D.mjs';
4
+ import { o as readDaemonState, l as logger, c as configuration, q as clearDaemonState, t as getProjectPath, p as projectPath, u as initializeSandbox, v as wrapCommand, g as contentLogMetadata, I as InvalidateSync, R as RawJSONLinesSchema, k as delay, x as AsyncLock, S as STREAM_DELTA_MAX_CHARS, y as ensurePrivateDirectory, M as MAX_CHAT_ATTACHMENT_SOURCE_BYTES, z as hardenPrivateFile, j as packageJson, B as commandOnPath, e as readSettings, C as readCredentials, D as credentialRelayProblem, E as deriveLocalCliUpdateSummary, f as encodeBase64, F as readSettingsOutcome, G as updateSettings, H as encodeBase64Url, J as decodeBase64, K as writeCredentialsLegacy, L as writeCredentialsDataKey, N as readCredentialsForConfiguredRelay, O as encrypt, r as readPersistedSessions, P as isClaudeSessionId, w as writePrivateFileSync, Q as getLocalHappyAgentCredentialPath, T as readLocalHappyAgentCredentials, U as decryptWithDataKey, V as decryptLegacy, W as compareExactVersions, X as extractCompleteLines, Y as decideBackfill, Z as parseTerminalHookPayload, _ as tmuxArgs, $ as scrubTmuxClientEnv, a0 as decideMirrorBinding, a1 as MIRROR_BACKFILL_LINES_DEFAULT, a2 as persistSession, a3 as mirrorLineKey, a4 as mirrorLocalId, a5 as detectCLIAvailability, a6 as detectResumeSupport, a7 as acquireDaemonLock, a8 as summarizeSpawnSessionForLog, a9 as deletePersistedSessions, aa as shellescape, ab as decrypt, ac as writeDaemonState, A as ApiClient, ad as writeSettings, ae as fetchCliUpdateState, af as resolveCliUpdateCheckInterval, ag as DEFAULT_CLI_UPDATE_CHECK_INTERVAL_MS, ah as releaseDaemonLock, i as isValidSessionId, ai as listVhTerminals, aj as isValidTerminalId, ak as readVhTerminal, al as sendToVhTerminal, am as TITLE_PROMPT_PREFIX, an as BOARD_ANALYZER_PROMPT_PREFIX, d as connectionState, ao as startOfflineReconnection, ap as clearCredentials, aq as clearMachineId, m as ensurePrivateDirectorySync, ar as SandboxConfigSchema, b as createEnvelope, as as CodexAppServerClient, at as getLatestDaemonLog } from './types-P2cJWD0E.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';
@@ -3384,9 +3384,7 @@ class StreamRelay {
3384
3384
  if (e.type === "content_block_start") {
3385
3385
  const kind = e.content_block?.type === "thinking" ? "thinking" : e.content_block?.type === "text" ? "text" : null;
3386
3386
  if (!kind || !this.currentMid || typeof e.index !== "number") return;
3387
- this.flushPending();
3388
- this.openBlocks.add(e.index);
3389
- this.send({ t: "block-start", mid: this.currentMid, idx: e.index, kind });
3387
+ this.openBlock(this.currentMid, e.index, kind);
3390
3388
  return;
3391
3389
  }
3392
3390
  if (e.type === "content_block_delta") {
@@ -3394,16 +3392,12 @@ class StreamRelay {
3394
3392
  const text = delta?.type === "text_delta" ? delta.text : delta?.type === "thinking_delta" ? delta.thinking : null;
3395
3393
  if (typeof text !== "string" || text.length === 0) return;
3396
3394
  if (!this.currentMid || typeof e.index !== "number") return;
3397
- if (!this.openBlocks.has(e.index)) return;
3398
3395
  this.appendDelta(this.currentMid, e.index, text);
3399
3396
  return;
3400
3397
  }
3401
3398
  if (e.type === "content_block_stop") {
3402
3399
  if (!this.currentMid || typeof e.index !== "number") return;
3403
- if (!this.openBlocks.has(e.index)) return;
3404
- this.flushPending();
3405
- this.openBlocks.delete(e.index);
3406
- this.send({ t: "block-end", mid: this.currentMid, idx: e.index });
3400
+ this.closeBlock(this.currentMid, e.index);
3407
3401
  return;
3408
3402
  }
3409
3403
  if (e.type === "message_delta") {
@@ -3414,7 +3408,29 @@ class StreamRelay {
3414
3408
  return;
3415
3409
  }
3416
3410
  }
3411
+ // ---- Block-level API (see module comment) --------------------------------
3412
+ /** Open a draft block. A different `mid` than the one currently open closes
3413
+ * whatever the previous message left open, exactly like the SDK's
3414
+ * `message_start` does. Re-opening an already open block is a no-op. */
3415
+ openBlock(mid, idx, kind) {
3416
+ this.idleSinceTurnEnd = false;
3417
+ if (this.currentMid !== mid) {
3418
+ this.flushPending();
3419
+ this.closeOpenBlocks();
3420
+ this.currentMid = mid;
3421
+ }
3422
+ if (this.openBlocks.has(idx)) return;
3423
+ this.flushPending();
3424
+ this.openBlocks.add(idx);
3425
+ this.send({ t: "block-start", mid, idx, kind });
3426
+ }
3427
+ /** Append text to an OPEN block; deltas for a block that was never opened
3428
+ * (or belongs to another message) are dropped — the SDK path relies on
3429
+ * this to ignore `input_json_delta` for tool_use blocks. */
3417
3430
  appendDelta(mid, idx, text) {
3431
+ this.idleSinceTurnEnd = false;
3432
+ if (text.length === 0) return;
3433
+ if (this.currentMid !== mid || !this.openBlocks.has(idx)) return;
3418
3434
  if (this.pending && (this.pending.mid !== mid || this.pending.idx !== idx)) {
3419
3435
  this.flushPending();
3420
3436
  }
@@ -3434,6 +3450,22 @@ class StreamRelay {
3434
3450
  }, STREAM_FLUSH_MS);
3435
3451
  }
3436
3452
  }
3453
+ /** Close an open block: pending text goes out first, then `block-end`. */
3454
+ closeBlock(mid, idx) {
3455
+ this.idleSinceTurnEnd = false;
3456
+ if (this.currentMid !== mid || !this.openBlocks.has(idx)) return;
3457
+ this.flushPending();
3458
+ this.openBlocks.delete(idx);
3459
+ this.send({ t: "block-end", mid, idx });
3460
+ }
3461
+ /** Merge quantified progress; flushed on the progress cadence. */
3462
+ setProgress(patch) {
3463
+ this.idleSinceTurnEnd = false;
3464
+ this.progress = { ...this.progress, ...patch };
3465
+ this.progressDirty = true;
3466
+ this.scheduleProgress();
3467
+ }
3468
+ // -------------------------------------------------------------------------
3437
3469
  flushPending() {
3438
3470
  if (this.flushHandle !== null) {
3439
3471
  this.clearTimer(this.flushHandle);
@@ -3456,11 +3488,6 @@ class StreamRelay {
3456
3488
  }
3457
3489
  this.openBlocks.clear();
3458
3490
  }
3459
- setProgress(patch) {
3460
- this.progress = { ...this.progress, ...patch };
3461
- this.progressDirty = true;
3462
- this.scheduleProgress();
3463
- }
3464
3491
  scheduleProgress() {
3465
3492
  if (this.progressHandle !== null) return;
3466
3493
  this.progressHandle = this.setTimer(() => {
@@ -4303,10 +4330,17 @@ class MessageQueue2 {
4303
4330
  waiter = null;
4304
4331
  closed = false;
4305
4332
  onMessageHandler = null;
4333
+ /**
4334
+ * B-332: fired for every item the queue itself destroys before it was ever
4335
+ * handed to the runner. The runner wires this to a queue-cancel tombstone
4336
+ * so the web stops painting a silently-lost message as "queued".
4337
+ */
4338
+ onDiscardHandler = null;
4306
4339
  modeHasher;
4307
- constructor(modeHasher, onMessageHandler = null) {
4340
+ constructor(modeHasher, onMessageHandler = null, onDiscardHandler = null) {
4308
4341
  this.modeHasher = modeHasher;
4309
4342
  this.onMessageHandler = onMessageHandler;
4343
+ this.onDiscardHandler = onDiscardHandler;
4310
4344
  logger.debug(`[MessageQueue2] Initialized`);
4311
4345
  }
4312
4346
  /**
@@ -4315,6 +4349,19 @@ class MessageQueue2 {
4315
4349
  setOnMessage(handler) {
4316
4350
  this.onMessageHandler = handler;
4317
4351
  }
4352
+ /**
4353
+ * Set a handler that will be called when messages are discarded without ever
4354
+ * being processed (B-332).
4355
+ */
4356
+ setOnDiscard(handler) {
4357
+ this.onDiscardHandler = handler;
4358
+ }
4359
+ /** Report the current items as destroyed for `reason` (caller clears next). */
4360
+ notifyDiscard(reason) {
4361
+ if (this.queue.length === 0) return;
4362
+ const entries = this.queue.map((item) => ({ sourceId: item.sourceId, reason }));
4363
+ this.onDiscardHandler?.(entries);
4364
+ }
4318
4365
  /**
4319
4366
  * Push a message to the queue with a mode and an optional list of
4320
4367
  * attachments that travel with this message.
@@ -4382,6 +4429,7 @@ class MessageQueue2 {
4382
4429
  }
4383
4430
  const modeHash = this.modeHasher(mode);
4384
4431
  logger.debug(`[MessageQueue2] pushIsolateAndClear() called with mode hash: ${modeHash} - clearing ${this.queue.length} pending messages`);
4432
+ this.notifyDiscard("cleared");
4385
4433
  this.queue = [];
4386
4434
  this.queue.push({
4387
4435
  message,
@@ -4433,6 +4481,7 @@ class MessageQueue2 {
4433
4481
  */
4434
4482
  reset() {
4435
4483
  logger.debug(`[MessageQueue2] reset() called. Clearing ${this.queue.length} messages`);
4484
+ this.notifyDiscard("aborted");
4436
4485
  this.queue = [];
4437
4486
  this.closed = false;
4438
4487
  this.waiter = null;
@@ -5291,6 +5340,23 @@ async function openBrowser(url) {
5291
5340
  }
5292
5341
  }
5293
5342
 
5343
+ function decideMachineId(input) {
5344
+ const { read, newAuth } = input;
5345
+ if (read.kind === "unreadable") {
5346
+ return {
5347
+ action: "refuse",
5348
+ reason: `settings exist but could not be read (${read.error}); refusing to mint a new machine id \u2014 fix or remove the settings file, or run \`very-happy auth login --force\` to deliberately re-register this machine`
5349
+ };
5350
+ }
5351
+ if (newAuth) return { action: "mint", reason: "new-auth" };
5352
+ if (read.kind === "absent") return { action: "mint", reason: "no-settings-file" };
5353
+ const existing = read.settings.machineId;
5354
+ if (typeof existing === "string" && existing.length > 0) {
5355
+ return { action: "reuse", machineId: existing };
5356
+ }
5357
+ return { action: "mint", reason: "no-machine-id" };
5358
+ }
5359
+
5294
5360
  async function doAuth() {
5295
5361
  console.clear();
5296
5362
  const secret = new Uint8Array(randomBytes(32));
@@ -5464,17 +5530,23 @@ async function authAndSetupMachineIfNeeded() {
5464
5530
  } else {
5465
5531
  logger.debug("[AUTH] Using existing credentials");
5466
5532
  }
5467
- const settings = await updateSettings(async (s) => {
5468
- if (newAuth || !s.machineId) {
5469
- return {
5470
- ...s,
5471
- machineId: randomUUID()
5472
- };
5473
- }
5474
- return s;
5533
+ const decision = decideMachineId({
5534
+ read: await readSettingsOutcome(),
5535
+ newAuth
5475
5536
  });
5476
- logger.debug(`[AUTH] Machine ID: ${settings.machineId}`);
5477
- return { credentials, machineId: settings.machineId };
5537
+ if (decision.action === "refuse") {
5538
+ throw new Error(`Cannot establish this machine's identity: ${decision.reason}`);
5539
+ }
5540
+ let machineId;
5541
+ if (decision.action === "reuse") {
5542
+ machineId = decision.machineId;
5543
+ } else {
5544
+ machineId = randomUUID();
5545
+ logger.warn(`[AUTH] Registering a NEW machine id (${decision.reason})`);
5546
+ await updateSettings(async (s) => ({ ...s, machineId }));
5547
+ }
5548
+ logger.debug(`[AUTH] Machine ID: ${machineId}`);
5549
+ return { credentials, machineId };
5478
5550
  }
5479
5551
 
5480
5552
  let caffeinateProcess = null;
@@ -9953,6 +10025,27 @@ async function openUserScopedSocket(token, options = {}) {
9953
10025
  };
9954
10026
  }
9955
10027
 
10028
+ const RPC_RATE_LIMIT_ERROR = "RPC rate limit reached";
10029
+ const RPC_ACCOUNT_RATE_LIMIT_ERROR = "RPC account rate limit reached";
10030
+ const RPC_RATE_RETRY_MIN_DELAY_MS = 2e3;
10031
+ const RPC_RATE_RETRY_MAX_DELAY_MS = 6e4;
10032
+ const RPC_RATE_MAX_RETRIES = 2;
10033
+ function isRpcRateLimitAck(ack) {
10034
+ if (!ack || typeof ack !== "object") return false;
10035
+ const a = ack;
10036
+ if (a.ok !== false) return false;
10037
+ if (a.code === "rpc_rate_limited" || a.code === "rpc_account_rate_limited") return true;
10038
+ return a.error === RPC_RATE_LIMIT_ERROR || a.error === RPC_ACCOUNT_RATE_LIMIT_ERROR;
10039
+ }
10040
+ function rpcRateRetryDelayMs(attempt, retryAfterMs, random = Math.random()) {
10041
+ if (typeof retryAfterMs === "number" && Number.isFinite(retryAfterMs) && retryAfterMs > 0) {
10042
+ return Math.min(RPC_RATE_RETRY_MAX_DELAY_MS, Math.round(retryAfterMs * (1 + random * 0.5)));
10043
+ }
10044
+ const exponent = Math.max(0, Math.min(attempt, 32) - 1);
10045
+ const ceiling = Math.min(RPC_RATE_RETRY_MIN_DELAY_MS * 2 ** exponent, RPC_RATE_RETRY_MAX_DELAY_MS);
10046
+ return Math.round(RPC_RATE_RETRY_MIN_DELAY_MS + random * (ceiling - RPC_RATE_RETRY_MIN_DELAY_MS));
10047
+ }
10048
+
9956
10049
  const PERMISSION_OPS_CLIENT = "session-ops";
9957
10050
  const PERMISSION_RPC_TIMEOUT_MS = 3e4;
9958
10051
  const SETTLE_TIMEOUT_MS = 5e3;
@@ -9984,6 +10077,9 @@ function interpretPermissionAck(ack, decryptResult) {
9984
10077
  if (/timed? ?out/i.test(message)) {
9985
10078
  return { status: "timeout", message };
9986
10079
  }
10080
+ if (isRpcRateLimitAck(ack)) {
10081
+ return { status: "rate-limited", message, retryAfterMs: rpcRateRetryDelayMs(1, ack.retryAfterMs, 0) };
10082
+ }
9987
10083
  return { status: "rejected", message };
9988
10084
  }
9989
10085
  let body = null;
@@ -10045,15 +10141,24 @@ async function resolvePermissionRequest(sessionId, requestId, verdict, deps = {}
10045
10141
  const params = encodeBase64(encrypt(key, persisted.encryptionVariant, payload));
10046
10142
  const transport = await (deps.openTransport ?? openUserScopedSocket)(token);
10047
10143
  const rpcTimeoutMs = deps.rpcTimeoutMs ?? PERMISSION_RPC_TIMEOUT_MS;
10144
+ const sleep = deps.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
10048
10145
  let ack;
10049
10146
  try {
10050
- ack = await withDeadline(
10051
- transport.rpcCall({ method: `${sessionId}:permission`, params }, rpcTimeoutMs),
10052
- rpcTimeoutMs + RPC_DEADLINE_GRACE_MS,
10053
- `permission RPC did not settle within ${rpcTimeoutMs}ms`
10054
- );
10055
- } catch (error) {
10056
- ack = { ok: false, error: error instanceof Error ? error.message : "RPC call failed" };
10147
+ for (let attempt = 1; ; attempt++) {
10148
+ try {
10149
+ ack = await withDeadline(
10150
+ transport.rpcCall({ method: `${sessionId}:permission`, params }, rpcTimeoutMs),
10151
+ rpcTimeoutMs + RPC_DEADLINE_GRACE_MS,
10152
+ `permission RPC did not settle within ${rpcTimeoutMs}ms`
10153
+ );
10154
+ } catch (error) {
10155
+ ack = { ok: false, error: error instanceof Error ? error.message : "RPC call failed" };
10156
+ }
10157
+ if (!isRpcRateLimitAck(ack) || attempt > RPC_RATE_MAX_RETRIES) break;
10158
+ const waitMs = rpcRateRetryDelayMs(attempt, ack.retryAfterMs, (deps.random ?? Math.random)());
10159
+ deps.onRateLimited?.({ attempt, waitMs, serverError: ack.error ?? "RPC rate limit reached" });
10160
+ await sleep(waitMs);
10161
+ }
10057
10162
  } finally {
10058
10163
  transport.close();
10059
10164
  }
@@ -10061,7 +10166,6 @@ async function resolvePermissionRequest(sessionId, requestId, verdict, deps = {}
10061
10166
  if (outcome.status !== "acknowledged") {
10062
10167
  return { sessionId, requestId, payload, outcome };
10063
10168
  }
10064
- const sleep = deps.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
10065
10169
  const deadline = now() + (deps.settleTimeoutMs ?? SETTLE_TIMEOUT_MS);
10066
10170
  let settled = false;
10067
10171
  for (; ; ) {
@@ -11192,7 +11296,7 @@ function registerSideQuestionHandler(rpc, deps) {
11192
11296
  const retainMs = deps.retainMs ?? SIDE_QUESTION_RETAIN_MS;
11193
11297
  const maxRunMs = deps.maxRunMs ?? SIDE_QUESTION_MAX_RUN_MS;
11194
11298
  const run = deps.run ?? (async (input) => {
11195
- const { query } = await import('./index-CGpkGfDj.mjs');
11299
+ const { query } = await import('./index-DtZFY8aS.mjs');
11196
11300
  return runSideQuestion(query, input);
11197
11301
  });
11198
11302
  const slots = /* @__PURE__ */ new Map();
@@ -11874,6 +11978,7 @@ async function runClaude(credentials, options = {}) {
11874
11978
  if (reconnectSessionId) {
11875
11979
  session.suppressNextArchiveSignal();
11876
11980
  if (!reconnectSeeded) session.skipExistingMessages();
11981
+ void session.cancelUndeliveredQueuedInputs();
11877
11982
  session.updateMetadata((meta) => ({
11878
11983
  ...mergeReconnectMetadata(meta, metadata, Date.now()),
11879
11984
  queueCancellation: true,
@@ -11998,6 +12103,16 @@ async function runClaude(credentials, options = {}) {
11998
12103
  controlledByUser: options.startingMode !== "remote"
11999
12104
  }));
12000
12105
  const messageQueue = new MessageQueue2(claudeModeHash);
12106
+ messageQueue.setOnDiscard((entries) => {
12107
+ const byReason = /* @__PURE__ */ new Map();
12108
+ for (const entry of entries) {
12109
+ if (!entry.sourceId) continue;
12110
+ const list = byReason.get(entry.reason) ?? [];
12111
+ list.push(entry.sourceId);
12112
+ byReason.set(entry.reason, list);
12113
+ }
12114
+ for (const [reason, keys] of byReason) session.sendQueueCancelReason(keys, reason);
12115
+ });
12001
12116
  session.rpcHandlerManager.registerHandler("cancelQueuedMessage", async (params) => ({
12002
12117
  removed: messageQueue.cancelPending(
12003
12118
  typeof params?.localKey === "string" ? params.localKey : void 0,
@@ -15210,10 +15325,10 @@ function isCodexClearText(text) {
15210
15325
  }
15211
15326
  function enqueueCodexUserText(opts) {
15212
15327
  if (isCodexClearText(opts.text)) {
15213
- opts.queue.pushIsolateAndClear(opts.text, opts.mode);
15328
+ opts.queue.pushIsolateAndClear(opts.text, opts.mode, void 0, opts.sourceId);
15214
15329
  return "clear";
15215
15330
  }
15216
- opts.queue.push(opts.text, opts.mode);
15331
+ opts.queue.push(opts.text, opts.mode, void 0, opts.sourceId);
15217
15332
  return "queued";
15218
15333
  }
15219
15334
 
@@ -15398,6 +15513,7 @@ async function runCodex(opts) {
15398
15513
  if (reconnectSessionId) {
15399
15514
  session.suppressNextArchiveSignal();
15400
15515
  if (!reconnectSeeded) session.skipExistingMessages();
15516
+ void session.cancelUndeliveredQueuedInputs();
15401
15517
  session.updateMetadata((meta) => mergeReconnectMetadata(meta, metadata, Date.now()));
15402
15518
  }
15403
15519
  if (response) {
@@ -15420,6 +15536,10 @@ async function runCodex(opts) {
15420
15536
  }
15421
15537
  }
15422
15538
  const messageQueue = new MessageQueue2(hashCodexEnhancedMode);
15539
+ messageQueue.setOnDiscard((entries) => {
15540
+ const keys = entries.map((entry) => entry.sourceId).filter((key) => typeof key === "string");
15541
+ if (keys.length > 0) session.sendQueueCancelReason(keys, entries[0].reason);
15542
+ });
15423
15543
  let currentPermissionMode = initialPermissionMode;
15424
15544
  let currentModel = DEFAULT_CODEX_MODEL;
15425
15545
  let currentEffort = DEFAULT_CODEX_EFFORT;
@@ -15501,7 +15621,8 @@ async function runCodex(opts) {
15501
15621
  const enqueueResult = enqueueCodexUserText({
15502
15622
  text: message.content.text,
15503
15623
  mode: enhancedMode,
15504
- queue: messageQueue
15624
+ queue: messageQueue,
15625
+ sourceId: message.localKey
15505
15626
  });
15506
15627
  if (enqueueResult === "clear") {
15507
15628
  logger.debug("[Codex] /clear command pushed to isolated queue");
@@ -16126,7 +16247,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
16126
16247
  process.exit(0);
16127
16248
  } else if (subcommand === "install-terminal-hooks") {
16128
16249
  try {
16129
- const { installTerminalHooks, parseTerminalHooksArgs, TERMINAL_HOOKS_HELP } = await import('./installTerminalHooks-Dmr3JUJR.mjs');
16250
+ const { installTerminalHooks, parseTerminalHooksArgs, TERMINAL_HOOKS_HELP } = await import('./installTerminalHooks-Bq11vOqF.mjs');
16130
16251
  const command = parseTerminalHooksArgs(args.slice(1));
16131
16252
  if (command.action === "help") {
16132
16253
  console.log(TERMINAL_HOOKS_HELP);
@@ -16143,7 +16264,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
16143
16264
  }
16144
16265
  } else if (subcommand === "spawn") {
16145
16266
  try {
16146
- const { handleSpawnCommand } = await import('./spawn-CsV0SKkw.mjs');
16267
+ const { handleSpawnCommand } = await import('./spawn-C4fF0q3Z.mjs');
16147
16268
  await handleSpawnCommand(args.slice(1));
16148
16269
  } catch (error) {
16149
16270
  console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
@@ -16155,7 +16276,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
16155
16276
  return;
16156
16277
  } else if (subcommand === "sessions") {
16157
16278
  try {
16158
- const { handleSessionsCommand } = await import('./sessions-DNALvNe8.mjs');
16279
+ const { handleSessionsCommand } = await import('./sessions-CelkWhqX.mjs');
16159
16280
  await handleSessionsCommand(args.slice(1));
16160
16281
  } catch (error) {
16161
16282
  console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
@@ -16167,7 +16288,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
16167
16288
  return;
16168
16289
  } else if (subcommand === "send") {
16169
16290
  try {
16170
- const { handleSendCommand } = await import('./send-z5SeDtKA.mjs');
16291
+ const { handleSendCommand } = await import('./send-M4b_hD7t.mjs');
16171
16292
  await handleSendCommand(args.slice(1));
16172
16293
  } catch (error) {
16173
16294
  console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
@@ -16273,9 +16394,9 @@ Conversation history is preserved on the server, but in-flight tool calls are in
16273
16394
  if (geminiSubcommand === "project" && args[2] === "set" && args[3]) {
16274
16395
  const projectId = args[3];
16275
16396
  try {
16276
- const { saveGoogleCloudProjectToConfig } = await import('./config-DpE-aSHW.mjs');
16277
- const { readCredentialsForConfiguredRelay: readCredentialsForConfiguredRelay2 } = await import('./types-DdyNuB7D.mjs').then(function (n) { return n.as; });
16278
- const { ApiClient: ApiClient2 } = await import('./types-DdyNuB7D.mjs').then(function (n) { return n.at; });
16397
+ const { saveGoogleCloudProjectToConfig } = await import('./config-osO37dR1.mjs');
16398
+ const { readCredentialsForConfiguredRelay: readCredentialsForConfiguredRelay2 } = await import('./types-P2cJWD0E.mjs').then(function (n) { return n.au; });
16399
+ const { ApiClient: ApiClient2 } = await import('./types-P2cJWD0E.mjs').then(function (n) { return n.av; });
16279
16400
  let userEmail = void 0;
16280
16401
  try {
16281
16402
  const credentials = await readCredentialsForConfiguredRelay2();
@@ -16306,7 +16427,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
16306
16427
  }
16307
16428
  if (geminiSubcommand === "project" && args[2] === "get") {
16308
16429
  try {
16309
- const { readGeminiLocalConfig } = await import('./config-DpE-aSHW.mjs');
16430
+ const { readGeminiLocalConfig } = await import('./config-osO37dR1.mjs');
16310
16431
  const config = readGeminiLocalConfig();
16311
16432
  if (config.googleCloudProject) {
16312
16433
  console.log(`Current Google Cloud Project: ${config.googleCloudProject}`);
@@ -16346,7 +16467,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
16346
16467
  process.exit(0);
16347
16468
  }
16348
16469
  try {
16349
- const { runGemini } = await import('./runGemini-Bexbg8pA.mjs');
16470
+ const { runGemini } = await import('./runGemini-CMgudbJs.mjs');
16350
16471
  let startedBy = void 0;
16351
16472
  for (let i = 1; i < args.length; i++) {
16352
16473
  if (args[i] === "--started-by") {
@@ -16376,7 +16497,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
16376
16497
  }
16377
16498
  };
16378
16499
  try {
16379
- const { runAcp, resolveAcpAgentConfig } = await import('./index-BITXLEZo.mjs');
16500
+ const { runAcp, resolveAcpAgentConfig } = await import('./index-BcwAnM9k.mjs');
16380
16501
  const parsed = parsePiRunnerArgs(args.slice(1));
16381
16502
  const resolved = resolveAcpAgentConfig(["pi", ...parsed.passthrough]);
16382
16503
  const { credentials } = await authAndSetupMachineIfNeeded();
@@ -16403,7 +16524,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
16403
16524
  return;
16404
16525
  } else if (subcommand === "acp") {
16405
16526
  try {
16406
- const { runAcp, resolveAcpAgentConfig } = await import('./index-BITXLEZo.mjs');
16527
+ const { runAcp, resolveAcpAgentConfig } = await import('./index-BcwAnM9k.mjs');
16407
16528
  let startedBy = void 0;
16408
16529
  let verbose = false;
16409
16530
  const acpArgs = [];
@@ -16443,7 +16564,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
16443
16564
  return;
16444
16565
  } else if (subcommand === "openclaw") {
16445
16566
  try {
16446
- const { runOpenClaw } = await import('./runOpenClaw-BpoIQWbe.mjs');
16567
+ const { runOpenClaw } = await import('./runOpenClaw-DR3fLtVf.mjs');
16447
16568
  let startedBy = void 0;
16448
16569
  let verbose = false;
16449
16570
  let gatewayUrl;
@@ -16494,7 +16615,7 @@ Conversation history is preserved on the server, but in-flight tool calls are in
16494
16615
  return;
16495
16616
  } else if (subcommand === "mcp" && args.length === 1) {
16496
16617
  try {
16497
- const { handleMcpCommand } = await import('./mcp-D4N7yn98.mjs');
16618
+ const { handleMcpCommand } = await import('./mcp-Bx1B3uny.mjs');
16498
16619
  await handleMcpCommand();
16499
16620
  } catch (error) {
16500
16621
  process.stderr.write(`[very-happy mcp] Fatal: ${error instanceof Error ? error.message : String(error)}
@@ -16855,4 +16976,4 @@ ${chalk.bold("Examples:")}
16855
16976
  }
16856
16977
  }
16857
16978
 
16858
- export { ALLOWED_SPAWN_PERMISSION_MODES as A, registerKillSessionHandler as B, BasePermissionHandler as C, DEFAULT_RECENT_LIMIT as D, writeSessionModeFile as E, normalizeAcpPermissionMode as F, GEMINI_MODEL_ENV as G, removeSessionModeFile as H, GEMINI_API_KEY_ENV as I, GOOGLE_API_KEY_ENV as J, BaseReasoningProcessor as K, MessageBuffer as L, MAX_READ_LIMIT as M, CHANGE_TITLE_INSTRUCTION as N, SPAWN_AGENTS as S, TitleGenerator as T, sessionWebUrl as a, sanitizeSpawnPermissionMode as b, checkIfDaemonRunningAndCleanupStaleState as c, sendUserMessage as d, listSessions as e, stopSession as f, resolvePermissionRequest as g, archiveSession as h, isValidSpawnOrigin as i, DEFAULT_GEMINI_MODEL as j, setTerminalTitleViaDaemon as k, listAccountSessions as l, registerAssistantSessionTools as m, initialMachineMetadata as n, createSessionMetadata as o, pushClipboardViaDaemon as p, query as q, readSessionTranscript as r, spawnDaemonSession as s, tmuxSupportsSessionEnv as t, setupOfflineReconnection as u, notifyDaemonSessionStarted as v, waitForSessionKey as w, MessageQueue2 as x, hashObject as y, startHappyServer as z };
16979
+ export { ALLOWED_SPAWN_PERMISSION_MODES as A, startHappyServer as B, registerKillSessionHandler as C, DEFAULT_RECENT_LIMIT as D, BasePermissionHandler as E, writeSessionModeFile as F, GEMINI_MODEL_ENV as G, normalizeAcpPermissionMode as H, removeSessionModeFile as I, GEMINI_API_KEY_ENV as J, GOOGLE_API_KEY_ENV as K, BaseReasoningProcessor as L, MAX_READ_LIMIT as M, MessageBuffer as N, CHANGE_TITLE_INSTRUCTION as O, SPAWN_AGENTS as S, TitleGenerator as T, sessionWebUrl as a, sanitizeSpawnPermissionMode as b, checkIfDaemonRunningAndCleanupStaleState as c, sendUserMessage as d, listSessions as e, stopSession as f, resolvePermissionRequest as g, archiveSession as h, isValidSpawnOrigin as i, DEFAULT_GEMINI_MODEL as j, setTerminalTitleViaDaemon as k, listAccountSessions as l, registerAssistantSessionTools as m, initialMachineMetadata as n, createSessionMetadata as o, pushClipboardViaDaemon as p, query as q, readSessionTranscript as r, spawnDaemonSession as s, tmuxSupportsSessionEnv as t, setupOfflineReconnection as u, notifyDaemonSessionStarted as v, waitForSessionKey as w, StreamRelay as x, MessageQueue2 as y, hashObject as z };
@@ -1,11 +1,11 @@
1
1
  'use strict';
2
2
 
3
- var index = require('./index-CRhOD3CQ.cjs');
3
+ var index = require('./index-DObePs_K.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-Bw_6p8NP.cjs');
8
+ require('./types-COOYpI8_.cjs');
9
9
  require('axios');
10
10
  require('node:util');
11
11
  require('node:fs');
package/dist/index.cjs CHANGED
@@ -1,8 +1,8 @@
1
1
  'use strict';
2
2
 
3
3
  require('chalk');
4
- require('./index-CRhOD3CQ.cjs');
5
- require('./types-Bw_6p8NP.cjs');
4
+ require('./index-DObePs_K.cjs');
5
+ require('./types-COOYpI8_.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-DNXCQz_A.mjs';
3
- import './types-DdyNuB7D.mjs';
2
+ import './index-Iz-M3qbz.mjs';
3
+ import './types-P2cJWD0E.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-DdyNuB7D.mjs';
7
- import { t as tmuxSupportsSessionEnv } from './index-DNXCQz_A.mjs';
6
+ import { p as projectPath } from './types-P2cJWD0E.mjs';
7
+ import { t as tmuxSupportsSessionEnv } from './index-Iz-M3qbz.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-Bw_6p8NP.cjs');
9
- var index = require('./index-CRhOD3CQ.cjs');
8
+ var persistence = require('./types-COOYpI8_.cjs');
9
+ var index = require('./index-DObePs_K.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-Bw_6p8NP.cjs');
3
+ var persistence = require('./types-COOYpI8_.cjs');
4
4
  require('axios');
5
5
  require('chalk');
6
6
  require('node:util');
package/dist/lib.d.cts CHANGED
@@ -137,6 +137,10 @@ declare const sessionEnvelopeSchema: z.ZodObject<{
137
137
  totalTokens: z.ZodOptional<z.ZodNumber>;
138
138
  durationMs: z.ZodOptional<z.ZodNumber>;
139
139
  summary: z.ZodOptional<z.ZodString>;
140
+ }, z.core.$strip>, z.ZodObject<{
141
+ t: z.ZodLiteral<"queue-cancel">;
142
+ targetLocalKeys: z.ZodArray<z.ZodString>;
143
+ reason: z.ZodOptional<z.ZodString>;
140
144
  }, z.core.$strip>], "t">;
141
145
  }, z.core.$strip>;
142
146
  type SessionEnvelope = z.infer<typeof sessionEnvelopeSchema>;
@@ -922,8 +926,24 @@ declare class ApiSessionClient extends EventEmitter {
922
926
  private connectSessionRelay;
923
927
  private pushCommittedMessagesToRelay;
924
928
  private enqueueMessage;
929
+ /** B-332: emit a queue-cancel tombstone, mirroring the web's own signal. */
930
+ sendQueueCancelReason(localKeys: string[], reason: 'cleared' | 'aborted' | 'restarted'): void;
925
931
  /**
926
- * Send message to session
932
+ * B-332 site ③: find queued user input this wrapper destroyed (it was still
933
+ * in the previous wrapper's in-memory queue when it died) and tell the web.
934
+ *
935
+ * Walks NEWEST → OLDEST via `before_seq`, decrypting each page, and stops at
936
+ * the first turn-end. The runner calls this on EVERY reconnect (runClaude /
937
+ * runCodex), seeded or not: a cursor seeded at the server's latest seq skips
938
+ * the undelivered tail just as thoroughly as skipExistingMessages does —
939
+ * and the daemon's resume/restart always seeds, so gating on the skip path
940
+ * would miss the common case. Best-effort: a failed scan must not take a
941
+ * session down. Needs no socket; POSTs through the same session-message
942
+ * channel so the web receives it identically to a cancel-button tombstone.
943
+ */
944
+ cancelUndeliveredQueuedInputs(): Promise<void>;
945
+ /**
946
+ * Send a message to session
927
947
  * @param body - Message body (can be MessageContent or raw content for agent messages)
928
948
  */
929
949
  sendClaudeSessionMessage(body: RawJSONLines, opts?: {
@@ -1296,6 +1316,10 @@ declare class ApiMachineClient {
1296
1316
  private keepAliveInterval;
1297
1317
  private lastKnownCLIAvailability;
1298
1318
  private lastKnownResumeSupport;
1319
+ /** Wall-clock of the last CLI availability probe. Initialised as "already
1320
+ * stale" so the FIRST keep-alive tick always probes and populates the
1321
+ * machine metadata that a connecting/reconnecting web client reads. */
1322
+ private lastCliProbeAt;
1299
1323
  private cliUpdateState;
1300
1324
  private cliUpdatePushChain;
1301
1325
  private claudeAuthState;
package/dist/lib.d.mts CHANGED
@@ -137,6 +137,10 @@ declare const sessionEnvelopeSchema: z.ZodObject<{
137
137
  totalTokens: z.ZodOptional<z.ZodNumber>;
138
138
  durationMs: z.ZodOptional<z.ZodNumber>;
139
139
  summary: z.ZodOptional<z.ZodString>;
140
+ }, z.core.$strip>, z.ZodObject<{
141
+ t: z.ZodLiteral<"queue-cancel">;
142
+ targetLocalKeys: z.ZodArray<z.ZodString>;
143
+ reason: z.ZodOptional<z.ZodString>;
140
144
  }, z.core.$strip>], "t">;
141
145
  }, z.core.$strip>;
142
146
  type SessionEnvelope = z.infer<typeof sessionEnvelopeSchema>;
@@ -922,8 +926,24 @@ declare class ApiSessionClient extends EventEmitter {
922
926
  private connectSessionRelay;
923
927
  private pushCommittedMessagesToRelay;
924
928
  private enqueueMessage;
929
+ /** B-332: emit a queue-cancel tombstone, mirroring the web's own signal. */
930
+ sendQueueCancelReason(localKeys: string[], reason: 'cleared' | 'aborted' | 'restarted'): void;
925
931
  /**
926
- * Send message to session
932
+ * B-332 site ③: find queued user input this wrapper destroyed (it was still
933
+ * in the previous wrapper's in-memory queue when it died) and tell the web.
934
+ *
935
+ * Walks NEWEST → OLDEST via `before_seq`, decrypting each page, and stops at
936
+ * the first turn-end. The runner calls this on EVERY reconnect (runClaude /
937
+ * runCodex), seeded or not: a cursor seeded at the server's latest seq skips
938
+ * the undelivered tail just as thoroughly as skipExistingMessages does —
939
+ * and the daemon's resume/restart always seeds, so gating on the skip path
940
+ * would miss the common case. Best-effort: a failed scan must not take a
941
+ * session down. Needs no socket; POSTs through the same session-message
942
+ * channel so the web receives it identically to a cancel-button tombstone.
943
+ */
944
+ cancelUndeliveredQueuedInputs(): Promise<void>;
945
+ /**
946
+ * Send a message to session
927
947
  * @param body - Message body (can be MessageContent or raw content for agent messages)
928
948
  */
929
949
  sendClaudeSessionMessage(body: RawJSONLines, opts?: {
@@ -1296,6 +1316,10 @@ declare class ApiMachineClient {
1296
1316
  private keepAliveInterval;
1297
1317
  private lastKnownCLIAvailability;
1298
1318
  private lastKnownResumeSupport;
1319
+ /** Wall-clock of the last CLI availability probe. Initialised as "already
1320
+ * stale" so the FIRST keep-alive tick always probes and populates the
1321
+ * machine metadata that a connecting/reconnecting web client reads. */
1322
+ private lastCliProbeAt;
1299
1323
  private cliUpdateState;
1300
1324
  private cliUpdatePushChain;
1301
1325
  private claudeAuthState;
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-DdyNuB7D.mjs';
1
+ export { A as ApiClient, a as ApiSessionClient, R as RawJSONLinesSchema, c as configuration, l as logger } from './types-P2cJWD0E.mjs';
2
2
  import 'axios';
3
3
  import 'chalk';
4
4
  import 'node:util';