very-happy-cli 0.2.97 → 0.2.98

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-CTUM4dvG.mjs → AcpBackend-BXiGiFC7.mjs} +1 -1
  2. package/dist/{AcpBackend-DrwdVSeJ.cjs → AcpBackend-DhR8ml8L.cjs} +1 -1
  3. package/dist/{AcpSessionManager-BoBuHee3.mjs → AcpSessionManager-CDnrxAfQ.mjs} +1 -1
  4. package/dist/{AcpSessionManager-BieE187D.cjs → AcpSessionManager-Ckc0NFmf.cjs} +1 -1
  5. package/dist/{config-DCH33LOM.mjs → config-B3pA9YsN.mjs} +2 -2
  6. package/dist/{config-COq6X7U7.cjs → config-GJQc_kpD.cjs} +2 -2
  7. package/dist/{index-Dn0iOBFd.cjs → index-BBm2b34F.cjs} +4 -4
  8. package/dist/{index-DkhMvofH.mjs → index-Bh5Rcy0s.mjs} +5 -5
  9. package/dist/{index-BD2fHJ8o.cjs → index-CO1gNtuP.cjs} +18 -21
  10. package/dist/{index-Bcgquogx.mjs → index-CuUVSZnK.mjs} +12 -15
  11. package/dist/index.cjs +2 -2
  12. package/dist/index.mjs +2 -2
  13. package/dist/{installTerminalHooks-H3kpFEZS.cjs → installTerminalHooks-Cp9JVxQ-.cjs} +2 -2
  14. package/dist/{installTerminalHooks-DqNtW6a2.mjs → installTerminalHooks-Ru0dn_zF.mjs} +2 -2
  15. package/dist/lib.cjs +1 -1
  16. package/dist/lib.d.cts +10 -0
  17. package/dist/lib.d.mts +10 -0
  18. package/dist/lib.mjs +1 -1
  19. package/dist/{mcp-BwNc5eRs.cjs → mcp-B5HMF24K.cjs} +2 -2
  20. package/dist/{mcp-BKAuzaLX.mjs → mcp-CQoVN0u2.mjs} +2 -2
  21. package/dist/{runGemini-w35qiFBB.cjs → runGemini-BBzPVzuc.cjs} +4 -4
  22. package/dist/{runGemini-Brp0UmLx.mjs → runGemini-CwZXQsgu.mjs} +4 -4
  23. package/dist/{runOpenClaw-CkHtTCpI.cjs → runOpenClaw-B4nC6XGL.cjs} +3 -3
  24. package/dist/{runOpenClaw-BvPRFx-L.mjs → runOpenClaw-C-jz4rWr.mjs} +3 -3
  25. package/dist/{send-tyKG13xt.cjs → send-CWgaINHC.cjs} +2 -2
  26. package/dist/{send-c-wEmh5U.mjs → send-DN-nVBEI.mjs} +2 -2
  27. package/dist/{spawn-xdCsG2RT.cjs → spawn-C0NSP84Y.cjs} +2 -2
  28. package/dist/{spawn-yS6yY8Xi.mjs → spawn-CqcfbOnk.mjs} +2 -2
  29. package/dist/{types-DoGaKVfK.cjs → types-BP-4phS_.cjs} +289 -70
  30. package/dist/{types-xZOYW0_G.mjs → types-Cl4vWuDY.mjs} +288 -70
  31. package/package.json +7 -7
@@ -29,7 +29,7 @@ import { SandboxManager } from '@anthropic-ai/sandbox-runtime';
29
29
  import { Expo } from 'expo-server-sdk';
30
30
 
31
31
  var name = "very-happy-cli";
32
- var version = "0.2.97";
32
+ var version = "0.2.98";
33
33
  var description = "Remote multi-agent workspace CLI with deep Claude integration, native terminal streaming, and self-hosting.";
34
34
  var author = "Kirill Dubovitskiy";
35
35
  var contributors = [
@@ -142,12 +142,12 @@ var dependencies = {
142
142
  zod: "^4.0.0"
143
143
  };
144
144
  var optionalDependencies = {
145
- "very-happy-tools-arm64-darwin": "0.2.97",
146
- "very-happy-tools-x64-darwin": "0.2.97",
147
- "very-happy-tools-arm64-linux": "0.2.97",
148
- "very-happy-tools-x64-linux": "0.2.97",
149
- "very-happy-tools-arm64-win32": "0.2.97",
150
- "very-happy-tools-x64-win32": "0.2.97"
145
+ "very-happy-tools-arm64-darwin": "0.2.98",
146
+ "very-happy-tools-x64-darwin": "0.2.98",
147
+ "very-happy-tools-arm64-linux": "0.2.98",
148
+ "very-happy-tools-x64-linux": "0.2.98",
149
+ "very-happy-tools-arm64-win32": "0.2.98",
150
+ "very-happy-tools-x64-win32": "0.2.98"
151
151
  };
152
152
  var devDependencies = {
153
153
  "@slopus/happy-wire": "workspace:*",
@@ -1019,7 +1019,9 @@ const WebTerminalListItemSchema = z$1.object({
1019
1019
  * conversation carried over, the processes did not. Cleared once opened. */
1020
1020
  restoredAt: z$1.number().optional(),
1021
1021
  /** B-265: @vh_title_manual is set (user renamed it). */
1022
- manual: z$1.boolean().optional()
1022
+ manual: z$1.boolean().optional(),
1023
+ /** B-273: opened attached to the user's tmux session of this name. */
1024
+ attachTmux: z$1.string().optional()
1023
1025
  });
1024
1026
  const ClosedTerminalRecordSchema = z$1.object({
1025
1027
  id: z$1.string(),
@@ -1036,6 +1038,8 @@ const ClosedTerminalRecordSchema = z$1.object({
1036
1038
  /** B-265: tags + manual-rename flag at close time (restored verbatim). */
1037
1039
  tags: z$1.array(z$1.string()).optional(),
1038
1040
  manual: z$1.boolean().optional(),
1041
+ /** B-273: attached user tmux session name — restore re-attaches. */
1042
+ attachTmux: z$1.string().optional(),
1039
1043
  closedAt: z$1.number()
1040
1044
  });
1041
1045
  z$1.object({
@@ -1089,6 +1093,16 @@ z$1.object({
1089
1093
  rpcAvailable: z$1.boolean(),
1090
1094
  detectedAt: z$1.number()
1091
1095
  }).optional(),
1096
+ /**
1097
+ * B-273 capability flag: this daemon answers `list-tmux-sessions` and
1098
+ * honours `open-terminal.attachTmux`. Same trust rule (`detectedAt >=
1099
+ * startedAt`). Stamped unconditionally — on a machine without tmux the
1100
+ * RPC just answers an empty list.
1101
+ */
1102
+ tmuxSessions: z$1.object({
1103
+ rpcAvailable: z$1.boolean(),
1104
+ detectedAt: z$1.number()
1105
+ }).optional(),
1092
1106
  /** Relay-owned CLI compatibility/update policy last checked by this daemon. */
1093
1107
  cliUpdate: CliUpdateStateSchema.optional(),
1094
1108
  /**
@@ -4790,7 +4804,7 @@ function sanitizeLiveSnapshot(raw, now, ttlMs = LIVE_SNAPSHOT_TTL_MS, max = LIVE
4790
4804
  const rows = [];
4791
4805
  for (const [id, value] of Object.entries(raw)) {
4792
4806
  if (!id || !value || typeof value !== "object") continue;
4793
- const { title, cwd, seenAt, tags, manual } = value;
4807
+ const { title, cwd, seenAt, tags, manual, attachTmux } = value;
4794
4808
  if (typeof seenAt !== "number" || !Number.isFinite(seenAt)) continue;
4795
4809
  if (now - seenAt >= ttlMs) continue;
4796
4810
  const tagList = sanitizeTagList(tags);
@@ -4799,6 +4813,7 @@ function sanitizeLiveSnapshot(raw, now, ttlMs = LIVE_SNAPSHOT_TTL_MS, max = LIVE
4799
4813
  cwd: typeof cwd === "string" && cwd ? cwd : void 0,
4800
4814
  ...tagList !== void 0 ? { tags: tagList } : {},
4801
4815
  ...manual === true ? { manual: true } : {},
4816
+ ...typeof attachTmux === "string" && attachTmux ? { attachTmux } : {},
4802
4817
  seenAt
4803
4818
  }]);
4804
4819
  }
@@ -4819,6 +4834,7 @@ function liveSnapshotChanged(prev, next) {
4819
4834
  if (!before) return true;
4820
4835
  if (before.title !== info.title || before.cwd !== info.cwd) return true;
4821
4836
  if (!!before.manual !== !!info.manual) return true;
4837
+ if ((before.attachTmux ?? "") !== (info.attachTmux ?? "")) return true;
4822
4838
  if ((before.tags ?? []).join("\0") !== (info.tags ?? []).join("\0")) return true;
4823
4839
  }
4824
4840
  return false;
@@ -4844,9 +4860,89 @@ function pickMirrorForTerminal(sessions, terminalId) {
4844
4860
  return best ? { sessionId: best.id, claudeSessionId: best.claude } : null;
4845
4861
  }
4846
4862
 
4863
+ function shellescape(s) {
4864
+ return "'" + s.replace(/'/g, "'\\''") + "'";
4865
+ }
4866
+
4867
+ const USER_SESSION_FIELD_SEP = "<~|~>";
4868
+ const USER_SESSIONS_FORMAT = [
4869
+ "#{session_id}",
4870
+ "#{session_name}",
4871
+ "#{session_windows}",
4872
+ "#{session_attached}",
4873
+ "#{session_activity}",
4874
+ "#{session_created}",
4875
+ "#{pane_current_command}",
4876
+ "#{pane_current_path}"
4877
+ ].join(USER_SESSION_FIELD_SEP);
4878
+ const TMUX_SESSION_ID_RE = /^\$\d{1,9}$/;
4879
+ const TMUX_SESSION_NAME_MAX = 128;
4880
+ function isSafeTmuxSessionName(name) {
4881
+ if (typeof name !== "string") return false;
4882
+ if (name.length === 0 || name.length > TMUX_SESSION_NAME_MAX) return false;
4883
+ return !/[\x00-\x1f\x7f]/.test(name);
4884
+ }
4885
+ function isVhSessionName(name) {
4886
+ return name.startsWith("vh-");
4887
+ }
4888
+ function parseUserSessionLine(line) {
4889
+ if (!line) return void 0;
4890
+ const parts = line.split(USER_SESSION_FIELD_SEP);
4891
+ if (parts.length < 8) return void 0;
4892
+ const [id, name, windows, attached, activity, created, command] = parts;
4893
+ if (!TMUX_SESSION_ID_RE.test(id) || !isSafeTmuxSessionName(name)) return void 0;
4894
+ const cwd = parts.slice(7).join(USER_SESSION_FIELD_SEP);
4895
+ const num = (v) => {
4896
+ const n = Number(v);
4897
+ return Number.isFinite(n) ? n : void 0;
4898
+ };
4899
+ return {
4900
+ id,
4901
+ name,
4902
+ windows: Math.max(0, Math.floor(num(windows) ?? 0)),
4903
+ attached: (num(attached) ?? 0) > 0,
4904
+ activityAt: activity ? (num(activity) ?? 0) * 1e3 || void 0 : void 0,
4905
+ createdAt: created ? (num(created) ?? 0) * 1e3 || void 0 : void 0,
4906
+ command: command.trim() || void 0,
4907
+ cwd: cwd || void 0
4908
+ };
4909
+ }
4910
+ const USER_SESSIONS_MAX = 50;
4911
+ function parseUserSessions(stdout, max = USER_SESSIONS_MAX) {
4912
+ const out = [];
4913
+ for (const line of stdout.split("\n")) {
4914
+ const s = parseUserSessionLine(line);
4915
+ if (!s || isVhSessionName(s.name)) continue;
4916
+ out.push(s);
4917
+ }
4918
+ out.sort((a, b) => (b.activityAt ?? 0) - (a.activityAt ?? 0));
4919
+ return out.slice(0, max);
4920
+ }
4921
+ function attachStartupCommand(sessionId, socket) {
4922
+ if (!TMUX_SESSION_ID_RE.test(sessionId)) throw new Error(`not a tmux session id: ${sessionId}`);
4923
+ const sock = socket ? ` -S ${shellescape(socket)}` : "";
4924
+ return ` TMUX= tmux${sock} attach-session -t ${shellescape(sessionId)}`;
4925
+ }
4926
+
4847
4927
  const TERMINAL_ID_RE$1 = /^[a-zA-Z0-9_-]{1,64}$/;
4848
4928
  function planTerminalRestore(record, facts) {
4849
4929
  if (facts.tmuxAlive) return { kind: "already-live" };
4930
+ if (record.attachTmux) {
4931
+ const matches = (facts.userSessions ?? []).filter((s) => s.name === record.attachTmux);
4932
+ if (matches.length !== 1) return { kind: "error", reason: "tmux-session-gone" };
4933
+ const cwd = record.cwd && facts.cwdExists(record.cwd) ? record.cwd : facts.homeDir;
4934
+ if (!cwd) return { kind: "error", reason: "missing-cwd" };
4935
+ return {
4936
+ kind: "create",
4937
+ terminalId: record.id,
4938
+ cwd,
4939
+ title: record.title ?? record.attachTmux,
4940
+ manual: true,
4941
+ tags: record.tags,
4942
+ command: attachStartupCommand(matches[0].id, facts.attachSocket),
4943
+ attachTmux: record.attachTmux
4944
+ };
4945
+ }
4850
4946
  if (!record.cwd || !facts.cwdExists(record.cwd)) return { kind: "error", reason: "missing-cwd" };
4851
4947
  const resumable = isClaudeSessionId(record.claudeSessionId) && facts.conversationExists(record.cwd, record.claudeSessionId);
4852
4948
  return {
@@ -4860,6 +4956,20 @@ function planTerminalRestore(record, facts) {
4860
4956
  };
4861
4957
  }
4862
4958
 
4959
+ const VH_TMUX_SOCKET_ENV = "VH_TMUX_SOCKET";
4960
+ function tmuxSocketFlags(env = process.env) {
4961
+ const socket = env[VH_TMUX_SOCKET_ENV];
4962
+ return socket ? ["-S", socket] : [];
4963
+ }
4964
+ function tmuxArgs(args, env = process.env) {
4965
+ return [...tmuxSocketFlags(env), ...args];
4966
+ }
4967
+ function scrubTmuxClientEnv(env) {
4968
+ delete env.TMUX;
4969
+ delete env.TMUX_PANE;
4970
+ return env;
4971
+ }
4972
+
4863
4973
  function getProjectPath(workingDirectory) {
4864
4974
  const projectId = resolve$1(workingDirectory).replace(/[^a-zA-Z0-9-]/g, "-");
4865
4975
  const claudeConfigDir = process.env.CLAUDE_CONFIG_DIR || join(homedir(), ".claude");
@@ -9193,20 +9303,6 @@ function previewAscii(line) {
9193
9303
  return line.length > 80 ? `${out}\u2026` : out;
9194
9304
  }
9195
9305
 
9196
- const VH_TMUX_SOCKET_ENV = "VH_TMUX_SOCKET";
9197
- function tmuxSocketFlags(env = process.env) {
9198
- const socket = env[VH_TMUX_SOCKET_ENV];
9199
- return socket ? ["-S", socket] : [];
9200
- }
9201
- function tmuxArgs(args, env = process.env) {
9202
- return [...tmuxSocketFlags(env), ...args];
9203
- }
9204
- function scrubTmuxClientEnv(env) {
9205
- delete env.TMUX;
9206
- delete env.TMUX_PANE;
9207
- return env;
9208
- }
9209
-
9210
9306
  const CONTROL_CLIENT_KILL_GRACE_MS = 2e3;
9211
9307
  const CONTROL_COMMAND_TIMEOUT_MS = 1e4;
9212
9308
  function isSafeControlCommand(command) {
@@ -10024,7 +10120,7 @@ function sanitizeClosedTerminals(raw, max = CLOSED_TERMINALS_MAX) {
10024
10120
  const out = [];
10025
10121
  for (const item of raw) {
10026
10122
  if (!item || typeof item !== "object") continue;
10027
- const { id, title, cwd, mirrorSessionId, claudeSessionId, reason, closedAt, tags, manual } = item;
10123
+ const { id, title, cwd, mirrorSessionId, claudeSessionId, reason, closedAt, tags, manual, attachTmux } = item;
10028
10124
  if (typeof id !== "string" || id.length === 0 || typeof closedAt !== "number") continue;
10029
10125
  if (seen.has(id)) continue;
10030
10126
  seen.add(id);
@@ -10038,6 +10134,7 @@ function sanitizeClosedTerminals(raw, max = CLOSED_TERMINALS_MAX) {
10038
10134
  reason: reason === "daemon-gap" || reason === "closed" ? reason : void 0,
10039
10135
  ...tagList !== void 0 ? { tags: tagList } : {},
10040
10136
  ...manual === true ? { manual: true } : {},
10137
+ ...typeof attachTmux === "string" && attachTmux ? { attachTmux } : {},
10041
10138
  closedAt
10042
10139
  });
10043
10140
  }
@@ -10162,7 +10259,7 @@ function startupInjectionArgs(tmuxSession, command) {
10162
10259
  ];
10163
10260
  }
10164
10261
  function seenInfoOf(t) {
10165
- return { title: t.title, cwd: t.cwd, tags: t.tags, manual: t.manual };
10262
+ return { title: t.title, cwd: t.cwd, tags: t.tags, manual: t.manual, attachTmux: t.attachTmux };
10166
10263
  }
10167
10264
  function terminalListSignature(items) {
10168
10265
  const canon = [...items].sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0).map((t) => [
@@ -10175,7 +10272,8 @@ function terminalListSignature(items) {
10175
10272
  t.agentState ?? "",
10176
10273
  // B-105: a mirror binding appearing/disappearing MUST push the
10177
10274
  // list, or the web never learns the toggle became available.
10178
- t.mirrorSessionId ?? ""
10275
+ t.mirrorSessionId ?? "",
10276
+ t.attachTmux ?? ""
10179
10277
  ]);
10180
10278
  return JSON.stringify(canon);
10181
10279
  }
@@ -10194,7 +10292,7 @@ function deriveAutoTitle(paneTitle, hostname) {
10194
10292
  const chars = Array.from(t);
10195
10293
  return chars.length > TITLE_MAX_CHARS ? chars.slice(0, TITLE_MAX_CHARS).join("") : t;
10196
10294
  }
10197
- const LIST_FIELD_SEP = "";
10295
+ const LIST_FIELD_SEP = "<~|~>";
10198
10296
  const LIST_SESSIONS_FORMAT = [
10199
10297
  "#{session_name}",
10200
10298
  "#{session_created}",
@@ -10203,6 +10301,9 @@ const LIST_SESSIONS_FORMAT = [
10203
10301
  "#{@vh_title}",
10204
10302
  "#{@vh_title_manual}",
10205
10303
  "#{@vh_tags}",
10304
+ // B-273: the user tmux session this terminal was opened to attach (name),
10305
+ // carried into close records so a restore can re-attach.
10306
+ "#{@vh_attach}",
10206
10307
  // B-121: the control-mode client has no `pty.process`, so the agent-state
10207
10308
  // fast path lost its live `#{pane_current_command}` equivalent. Carry it in
10208
10309
  // the ONE list-sessions call the tracker already makes — the value goes
@@ -10217,8 +10318,8 @@ const LIST_SESSIONS_FORMAT = [
10217
10318
  function parseSessionListLine(line) {
10218
10319
  if (!line) return void 0;
10219
10320
  const parts = line.split(LIST_FIELD_SEP);
10220
- if (parts.length < 9) return void 0;
10221
- const [name, created, activity, cwd, vhTitle, manual, vhTags, paneCommand] = parts;
10321
+ if (parts.length < 10) return void 0;
10322
+ const [name, created, activity, cwd, vhTitle, manual, vhTags, vhAttach, paneCommand] = parts;
10222
10323
  if (!name) return void 0;
10223
10324
  return {
10224
10325
  name,
@@ -10229,9 +10330,12 @@ function parseSessionListLine(line) {
10229
10330
  manual: manual.trim().length > 0,
10230
10331
  tags: parseTerminalTags(vhTags),
10231
10332
  paneCurrentCommand: paneCommand.trim() || void 0,
10232
- // pane_title is last, so anything after field 8 is title content that
10333
+ // Verbatim (no trim): tmux allows edge spaces in a session name and the
10334
+ // restore lookup / attach echo compare it exactly.
10335
+ attachTmux: isSafeTmuxSessionName(vhAttach) ? vhAttach : void 0,
10336
+ // pane_title is last, so anything after field 9 is title content that
10233
10337
  // contained the separator — rejoin it rather than dropping it.
10234
- paneTitle: parts.slice(8).join(LIST_FIELD_SEP) || void 0
10338
+ paneTitle: parts.slice(9).join(LIST_FIELD_SEP) || void 0
10235
10339
  };
10236
10340
  }
10237
10341
  const TERMINAL_TAG_MAX_COUNT = 64;
@@ -10288,12 +10392,12 @@ function isClaudeConfident(tail) {
10288
10392
  const last15 = lines.slice(-15).join("\n");
10289
10393
  return last15.includes("Do you want") || last15.includes("Would you like to proceed") || /^[\s│]*[❯>]\s*1\.\s/m.test(last15) || text.includes("esc to interrupt") || text.includes("? for shortcuts") || text.includes("bypass permissions on") || text.includes("\u23F5\u23F5");
10290
10394
  }
10291
- function planScrollAction(paneInMode, alternateOn, paneWantsMouse, lines, claudeLike = false, paneRows = 24) {
10395
+ function planScrollAction(paneInMode, alternateOn, paneWantsMouse, lines, claudeLike = false, paneRows = 24, nestedTmux = false) {
10292
10396
  const count = Math.min(Math.abs(Math.trunc(lines)), 200);
10293
10397
  if (count === 0) return { kind: "none" };
10294
10398
  const up = lines > 0;
10295
10399
  if (paneInMode) return { kind: "copy-scroll", dir: up ? "up" : "down", count };
10296
- if (alternateOn && paneWantsMouse) return { kind: "mouse-wheel", dir: up ? "up" : "down", count };
10400
+ if (alternateOn && (paneWantsMouse || nestedTmux)) return { kind: "mouse-wheel", dir: up ? "up" : "down", count };
10297
10401
  if (alternateOn && claudeLike) {
10298
10402
  const halfPage = Math.max(1, Math.floor(paneRows / 2));
10299
10403
  const pages = Math.max(1, Math.round(count / halfPage));
@@ -10408,6 +10512,9 @@ class TerminalSession {
10408
10512
  id;
10409
10513
  tmuxSession;
10410
10514
  transport;
10515
+ /** B-273: the user tmux session this terminal was created to attach —
10516
+ * echoed to any later open of the same live session. */
10517
+ attachedTmux;
10411
10518
  /**
10412
10519
  * The pane this terminal follows, latched from the open capture's
10413
10520
  * `list-panes` (first pane of the window). `%output` from any OTHER pane is
@@ -10918,6 +11025,7 @@ class WebTerminalManager {
10918
11025
  cwd: info.cwd,
10919
11026
  tags: info.tags,
10920
11027
  manual: info.manual,
11028
+ ...info.attachTmux ? { attachTmux: info.attachTmux } : {},
10921
11029
  // Live binding first (authoritative while it exists), persisted
10922
11030
  // metadata as the fallback once the mirror has been torn down.
10923
11031
  mirrorSessionId: this.mirrorResolver?.(id) ?? mirror?.sessionId,
@@ -10970,6 +11078,7 @@ class WebTerminalManager {
10970
11078
  cwd: info.cwd,
10971
11079
  tags: info.tags,
10972
11080
  manual: info.manual,
11081
+ ...info.attachTmux ? { attachTmux: info.attachTmux } : {},
10973
11082
  mirrorSessionId: mirror?.sessionId,
10974
11083
  claudeSessionId: mirror?.claudeSessionId,
10975
11084
  reason: "daemon-gap",
@@ -11089,12 +11198,17 @@ class WebTerminalManager {
11089
11198
  return false;
11090
11199
  }
11091
11200
  },
11092
- conversationExists: (cwd, claudeSessionId) => existsSync(join(getProjectPath(cwd), `${claudeSessionId}.jsonl`))
11201
+ conversationExists: (cwd, claudeSessionId) => existsSync(join(getProjectPath(cwd), `${claudeSessionId}.jsonl`)),
11202
+ // B-273: only consulted for records that were attach terminals.
11203
+ // Unbounded: the panel's 50-row cap must not hide a restore target.
11204
+ userSessions: record.attachTmux ? this.listUserTmuxSessions(Infinity) : [],
11205
+ attachSocket: process.env[VH_TMUX_SOCKET_ENV],
11206
+ homeDir: os$1.homedir()
11093
11207
  });
11094
11208
  if (plan.kind === "already-live") return { type: "success", terminalId };
11095
11209
  if (plan.kind === "error") return { type: "error", reason: plan.reason };
11096
11210
  if (!this.createDetachedTerminal(plan)) return { type: "error", reason: "create-failed" };
11097
- logger.info(`[WEB TERMINAL] restored closed terminal ${terminalId} in ${plan.cwd}${plan.command ? " (conversation resumed)" : ""}`);
11211
+ logger.info(`[WEB TERMINAL] restored closed terminal ${terminalId} in ${plan.cwd}${plan.attachTmux ? ` (attached tmux session ${plan.attachTmux})` : plan.command ? " (conversation resumed)" : ""}`);
11098
11212
  this.kickListRefresh();
11099
11213
  return { type: "success", terminalId };
11100
11214
  }
@@ -11154,6 +11268,13 @@ class WebTerminalManager {
11154
11268
  { stdio: "ignore", timeout: TMUX_PROBE_TIMEOUT_MS, env }
11155
11269
  );
11156
11270
  }
11271
+ if (plan.attachTmux) {
11272
+ spawnSync(
11273
+ "tmux",
11274
+ tmuxArgs(["set-option", "-t", `=${name}:`, "@vh_attach", plan.attachTmux]),
11275
+ { stdio: "ignore", timeout: TMUX_PROBE_TIMEOUT_MS, env }
11276
+ );
11277
+ }
11157
11278
  if (plan.command) {
11158
11279
  for (const argv of startupInjectionArgs(name, plan.command)) {
11159
11280
  spawnSync("tmux", tmuxArgs(argv), { stdio: "ignore", timeout: TMUX_PROBE_TIMEOUT_MS, env });
@@ -11166,7 +11287,7 @@ class WebTerminalManager {
11166
11287
  * would care about changed (see liveSnapshotChanged). */
11167
11288
  persistLiveSnapshot(live, now) {
11168
11289
  const candidate = /* @__PURE__ */ new Map();
11169
- for (const [id, info] of live) candidate.set(id, { title: info.title, cwd: info.cwd, tags: info.tags, manual: info.manual, seenAt: now });
11290
+ for (const [id, info] of live) candidate.set(id, { title: info.title, cwd: info.cwd, tags: info.tags, manual: info.manual, ...info.attachTmux ? { attachTmux: info.attachTmux } : {}, seenAt: now });
11170
11291
  if (!liveSnapshotChanged(this.lastPersistedSnapshot, candidate)) return;
11171
11292
  this.lastPersistedSnapshot = candidate;
11172
11293
  saveLiveSnapshot(candidate);
@@ -11264,6 +11385,7 @@ class WebTerminalManager {
11264
11385
  const lines = opts.streamMode === "lines";
11265
11386
  const existing = this.terminals.get(id);
11266
11387
  if (existing) {
11388
+ const echo2 = opts.attachTmux && !(opts.attachOnly || opts.resub) && existing.attachedTmux?.name === opts.attachTmux.name ? { attachedTmux: existing.attachedTmux } : {};
11267
11389
  if (!opts.resub || existing.subscribers === 0) existing.subscribers += 1;
11268
11390
  existing.lastTouch = Date.now();
11269
11391
  this.applyResize(existing, cols, rows);
@@ -11276,15 +11398,16 @@ class WebTerminalManager {
11276
11398
  seq: existing.seq,
11277
11399
  mode: "replay",
11278
11400
  chunks,
11279
- ...lines ? { streamMode: "lines" } : {}
11401
+ ...lines ? { streamMode: "lines" } : {},
11402
+ ...echo2
11280
11403
  };
11281
11404
  }
11282
11405
  if (lines && existing.transport.kind === "control") {
11283
- return await this.linesSnapshotResponse(id, existing);
11406
+ return { ...await this.linesSnapshotResponse(id, existing), ...echo2 };
11284
11407
  }
11285
11408
  const state = existing.subscribeState(opts.fromSeq);
11286
11409
  logger.debug(`[WEB TERMINAL] re-subscribed ${id} (subs=${existing.subscribers}, mode=${state.mode}, seq=${state.seq})`);
11287
- return { terminalId: id, tmuxSession: existing.tmuxSession, ...state };
11410
+ return { terminalId: id, tmuxSession: existing.tmuxSession, ...state, ...echo2 };
11288
11411
  }
11289
11412
  if (opts.attachOnly || opts.resub) {
11290
11413
  const alive = isTmuxAvailable() && spawnSync(
@@ -11309,6 +11432,10 @@ class WebTerminalManager {
11309
11432
  let file = defaultShell();
11310
11433
  let args = [];
11311
11434
  let tmuxSession;
11435
+ let attachedTmux;
11436
+ if (opts.attachTmux && !(opts.attachOnly || opts.resub) && !isTmuxAvailable()) {
11437
+ throw new Error("tmux-unavailable");
11438
+ }
11312
11439
  if (isTmuxAvailable()) {
11313
11440
  tmuxSession = `vh-${id}`;
11314
11441
  let createdNew = false;
@@ -11321,6 +11448,7 @@ class WebTerminalManager {
11321
11448
  `VH_HAPPY_HOME_DIR=${configuration.happyHomeDir}`
11322
11449
  ] : [];
11323
11450
  const attachOnly = !!(opts.attachOnly || opts.resub);
11451
+ const attachTarget = !attachOnly && opts.attachTmux ? this.resolveAttachTarget(opts.attachTmux) : void 0;
11324
11452
  if (!attachOnly) {
11325
11453
  try {
11326
11454
  const created = spawnSync(
@@ -11362,16 +11490,15 @@ class WebTerminalManager {
11362
11490
  }
11363
11491
  }
11364
11492
  if (createdNew) {
11365
- const startup = normalizeStartupCommand(opts.startupCommand);
11366
- if (startup) {
11367
- try {
11368
- for (const a of startupInjectionArgs(tmuxSession, startup)) {
11369
- spawnSync("tmux", tmuxArgs(a), { stdio: "ignore", timeout: TMUX_PROBE_TIMEOUT_MS, env });
11370
- }
11371
- logger.debug(`[WEB TERMINAL] injected startup command into new session ${tmuxSession}`);
11372
- } catch {
11373
- }
11374
- }
11493
+ attachedTmux = this.injectIntoCreated(tmuxSession, attachTarget, opts.startupCommand, env);
11494
+ } else if (opts.attachTmux && !attachOnly) {
11495
+ const stored = spawnSync(
11496
+ "tmux",
11497
+ tmuxArgs(["show-options", "-qv", "-t", `=${tmuxSession}:`, "@vh_attach"]),
11498
+ { encoding: "utf8", timeout: TMUX_PROBE_TIMEOUT_MS, env }
11499
+ );
11500
+ const name = stored.status === 0 && typeof stored.stdout === "string" ? stored.stdout.replace(/\r?\n$/, "") : "";
11501
+ if (name && name === opts.attachTmux.name) attachedTmux = { id: opts.attachTmux.id, name };
11375
11502
  }
11376
11503
  if (!attachOnly && !createdNew) {
11377
11504
  const alive = spawnSync(
@@ -11386,17 +11513,7 @@ class WebTerminalManager {
11386
11513
  { stdio: "ignore", timeout: TMUX_CREATE_TIMEOUT_MS, env }
11387
11514
  );
11388
11515
  createdNew = retry.status === 0;
11389
- if (createdNew) {
11390
- const startup = normalizeStartupCommand(opts.startupCommand);
11391
- if (startup) {
11392
- for (const a of startupInjectionArgs(tmuxSession, startup)) {
11393
- try {
11394
- spawnSync("tmux", tmuxArgs(a), { stdio: "ignore", timeout: TMUX_PROBE_TIMEOUT_MS, env });
11395
- } catch {
11396
- }
11397
- }
11398
- }
11399
- }
11516
+ if (createdNew) attachedTmux = this.injectIntoCreated(tmuxSession, attachTarget, opts.startupCommand, env);
11400
11517
  }
11401
11518
  }
11402
11519
  if (!attachOnly && !createdNew) {
@@ -11406,6 +11523,7 @@ class WebTerminalManager {
11406
11523
  { stdio: "ignore", timeout: TMUX_PROBE_TIMEOUT_MS, env }
11407
11524
  ).status === 0;
11408
11525
  if (!alive) {
11526
+ if (attachTarget) throw new Error("tmux-unavailable");
11409
11527
  logger.info(`[WEB TERMINAL] tmux session creation unavailable; using direct PTY for ${id}`);
11410
11528
  tmuxSession = void 0;
11411
11529
  }
@@ -11488,20 +11606,110 @@ class WebTerminalManager {
11488
11606
  session = created;
11489
11607
  }
11490
11608
  session.subscribers = 1;
11609
+ if (attachedTmux) session.attachedTmux = attachedTmux;
11491
11610
  this.terminals.set(id, session);
11492
11611
  session.onTitleChange(() => this.kickListRefresh());
11493
11612
  logger.debug(`[WEB TERMINAL] opened ${id} (${tmuxSession ? "control" : "pty"}) ${cols}x${rows} cwd=${cwd}`);
11494
11613
  this.kickListRefresh();
11614
+ const echo = attachedTmux ? { attachedTmux } : {};
11495
11615
  if (session.transport.kind !== "control") {
11496
11616
  const state = session.subscribeState(void 0);
11497
- return { terminalId: id, tmuxSession, ...state, ...lines ? { streamMode: "lines" } : {} };
11617
+ return { terminalId: id, tmuxSession, ...state, ...lines ? { streamMode: "lines" } : {}, ...echo };
11498
11618
  }
11499
11619
  const restored = await this.captureRestore(session, true);
11500
11620
  if (!lines) {
11501
11621
  const state = session.subscribeState(void 0);
11502
- return { terminalId: id, tmuxSession, ...state };
11622
+ return { terminalId: id, tmuxSession, ...state, ...echo };
11623
+ }
11624
+ return { ...this.linesResponse(id, session, restored), ...echo };
11625
+ }
11626
+ /**
11627
+ * Post-create injection shared by BOTH creation sites in open(): an attach
11628
+ * request (B-273) pins the title to the user session's name (the inner
11629
+ * tmux's pane title is not reliable), stores `@vh_attach` for close
11630
+ * records / restore and types the attach line; otherwise the ordinary
11631
+ * startup command runs. Returns what to echo as `attachedTmux`.
11632
+ */
11633
+ injectIntoCreated(tmuxSession, attachTarget, startupCommand, env) {
11634
+ if (attachTarget) {
11635
+ for (const a of [
11636
+ ["set-option", "-t", `=${tmuxSession}:`, "@vh_title", attachTarget.name],
11637
+ ["set-option", "-t", `=${tmuxSession}:`, "@vh_title_manual", "1"],
11638
+ ["set-option", "-t", `=${tmuxSession}:`, "@vh_attach", attachTarget.name],
11639
+ ...startupInjectionArgs(tmuxSession, attachStartupCommand(attachTarget.id, process.env[VH_TMUX_SOCKET_ENV]))
11640
+ ]) {
11641
+ try {
11642
+ spawnSync("tmux", tmuxArgs(a), { stdio: "ignore", timeout: TMUX_PROBE_TIMEOUT_MS, env });
11643
+ } catch {
11644
+ }
11645
+ }
11646
+ logger.debug(`[WEB TERMINAL] ${tmuxSession} attaches user tmux session ${attachTarget.id} (${attachTarget.name})`);
11647
+ return { id: attachTarget.id, name: attachTarget.name };
11648
+ }
11649
+ const startup = normalizeStartupCommand(startupCommand);
11650
+ if (startup) {
11651
+ try {
11652
+ for (const a of startupInjectionArgs(tmuxSession, startup)) {
11653
+ spawnSync("tmux", tmuxArgs(a), { stdio: "ignore", timeout: TMUX_PROBE_TIMEOUT_MS, env });
11654
+ }
11655
+ logger.debug(`[WEB TERMINAL] injected startup command into new session ${tmuxSession}`);
11656
+ } catch {
11657
+ }
11658
+ }
11659
+ return void 0;
11660
+ }
11661
+ /**
11662
+ * B-273: check an attach request against the live tmux server. The id
11663
+ * must be a real `$N` (session ids only reset when the tmux server
11664
+ * restarts, so a stale id from an older list simply does not exist)
11665
+ * whose CURRENT name equals the name the web saw — the name cross-check
11666
+ * is what refuses a target that was killed and re-created in between —
11667
+ * and must not be a vh-* web terminal. Throws the 'tmux-session-gone'
11668
+ * contract string. Note `display-message -t '$N'` exits 0 with an EMPTY
11669
+ * line for an unknown id (its target lookup may fail silently), which is
11670
+ * why existence is probed with `has-session` first.
11671
+ */
11672
+ resolveAttachTarget(req) {
11673
+ const id = typeof req?.id === "string" ? req.id : "";
11674
+ const name = typeof req?.name === "string" ? req.name : "";
11675
+ if (!TMUX_SESSION_ID_RE.test(id) || !isSafeTmuxSessionName(name) || isVhSessionName(name)) throw new Error("tmux-session-gone");
11676
+ const env = ptyEnv();
11677
+ const exists = spawnSync("tmux", tmuxArgs(["has-session", "-t", id]), { stdio: "ignore", timeout: TMUX_PROBE_TIMEOUT_MS, env }).status === 0;
11678
+ if (!exists) throw new Error("tmux-session-gone");
11679
+ const r = spawnSync(
11680
+ "tmux",
11681
+ tmuxArgs(["display-message", "-p", "-t", id, "#{session_name}"]),
11682
+ { encoding: "utf8", timeout: TMUX_PROBE_TIMEOUT_MS, env }
11683
+ );
11684
+ const current = r.status === 0 && typeof r.stdout === "string" ? r.stdout.replace(/\r?\n$/, "") : void 0;
11685
+ if (current !== name) throw new Error("tmux-session-gone");
11686
+ return { id, name };
11687
+ }
11688
+ /** B-273: the user's own tmux sessions (never vh-*), newest activity
11689
+ * first. Empty when tmux is missing or no server runs. */
11690
+ /** Last raw outcome of the list-tmux-sessions spawn (diagnostics only). */
11691
+ lastUserSessionsProbe;
11692
+ listUserTmuxSessions(max) {
11693
+ if (!isTmuxAvailable()) {
11694
+ this.lastUserSessionsProbe = { status: null, error: "tmux-unavailable", stdoutBytes: 0 };
11695
+ return [];
11696
+ }
11697
+ try {
11698
+ const r = spawnSync(
11699
+ "tmux",
11700
+ tmuxArgs(["list-sessions", "-F", USER_SESSIONS_FORMAT]),
11701
+ { encoding: "utf8", timeout: TMUX_CREATE_TIMEOUT_MS, env: ptyEnv() }
11702
+ );
11703
+ this.lastUserSessionsProbe = { status: r.status, error: r.error?.message, stderr: (r.stderr || "").toString().trim() || void 0, stdoutBytes: typeof r.stdout === "string" ? r.stdout.length : 0 };
11704
+ if (r.status !== 0 || typeof r.stdout !== "string") {
11705
+ logger.debug(`[WEB TERMINAL] list-tmux-sessions failed: ${JSON.stringify(this.lastUserSessionsProbe)}`);
11706
+ return [];
11707
+ }
11708
+ return parseUserSessions(r.stdout, max);
11709
+ } catch (e) {
11710
+ this.lastUserSessionsProbe = { status: null, error: e instanceof Error ? e.message : String(e), stdoutBytes: 0 };
11711
+ return [];
11503
11712
  }
11504
- return this.linesResponse(id, session, restored);
11505
11713
  }
11506
11714
  /**
11507
11715
  * Run the opening/catch-up capture for a control session and prime the
@@ -11743,6 +11951,7 @@ class WebTerminalManager {
11743
11951
  cwd: info.cwd,
11744
11952
  tags: info.tags,
11745
11953
  manual: info.manual,
11954
+ ...info.attachTmux ? { attachTmux: info.attachTmux } : {},
11746
11955
  mirrorSessionId: this.mirrorResolver?.(terminalId) ?? mirror?.sessionId,
11747
11956
  claudeSessionId: mirror?.claudeSessionId,
11748
11957
  reason: "closed",
@@ -11831,7 +12040,8 @@ class WebTerminalManager {
11831
12040
  // Daemon-internal (not pushed to web / not in the signature):
11832
12041
  // the mirror reconciler's stricter "claude is really here" gate.
11833
12042
  claudeConfident: probe.claudeConfident,
11834
- manual: s.manual
12043
+ manual: s.manual,
12044
+ ...s.attachTmux ? { attachTmux: s.attachTmux } : {}
11835
12045
  });
11836
12046
  }
11837
12047
  return out;
@@ -11898,18 +12108,19 @@ class WebTerminalManager {
11898
12108
  try {
11899
12109
  const probe = spawnSync(
11900
12110
  "tmux",
11901
- tmuxArgs(["display-message", "-p", "-t", name, "#{pane_in_mode} #{alternate_on} #{mouse_any_flag} #{pane_width} #{pane_height} #{pane_current_command}"]),
12111
+ tmuxArgs(["display-message", "-p", "-t", name, ["#{pane_in_mode}", "#{alternate_on}", "#{mouse_any_flag}", "#{pane_width}", "#{pane_height}", "#{pane_current_command}"].join(LIST_FIELD_SEP)]),
11902
12112
  { encoding: "utf8", timeout: TMUX_PROBE_TIMEOUT_MS, env: ptyEnv() }
11903
12113
  );
11904
12114
  if (probe.status !== 0 || typeof probe.stdout !== "string") return;
11905
- const [inMode, altOn, wantsMouse, paneW, paneH, paneCmd] = probe.stdout.trim().split(" ");
12115
+ const [inMode, altOn, wantsMouse, paneW, paneH, paneCmd] = probe.stdout.trim().split(LIST_FIELD_SEP);
11906
12116
  const action = planScrollAction(
11907
12117
  inMode === "1",
11908
12118
  altOn === "1",
11909
12119
  wantsMouse === "1",
11910
12120
  lines,
11911
12121
  looksLikeClaudeCommand(paneCmd || ""),
11912
- Number(paneH) || 24
12122
+ Number(paneH) || 24,
12123
+ (paneCmd || "").trim() === "tmux"
11913
12124
  );
11914
12125
  if (action.kind === "none") return;
11915
12126
  if (action.kind === "mouse-wheel") {
@@ -13921,7 +14132,8 @@ class ApiMachineClient {
13921
14132
  this.syncResumeSessionRpcRegistration();
13922
14133
  this.syncRestartSessionRpcRegistration();
13923
14134
  this.rpcHandlerManager.registerHandler("open-terminal", async (params) => {
13924
- const { terminalId, cols, rows, cwd, fromSeq, encStream, startupCommand, resub, attachOnly, streamMode } = params || {};
14135
+ const { terminalId, cols, rows, cwd, fromSeq, encStream, startupCommand, resub, attachOnly, streamMode, attachTmux } = params || {};
14136
+ const attach = attachTmux && typeof attachTmux === "object" && typeof attachTmux.id === "string" && typeof attachTmux.name === "string" ? { id: attachTmux.id, name: attachTmux.name } : void 0;
13925
14137
  const result = await this.webTerminal.open({
13926
14138
  terminalId,
13927
14139
  cols,
@@ -13931,7 +14143,8 @@ class ApiMachineClient {
13931
14143
  startupCommand,
13932
14144
  resub,
13933
14145
  attachOnly,
13934
- streamMode: streamMode === "lines" ? "lines" : void 0
14146
+ streamMode: streamMode === "lines" ? "lines" : void 0,
14147
+ ...attach ? { attachTmux: attach } : {}
13935
14148
  });
13936
14149
  if (encStream) {
13937
14150
  this.encTerminals.add(result.terminalId);
@@ -14006,6 +14219,9 @@ class ApiMachineClient {
14006
14219
  this.rpcHandlerManager.registerHandler("list-terminals", async () => {
14007
14220
  return { type: "success", terminals: this.webTerminal.buildTerminalList() };
14008
14221
  });
14222
+ this.rpcHandlerManager.registerHandler("list-tmux-sessions", async () => {
14223
+ return { type: "success", sessions: this.webTerminal.listUserTmuxSessions() };
14224
+ });
14009
14225
  this.rpcHandlerManager.registerHandler("set-terminal-title", async (params) => {
14010
14226
  const { terminalId, title, ifAbsent } = params || {};
14011
14227
  if (!terminalId || typeof title !== "string") {
@@ -14399,6 +14615,8 @@ class ApiMachineClient {
14399
14615
  // B-265 capability flag; restamped every connect so the
14400
14616
  // web's `detectedAt >= startedAt` trust rule holds.
14401
14617
  terminalRestore: { rpcAvailable: true, detectedAt: now },
14618
+ // B-273 capability flag (same restamp discipline).
14619
+ tmuxSessions: { rpcAvailable: true, detectedAt: now },
14402
14620
  // B-084: closed records survive daemon restarts (persisted
14403
14621
  // in closed-terminals.json), so the connect snapshot ships
14404
14622
  // them too — not just the incremental pushes.
@@ -15740,4 +15958,4 @@ const RawJSONLinesSchema = z$1.discriminatedUnion("type", [
15740
15958
  }).passthrough()
15741
15959
  ]);
15742
15960
 
15743
- export { detectResumeSupport as $, ApiClient as A, deriveLocalCliUpdateSummary as B, updateSettings as C, encodeBase64Url as D, decodeBase64 as E, writeCredentialsLegacy as F, writeCredentialsDataKey as G, readCredentialsForConfiguredRelay as H, InvalidateSync as I, encrypt as J, isClaudeSessionId as K, getLocalHappyAgentCredentialPath as L, MAX_CHAT_ATTACHMENT_SOURCE_BYTES as M, readLocalHappyAgentCredentials as N, decryptWithDataKey as O, decryptLegacy as P, extractCompleteLines as Q, RawJSONLinesSchema as R, decideBackfill as S, parseTerminalHookPayload as T, tmuxArgs as U, scrubTmuxClientEnv as V, decideMirrorBinding as W, MIRROR_BACKFILL_LINES_DEFAULT as X, persistSession as Y, mirrorLineKey as Z, mirrorLocalId as _, ApiSessionClient as a, detectCLIAvailability as a0, acquireDaemonLock as a1, summarizeSpawnSessionForLog as a2, deletePersistedSessions as a3, decrypt as a4, writeDaemonState as a5, writeSettings as a6, fetchCliUpdateState as a7, resolveCliUpdateCheckInterval as a8, DEFAULT_CLI_UPDATE_CHECK_INTERVAL_MS as a9, releaseDaemonLock as aa, isValidSessionId as ab, listVhTerminals as ac, isValidTerminalId as ad, readVhTerminal as ae, sendToVhTerminal as af, startOfflineReconnection as ag, clearCredentials as ah, clearMachineId as ai, SandboxConfigSchema as aj, CodexAppServerClient as ak, getLatestDaemonLog as al, persistence as am, api as an, createEnvelope as b, configuration as c, contentLogMetadata as d, errorLogMetadata as e, packageJson as f, delay as g, connectionState as h, readSettings as i, encodeBase64 as j, ensurePrivateDirectorySync as k, logger as l, hardenPrivateFileSync as m, readDaemonState as n, clearDaemonState as o, projectPath as p, getProjectPath as q, readPersistedSessions as r, initializeSandbox as s, wrapCommand as t, AsyncLock as u, ensurePrivateDirectory as v, writePrivateFileSync as w, hardenPrivateFile as x, readCredentials as y, credentialRelayProblem as z };
15961
+ export { detectResumeSupport as $, ApiClient as A, deriveLocalCliUpdateSummary as B, updateSettings as C, encodeBase64Url as D, decodeBase64 as E, writeCredentialsLegacy as F, writeCredentialsDataKey as G, readCredentialsForConfiguredRelay as H, InvalidateSync as I, encrypt as J, isClaudeSessionId as K, getLocalHappyAgentCredentialPath as L, MAX_CHAT_ATTACHMENT_SOURCE_BYTES as M, readLocalHappyAgentCredentials as N, decryptWithDataKey as O, decryptLegacy as P, extractCompleteLines as Q, RawJSONLinesSchema as R, decideBackfill as S, parseTerminalHookPayload as T, tmuxArgs as U, scrubTmuxClientEnv as V, decideMirrorBinding as W, MIRROR_BACKFILL_LINES_DEFAULT as X, persistSession as Y, mirrorLineKey as Z, mirrorLocalId as _, ApiSessionClient as a, detectCLIAvailability as a0, acquireDaemonLock as a1, summarizeSpawnSessionForLog as a2, deletePersistedSessions as a3, shellescape as a4, decrypt as a5, writeDaemonState as a6, writeSettings as a7, fetchCliUpdateState as a8, resolveCliUpdateCheckInterval as a9, DEFAULT_CLI_UPDATE_CHECK_INTERVAL_MS as aa, releaseDaemonLock as ab, isValidSessionId as ac, listVhTerminals as ad, isValidTerminalId as ae, readVhTerminal as af, sendToVhTerminal as ag, startOfflineReconnection as ah, clearCredentials as ai, clearMachineId as aj, SandboxConfigSchema as ak, CodexAppServerClient as al, getLatestDaemonLog as am, persistence as an, api as ao, createEnvelope as b, configuration as c, contentLogMetadata as d, errorLogMetadata as e, packageJson as f, delay as g, connectionState as h, readSettings as i, encodeBase64 as j, ensurePrivateDirectorySync as k, logger as l, hardenPrivateFileSync as m, readDaemonState as n, clearDaemonState as o, projectPath as p, getProjectPath as q, readPersistedSessions as r, initializeSandbox as s, wrapCommand as t, AsyncLock as u, ensurePrivateDirectory as v, writePrivateFileSync as w, hardenPrivateFile as x, readCredentials as y, credentialRelayProblem as z };