very-happy-cli 0.2.115 → 0.2.116

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-BrZN8KM-.cjs → AcpBackend-CauAQpC1.cjs} +1 -1
  2. package/dist/{AcpBackend-DEZTWZZu.mjs → AcpBackend-CfKPvoTU.mjs} +1 -1
  3. package/dist/{AcpSessionManager-BzP3eiOG.mjs → AcpSessionManager-BQ6pBRW4.mjs} +1 -1
  4. package/dist/{AcpSessionManager-DjulpKY0.cjs → AcpSessionManager-CA6xeET_.cjs} +1 -1
  5. package/dist/{config-BNAsx9Wg.cjs → config-BoteFn-v.cjs} +2 -2
  6. package/dist/{config-CMgRBQMS.mjs → config-BpR_CwZu.mjs} +2 -2
  7. package/dist/{index-tPXwlc_t.cjs → index-4jiUeiUz.cjs} +110 -18
  8. package/dist/{index-CpGR7rIw.mjs → index-C45xN8s0.mjs} +5 -5
  9. package/dist/{index-B2KfhNku.mjs → index-D7azxfcs.mjs} +2 -2
  10. package/dist/{index-ClR0jhjm.cjs → index-DWmhmji9.cjs} +4 -4
  11. package/dist/{index-BHNEb4bc.mjs → index-Dq-eFp89.mjs} +106 -14
  12. package/dist/{index-B3UkbonP.cjs → index-lX1UliOs.cjs} +2 -2
  13. package/dist/index.cjs +2 -2
  14. package/dist/index.mjs +2 -2
  15. package/dist/{installTerminalHooks-CWfCF68p.cjs → installTerminalHooks-DnaFkrTF.cjs} +2 -2
  16. package/dist/{installTerminalHooks-DD6QuHfT.mjs → installTerminalHooks-k3TrBHD0.mjs} +2 -2
  17. package/dist/lib.cjs +1 -1
  18. package/dist/lib.d.cts +25 -0
  19. package/dist/lib.d.mts +25 -0
  20. package/dist/lib.mjs +1 -1
  21. package/dist/{mcp-CiNrjYox.mjs → mcp-DRePTOap.mjs} +2 -2
  22. package/dist/{mcp-CqjiDRnG.cjs → mcp-DSpEpM_2.cjs} +2 -2
  23. package/dist/{runGemini-UN5IznJp.cjs → runGemini-IWHyBq0A.cjs} +4 -4
  24. package/dist/{runGemini-DdJqpywr.mjs → runGemini-_9-a7duU.mjs} +4 -4
  25. package/dist/{runOpenClaw-9hhfGllZ.cjs → runOpenClaw-0myYIz1U.cjs} +3 -3
  26. package/dist/{runOpenClaw-CM5UrVBy.mjs → runOpenClaw-Boq6LQ9a.mjs} +3 -3
  27. package/dist/{send-CnVtXOj0.cjs → send-CwWvFfay.cjs} +2 -2
  28. package/dist/{send-C6G2jsQp.mjs → send-Cyo6mgMl.mjs} +2 -2
  29. package/dist/{sessions-CGdcF05T.cjs → sessions-CbzqYiAy.cjs} +2 -2
  30. package/dist/{sessions-CmabJzYS.mjs → sessions-lvfTH_ql.mjs} +2 -2
  31. package/dist/{spawn-qb344Y5E.mjs → spawn-CU0DMauA.mjs} +2 -2
  32. package/dist/{spawn-CfYddkS_.cjs → spawn-DQ6FTUK9.cjs} +2 -2
  33. package/dist/{types-CpnzfHHS.cjs → types-C5kEpkcZ.cjs} +150 -15
  34. package/dist/{types-B3qx8Ga1.mjs → types-Ds0SiQ1D.mjs} +149 -14
  35. 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.115";
32
+ var version = "0.2.116";
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.115",
146
- "very-happy-tools-x64-darwin": "0.2.115",
147
- "very-happy-tools-arm64-linux": "0.2.115",
148
- "very-happy-tools-x64-linux": "0.2.115",
149
- "very-happy-tools-arm64-win32": "0.2.115",
150
- "very-happy-tools-x64-win32": "0.2.115"
145
+ "very-happy-tools-arm64-darwin": "0.2.116",
146
+ "very-happy-tools-x64-darwin": "0.2.116",
147
+ "very-happy-tools-arm64-linux": "0.2.116",
148
+ "very-happy-tools-x64-linux": "0.2.116",
149
+ "very-happy-tools-arm64-win32": "0.2.116",
150
+ "very-happy-tools-x64-win32": "0.2.116"
151
151
  };
152
152
  var devDependencies = {
153
153
  "@slopus/happy-wire": "workspace:*",
@@ -1054,7 +1054,14 @@ const CliUpdateStateSchema = z$1.object({
1054
1054
  * serving the old code. Present only while that is true. Optional and additive:
1055
1055
  * old web builds ignore it, old daemons never send it (iron rule 4).
1056
1056
  */
1057
- handoverHold: z$1.object({ reason: z$1.string(), at: z$1.number() }).nullable().optional()
1057
+ handoverHold: z$1.object({ reason: z$1.string(), at: z$1.number() }).nullable().optional(),
1058
+ /** B-327: the last thing the machine's auto-update did, or why it did nothing. */
1059
+ autoUpdate: z$1.object({
1060
+ state: z$1.union([z$1.enum(["installing", "installed", "failed", "skipped"]), z$1.string()]),
1061
+ version: z$1.string().nullable(),
1062
+ detail: z$1.string().optional(),
1063
+ at: z$1.number()
1064
+ }).nullable().optional()
1058
1065
  });
1059
1066
  const WebTerminalListItemSchema = z$1.object({
1060
1067
  id: z$1.string(),
@@ -9834,6 +9841,49 @@ class SnapshotStore {
9834
9841
  }
9835
9842
  }
9836
9843
 
9844
+ const OUTPUT_COALESCE_MS = 16;
9845
+ const OUTPUT_COALESCE_MAX_BYTES = 64 * 1024;
9846
+ function createOutputCoalescer({ maxDelayMs, maxBytes } = {
9847
+ maxDelayMs: OUTPUT_COALESCE_MS,
9848
+ maxBytes: OUTPUT_COALESCE_MAX_BYTES
9849
+ }) {
9850
+ let buffered = [];
9851
+ let bufferedBytes = 0;
9852
+ let firstBufferedAt = 0;
9853
+ let lastEmitAt = Number.NEGATIVE_INFINITY;
9854
+ const drain = (now) => {
9855
+ if (bufferedBytes === 0) return null;
9856
+ const out = buffered.length === 1 ? buffered[0] : Buffer.concat(buffered, bufferedBytes);
9857
+ buffered = [];
9858
+ bufferedBytes = 0;
9859
+ firstBufferedAt = 0;
9860
+ lastEmitAt = now;
9861
+ return out;
9862
+ };
9863
+ return {
9864
+ push(data, now) {
9865
+ if (data.length === 0) return null;
9866
+ if (bufferedBytes === 0 && now - lastEmitAt >= maxDelayMs) {
9867
+ lastEmitAt = now;
9868
+ return data;
9869
+ }
9870
+ if (bufferedBytes === 0) firstBufferedAt = now;
9871
+ buffered.push(data);
9872
+ bufferedBytes += data.length;
9873
+ return bufferedBytes >= maxBytes ? drain(now) : null;
9874
+ },
9875
+ flush(now) {
9876
+ return drain(now);
9877
+ },
9878
+ dueAt() {
9879
+ return bufferedBytes === 0 ? null : firstBufferedAt + maxDelayMs;
9880
+ },
9881
+ pendingBytes() {
9882
+ return bufferedBytes;
9883
+ }
9884
+ };
9885
+ }
9886
+
9837
9887
  const MAX_SEND_KEYS_PAYLOAD_BYTES = 1e3;
9838
9888
  function tmuxSingleQuote(text) {
9839
9889
  return `'${text.split("'").join("'\\''")}'`;
@@ -10744,6 +10794,18 @@ class TerminalSession {
10744
10794
  lastOutputAt;
10745
10795
  cols;
10746
10796
  rows;
10797
+ /**
10798
+ * B-334: `%output` arrives as one message per producer write — pi's startup
10799
+ * frame measured 1029 chunks of a median 9 bytes. Merging them before they
10800
+ * are given a seq is what keeps the wire (and the web's write chain) from
10801
+ * carrying a thousand frames for 12 KB. See outputCoalescer.ts.
10802
+ */
10803
+ coalescer = createOutputCoalescer();
10804
+ flushTimer = null;
10805
+ /** Set by the manager right after construction: how a merged chunk reaches
10806
+ * the subscribers. Left undefined ⇒ the session buffers and drops nothing,
10807
+ * it just has nobody to tell. */
10808
+ onOutputChunk;
10747
10809
  constructor(id, transport, tmuxSession, cols, rows) {
10748
10810
  this.id = id;
10749
10811
  this.transport = transport;
@@ -10790,6 +10852,61 @@ class TerminalSession {
10790
10852
  }
10791
10853
  return chunk;
10792
10854
  }
10855
+ /**
10856
+ * The live-output entry point (B-334). Every `%output` / pty chunk goes
10857
+ * through here instead of straight to `ingest`: the coalescer decides
10858
+ * whether this byte run is emitted now (the common interactive case — an
10859
+ * echo after an idle window pays zero extra latency) or merged into the
10860
+ * frame currently forming. Merging is byte-exact concatenation, so one seq
10861
+ * covering the merged run is indistinguishable from tmux having handed us
10862
+ * one bigger read — the ring/replay/gap protocol is untouched.
10863
+ */
10864
+ pushOutput(data) {
10865
+ const ready = this.coalescer.push(data, Date.now());
10866
+ if (ready) {
10867
+ this.clearFlushTimer();
10868
+ this.emitOutput(ready);
10869
+ }
10870
+ this.armFlushTimer();
10871
+ }
10872
+ /**
10873
+ * Emit whatever is buffered right now. MUST be called before anything that
10874
+ * reads or changes the screen's identity, because buffered bytes have not
10875
+ * been ingested yet and therefore own no seq:
10876
+ * - the capture anchor (`runCaptureBatch`) — pre-anchor output is already
10877
+ * inside the capture, so it has to own a seq <= `seqAtAnchor` or the
10878
+ * client applies it a second time on top of the restore;
10879
+ * - `resizeHeadless` — those bytes were produced at the OLD size;
10880
+ * - transport exit — otherwise the last frame before a shell dies is lost.
10881
+ */
10882
+ flushOutput() {
10883
+ this.clearFlushTimer();
10884
+ const ready = this.coalescer.flush(Date.now());
10885
+ if (ready) this.emitOutput(ready);
10886
+ }
10887
+ emitOutput(data) {
10888
+ const chunk = this.ingest(data);
10889
+ this.onOutputChunk?.(chunk);
10890
+ }
10891
+ armFlushTimer() {
10892
+ const due = this.coalescer.dueAt();
10893
+ if (due === null) {
10894
+ this.clearFlushTimer();
10895
+ return;
10896
+ }
10897
+ if (this.flushTimer) return;
10898
+ const timer = setTimeout(() => {
10899
+ this.flushTimer = null;
10900
+ this.flushOutput();
10901
+ }, Math.max(0, due - Date.now()));
10902
+ timer.unref?.();
10903
+ this.flushTimer = timer;
10904
+ }
10905
+ clearFlushTimer() {
10906
+ if (!this.flushTimer) return;
10907
+ clearTimeout(this.flushTimer);
10908
+ this.flushTimer = null;
10909
+ }
10793
10910
  /**
10794
10911
  * Write a RESTORE payload (the open capture) into the authoritative screen
10795
10912
  * WITHOUT giving it a seq: it is not a live chunk, so it must not enter the
@@ -10809,6 +10926,7 @@ class TerminalSession {
10809
10926
  /** Keep the authoritative screen's dimensions in lockstep with the terminal
10810
10927
  * so a later snapshot serialize() reflects the real geometry. */
10811
10928
  resizeHeadless(cols, rows) {
10929
+ this.flushOutput();
10812
10930
  this.cols = cols;
10813
10931
  this.rows = rows;
10814
10932
  try {
@@ -10888,6 +11006,7 @@ class TerminalSession {
10888
11006
  } catch {
10889
11007
  }
10890
11008
  }
11009
+ this.clearFlushTimer();
10891
11010
  try {
10892
11011
  this.headless.dispose();
10893
11012
  } catch {
@@ -11739,14 +11858,16 @@ class WebTerminalManager {
11739
11858
  client: void 0
11740
11859
  // replaced below
11741
11860
  }, tmuxSession, cols, rows);
11861
+ created.onOutputChunk = (chunk) => {
11862
+ this.emit("terminal-output", { terminalId: id, data: chunk.data, seq: chunk.seq });
11863
+ this.noteActivity();
11864
+ };
11742
11865
  const client = new ControlClient(tmuxSession, env, {
11743
11866
  onOutput: (pane, data) => {
11744
11867
  if (this.terminals.get(id) !== created) return;
11745
11868
  if (!created.ingesting) return;
11746
11869
  if (created.paneId && pane !== created.paneId) return;
11747
- const chunk = created.ingest(data);
11748
- this.emit("terminal-output", { terminalId: id, data: chunk.data, seq: chunk.seq });
11749
- this.noteActivity();
11870
+ created.pushOutput(data);
11750
11871
  },
11751
11872
  onNotification: (name, args2) => {
11752
11873
  if (this.terminals.get(id) !== created) return;
@@ -11768,6 +11889,7 @@ class WebTerminalManager {
11768
11889
  },
11769
11890
  onExit: (code) => {
11770
11891
  if (this.terminals.get(id) !== created) return;
11892
+ created.flushOutput();
11771
11893
  this.terminals.delete(id);
11772
11894
  this.snapshots.drop(id);
11773
11895
  created.dispose();
@@ -11788,14 +11910,17 @@ class WebTerminalManager {
11788
11910
  }
11789
11911
  }
11790
11912
  const created = new TerminalSession(id, { kind: "pty", pty: proc }, void 0, cols, rows);
11791
- proc.onData((data) => {
11792
- if (this.terminals.get(id) !== created) return;
11793
- const chunk = created.ingest(Buffer.from(data, "utf8"));
11913
+ created.onOutputChunk = (chunk) => {
11794
11914
  this.emit("terminal-output", { terminalId: id, data: chunk.data, seq: chunk.seq });
11795
11915
  this.noteActivity();
11916
+ };
11917
+ proc.onData((data) => {
11918
+ if (this.terminals.get(id) !== created) return;
11919
+ created.pushOutput(Buffer.from(data, "utf8"));
11796
11920
  });
11797
11921
  proc.onExit(({ exitCode }) => {
11798
11922
  if (this.terminals.get(id) !== created) return;
11923
+ created.flushOutput();
11799
11924
  this.terminals.delete(id);
11800
11925
  this.snapshots.drop(id);
11801
11926
  created.dispose();
@@ -11939,6 +12064,7 @@ class WebTerminalManager {
11939
12064
  command: c.command,
11940
12065
  label: c.key,
11941
12066
  onBlock: c.key === "anchor" ? () => {
12067
+ session.flushOutput();
11942
12068
  seqAtAnchor = session.seq;
11943
12069
  if (fresh) session.ingesting = true;
11944
12070
  } : void 0
@@ -14409,6 +14535,15 @@ class ApiMachineClient {
14409
14535
  // recover account material or impersonate a Web client. (Declared before
14410
14536
  // webTerminal so the emit closure can read it.)
14411
14537
  encTerminals = /* @__PURE__ */ new Set();
14538
+ /** B-327: an auto-update swaps the process that owns these terminals, so it
14539
+ * waits until there are none. */
14540
+ hasLiveTerminals() {
14541
+ try {
14542
+ return this.webTerminal.buildTerminalList().length > 0;
14543
+ } catch {
14544
+ return true;
14545
+ }
14546
+ }
14412
14547
  webTerminal = new WebTerminalManager((event, payload) => {
14413
14548
  let out = payload;
14414
14549
  if (event === "terminal-output" && this.encTerminals.has(payload.terminalId)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "very-happy-cli",
3
- "version": "0.2.115",
3
+ "version": "0.2.116",
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.115",
117
- "very-happy-tools-x64-darwin": "0.2.115",
118
- "very-happy-tools-arm64-linux": "0.2.115",
119
- "very-happy-tools-x64-linux": "0.2.115",
120
- "very-happy-tools-arm64-win32": "0.2.115",
121
- "very-happy-tools-x64-win32": "0.2.115"
116
+ "very-happy-tools-arm64-darwin": "0.2.116",
117
+ "very-happy-tools-x64-darwin": "0.2.116",
118
+ "very-happy-tools-arm64-linux": "0.2.116",
119
+ "very-happy-tools-x64-linux": "0.2.116",
120
+ "very-happy-tools-arm64-win32": "0.2.116",
121
+ "very-happy-tools-x64-win32": "0.2.116"
122
122
  },
123
123
  "devDependencies": {
124
124
  "@slopus/happy-wire": "workspace:*",