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.
- package/dist/{AcpBackend-BrZN8KM-.cjs → AcpBackend-CauAQpC1.cjs} +1 -1
- package/dist/{AcpBackend-DEZTWZZu.mjs → AcpBackend-CfKPvoTU.mjs} +1 -1
- package/dist/{AcpSessionManager-BzP3eiOG.mjs → AcpSessionManager-BQ6pBRW4.mjs} +1 -1
- package/dist/{AcpSessionManager-DjulpKY0.cjs → AcpSessionManager-CA6xeET_.cjs} +1 -1
- package/dist/{config-BNAsx9Wg.cjs → config-BoteFn-v.cjs} +2 -2
- package/dist/{config-CMgRBQMS.mjs → config-BpR_CwZu.mjs} +2 -2
- package/dist/{index-tPXwlc_t.cjs → index-4jiUeiUz.cjs} +110 -18
- package/dist/{index-CpGR7rIw.mjs → index-C45xN8s0.mjs} +5 -5
- package/dist/{index-B2KfhNku.mjs → index-D7azxfcs.mjs} +2 -2
- package/dist/{index-ClR0jhjm.cjs → index-DWmhmji9.cjs} +4 -4
- package/dist/{index-BHNEb4bc.mjs → index-Dq-eFp89.mjs} +106 -14
- package/dist/{index-B3UkbonP.cjs → index-lX1UliOs.cjs} +2 -2
- package/dist/index.cjs +2 -2
- package/dist/index.mjs +2 -2
- package/dist/{installTerminalHooks-CWfCF68p.cjs → installTerminalHooks-DnaFkrTF.cjs} +2 -2
- package/dist/{installTerminalHooks-DD6QuHfT.mjs → installTerminalHooks-k3TrBHD0.mjs} +2 -2
- package/dist/lib.cjs +1 -1
- package/dist/lib.d.cts +25 -0
- package/dist/lib.d.mts +25 -0
- package/dist/lib.mjs +1 -1
- package/dist/{mcp-CiNrjYox.mjs → mcp-DRePTOap.mjs} +2 -2
- package/dist/{mcp-CqjiDRnG.cjs → mcp-DSpEpM_2.cjs} +2 -2
- package/dist/{runGemini-UN5IznJp.cjs → runGemini-IWHyBq0A.cjs} +4 -4
- package/dist/{runGemini-DdJqpywr.mjs → runGemini-_9-a7duU.mjs} +4 -4
- package/dist/{runOpenClaw-9hhfGllZ.cjs → runOpenClaw-0myYIz1U.cjs} +3 -3
- package/dist/{runOpenClaw-CM5UrVBy.mjs → runOpenClaw-Boq6LQ9a.mjs} +3 -3
- package/dist/{send-CnVtXOj0.cjs → send-CwWvFfay.cjs} +2 -2
- package/dist/{send-C6G2jsQp.mjs → send-Cyo6mgMl.mjs} +2 -2
- package/dist/{sessions-CGdcF05T.cjs → sessions-CbzqYiAy.cjs} +2 -2
- package/dist/{sessions-CmabJzYS.mjs → sessions-lvfTH_ql.mjs} +2 -2
- package/dist/{spawn-qb344Y5E.mjs → spawn-CU0DMauA.mjs} +2 -2
- package/dist/{spawn-CfYddkS_.cjs → spawn-DQ6FTUK9.cjs} +2 -2
- package/dist/{types-CpnzfHHS.cjs → types-C5kEpkcZ.cjs} +150 -15
- package/dist/{types-B3qx8Ga1.mjs → types-Ds0SiQ1D.mjs} +149 -14
- 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.
|
|
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.
|
|
146
|
-
"very-happy-tools-x64-darwin": "0.2.
|
|
147
|
-
"very-happy-tools-arm64-linux": "0.2.
|
|
148
|
-
"very-happy-tools-x64-linux": "0.2.
|
|
149
|
-
"very-happy-tools-arm64-win32": "0.2.
|
|
150
|
-
"very-happy-tools-x64-win32": "0.2.
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
117
|
-
"very-happy-tools-x64-darwin": "0.2.
|
|
118
|
-
"very-happy-tools-arm64-linux": "0.2.
|
|
119
|
-
"very-happy-tools-x64-linux": "0.2.
|
|
120
|
-
"very-happy-tools-arm64-win32": "0.2.
|
|
121
|
-
"very-happy-tools-x64-win32": "0.2.
|
|
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:*",
|