very-happy-cli 0.2.93 → 0.2.95

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-COv_3umE.cjs → AcpBackend-BXsbwTbl.cjs} +1 -1
  2. package/dist/{AcpBackend-C5507ePk.mjs → AcpBackend-Cuiff4Nu.mjs} +1 -1
  3. package/dist/{AcpSessionManager-Bd1trXWx.mjs → AcpSessionManager-BU69q_vR.mjs} +1 -1
  4. package/dist/{AcpSessionManager-vm4rIcH0.cjs → AcpSessionManager-BXGmMoe8.cjs} +1 -1
  5. package/dist/{config-BJjvra5x.mjs → config-2GD21yg_.mjs} +2 -2
  6. package/dist/{config-SZEQWS8k.cjs → config-BVOE0pni.cjs} +2 -2
  7. package/dist/{index-DaVhaBF2.cjs → index-BzbaMrIa.cjs} +333 -49
  8. package/dist/{index-17NbIAkU.cjs → index-CbPc1iVx.cjs} +4 -4
  9. package/dist/{index-C00jxV83.mjs → index-DVEdpWq0.mjs} +331 -47
  10. package/dist/{index-D-U-7DAy.mjs → index-DZ0uw6ef.mjs} +5 -5
  11. package/dist/index.cjs +2 -2
  12. package/dist/index.mjs +2 -2
  13. package/dist/{installTerminalHooks-CRjlRj9K.cjs → installTerminalHooks-BrrsyTMd.cjs} +2 -2
  14. package/dist/{installTerminalHooks-DsECLFy3.mjs → installTerminalHooks-FiujjEvb.mjs} +2 -2
  15. package/dist/lib.cjs +1 -1
  16. package/dist/lib.d.cts +9 -0
  17. package/dist/lib.d.mts +9 -0
  18. package/dist/lib.mjs +1 -1
  19. package/dist/{mcp-Bs6R9P7q.mjs → mcp-0mZcHPtz.mjs} +2 -2
  20. package/dist/{mcp-C-dDCrXm.cjs → mcp-ziyMk3eS.cjs} +2 -2
  21. package/dist/{runGemini-DapsCPLi.cjs → runGemini-C91O1oxi.cjs} +4 -4
  22. package/dist/{runGemini-CrSCxKqI.mjs → runGemini-D_InkOhk.mjs} +4 -4
  23. package/dist/{runOpenClaw-C9dhyV4x.cjs → runOpenClaw-B1RRfA1V.cjs} +3 -3
  24. package/dist/{runOpenClaw-Cnagj-hv.mjs → runOpenClaw-BjebGmV6.mjs} +3 -3
  25. package/dist/{send-DlHES1EO.mjs → send-CxLL0z9J.mjs} +2 -2
  26. package/dist/{send-PmtzSBVM.cjs → send-DgK6MtgT.cjs} +2 -2
  27. package/dist/{spawn-OE9kENQi.cjs → spawn-Dian7x_g.cjs} +2 -2
  28. package/dist/{spawn-DgZUVvT2.mjs → spawn-uTPD3qOt.mjs} +2 -2
  29. package/dist/{types-Cx5wn5Hf.mjs → types-BLE8Hfmh.mjs} +34 -14
  30. package/dist/{types-BHVxYajT.cjs → types-HRuZ13LS.cjs} +34 -14
  31. package/package.json +7 -7
package/dist/lib.cjs CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var persistence = require('./types-BHVxYajT.cjs');
3
+ var persistence = require('./types-HRuZ13LS.cjs');
4
4
  require('axios');
5
5
  require('chalk');
6
6
  require('node:util');
package/dist/lib.d.cts CHANGED
@@ -1055,6 +1055,12 @@ interface TerminalListItem {
1055
1055
  createdAt?: number;
1056
1056
  activityAt?: number;
1057
1057
  agentState?: AgentState;
1058
+ /** Mirror reconcile (design v3): stricter "claude is really here" gate (its
1059
+ * own TUI footer/dialog present) — daemon-internal, NOT pushed to web and
1060
+ * NOT part of the list signature. Only this may trigger mirror adopt (which
1061
+ * re-opens the B-107 input gate); `agentState` alone would false-positive
1062
+ * on a bare `node` process. */
1063
+ claudeConfident?: boolean;
1058
1064
  /** Terminal mirror (B-105): shadow session id of the hand-typed claude
1059
1065
  * running inside this terminal (set via the daemon's mirror resolver).
1060
1066
  * The web shows the xterm ↔ structured toggle when present. */
@@ -1147,6 +1153,9 @@ declare class ApiMachineClient {
1147
1153
  resolveMirrorSessionId: (terminalId: string) => string | undefined;
1148
1154
  onTerminalClosed: (terminalId: string) => void;
1149
1155
  onTerminalList: (terminals: TerminalListItem[]) => void;
1156
+ /** design v3: unconditional per-tick reconcile (self-heals lost bindings
1157
+ * on an unchanged-signature list). */
1158
+ onTerminalListTick: (terminals: TerminalListItem[]) => void;
1150
1159
  isMirrorInputAllowed: (terminalId: string) => boolean;
1151
1160
  }): void;
1152
1161
  /** Mirror bindings changed → re-derive and (on diff) push the list now. */
package/dist/lib.d.mts CHANGED
@@ -1055,6 +1055,12 @@ interface TerminalListItem {
1055
1055
  createdAt?: number;
1056
1056
  activityAt?: number;
1057
1057
  agentState?: AgentState;
1058
+ /** Mirror reconcile (design v3): stricter "claude is really here" gate (its
1059
+ * own TUI footer/dialog present) — daemon-internal, NOT pushed to web and
1060
+ * NOT part of the list signature. Only this may trigger mirror adopt (which
1061
+ * re-opens the B-107 input gate); `agentState` alone would false-positive
1062
+ * on a bare `node` process. */
1063
+ claudeConfident?: boolean;
1058
1064
  /** Terminal mirror (B-105): shadow session id of the hand-typed claude
1059
1065
  * running inside this terminal (set via the daemon's mirror resolver).
1060
1066
  * The web shows the xterm ↔ structured toggle when present. */
@@ -1147,6 +1153,9 @@ declare class ApiMachineClient {
1147
1153
  resolveMirrorSessionId: (terminalId: string) => string | undefined;
1148
1154
  onTerminalClosed: (terminalId: string) => void;
1149
1155
  onTerminalList: (terminals: TerminalListItem[]) => void;
1156
+ /** design v3: unconditional per-tick reconcile (self-heals lost bindings
1157
+ * on an unchanged-signature list). */
1158
+ onTerminalListTick: (terminals: TerminalListItem[]) => void;
1150
1159
  isMirrorInputAllowed: (terminalId: string) => boolean;
1151
1160
  }): void;
1152
1161
  /** Mirror bindings changed → re-derive and (on diff) push the list now. */
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-Cx5wn5Hf.mjs';
1
+ export { A as ApiClient, a as ApiSessionClient, R as RawJSONLinesSchema, c as configuration, l as logger } from './types-BLE8Hfmh.mjs';
2
2
  import 'axios';
3
3
  import 'chalk';
4
4
  import 'node:util';
@@ -1,9 +1,9 @@
1
1
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
2
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
3
  import { z } from 'zod';
4
- import { p as pushClipboardViaDaemon } from './index-C00jxV83.mjs';
4
+ import { p as pushClipboardViaDaemon } from './index-DVEdpWq0.mjs';
5
5
  import { C as CLIPBOARD_TOOL_NAME, a as CLIPBOARD_TOOL_TITLE, b as CLIPBOARD_TOOL_DESCRIPTION, c as CLIPBOARD_MAX_BYTES } from './limits-CwFb5S2b.mjs';
6
- import { l as logger } from './types-Cx5wn5Hf.mjs';
6
+ import { l as logger } from './types-BLE8Hfmh.mjs';
7
7
  import 'chalk';
8
8
  import 'node:os';
9
9
  import 'node:crypto';
@@ -3,9 +3,9 @@
3
3
  var mcp_js = require('@modelcontextprotocol/sdk/server/mcp.js');
4
4
  var stdio_js = require('@modelcontextprotocol/sdk/server/stdio.js');
5
5
  var z = require('zod');
6
- var index = require('./index-DaVhaBF2.cjs');
6
+ var index = require('./index-BzbaMrIa.cjs');
7
7
  var limits = require('./limits-KdWKmwtH.cjs');
8
- var persistence = require('./types-BHVxYajT.cjs');
8
+ var persistence = require('./types-HRuZ13LS.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-BHVxYajT.cjs');
8
- var index = require('./index-DaVhaBF2.cjs');
9
- var AcpBackend = require('./AcpBackend-COv_3umE.cjs');
10
- var config = require('./config-SZEQWS8k.cjs');
7
+ var persistence = require('./types-HRuZ13LS.cjs');
8
+ var index = require('./index-BzbaMrIa.cjs');
9
+ var AcpBackend = require('./AcpBackend-BXsbwTbl.cjs');
10
+ var config = require('./config-BVOE0pni.cjs');
11
11
  require('axios');
12
12
  require('chalk');
13
13
  require('node:util');
@@ -2,10 +2,10 @@ import { useStdout, useInput, Box, Text, render } from 'ink';
2
2
  import React, { useState, useRef, useEffect, useCallback } from 'react';
3
3
  import { randomUUID } from 'node:crypto';
4
4
  import { join } from 'node:path';
5
- import { l as logger, h as connectionState, A as ApiClient, i as readSettings, e as errorLogMetadata, j as encodeBase64, p as projectPath, d as contentLogMetadata } from './types-Cx5wn5Hf.mjs';
6
- import { g as GEMINI_API_KEY_ENV, j as GOOGLE_API_KEY_ENV, G as GEMINI_MODEL_ENV, B as BasePermissionHandler, k as BaseReasoningProcessor, i as initialMachineMetadata, d as createSessionMetadata, e as setupOfflineReconnection, n as notifyDaemonSessionStarted, M as MessageQueue2, h as hashObject, l as MessageBuffer, C as CHANGE_TITLE_INSTRUCTION, r as registerKillSessionHandler, f as startHappyServer } from './index-C00jxV83.mjs';
7
- import { A as AcpBackend } from './AcpBackend-C5507ePk.mjs';
8
- import { readGeminiLocalConfig, determineGeminiModel, getGeminiModelSource, getInitialGeminiModel, saveGeminiModelToConfig } from './config-BJjvra5x.mjs';
5
+ import { l as logger, h as connectionState, A as ApiClient, i as readSettings, e as errorLogMetadata, j as encodeBase64, p as projectPath, d as contentLogMetadata } from './types-BLE8Hfmh.mjs';
6
+ import { g as GEMINI_API_KEY_ENV, j as GOOGLE_API_KEY_ENV, G as GEMINI_MODEL_ENV, B as BasePermissionHandler, k as BaseReasoningProcessor, i as initialMachineMetadata, d as createSessionMetadata, e as setupOfflineReconnection, n as notifyDaemonSessionStarted, M as MessageQueue2, h as hashObject, l as MessageBuffer, C as CHANGE_TITLE_INSTRUCTION, r as registerKillSessionHandler, f as startHappyServer } from './index-DVEdpWq0.mjs';
7
+ import { A as AcpBackend } from './AcpBackend-Cuiff4Nu.mjs';
8
+ import { readGeminiLocalConfig, determineGeminiModel, getGeminiModelSource, getInitialGeminiModel, saveGeminiModelToConfig } from './config-2GD21yg_.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-BHVxYajT.cjs');
9
- var AcpSessionManager = require('./AcpSessionManager-vm4rIcH0.cjs');
10
- var index = require('./index-DaVhaBF2.cjs');
8
+ var persistence = require('./types-HRuZ13LS.cjs');
9
+ var AcpSessionManager = require('./AcpSessionManager-BXGmMoe8.cjs');
10
+ var index = require('./index-BzbaMrIa.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 { k as ensurePrivateDirectorySync, m as hardenPrivateFileSync, w as writePrivateFileSync, e as errorLogMetadata, d as contentLogMetadata, h as connectionState, A as ApiClient, i as readSettings, j as encodeBase64, l as logger, c as configuration } from './types-Cx5wn5Hf.mjs';
7
- import { A as AcpSessionManager } from './AcpSessionManager-Bd1trXWx.mjs';
8
- import { i as initialMachineMetadata, d as createSessionMetadata, e as setupOfflineReconnection, n as notifyDaemonSessionStarted, M as MessageQueue2, r as registerKillSessionHandler } from './index-C00jxV83.mjs';
6
+ import { k as ensurePrivateDirectorySync, m as hardenPrivateFileSync, w as writePrivateFileSync, e as errorLogMetadata, d as contentLogMetadata, h as connectionState, A as ApiClient, i as readSettings, j as encodeBase64, l as logger, c as configuration } from './types-BLE8Hfmh.mjs';
7
+ import { A as AcpSessionManager } from './AcpSessionManager-BU69q_vR.mjs';
8
+ import { i as initialMachineMetadata, d as createSessionMetadata, e as setupOfflineReconnection, n as notifyDaemonSessionStarted, M as MessageQueue2, r as registerKillSessionHandler } from './index-DVEdpWq0.mjs';
9
9
  import WebSocket from 'ws';
10
10
  import * as ed from '@noble/ed25519';
11
11
  import { sha512 } from '@noble/hashes/sha2.js';
@@ -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-Cx5wn5Hf.mjs';
5
- import { b as sendUserMessage, a as sessionWebUrl } from './index-C00jxV83.mjs';
4
+ import { r as readPersistedSessions, c as configuration } from './types-BLE8Hfmh.mjs';
5
+ import { b as sendUserMessage, a as sessionWebUrl } from './index-DVEdpWq0.mjs';
6
6
  import 'axios';
7
7
  import 'node:util';
8
8
  import 'node:os';
@@ -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-BHVxYajT.cjs');
7
- var index = require('./index-DaVhaBF2.cjs');
6
+ var persistence = require('./types-HRuZ13LS.cjs');
7
+ var index = require('./index-BzbaMrIa.cjs');
8
8
  require('axios');
9
9
  require('node:util');
10
10
  require('node:os');
@@ -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-DaVhaBF2.cjs');
7
- var persistence = require('./types-BHVxYajT.cjs');
6
+ var index = require('./index-BzbaMrIa.cjs');
7
+ var persistence = require('./types-HRuZ13LS.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, w as waitForSessionKey, b as sendUserMessage } from './index-C00jxV83.mjs';
5
- import { l as logger } from './types-Cx5wn5Hf.mjs';
4
+ import { c as checkIfDaemonRunningAndCleanupStaleState, s as spawnDaemonSession, a as sessionWebUrl, w as waitForSessionKey, b as sendUserMessage } from './index-DVEdpWq0.mjs';
5
+ import { l as logger } from './types-BLE8Hfmh.mjs';
6
6
  import 'node:os';
7
7
  import 'node:crypto';
8
8
  import 'cross-spawn';
@@ -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.93";
32
+ var version = "0.2.95";
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.93",
146
- "very-happy-tools-x64-darwin": "0.2.93",
147
- "very-happy-tools-arm64-linux": "0.2.93",
148
- "very-happy-tools-x64-linux": "0.2.93",
149
- "very-happy-tools-arm64-win32": "0.2.93",
150
- "very-happy-tools-x64-win32": "0.2.93"
145
+ "very-happy-tools-arm64-darwin": "0.2.95",
146
+ "very-happy-tools-x64-darwin": "0.2.95",
147
+ "very-happy-tools-arm64-linux": "0.2.95",
148
+ "very-happy-tools-x64-linux": "0.2.95",
149
+ "very-happy-tools-arm64-win32": "0.2.95",
150
+ "very-happy-tools-x64-win32": "0.2.95"
151
151
  };
152
152
  var devDependencies = {
153
153
  "@slopus/happy-wire": "workspace:*",
@@ -10257,6 +10257,13 @@ function looksLikeClaudeCommand(currentCommand) {
10257
10257
  const cmd = (currentCommand || "").trim().replace(/^-/, "").toLowerCase();
10258
10258
  return cmd === "claude" || cmd === "node" || /^\d+\.\d+(\.\d+)?$/.test(cmd);
10259
10259
  }
10260
+ function isClaudeConfident(tail) {
10261
+ const lines = tail.replace(/\r/g, "").split("\n").map((l) => l.trimEnd());
10262
+ while (lines.length > 0 && lines[lines.length - 1] === "") lines.pop();
10263
+ const text = lines.join("\n");
10264
+ const last15 = lines.slice(-15).join("\n");
10265
+ 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");
10266
+ }
10260
10267
  function planScrollAction(paneInMode, alternateOn, paneWantsMouse, lines, claudeLike = false, paneRows = 24) {
10261
10268
  const count = Math.min(Math.abs(Math.trunc(lines)), 200);
10262
10269
  if (count === 0) return { kind: "none" };
@@ -10643,9 +10650,16 @@ class WebTerminalManager {
10643
10650
  /** terminalId → shadow session id (daemon's mirror manager). Feeds the
10644
10651
  * list push (toggle availability) and closed-terminal records. */
10645
10652
  mirrorResolver = null;
10653
+ mirrorTickObserver = null;
10646
10654
  /** Fired once per terminal whose close was just recorded — the mirror
10647
10655
  * manager archives + tears down that terminal's binding. */
10648
10656
  onTerminalClosedCb = null;
10657
+ /** Mirror reconcile (design v3): fired every list-track tick, BEFORE the
10658
+ * signature short-circuit, so a claude-running terminal whose binding was
10659
+ * lost self-heals even when nothing about the list changed. */
10660
+ setMirrorTickObserver(fn) {
10661
+ this.mirrorTickObserver = fn;
10662
+ }
10649
10663
  setMirrorSessionResolver(fn) {
10650
10664
  this.mirrorResolver = fn;
10651
10665
  this.kickListRefresh();
@@ -10828,6 +10842,7 @@ class WebTerminalManager {
10828
10842
  this.emitActivity(Object.fromEntries(
10829
10843
  list.filter((t) => t.activityAt).map((t) => [t.id, t.activityAt])
10830
10844
  ));
10845
+ this.mirrorTickObserver?.(list);
10831
10846
  const sig = terminalListSignature(list);
10832
10847
  if (sig === this.lastListSignature) return;
10833
10848
  this.lastListSignature = sig;
@@ -11767,6 +11782,7 @@ class WebTerminalManager {
11767
11782
  } catch {
11768
11783
  }
11769
11784
  }
11785
+ const probe = this.probeAgentState(s.name, s.paneCurrentCommand);
11770
11786
  out.push({
11771
11787
  id,
11772
11788
  title,
@@ -11777,7 +11793,10 @@ class WebTerminalManager {
11777
11793
  // tmux last-activity (epoch s) → ms; optional so old daemons
11778
11794
  // simply omit it and web clients fall back to createdAt.
11779
11795
  activityAt: s.activity,
11780
- agentState: this.probeAgentState(s.name, s.paneCurrentCommand),
11796
+ agentState: probe.agentState,
11797
+ // Daemon-internal (not pushed to web / not in the signature):
11798
+ // the mirror reconciler's stricter "claude is really here" gate.
11799
+ claudeConfident: probe.claudeConfident,
11781
11800
  manual: s.manual
11782
11801
  });
11783
11802
  }
@@ -11799,7 +11818,7 @@ class WebTerminalManager {
11799
11818
  if (live) {
11800
11819
  try {
11801
11820
  const { command, tail } = live.agentProbeInput();
11802
- return classifyPane(command || polledCommand || "", tail);
11821
+ return { agentState: classifyPane(command || polledCommand || "", tail), claudeConfident: isClaudeConfident(tail) };
11803
11822
  } catch {
11804
11823
  }
11805
11824
  }
@@ -11815,16 +11834,16 @@ class WebTerminalManager {
11815
11834
  tmuxArgs(["display-message", "-p", "-t", sessionName, "#{pane_current_command}"]),
11816
11835
  { encoding: "utf8", timeout: TMUX_PROBE_TIMEOUT_MS, env: ptyEnv() }
11817
11836
  );
11818
- if (cmd.status !== 0 || typeof cmd.stdout !== "string") return void 0;
11837
+ if (cmd.status !== 0 || typeof cmd.stdout !== "string") return { agentState: void 0, claudeConfident: false };
11819
11838
  const cap = spawnSync(
11820
11839
  "tmux",
11821
11840
  tmuxArgs(["capture-pane", "-p", "-t", sessionName, "-S", "-40"]),
11822
11841
  { encoding: "utf8", timeout: TMUX_PROBE_TIMEOUT_MS, env: ptyEnv() }
11823
11842
  );
11824
- if (cap.status !== 0 || typeof cap.stdout !== "string") return void 0;
11825
- return classifyPane(cmd.stdout.trim(), cap.stdout);
11843
+ if (cap.status !== 0 || typeof cap.stdout !== "string") return { agentState: void 0, claudeConfident: false };
11844
+ return { agentState: classifyPane(cmd.stdout.trim(), cap.stdout), claudeConfident: isClaudeConfident(cap.stdout) };
11826
11845
  } catch {
11827
- return void 0;
11846
+ return { agentState: void 0, claudeConfident: false };
11828
11847
  }
11829
11848
  }
11830
11849
  /**
@@ -14151,6 +14170,7 @@ class ApiMachineClient {
14151
14170
  setMirrorIntegration(integration) {
14152
14171
  this.webTerminal.setMirrorSessionResolver(integration.resolveMirrorSessionId);
14153
14172
  this.webTerminal.setOnTerminalClosed(integration.onTerminalClosed);
14173
+ this.webTerminal.setMirrorTickObserver(integration.onTerminalListTick);
14154
14174
  this.mirrorListObserver = integration.onTerminalList;
14155
14175
  this.mirrorInputAllowed = integration.isMirrorInputAllowed;
14156
14176
  }
@@ -15644,4 +15664,4 @@ const RawJSONLinesSchema = z$1.discriminatedUnion("type", [
15644
15664
  }).passthrough()
15645
15665
  ]);
15646
15666
 
15647
- 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, persistSession as X, MIRROR_BACKFILL_LINES_DEFAULT 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, fetchCliUpdateState as a6, resolveCliUpdateCheckInterval as a7, DEFAULT_CLI_UPDATE_CHECK_INTERVAL_MS as a8, releaseDaemonLock as a9, isValidSessionId as aa, listVhTerminals as ab, isValidTerminalId as ac, readVhTerminal as ad, sendToVhTerminal as ae, startOfflineReconnection as af, clearCredentials as ag, clearMachineId as ah, SandboxConfigSchema as ai, CodexAppServerClient as aj, getLatestDaemonLog as ak, persistence as al, api as am, 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 };
15667
+ 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, fetchCliUpdateState as a6, resolveCliUpdateCheckInterval as a7, DEFAULT_CLI_UPDATE_CHECK_INTERVAL_MS as a8, releaseDaemonLock as a9, isValidSessionId as aa, listVhTerminals as ab, isValidTerminalId as ac, readVhTerminal as ad, sendToVhTerminal as ae, startOfflineReconnection as af, clearCredentials as ag, clearMachineId as ah, SandboxConfigSchema as ai, CodexAppServerClient as aj, getLatestDaemonLog as ak, persistence as al, api as am, 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 };
@@ -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.93";
54
+ var version = "0.2.95";
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.93",
168
- "very-happy-tools-x64-darwin": "0.2.93",
169
- "very-happy-tools-arm64-linux": "0.2.93",
170
- "very-happy-tools-x64-linux": "0.2.93",
171
- "very-happy-tools-arm64-win32": "0.2.93",
172
- "very-happy-tools-x64-win32": "0.2.93"
167
+ "very-happy-tools-arm64-darwin": "0.2.95",
168
+ "very-happy-tools-x64-darwin": "0.2.95",
169
+ "very-happy-tools-arm64-linux": "0.2.95",
170
+ "very-happy-tools-x64-linux": "0.2.95",
171
+ "very-happy-tools-arm64-win32": "0.2.95",
172
+ "very-happy-tools-x64-win32": "0.2.95"
173
173
  };
174
174
  var devDependencies = {
175
175
  "@slopus/happy-wire": "workspace:*",
@@ -1545,7 +1545,7 @@ class RpcHandlerManager {
1545
1545
  }
1546
1546
  }
1547
1547
 
1548
- 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-BHVxYajT.cjs', document.baseURI).href))));
1548
+ 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-HRuZ13LS.cjs', document.baseURI).href))));
1549
1549
  function projectPath() {
1550
1550
  const path = path$1.resolve(__dirname$1, "..");
1551
1551
  return path;
@@ -10279,6 +10279,13 @@ function looksLikeClaudeCommand(currentCommand) {
10279
10279
  const cmd = (currentCommand || "").trim().replace(/^-/, "").toLowerCase();
10280
10280
  return cmd === "claude" || cmd === "node" || /^\d+\.\d+(\.\d+)?$/.test(cmd);
10281
10281
  }
10282
+ function isClaudeConfident(tail) {
10283
+ const lines = tail.replace(/\r/g, "").split("\n").map((l) => l.trimEnd());
10284
+ while (lines.length > 0 && lines[lines.length - 1] === "") lines.pop();
10285
+ const text = lines.join("\n");
10286
+ const last15 = lines.slice(-15).join("\n");
10287
+ 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");
10288
+ }
10282
10289
  function planScrollAction(paneInMode, alternateOn, paneWantsMouse, lines, claudeLike = false, paneRows = 24) {
10283
10290
  const count = Math.min(Math.abs(Math.trunc(lines)), 200);
10284
10291
  if (count === 0) return { kind: "none" };
@@ -10665,9 +10672,16 @@ class WebTerminalManager {
10665
10672
  /** terminalId → shadow session id (daemon's mirror manager). Feeds the
10666
10673
  * list push (toggle availability) and closed-terminal records. */
10667
10674
  mirrorResolver = null;
10675
+ mirrorTickObserver = null;
10668
10676
  /** Fired once per terminal whose close was just recorded — the mirror
10669
10677
  * manager archives + tears down that terminal's binding. */
10670
10678
  onTerminalClosedCb = null;
10679
+ /** Mirror reconcile (design v3): fired every list-track tick, BEFORE the
10680
+ * signature short-circuit, so a claude-running terminal whose binding was
10681
+ * lost self-heals even when nothing about the list changed. */
10682
+ setMirrorTickObserver(fn) {
10683
+ this.mirrorTickObserver = fn;
10684
+ }
10671
10685
  setMirrorSessionResolver(fn) {
10672
10686
  this.mirrorResolver = fn;
10673
10687
  this.kickListRefresh();
@@ -10850,6 +10864,7 @@ class WebTerminalManager {
10850
10864
  this.emitActivity(Object.fromEntries(
10851
10865
  list.filter((t) => t.activityAt).map((t) => [t.id, t.activityAt])
10852
10866
  ));
10867
+ this.mirrorTickObserver?.(list);
10853
10868
  const sig = terminalListSignature(list);
10854
10869
  if (sig === this.lastListSignature) return;
10855
10870
  this.lastListSignature = sig;
@@ -11789,6 +11804,7 @@ class WebTerminalManager {
11789
11804
  } catch {
11790
11805
  }
11791
11806
  }
11807
+ const probe = this.probeAgentState(s.name, s.paneCurrentCommand);
11792
11808
  out.push({
11793
11809
  id,
11794
11810
  title,
@@ -11799,7 +11815,10 @@ class WebTerminalManager {
11799
11815
  // tmux last-activity (epoch s) → ms; optional so old daemons
11800
11816
  // simply omit it and web clients fall back to createdAt.
11801
11817
  activityAt: s.activity,
11802
- agentState: this.probeAgentState(s.name, s.paneCurrentCommand),
11818
+ agentState: probe.agentState,
11819
+ // Daemon-internal (not pushed to web / not in the signature):
11820
+ // the mirror reconciler's stricter "claude is really here" gate.
11821
+ claudeConfident: probe.claudeConfident,
11803
11822
  manual: s.manual
11804
11823
  });
11805
11824
  }
@@ -11821,7 +11840,7 @@ class WebTerminalManager {
11821
11840
  if (live) {
11822
11841
  try {
11823
11842
  const { command, tail } = live.agentProbeInput();
11824
- return classifyPane(command || polledCommand || "", tail);
11843
+ return { agentState: classifyPane(command || polledCommand || "", tail), claudeConfident: isClaudeConfident(tail) };
11825
11844
  } catch {
11826
11845
  }
11827
11846
  }
@@ -11837,16 +11856,16 @@ class WebTerminalManager {
11837
11856
  tmuxArgs(["display-message", "-p", "-t", sessionName, "#{pane_current_command}"]),
11838
11857
  { encoding: "utf8", timeout: TMUX_PROBE_TIMEOUT_MS, env: ptyEnv() }
11839
11858
  );
11840
- if (cmd.status !== 0 || typeof cmd.stdout !== "string") return void 0;
11859
+ if (cmd.status !== 0 || typeof cmd.stdout !== "string") return { agentState: void 0, claudeConfident: false };
11841
11860
  const cap = node_child_process.spawnSync(
11842
11861
  "tmux",
11843
11862
  tmuxArgs(["capture-pane", "-p", "-t", sessionName, "-S", "-40"]),
11844
11863
  { encoding: "utf8", timeout: TMUX_PROBE_TIMEOUT_MS, env: ptyEnv() }
11845
11864
  );
11846
- if (cap.status !== 0 || typeof cap.stdout !== "string") return void 0;
11847
- return classifyPane(cmd.stdout.trim(), cap.stdout);
11865
+ if (cap.status !== 0 || typeof cap.stdout !== "string") return { agentState: void 0, claudeConfident: false };
11866
+ return { agentState: classifyPane(cmd.stdout.trim(), cap.stdout), claudeConfident: isClaudeConfident(cap.stdout) };
11848
11867
  } catch {
11849
- return void 0;
11868
+ return { agentState: void 0, claudeConfident: false };
11850
11869
  }
11851
11870
  }
11852
11871
  /**
@@ -14173,6 +14192,7 @@ class ApiMachineClient {
14173
14192
  setMirrorIntegration(integration) {
14174
14193
  this.webTerminal.setMirrorSessionResolver(integration.resolveMirrorSessionId);
14175
14194
  this.webTerminal.setOnTerminalClosed(integration.onTerminalClosed);
14195
+ this.webTerminal.setMirrorTickObserver(integration.onTerminalListTick);
14176
14196
  this.mirrorListObserver = integration.onTerminalList;
14177
14197
  this.mirrorInputAllowed = integration.isMirrorInputAllowed;
14178
14198
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "very-happy-cli",
3
- "version": "0.2.93",
3
+ "version": "0.2.95",
4
4
  "description": "Remote multi-agent workspace CLI with deep Claude integration, native terminal streaming, and self-hosting.",
5
5
  "author": "Kirill Dubovitskiy",
6
6
  "contributors": [
@@ -113,12 +113,12 @@
113
113
  "zod": "^4.0.0"
114
114
  },
115
115
  "optionalDependencies": {
116
- "very-happy-tools-arm64-darwin": "0.2.93",
117
- "very-happy-tools-x64-darwin": "0.2.93",
118
- "very-happy-tools-arm64-linux": "0.2.93",
119
- "very-happy-tools-x64-linux": "0.2.93",
120
- "very-happy-tools-arm64-win32": "0.2.93",
121
- "very-happy-tools-x64-win32": "0.2.93"
116
+ "very-happy-tools-arm64-darwin": "0.2.95",
117
+ "very-happy-tools-x64-darwin": "0.2.95",
118
+ "very-happy-tools-arm64-linux": "0.2.95",
119
+ "very-happy-tools-x64-linux": "0.2.95",
120
+ "very-happy-tools-arm64-win32": "0.2.95",
121
+ "very-happy-tools-x64-win32": "0.2.95"
122
122
  },
123
123
  "devDependencies": {
124
124
  "@slopus/happy-wire": "workspace:*",