very-happy-cli 0.2.111 → 0.2.113

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-B_olk79F.cjs → AcpBackend-33KrZ5lq.cjs} +1 -1
  2. package/dist/{AcpBackend-CeXVb_s_.mjs → AcpBackend-Dldla8c8.mjs} +1 -1
  3. package/dist/{AcpSessionManager-vqdqfD1l.mjs → AcpSessionManager-BPSy8e5x.mjs} +1 -1
  4. package/dist/{AcpSessionManager-uXwWO2Tn.cjs → AcpSessionManager-DnodFLES.cjs} +1 -1
  5. package/dist/{config-vGkcq9nM.mjs → config-Bp2eRu4s.mjs} +2 -2
  6. package/dist/{config-BR3tkFL_.cjs → config-CzhVSd7F.cjs} +2 -2
  7. package/dist/{index-BOoGJGQc.cjs → index-Bhej85vt.cjs} +240 -19
  8. package/dist/{index-Bn-xsguz.mjs → index-C1KeV2ip.mjs} +236 -15
  9. package/dist/{index-C-RLk8-e.mjs → index-D7dc2QHa.mjs} +2 -2
  10. package/dist/{index-JAhGeW4_.mjs → index-DfAGG99Q.mjs} +5 -5
  11. package/dist/{index-CtBP0Qvc.cjs → index-H47khBtq.cjs} +4 -4
  12. package/dist/{index-SjthsMid.cjs → index-fjyucZBO.cjs} +2 -2
  13. package/dist/index.cjs +2 -2
  14. package/dist/index.mjs +2 -2
  15. package/dist/{installTerminalHooks-FbKaK149.cjs → installTerminalHooks-CxBFNpwn.cjs} +2 -2
  16. package/dist/{installTerminalHooks-ADNcNdnB.mjs → installTerminalHooks-hnyO1Trp.mjs} +2 -2
  17. package/dist/lib.cjs +1 -1
  18. package/dist/lib.d.cts +44 -0
  19. package/dist/lib.d.mts +44 -0
  20. package/dist/lib.mjs +1 -1
  21. package/dist/{mcp-DHPm_gvL.mjs → mcp-BlAJfG7B.mjs} +2 -2
  22. package/dist/{mcp-DPp-bYNE.cjs → mcp-D1XCJ2xc.cjs} +2 -2
  23. package/dist/{runGemini-D5ySpYIZ.cjs → runGemini-BhLjxHGD.cjs} +4 -4
  24. package/dist/{runGemini-CWWyjQMz.mjs → runGemini-DVb3uHxe.mjs} +4 -4
  25. package/dist/{runOpenClaw-Bwrb1LXP.cjs → runOpenClaw-BrGj8DO6.cjs} +3 -3
  26. package/dist/{runOpenClaw-Bv4rUTyJ.mjs → runOpenClaw-D_GPGmD3.mjs} +3 -3
  27. package/dist/{send-DBfSNILO.cjs → send-B9lvSBVC.cjs} +2 -2
  28. package/dist/{send-BlkxFZu4.mjs → send-XfDlFCXz.mjs} +2 -2
  29. package/dist/{sessions-CVuEsdRl.cjs → sessions-BkeCXy8-.cjs} +2 -2
  30. package/dist/{sessions-1fW8soBM.mjs → sessions-DwSgYAEK.mjs} +2 -2
  31. package/dist/{spawn-D_ZexrY9.cjs → spawn-BNvGZjic.cjs} +2 -2
  32. package/dist/{spawn-BorKN3W2.mjs → spawn-CwORJ-bq.mjs} +2 -2
  33. package/dist/{types-BG_5QKX5.cjs → types-BnsRaANk.cjs} +166 -11
  34. package/dist/{types-k_ipDb0c.mjs → types-CAzzJ4tO.mjs} +165 -11
  35. package/package.json +7 -7
@@ -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-BOoGJGQc.cjs');
6
+ var index = require('./index-Bhej85vt.cjs');
7
7
  var limits = require('./limits-KdWKmwtH.cjs');
8
- var persistence = require('./types-BG_5QKX5.cjs');
8
+ var persistence = require('./types-BnsRaANk.cjs');
9
9
  require('chalk');
10
10
  require('node:os');
11
11
  require('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-BG_5QKX5.cjs');
8
- var index = require('./index-BOoGJGQc.cjs');
9
- var AcpBackend = require('./AcpBackend-B_olk79F.cjs');
10
- var config = require('./config-BR3tkFL_.cjs');
7
+ var persistence = require('./types-BnsRaANk.cjs');
8
+ var index = require('./index-Bhej85vt.cjs');
9
+ var AcpBackend = require('./AcpBackend-33KrZ5lq.cjs');
10
+ var config = require('./config-CzhVSd7F.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, j as readSettings, e as errorLogMetadata, k as encodeBase64, p as projectPath, d as contentLogMetadata } from './types-k_ipDb0c.mjs';
6
- import { x as GEMINI_API_KEY_ENV, y as GOOGLE_API_KEY_ENV, G as GEMINI_MODEL_ENV, B as BasePermissionHandler, z as BaseReasoningProcessor, h as initialMachineMetadata, j as createSessionMetadata, k as setupOfflineReconnection, n as notifyDaemonSessionStarted, m as MessageQueue2, o as hashObject, C as MessageBuffer, E as CHANGE_TITLE_INSTRUCTION, v as registerKillSessionHandler, u as startHappyServer } from './index-Bn-xsguz.mjs';
7
- import { A as AcpBackend } from './AcpBackend-CeXVb_s_.mjs';
8
- import { readGeminiLocalConfig, determineGeminiModel, getGeminiModelSource, getInitialGeminiModel, saveGeminiModelToConfig } from './config-vGkcq9nM.mjs';
5
+ import { l as logger, h as connectionState, A as ApiClient, j as readSettings, e as errorLogMetadata, k as encodeBase64, p as projectPath, d as contentLogMetadata } from './types-CAzzJ4tO.mjs';
6
+ import { x as GEMINI_API_KEY_ENV, y as GOOGLE_API_KEY_ENV, G as GEMINI_MODEL_ENV, B as BasePermissionHandler, z as BaseReasoningProcessor, h as initialMachineMetadata, j as createSessionMetadata, k as setupOfflineReconnection, n as notifyDaemonSessionStarted, m as MessageQueue2, o as hashObject, C as MessageBuffer, E as CHANGE_TITLE_INSTRUCTION, v as registerKillSessionHandler, u as startHappyServer } from './index-C1KeV2ip.mjs';
7
+ import { A as AcpBackend } from './AcpBackend-Dldla8c8.mjs';
8
+ import { readGeminiLocalConfig, determineGeminiModel, getGeminiModelSource, getInitialGeminiModel, saveGeminiModelToConfig } from './config-Bp2eRu4s.mjs';
9
9
  import 'axios';
10
10
  import 'chalk';
11
11
  import 'node:util';
@@ -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-BG_5QKX5.cjs');
9
- var AcpSessionManager = require('./AcpSessionManager-uXwWO2Tn.cjs');
10
- var index = require('./index-BOoGJGQc.cjs');
8
+ var persistence = require('./types-BnsRaANk.cjs');
9
+ var AcpSessionManager = require('./AcpSessionManager-DnodFLES.cjs');
10
+ var index = require('./index-Bhej85vt.cjs');
11
11
  var WebSocket = require('ws');
12
12
  var ed = require('@noble/ed25519');
13
13
  var sha2_js = require('@noble/hashes/sha2.js');
@@ -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 { m as ensurePrivateDirectorySync, n as hardenPrivateFileSync, w as writePrivateFileSync, e as errorLogMetadata, d as contentLogMetadata, h as connectionState, A as ApiClient, j as readSettings, k as encodeBase64, l as logger, c as configuration } from './types-k_ipDb0c.mjs';
7
- import { A as AcpSessionManager } from './AcpSessionManager-vqdqfD1l.mjs';
8
- import { h as initialMachineMetadata, j as createSessionMetadata, k as setupOfflineReconnection, n as notifyDaemonSessionStarted, m as MessageQueue2, v as registerKillSessionHandler } from './index-Bn-xsguz.mjs';
6
+ import { m as ensurePrivateDirectorySync, n as hardenPrivateFileSync, w as writePrivateFileSync, e as errorLogMetadata, d as contentLogMetadata, h as connectionState, A as ApiClient, j as readSettings, k as encodeBase64, l as logger, c as configuration } from './types-CAzzJ4tO.mjs';
7
+ import { A as AcpSessionManager } from './AcpSessionManager-BPSy8e5x.mjs';
8
+ import { h as initialMachineMetadata, j as createSessionMetadata, k as setupOfflineReconnection, n as notifyDaemonSessionStarted, m as MessageQueue2, v as registerKillSessionHandler } from './index-C1KeV2ip.mjs';
9
9
  import WebSocket from 'ws';
10
10
  import * as ed from '@noble/ed25519';
11
11
  import { sha512 } from '@noble/hashes/sha2.js';
@@ -3,8 +3,8 @@
3
3
  var chalk = require('chalk');
4
4
  var node_fs = require('node:fs');
5
5
  var path = require('node:path');
6
- var persistence = require('./types-BG_5QKX5.cjs');
7
- var index = require('./index-BOoGJGQc.cjs');
6
+ var persistence = require('./types-BnsRaANk.cjs');
7
+ var index = require('./index-Bhej85vt.cjs');
8
8
  require('axios');
9
9
  require('node:util');
10
10
  require('node:os');
@@ -1,8 +1,8 @@
1
1
  import chalk from 'chalk';
2
2
  import { readFileSync } from 'node:fs';
3
3
  import { resolve } from 'node:path';
4
- import { r as readPersistedSessions, c as configuration } from './types-k_ipDb0c.mjs';
5
- import { d as sendUserMessage, a as sessionWebUrl } from './index-Bn-xsguz.mjs';
4
+ import { r as readPersistedSessions, c as configuration } from './types-CAzzJ4tO.mjs';
5
+ import { d as sendUserMessage, a as sessionWebUrl } from './index-C1KeV2ip.mjs';
6
6
  import 'axios';
7
7
  import 'node:util';
8
8
  import 'node:os';
@@ -1,8 +1,8 @@
1
1
  'use strict';
2
2
 
3
3
  var chalk = require('chalk');
4
- var index = require('./index-BOoGJGQc.cjs');
5
- var persistence = require('./types-BG_5QKX5.cjs');
4
+ var index = require('./index-Bhej85vt.cjs');
5
+ var persistence = require('./types-BnsRaANk.cjs');
6
6
  require('node:os');
7
7
  require('node:crypto');
8
8
  require('cross-spawn');
@@ -1,6 +1,6 @@
1
1
  import chalk from 'chalk';
2
- import { D as DEFAULT_RECENT_LIMIT, M as MAX_READ_LIMIT, l as listSessions, r as readSessionTranscript, e as stopSession, f as archiveSession } from './index-Bn-xsguz.mjs';
3
- import { i as isValidSessionId } from './types-k_ipDb0c.mjs';
2
+ import { D as DEFAULT_RECENT_LIMIT, M as MAX_READ_LIMIT, l as listSessions, r as readSessionTranscript, e as stopSession, f as archiveSession } from './index-C1KeV2ip.mjs';
3
+ import { i as isValidSessionId } from './types-CAzzJ4tO.mjs';
4
4
  import 'node:os';
5
5
  import 'node:crypto';
6
6
  import 'cross-spawn';
@@ -3,8 +3,8 @@
3
3
  var chalk = require('chalk');
4
4
  var node_fs = require('node:fs');
5
5
  var path = require('node:path');
6
- var index = require('./index-BOoGJGQc.cjs');
7
- var persistence = require('./types-BG_5QKX5.cjs');
6
+ var index = require('./index-Bhej85vt.cjs');
7
+ var persistence = require('./types-BnsRaANk.cjs');
8
8
  require('node:os');
9
9
  require('node:crypto');
10
10
  require('cross-spawn');
@@ -1,8 +1,8 @@
1
1
  import chalk from 'chalk';
2
2
  import { readFileSync, statSync } from 'node:fs';
3
3
  import { resolve } from 'node:path';
4
- import { c as checkIfDaemonRunningAndCleanupStaleState, s as spawnDaemonSession, a as sessionWebUrl, i as isValidSpawnOrigin, b as sanitizeSpawnPermissionMode, A as ALLOWED_SPAWN_PERMISSION_MODES, w as waitForSessionKey, d as sendUserMessage } from './index-Bn-xsguz.mjs';
5
- import { l as logger } from './types-k_ipDb0c.mjs';
4
+ import { c as checkIfDaemonRunningAndCleanupStaleState, s as spawnDaemonSession, a as sessionWebUrl, i as isValidSpawnOrigin, b as sanitizeSpawnPermissionMode, A as ALLOWED_SPAWN_PERMISSION_MODES, w as waitForSessionKey, d as sendUserMessage } from './index-C1KeV2ip.mjs';
5
+ import { l as logger } from './types-CAzzJ4tO.mjs';
6
6
  import 'node:os';
7
7
  import 'node:crypto';
8
8
  import 'cross-spawn';
@@ -51,7 +51,7 @@ var z__namespace = /*#__PURE__*/_interopNamespaceDefault(z);
51
51
  var pty__namespace = /*#__PURE__*/_interopNamespaceDefault(pty);
52
52
 
53
53
  var name = "very-happy-cli";
54
- var version = "0.2.111";
54
+ var version = "0.2.113";
55
55
  var description = "Remote multi-agent workspace CLI with deep Claude integration, native terminal streaming, and self-hosting.";
56
56
  var author = "Kirill Dubovitskiy";
57
57
  var contributors = [
@@ -164,12 +164,12 @@ var dependencies = {
164
164
  zod: "^4.0.0"
165
165
  };
166
166
  var optionalDependencies = {
167
- "very-happy-tools-arm64-darwin": "0.2.111",
168
- "very-happy-tools-x64-darwin": "0.2.111",
169
- "very-happy-tools-arm64-linux": "0.2.111",
170
- "very-happy-tools-x64-linux": "0.2.111",
171
- "very-happy-tools-arm64-win32": "0.2.111",
172
- "very-happy-tools-x64-win32": "0.2.111"
167
+ "very-happy-tools-arm64-darwin": "0.2.113",
168
+ "very-happy-tools-x64-darwin": "0.2.113",
169
+ "very-happy-tools-arm64-linux": "0.2.113",
170
+ "very-happy-tools-x64-linux": "0.2.113",
171
+ "very-happy-tools-arm64-win32": "0.2.113",
172
+ "very-happy-tools-x64-win32": "0.2.113"
173
173
  };
174
174
  var devDependencies = {
175
175
  "@slopus/happy-wire": "workspace:*",
@@ -769,6 +769,14 @@ const sessionEnvelopeSchema = z__namespace.object({
769
769
  // Codex app-server item id for this envelope. Used as the precise
770
770
  // rollback point for Codex thread duplicate/fork-from-message.
771
771
  codexItemId: z__namespace.string().min(1).optional(),
772
+ // B-309: identity of the live draft this envelope supersedes,
773
+ // `"<api message id>:<content block index>"`. Set on text/thinking
774
+ // envelopes mapped from a Claude assistant message so the web can replace
775
+ // the streamed draft with the persisted message in a single frame instead
776
+ // of letting it flicker out and back in. Absent from every other producer
777
+ // (and from CLIs older than the streaming relay) — consumers must treat a
778
+ // missing key as "no draft to claim", never as an error.
779
+ streamKey: z__namespace.string().min(1).optional(),
772
780
  // Per-API-call token usage of the assistant message this envelope was
773
781
  // mapped from (B-108). Envelope-level (not inside `ev`) so every envelope
774
782
  // type carries it uniformly — the web feeds it into its context meter.
@@ -807,6 +815,7 @@ function createEnvelope(role, ev, opts = {}) {
807
815
  ...opts.subagent ? { subagent: opts.subagent } : {},
808
816
  ...opts.claudeUuid ? { claudeUuid: opts.claudeUuid } : {},
809
817
  ...opts.codexItemId ? { codexItemId: opts.codexItemId } : {},
818
+ ...opts.streamKey ? { streamKey: opts.streamKey } : {},
810
819
  ...opts.usage ? { usage: opts.usage } : {},
811
820
  ev
812
821
  });
@@ -909,6 +918,44 @@ z__namespace.object({
909
918
  body: CoreUpdateBodySchema,
910
919
  createdAt: z__namespace.number()
911
920
  });
921
+ const STREAM_DELTA_MAX_CHARS = 16 * 1024;
922
+ const sessionStreamBlockKindSchema = z__namespace.enum(["text", "thinking"]);
923
+ const sessionStreamBlockStartSchema = z__namespace.object({
924
+ t: z__namespace.literal("block-start"),
925
+ mid: z__namespace.string().min(1),
926
+ idx: z__namespace.number().int().min(0),
927
+ kind: sessionStreamBlockKindSchema
928
+ });
929
+ const sessionStreamBlockDeltaSchema = z__namespace.object({
930
+ t: z__namespace.literal("block-delta"),
931
+ mid: z__namespace.string().min(1),
932
+ idx: z__namespace.number().int().min(0),
933
+ text: z__namespace.string().max(STREAM_DELTA_MAX_CHARS)
934
+ });
935
+ const sessionStreamBlockEndSchema = z__namespace.object({
936
+ t: z__namespace.literal("block-end"),
937
+ mid: z__namespace.string().min(1),
938
+ idx: z__namespace.number().int().min(0)
939
+ });
940
+ const sessionStreamProgressSchema = z__namespace.object({
941
+ t: z__namespace.literal("progress"),
942
+ thinkingTokens: z__namespace.number().int().min(0).optional(),
943
+ outputTokens: z__namespace.number().int().min(0).optional(),
944
+ status: z__namespace.enum(["requesting", "compacting"]).optional()
945
+ });
946
+ const sessionStreamTurnEndSchema = z__namespace.object({
947
+ t: z__namespace.literal("turn-end")
948
+ });
949
+ z__namespace.discriminatedUnion("t", [
950
+ sessionStreamBlockStartSchema,
951
+ sessionStreamBlockDeltaSchema,
952
+ sessionStreamBlockEndSchema,
953
+ sessionStreamProgressSchema,
954
+ sessionStreamTurnEndSchema
955
+ ]);
956
+ function streamKeyOf(messageId, blockIndex) {
957
+ return `${messageId}:${blockIndex}`;
958
+ }
912
959
  const VoiceConversationGrantedSchema = z__namespace.object({
913
960
  allowed: z__namespace.literal(true),
914
961
  conversationToken: z__namespace.string(),
@@ -1390,6 +1437,23 @@ let backoff = createBackoff({
1390
1437
  }
1391
1438
  });
1392
1439
 
1440
+ function isRateQuotaCode(code) {
1441
+ return typeof code === "string" && code.endsWith("_rate_quota_exceeded");
1442
+ }
1443
+ const RATE_RETRY_MIN_DELAY_MS = 2e3;
1444
+ const RATE_RETRY_MAX_DELAY_MS = 6e4;
1445
+ function rateRetryDelayMs(attempt, random = Math.random()) {
1446
+ const exponent = Math.max(0, Math.min(attempt, 32) - 1);
1447
+ const ceiling = Math.min(RATE_RETRY_MIN_DELAY_MS * 2 ** exponent, RATE_RETRY_MAX_DELAY_MS);
1448
+ return Math.round(RATE_RETRY_MIN_DELAY_MS + random * (ceiling - RATE_RETRY_MIN_DELAY_MS));
1449
+ }
1450
+ async function pauseForRateQuota(opts) {
1451
+ const ms = rateRetryDelayMs(opts.attempt);
1452
+ opts.log?.(`[RATE] ${opts.label} write refused (${opts.code}), attempt ${opts.attempt} \u2014 waiting ${ms}ms`);
1453
+ await opts.sleep(ms);
1454
+ return ms;
1455
+ }
1456
+
1393
1457
  class AsyncLock {
1394
1458
  permits = 1;
1395
1459
  promiseResolverQueue = [];
@@ -1603,7 +1667,7 @@ class RpcHandlerManager {
1603
1667
  }
1604
1668
  }
1605
1669
 
1606
- const __dirname$1 = path$1.dirname(url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('types-BG_5QKX5.cjs', document.baseURI).href))));
1670
+ const __dirname$1 = path$1.dirname(url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('types-BnsRaANk.cjs', document.baseURI).href))));
1607
1671
  function projectPath() {
1608
1672
  const path = path$1.resolve(__dirname$1, "..");
1609
1673
  return path;
@@ -2791,13 +2855,24 @@ function mapClaudeLogMessageToSessionEnvelopesInternal(message, state) {
2791
2855
  maybeEmitSubagentStart(state, turnId, subagent, envelopes);
2792
2856
  const usage = pickAssistantUsage(message);
2793
2857
  const blocks = Array.isArray(message.message?.content) ? message.message.content : [];
2794
- for (const block of blocks) {
2858
+ const apiMessageId = !isSidechainMessage(message) && typeof message.message?.id === "string" && message.message.id.length > 0 ? message.message.id : void 0;
2859
+ let blockCursor = 0;
2860
+ if (apiMessageId) {
2861
+ if (state.streamBlockMessageId !== apiMessageId) {
2862
+ state.streamBlockMessageId = apiMessageId;
2863
+ state.streamBlockCursor = 0;
2864
+ }
2865
+ blockCursor = state.streamBlockCursor ?? 0;
2866
+ state.streamBlockCursor = blockCursor + blocks.length;
2867
+ }
2868
+ const draftKey = (index) => apiMessageId ? streamKeyOf(apiMessageId, blockCursor + index) : void 0;
2869
+ for (const [blockIndex, block] of blocks.entries()) {
2795
2870
  if (block.type === "text" && typeof block.text === "string" && block.text.trim().length > 0) {
2796
- envelopes.push(createEnvelope("agent", { t: "text", text: block.text }, { turn: turnId, subagent, claudeUuid, usage }));
2871
+ envelopes.push(createEnvelope("agent", { t: "text", text: block.text }, { turn: turnId, subagent, claudeUuid, usage, streamKey: draftKey(blockIndex) }));
2797
2872
  continue;
2798
2873
  }
2799
2874
  if (block.type === "thinking" && typeof block.thinking === "string" && block.thinking.trim().length > 0) {
2800
- envelopes.push(createEnvelope("agent", { t: "text", text: block.thinking, thinking: true }, { turn: turnId, subagent, claudeUuid, usage }));
2875
+ envelopes.push(createEnvelope("agent", { t: "text", text: block.thinking, thinking: true }, { turn: turnId, subagent, claudeUuid, usage, streamKey: draftKey(blockIndex) }));
2801
2876
  continue;
2802
2877
  }
2803
2878
  if (block.type === "tool_use") {
@@ -3796,6 +3871,33 @@ class ApiSessionClient extends node_events.EventEmitter {
3796
3871
  mode
3797
3872
  });
3798
3873
  }
3874
+ /**
3875
+ * B-309: push one live stream frame (thinking/text delta or progress) to
3876
+ * this account's web clients.
3877
+ *
3878
+ * Encrypted with the SESSION key and relayed without being stored —
3879
+ * clipboard-push's shape, for clipboard-push's reasons: the relay must not
3880
+ * be able to read thinking text, and the session id is stamped by the
3881
+ * server from the authenticated connection rather than trusted from here.
3882
+ *
3883
+ * `volatile` on purpose. A draft that misses the wire while disconnected
3884
+ * is worth nothing — the persisted message is still coming, and replaying
3885
+ * stale deltas after a reconnect would paint text that has already landed.
3886
+ */
3887
+ sendStreamFrame(frame) {
3888
+ if (!this.socket.connected) return;
3889
+ try {
3890
+ this.socket.volatile.emit("session-stream", {
3891
+ // The frame object, NOT a JSON string: `encrypt` serializes
3892
+ // internally, so pre-stringifying would double-encode and
3893
+ // escape every quote in the streamed text.
3894
+ payload: encodeBase64(encrypt(this.encryptionKey, this.encryptionVariant, frame)),
3895
+ enc: true
3896
+ });
3897
+ } catch (error) {
3898
+ logger.debug(`[API] Failed to relay stream frame: ${error}`);
3899
+ }
3900
+ }
3799
3901
  /**
3800
3902
  * Send session death message
3801
3903
  */
@@ -3926,12 +4028,14 @@ class ApiSessionClient extends node_events.EventEmitter {
3926
4028
  }
3927
4029
  updateMetadata(handler) {
3928
4030
  this.metadataLock.inLock(async () => {
4031
+ let rateRefusals = 0;
3929
4032
  await backoff(async () => {
3930
4033
  let updated = handler(this.metadata);
3931
4034
  const answer = await this.socket.emitWithAck("update-metadata", { sid: this.sessionId, expectedVersion: this.metadataVersion, metadata: encodeBase64(encrypt(this.encryptionKey, this.encryptionVariant, updated)) });
3932
4035
  if (answer.result === "success") {
3933
4036
  this.metadata = decrypt(this.encryptionKey, this.encryptionVariant, decodeBase64(answer.metadata));
3934
4037
  this.metadataVersion = answer.version;
4038
+ rateRefusals = 0;
3935
4039
  } else if (answer.result === "version-mismatch") {
3936
4040
  if (answer.version > this.metadataVersion) {
3937
4041
  this.metadataVersion = answer.version;
@@ -3939,6 +4043,16 @@ class ApiSessionClient extends node_events.EventEmitter {
3939
4043
  }
3940
4044
  throw new Error("Metadata version mismatch");
3941
4045
  } else if (answer.result === "error") {
4046
+ if (isRateQuotaCode(answer.error)) {
4047
+ rateRefusals += 1;
4048
+ await pauseForRateQuota({
4049
+ label: "session metadata",
4050
+ code: answer.error,
4051
+ attempt: rateRefusals,
4052
+ sleep: delay,
4053
+ log: (message) => logger.debug(message)
4054
+ });
4055
+ }
3942
4056
  throw new Error("Metadata update failed");
3943
4057
  }
3944
4058
  });
@@ -3951,12 +4065,14 @@ class ApiSessionClient extends node_events.EventEmitter {
3951
4065
  updateAgentState(handler) {
3952
4066
  logger.debugLargeJson("Updating agent state", this.agentState);
3953
4067
  this.agentStateLock.inLock(async () => {
4068
+ let rateRefusals = 0;
3954
4069
  await backoff(async () => {
3955
4070
  let updated = handler(this.agentState || {});
3956
4071
  const answer = await this.socket.emitWithAck("update-state", { sid: this.sessionId, expectedVersion: this.agentStateVersion, agentState: updated ? encodeBase64(encrypt(this.encryptionKey, this.encryptionVariant, updated)) : null });
3957
4072
  if (answer.result === "success") {
3958
4073
  this.agentState = answer.agentState ? decrypt(this.encryptionKey, this.encryptionVariant, decodeBase64(answer.agentState)) : null;
3959
4074
  this.agentStateVersion = answer.version;
4075
+ rateRefusals = 0;
3960
4076
  logger.debug("Agent state updated", this.agentState);
3961
4077
  } else if (answer.result === "version-mismatch") {
3962
4078
  if (answer.version > this.agentStateVersion) {
@@ -3965,6 +4081,16 @@ class ApiSessionClient extends node_events.EventEmitter {
3965
4081
  }
3966
4082
  throw new Error("Agent state version mismatch");
3967
4083
  } else if (answer.result === "error") {
4084
+ if (isRateQuotaCode(answer.error)) {
4085
+ rateRefusals += 1;
4086
+ await pauseForRateQuota({
4087
+ label: "session agent state",
4088
+ code: answer.error,
4089
+ attempt: rateRefusals,
4090
+ sleep: delay,
4091
+ log: (message) => logger.debug(message)
4092
+ });
4093
+ }
3968
4094
  throw new Error("Agent state update failed");
3969
4095
  }
3970
4096
  });
@@ -14866,6 +14992,7 @@ class ApiMachineClient {
14866
14992
  * for example to set a custom name.
14867
14993
  */
14868
14994
  async updateMachineMetadata(handler) {
14995
+ let rateRefusals = 0;
14869
14996
  await backoff(async () => {
14870
14997
  const updated = handler(this.machine.metadata);
14871
14998
  const answer = await this.socket.emitWithAck("machine-update-metadata", {
@@ -14876,6 +15003,7 @@ class ApiMachineClient {
14876
15003
  if (answer.result === "success") {
14877
15004
  this.machine.metadata = decrypt(this.machine.encryptionKey, this.machine.encryptionVariant, decodeBase64(answer.metadata));
14878
15005
  this.machine.metadataVersion = answer.version;
15006
+ rateRefusals = 0;
14879
15007
  logger.debug("[API MACHINE] Metadata updated successfully");
14880
15008
  } else if (answer.result === "version-mismatch") {
14881
15009
  if (answer.version > this.machine.metadataVersion) {
@@ -14883,6 +15011,18 @@ class ApiMachineClient {
14883
15011
  this.machine.metadata = decrypt(this.machine.encryptionKey, this.machine.encryptionVariant, decodeBase64(answer.metadata));
14884
15012
  }
14885
15013
  throw new Error("Metadata version mismatch");
15014
+ } else if (answer.result === "error") {
15015
+ if (isRateQuotaCode(answer.error)) {
15016
+ rateRefusals += 1;
15017
+ await pauseForRateQuota({
15018
+ label: "machine metadata",
15019
+ code: answer.error,
15020
+ attempt: rateRefusals,
15021
+ sleep: delay,
15022
+ log: (message) => logger.debug(message)
15023
+ });
15024
+ }
15025
+ throw new Error("Machine metadata update failed");
14886
15026
  }
14887
15027
  });
14888
15028
  }
@@ -14891,6 +15031,7 @@ class ApiMachineClient {
14891
15031
  * Simplified without lock - relies on backoff for retry
14892
15032
  */
14893
15033
  async updateDaemonState(handler) {
15034
+ let rateRefusals = 0;
14894
15035
  await backoff(async () => {
14895
15036
  const updated = handler(this.machine.daemonState);
14896
15037
  const answer = await this.socket.emitWithAck("machine-update-state", {
@@ -14901,6 +15042,7 @@ class ApiMachineClient {
14901
15042
  if (answer.result === "success") {
14902
15043
  this.machine.daemonState = decrypt(this.machine.encryptionKey, this.machine.encryptionVariant, decodeBase64(answer.daemonState));
14903
15044
  this.machine.daemonStateVersion = answer.version;
15045
+ rateRefusals = 0;
14904
15046
  logger.debug("[API MACHINE] Daemon state updated successfully");
14905
15047
  } else if (answer.result === "version-mismatch") {
14906
15048
  if (answer.version > this.machine.daemonStateVersion) {
@@ -14908,6 +15050,18 @@ class ApiMachineClient {
14908
15050
  this.machine.daemonState = decrypt(this.machine.encryptionKey, this.machine.encryptionVariant, decodeBase64(answer.daemonState));
14909
15051
  }
14910
15052
  throw new Error("Daemon state version mismatch");
15053
+ } else if (answer.result === "error") {
15054
+ if (isRateQuotaCode(answer.error)) {
15055
+ rateRefusals += 1;
15056
+ await pauseForRateQuota({
15057
+ label: "daemon state",
15058
+ code: answer.error,
15059
+ attempt: rateRefusals,
15060
+ sleep: delay,
15061
+ log: (message) => logger.debug(message)
15062
+ });
15063
+ }
15064
+ throw new Error("Daemon state update failed");
14911
15065
  }
14912
15066
  });
14913
15067
  }
@@ -16334,6 +16488,7 @@ exports.InvalidateSync = InvalidateSync;
16334
16488
  exports.MAX_CHAT_ATTACHMENT_SOURCE_BYTES = MAX_CHAT_ATTACHMENT_SOURCE_BYTES;
16335
16489
  exports.MIRROR_BACKFILL_LINES_DEFAULT = MIRROR_BACKFILL_LINES_DEFAULT;
16336
16490
  exports.RawJSONLinesSchema = RawJSONLinesSchema;
16491
+ exports.STREAM_DELTA_MAX_CHARS = STREAM_DELTA_MAX_CHARS;
16337
16492
  exports.SandboxConfigSchema = SandboxConfigSchema;
16338
16493
  exports.TITLE_PROMPT_PREFIX = TITLE_PROMPT_PREFIX;
16339
16494
  exports.acquireDaemonLock = acquireDaemonLock;