very-happy-cli 0.2.155 → 0.2.157
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-DmFcRh54.cjs → AcpBackend-BymWX-LI.cjs} +2 -2
- package/dist/{AcpBackend-DVB0nzgR.mjs → AcpBackend-Cy18U7yw.mjs} +2 -2
- package/dist/{AcpSessionManager-CWaSYTnT.cjs → AcpSessionManager-BF7zBGAi.cjs} +1 -1
- package/dist/{AcpSessionManager-dGDgNb-p.mjs → AcpSessionManager-Ds8rcp95.mjs} +1 -1
- package/dist/{agentGuidance-BWxFC6UL.mjs → agentGuidance-CU6YD_Ms.mjs} +560 -29
- package/dist/{agentGuidance-un7YPryB.cjs → agentGuidance-Db76O1kw.cjs} +579 -28
- package/dist/{agentHome-aucT8BHw.cjs → agentHome-B0CLy0Qx.cjs} +2 -2
- package/dist/{agentHome-ByLb2HFg.mjs → agentHome-BaJ9xtxB.mjs} +2 -2
- package/dist/{auto-BQnD6gVx.mjs → auto-B1YAwaeU.mjs} +6 -6
- package/dist/{auto-P9RoJaY0.cjs → auto-DqKGFq8U.cjs} +6 -6
- package/dist/codex/happyMcpStdioBridge.cjs +2 -2
- package/dist/codex/happyMcpStdioBridge.mjs +2 -2
- package/dist/{config-BoePh9PX.cjs → config-CzkBjZnV.cjs} +4 -4
- package/dist/{config-BPZ2Y6o8.mjs → config-aAbhJllb.mjs} +4 -4
- package/dist/{index-BjzdXb9x.mjs → index-BVu8Cufk.mjs} +8 -8
- package/dist/{index-B4oXnbYw.mjs → index-BYU1e81k.mjs} +372 -65
- package/dist/{index-D_fYUxVg.cjs → index-C9QzZsUQ.cjs} +375 -68
- package/dist/{index-DRrLaljt.cjs → index-CTbGqVFY.cjs} +7 -7
- package/dist/{index--KCpVs1c.cjs → index-Cz-uqnJk.cjs} +4 -4
- package/dist/{index-DVVRqaVG.mjs → index-DQcRkI9r.mjs} +4 -4
- package/dist/index.cjs +4 -4
- package/dist/index.mjs +4 -4
- package/dist/{install-CGigiocs.cjs → install-BMlV0Tnr.cjs} +1 -1
- package/dist/{install-DP5aPNEX.mjs → install-BuYnqtVa.mjs} +1 -1
- package/dist/{installTerminalHooks-XrcPvfBs.cjs → installTerminalHooks-BVVOEGhG.cjs} +4 -4
- package/dist/{installTerminalHooks-tIg7mANc.mjs → installTerminalHooks-ELDoqh0A.mjs} +4 -4
- package/dist/lib.cjs +2 -2
- package/dist/lib.d.cts +101 -9
- package/dist/lib.d.mts +101 -9
- package/dist/lib.mjs +2 -2
- package/dist/{limits-C_DW0M0q.mjs → limits-A2c-3M-_.mjs} +162 -11
- package/dist/{limits-DkfHJ3nS.cjs → limits-CyuIt-Vl.cjs} +169 -10
- package/dist/{mcp-o7QglAQM.mjs → mcp-Bo934Kpy.mjs} +4 -4
- package/dist/{mcp-BqlXl10F.cjs → mcp-CXkvnztT.cjs} +4 -4
- package/dist/{piRuntime-BxP4B_zY.cjs → piRuntime-CJjA26oD.cjs} +2 -2
- package/dist/{piRuntime-MjiJI-nV.mjs → piRuntime-DdEY2mE9.mjs} +2 -2
- package/dist/{piTerminal-DYzRcwgb.cjs → piTerminal-CpU3hzrX.cjs} +6 -6
- package/dist/{piTerminal-Dpq6yqKU.mjs → piTerminal-DwWmgumi.mjs} +6 -6
- package/dist/{runGemini-CDlcQ-OA.mjs → runGemini-Cj6oJQ-I.mjs} +6 -6
- package/dist/{runGemini-8Pxh94Fm.cjs → runGemini-DS-0qm0L.cjs} +6 -6
- package/dist/{runOpenClaw-B5FSZIbK.cjs → runOpenClaw-CvSm8oTc.cjs} +5 -5
- package/dist/{runOpenClaw-YkhsgPs0.mjs → runOpenClaw-DUCQxkI-.mjs} +5 -5
- package/dist/{send-CwrYXTPr.cjs → send-CHxobaoI.cjs} +43 -31
- package/dist/{send-DJStmLBv.mjs → send-D5CyRyZ_.mjs} +43 -31
- package/dist/{sessions-MX21NJXd.cjs → sessions-BFlIrKCY.cjs} +133 -49
- package/dist/{sessions-JaVXlSqj.mjs → sessions-DsE01xKc.mjs} +133 -49
- package/dist/{spawn-BaPnjClT.mjs → spawn-CdihhwHs.mjs} +4 -4
- package/dist/{spawn-cIoglKVf.cjs → spawn-HV345RN-.cjs} +4 -4
- package/dist/{teams-DXV5Pwwt.mjs → teams-BE8nMB6O.mjs} +5 -5
- package/dist/{teams-Dqk7F3D-.cjs → teams-BahinMnH.cjs} +5 -5
- package/dist/{todo-Buuqn9gm.cjs → todo-CMMQ6yhU.cjs} +2 -2
- package/dist/{todo-2Z1REhfx.mjs → todo-CuQAkSp2.mjs} +2 -2
- package/dist/{types-a2xsH2UI.cjs → types-B0sSi6eM.cjs} +125 -13
- package/dist/{types-CkbfdRzq.mjs → types-BrGJ7PJz.mjs} +125 -13
- package/package.json +7 -7
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var axios = require('axios');
|
|
4
|
-
var persistence = require('./limits-
|
|
4
|
+
var persistence = require('./limits-CyuIt-Vl.cjs');
|
|
5
5
|
var node_events = require('node:events');
|
|
6
6
|
var socket_ioClient = require('socket.io-client');
|
|
7
7
|
var z = require('zod');
|
|
@@ -438,6 +438,15 @@ async function deriveKey$1(master, usage, path) {
|
|
|
438
438
|
|
|
439
439
|
class RpcHandlerManager {
|
|
440
440
|
handlers = /* @__PURE__ */ new Map();
|
|
441
|
+
/**
|
|
442
|
+
* B-506: methods whose params/result are PLAINTEXT JSON strings instead of
|
|
443
|
+
* machine-key ciphertext. The caller is a CLI on another machine of the
|
|
444
|
+
* same account, which holds neither this machine's key nor the account
|
|
445
|
+
* content key; the trusted relay sees the payload either way (this fork
|
|
446
|
+
* is server-trusted, not e2e). Only `registerPlainHandler` puts a method
|
|
447
|
+
* here; everything else keeps decrypting.
|
|
448
|
+
*/
|
|
449
|
+
plainMethods = /* @__PURE__ */ new Set();
|
|
441
450
|
scopePrefix;
|
|
442
451
|
encryptionKey;
|
|
443
452
|
encryptionVariant;
|
|
@@ -462,14 +471,31 @@ class RpcHandlerManager {
|
|
|
462
471
|
unregisterHandler(method) {
|
|
463
472
|
const prefixedMethod = this.getPrefixedMethod(method);
|
|
464
473
|
this.handlers.delete(prefixedMethod);
|
|
474
|
+
this.plainMethods.delete(prefixedMethod);
|
|
465
475
|
for (const socket of this.sockets) socket.emit("rpc-unregister", { method: prefixedMethod });
|
|
466
476
|
}
|
|
477
|
+
/**
|
|
478
|
+
* B-506: register a method whose request is a plaintext JSON string and
|
|
479
|
+
* whose response is returned as a plaintext JSON string (see
|
|
480
|
+
* `plainMethods`). The handler receives the parsed params; a throw is
|
|
481
|
+
* answered with `{ error }` like the encrypted path.
|
|
482
|
+
*/
|
|
483
|
+
registerPlainHandler(method, handler) {
|
|
484
|
+
this.plainMethods.add(this.getPrefixedMethod(method));
|
|
485
|
+
this.registerHandler(method, handler);
|
|
486
|
+
}
|
|
487
|
+
isPlainMethod(prefixedMethod) {
|
|
488
|
+
return this.plainMethods.has(prefixedMethod);
|
|
489
|
+
}
|
|
467
490
|
/**
|
|
468
491
|
* Handle an incoming RPC request
|
|
469
492
|
* @param request - The RPC request data
|
|
470
493
|
* @param callback - The response callback
|
|
471
494
|
*/
|
|
472
495
|
async handleRequest(request) {
|
|
496
|
+
if (this.plainMethods.has(request.method)) {
|
|
497
|
+
return this.handlePlainRequest(request);
|
|
498
|
+
}
|
|
473
499
|
try {
|
|
474
500
|
const handler = this.handlers.get(request.method);
|
|
475
501
|
if (!handler) {
|
|
@@ -493,6 +519,25 @@ class RpcHandlerManager {
|
|
|
493
519
|
return persistence.encodeBase64(persistence.encrypt(this.encryptionKey, this.encryptionVariant, errorResponse));
|
|
494
520
|
}
|
|
495
521
|
}
|
|
522
|
+
/** Plaintext variant of handleRequest (B-506). Never throws; never encrypts. */
|
|
523
|
+
async handlePlainRequest(request) {
|
|
524
|
+
const handler = this.handlers.get(request.method);
|
|
525
|
+
if (!handler) return JSON.stringify({ error: "Method not found" });
|
|
526
|
+
let params;
|
|
527
|
+
try {
|
|
528
|
+
params = typeof request.params === "string" && request.params.length > 0 ? JSON.parse(request.params) : {};
|
|
529
|
+
} catch {
|
|
530
|
+
return JSON.stringify({ error: "Invalid JSON params" });
|
|
531
|
+
}
|
|
532
|
+
try {
|
|
533
|
+
this.logger("[RPC] Calling plain handler", { method: request.method });
|
|
534
|
+
const result = await handler(params);
|
|
535
|
+
return JSON.stringify(result === void 0 ? null : result);
|
|
536
|
+
} catch (error) {
|
|
537
|
+
this.logger("[RPC] [ERROR] Error handling plain request", { error });
|
|
538
|
+
return JSON.stringify({ error: error instanceof Error ? error.message : "Unknown error" });
|
|
539
|
+
}
|
|
540
|
+
}
|
|
496
541
|
onSocketConnect(socket) {
|
|
497
542
|
this.sockets.add(socket);
|
|
498
543
|
for (const [prefixedMethod] of this.handlers) {
|
|
@@ -3000,16 +3045,29 @@ class ApiSessionClient extends node_events.EventEmitter {
|
|
|
3000
3045
|
/** B-466: turn edges (and a periodic lease) reported to the local daemon so
|
|
3001
3046
|
* auto-update waits for "no turn in flight" instead of "no sessions". */
|
|
3002
3047
|
turnReporter = new TurnReporter((event) => void persistence.notifyDaemonTurnEvent(this.sessionId, event).catch((error) => persistence.logger.debug("[API] Failed to report turn event to daemon:", error)));
|
|
3003
|
-
|
|
3048
|
+
/** B-507: background tasks in flight, published on change and renewed every
|
|
3049
|
+
* 60s to the daemon (`/session-event background_tasks`) and into agentState
|
|
3050
|
+
* (`backgroundTasks: { updatedAt, tasks }`) — the two readers that outlive a
|
|
3051
|
+
* single heartbeat. The heartbeat itself carries only the count. */
|
|
3052
|
+
backgroundTaskReporter = new persistence.BackgroundTaskReporter((tasks, now) => {
|
|
3053
|
+
void persistence.notifyDaemonBackgroundTasks(this.sessionId, tasks).catch((error) => persistence.logger.debug("[API] Failed to report background tasks to daemon:", error));
|
|
3054
|
+
this.updateAgentState((state) => ({
|
|
3055
|
+
...state,
|
|
3056
|
+
backgroundTasks: tasks.length > 0 ? { updatedAt: now, tasks } : void 0
|
|
3057
|
+
}));
|
|
3058
|
+
});
|
|
3059
|
+
keepAlive(thinking, mode, backgroundTasks) {
|
|
3004
3060
|
if (process.env.DEBUG) {
|
|
3005
3061
|
persistence.logger.debug(`[API] Sending keep alive message: ${thinking}`);
|
|
3006
3062
|
}
|
|
3007
3063
|
this.turnReporter.observe(thinking);
|
|
3064
|
+
if (backgroundTasks) this.backgroundTaskReporter.observe(backgroundTasks);
|
|
3008
3065
|
this.socket.volatile.emit("session-alive", {
|
|
3009
3066
|
sid: this.sessionId,
|
|
3010
3067
|
time: Date.now(),
|
|
3011
3068
|
thinking,
|
|
3012
|
-
mode
|
|
3069
|
+
mode,
|
|
3070
|
+
...backgroundTasks ? { backgroundTasks: backgroundTasks.length } : {}
|
|
3013
3071
|
});
|
|
3014
3072
|
}
|
|
3015
3073
|
/**
|
|
@@ -14356,6 +14414,17 @@ class ApiMachineClient {
|
|
|
14356
14414
|
return { claudeAuth };
|
|
14357
14415
|
});
|
|
14358
14416
|
}
|
|
14417
|
+
/**
|
|
14418
|
+
* B-506: the five `sessions.*` methods a CLI on ANOTHER machine of this
|
|
14419
|
+
* account calls to read / message sessions this daemon spawned. Plaintext
|
|
14420
|
+
* on purpose (no shared key with the caller); see
|
|
14421
|
+
* `api/rpc/RpcHandlerManager.ts` `registerPlainHandler`.
|
|
14422
|
+
*/
|
|
14423
|
+
setRemoteSessionOpsHandlers(handlers) {
|
|
14424
|
+
for (const [method, handler] of Object.entries(handlers)) {
|
|
14425
|
+
this.rpcHandlerManager.registerPlainHandler(method, handler);
|
|
14426
|
+
}
|
|
14427
|
+
}
|
|
14359
14428
|
setRPCHandlers({
|
|
14360
14429
|
spawnSession,
|
|
14361
14430
|
resumeSession,
|
|
@@ -15184,7 +15253,11 @@ class ApiMachineClient {
|
|
|
15184
15253
|
}
|
|
15185
15254
|
bindRpcRequestHandler(socket, transport) {
|
|
15186
15255
|
socket.on("rpc-request", async (data, callback) => {
|
|
15187
|
-
|
|
15256
|
+
if (typeof data?.method === "string" && this.rpcHandlerManager.isPlainMethod(data.method)) {
|
|
15257
|
+
persistence.logger.debug(`[API MACHINE] Received plain RPC request via ${transport}: ${data.method}`);
|
|
15258
|
+
} else {
|
|
15259
|
+
persistence.logger.debugLargeJson(`[API MACHINE] Received RPC request via ${transport}:`, data);
|
|
15260
|
+
}
|
|
15188
15261
|
await answerSocketRequest(
|
|
15189
15262
|
callback,
|
|
15190
15263
|
() => this.rpcHandlerManager.handleRequest(data),
|
|
@@ -16222,17 +16295,56 @@ class ApiClient {
|
|
|
16222
16295
|
}
|
|
16223
16296
|
}
|
|
16224
16297
|
/**
|
|
16225
|
-
*
|
|
16226
|
-
*
|
|
16227
|
-
*
|
|
16228
|
-
*
|
|
16298
|
+
* B-505: mark a session OFFLINE on the server (active=false, `archivedAt`
|
|
16299
|
+
* untouched) — the same row state a missed keepalive produces, so the web
|
|
16300
|
+
* shows it offline and resumable instead of archived.
|
|
16301
|
+
*
|
|
16302
|
+
* Used when the wrapper exits for an infrastructure reason (SIGTERM /
|
|
16303
|
+
* SIGINT: daemon stop, supervisor restart, machine shutdown) as a
|
|
16304
|
+
* synchronous fallback for the socket `session-end` signal: even if the
|
|
16305
|
+
* socket emit doesn't drain before the process exits, the HTTP response
|
|
16306
|
+
* confirms the row went inactive.
|
|
16229
16307
|
*
|
|
16230
|
-
*
|
|
16231
|
-
*
|
|
16232
|
-
*
|
|
16233
|
-
*
|
|
16308
|
+
* This MUST NOT hit `/archive`: since B-265 that route writes the
|
|
16309
|
+
* server-owned `archivedAt` tombstone (the only thing that distinguishes
|
|
16310
|
+
* "archived" from "offline"), and the server answers it with a
|
|
16311
|
+
* `session-archive` command that makes the wrapper archive itself. That is
|
|
16312
|
+
* exactly how every dev-sg upgrade archived the sessions mid-turn on
|
|
16313
|
+
* 2026-09-25. A server without `/deactivate` returns 404, which is a
|
|
16314
|
+
* compatible no-op: `session-end` already sets active=false there.
|
|
16234
16315
|
*/
|
|
16235
16316
|
async deactivateSession(sessionId) {
|
|
16317
|
+
try {
|
|
16318
|
+
const response = await axios.post(
|
|
16319
|
+
`${persistence.configuration.serverUrl}/v1/sessions/${sessionId}/deactivate`,
|
|
16320
|
+
{},
|
|
16321
|
+
{
|
|
16322
|
+
headers: {
|
|
16323
|
+
"Authorization": `Bearer ${this.credential.token}`,
|
|
16324
|
+
"X-Happy-Client": `cli-coding-session/${persistence.configuration.currentCliVersion}`
|
|
16325
|
+
},
|
|
16326
|
+
timeout: 3e3,
|
|
16327
|
+
validateStatus: (status) => status >= 200 && status < 300 || status === 404
|
|
16328
|
+
}
|
|
16329
|
+
);
|
|
16330
|
+
if (response.status === 404) {
|
|
16331
|
+
persistence.logger.debug("[API] deactivateSession: server has no /deactivate route; relying on session-end");
|
|
16332
|
+
return false;
|
|
16333
|
+
}
|
|
16334
|
+
return true;
|
|
16335
|
+
} catch (error) {
|
|
16336
|
+
persistence.logger.debug("[API] deactivateSession failed:", error);
|
|
16337
|
+
return false;
|
|
16338
|
+
}
|
|
16339
|
+
}
|
|
16340
|
+
/**
|
|
16341
|
+
* Archive a session on the server: writes the `archivedAt` tombstone and
|
|
16342
|
+
* makes the server tell every wrapper of this session to exit. Only for an
|
|
16343
|
+
* intentional end of life — the user's Archive / kill, a crash the wrapper
|
|
16344
|
+
* cannot recover from, a terminal mirror whose pane went back to a shell.
|
|
16345
|
+
* Never call this from a signal handler; see `deactivateSession`.
|
|
16346
|
+
*/
|
|
16347
|
+
async archiveSession(sessionId) {
|
|
16236
16348
|
try {
|
|
16237
16349
|
const response = await axios.post(
|
|
16238
16350
|
`${persistence.configuration.serverUrl}/v1/sessions/${sessionId}/archive`,
|
|
@@ -16247,7 +16359,7 @@ class ApiClient {
|
|
|
16247
16359
|
);
|
|
16248
16360
|
return response.status >= 200 && response.status < 300;
|
|
16249
16361
|
} catch (error) {
|
|
16250
|
-
persistence.logger.debug("[API]
|
|
16362
|
+
persistence.logger.debug("[API] archiveSession failed:", error);
|
|
16251
16363
|
return false;
|
|
16252
16364
|
}
|
|
16253
16365
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import axios from 'axios';
|
|
2
|
-
import {
|
|
2
|
+
import { S as AgentVersionsSchema, l as logger, w as encodeBase64, L as encrypt, N as decrypt, M as decodeBase64, c as configuration, e as createEnvelope, s as streamKeyOf, T as backoff, U as ReleaseDrainNoticeSchema, V as decryptBlob, W as RelayAssignmentResponseSchema, X as notifyDaemonTurnEvent, Y as BackgroundTaskReporter, Z as notifyDaemonBackgroundTasks, _ as prepareClipboardText, $ as prepareClipboardHistoryText, g as delay, r as readSettings, d as readPersistedSessions, a0 as credentialRelayProblem, F as packageJson, a1 as RelayCandidatesResponseSchema, a2 as readTrackedClaudeSessionIds, a3 as readTrackedCodexThreadIds, a4 as isValidTerminalId, a5 as exponentialBackoffDelay, a6 as libsodiumEncryptForPublicKey, a7 as libsodiumPublicKeyFromSecretKey, a8 as getRandomBytes, a9 as redactLogValue } from './limits-A2c-3M-_.mjs';
|
|
3
3
|
import { EventEmitter } from 'node:events';
|
|
4
4
|
import { io } from 'socket.io-client';
|
|
5
5
|
import { z } from 'zod';
|
|
@@ -417,6 +417,15 @@ async function deriveKey$1(master, usage, path) {
|
|
|
417
417
|
|
|
418
418
|
class RpcHandlerManager {
|
|
419
419
|
handlers = /* @__PURE__ */ new Map();
|
|
420
|
+
/**
|
|
421
|
+
* B-506: methods whose params/result are PLAINTEXT JSON strings instead of
|
|
422
|
+
* machine-key ciphertext. The caller is a CLI on another machine of the
|
|
423
|
+
* same account, which holds neither this machine's key nor the account
|
|
424
|
+
* content key; the trusted relay sees the payload either way (this fork
|
|
425
|
+
* is server-trusted, not e2e). Only `registerPlainHandler` puts a method
|
|
426
|
+
* here; everything else keeps decrypting.
|
|
427
|
+
*/
|
|
428
|
+
plainMethods = /* @__PURE__ */ new Set();
|
|
420
429
|
scopePrefix;
|
|
421
430
|
encryptionKey;
|
|
422
431
|
encryptionVariant;
|
|
@@ -441,14 +450,31 @@ class RpcHandlerManager {
|
|
|
441
450
|
unregisterHandler(method) {
|
|
442
451
|
const prefixedMethod = this.getPrefixedMethod(method);
|
|
443
452
|
this.handlers.delete(prefixedMethod);
|
|
453
|
+
this.plainMethods.delete(prefixedMethod);
|
|
444
454
|
for (const socket of this.sockets) socket.emit("rpc-unregister", { method: prefixedMethod });
|
|
445
455
|
}
|
|
456
|
+
/**
|
|
457
|
+
* B-506: register a method whose request is a plaintext JSON string and
|
|
458
|
+
* whose response is returned as a plaintext JSON string (see
|
|
459
|
+
* `plainMethods`). The handler receives the parsed params; a throw is
|
|
460
|
+
* answered with `{ error }` like the encrypted path.
|
|
461
|
+
*/
|
|
462
|
+
registerPlainHandler(method, handler) {
|
|
463
|
+
this.plainMethods.add(this.getPrefixedMethod(method));
|
|
464
|
+
this.registerHandler(method, handler);
|
|
465
|
+
}
|
|
466
|
+
isPlainMethod(prefixedMethod) {
|
|
467
|
+
return this.plainMethods.has(prefixedMethod);
|
|
468
|
+
}
|
|
446
469
|
/**
|
|
447
470
|
* Handle an incoming RPC request
|
|
448
471
|
* @param request - The RPC request data
|
|
449
472
|
* @param callback - The response callback
|
|
450
473
|
*/
|
|
451
474
|
async handleRequest(request) {
|
|
475
|
+
if (this.plainMethods.has(request.method)) {
|
|
476
|
+
return this.handlePlainRequest(request);
|
|
477
|
+
}
|
|
452
478
|
try {
|
|
453
479
|
const handler = this.handlers.get(request.method);
|
|
454
480
|
if (!handler) {
|
|
@@ -472,6 +498,25 @@ class RpcHandlerManager {
|
|
|
472
498
|
return encodeBase64(encrypt(this.encryptionKey, this.encryptionVariant, errorResponse));
|
|
473
499
|
}
|
|
474
500
|
}
|
|
501
|
+
/** Plaintext variant of handleRequest (B-506). Never throws; never encrypts. */
|
|
502
|
+
async handlePlainRequest(request) {
|
|
503
|
+
const handler = this.handlers.get(request.method);
|
|
504
|
+
if (!handler) return JSON.stringify({ error: "Method not found" });
|
|
505
|
+
let params;
|
|
506
|
+
try {
|
|
507
|
+
params = typeof request.params === "string" && request.params.length > 0 ? JSON.parse(request.params) : {};
|
|
508
|
+
} catch {
|
|
509
|
+
return JSON.stringify({ error: "Invalid JSON params" });
|
|
510
|
+
}
|
|
511
|
+
try {
|
|
512
|
+
this.logger("[RPC] Calling plain handler", { method: request.method });
|
|
513
|
+
const result = await handler(params);
|
|
514
|
+
return JSON.stringify(result === void 0 ? null : result);
|
|
515
|
+
} catch (error) {
|
|
516
|
+
this.logger("[RPC] [ERROR] Error handling plain request", { error });
|
|
517
|
+
return JSON.stringify({ error: error instanceof Error ? error.message : "Unknown error" });
|
|
518
|
+
}
|
|
519
|
+
}
|
|
475
520
|
onSocketConnect(socket) {
|
|
476
521
|
this.sockets.add(socket);
|
|
477
522
|
for (const [prefixedMethod] of this.handlers) {
|
|
@@ -2979,16 +3024,29 @@ class ApiSessionClient extends EventEmitter {
|
|
|
2979
3024
|
/** B-466: turn edges (and a periodic lease) reported to the local daemon so
|
|
2980
3025
|
* auto-update waits for "no turn in flight" instead of "no sessions". */
|
|
2981
3026
|
turnReporter = new TurnReporter((event) => void notifyDaemonTurnEvent(this.sessionId, event).catch((error) => logger.debug("[API] Failed to report turn event to daemon:", error)));
|
|
2982
|
-
|
|
3027
|
+
/** B-507: background tasks in flight, published on change and renewed every
|
|
3028
|
+
* 60s to the daemon (`/session-event background_tasks`) and into agentState
|
|
3029
|
+
* (`backgroundTasks: { updatedAt, tasks }`) — the two readers that outlive a
|
|
3030
|
+
* single heartbeat. The heartbeat itself carries only the count. */
|
|
3031
|
+
backgroundTaskReporter = new BackgroundTaskReporter((tasks, now) => {
|
|
3032
|
+
void notifyDaemonBackgroundTasks(this.sessionId, tasks).catch((error) => logger.debug("[API] Failed to report background tasks to daemon:", error));
|
|
3033
|
+
this.updateAgentState((state) => ({
|
|
3034
|
+
...state,
|
|
3035
|
+
backgroundTasks: tasks.length > 0 ? { updatedAt: now, tasks } : void 0
|
|
3036
|
+
}));
|
|
3037
|
+
});
|
|
3038
|
+
keepAlive(thinking, mode, backgroundTasks) {
|
|
2983
3039
|
if (process.env.DEBUG) {
|
|
2984
3040
|
logger.debug(`[API] Sending keep alive message: ${thinking}`);
|
|
2985
3041
|
}
|
|
2986
3042
|
this.turnReporter.observe(thinking);
|
|
3043
|
+
if (backgroundTasks) this.backgroundTaskReporter.observe(backgroundTasks);
|
|
2987
3044
|
this.socket.volatile.emit("session-alive", {
|
|
2988
3045
|
sid: this.sessionId,
|
|
2989
3046
|
time: Date.now(),
|
|
2990
3047
|
thinking,
|
|
2991
|
-
mode
|
|
3048
|
+
mode,
|
|
3049
|
+
...backgroundTasks ? { backgroundTasks: backgroundTasks.length } : {}
|
|
2992
3050
|
});
|
|
2993
3051
|
}
|
|
2994
3052
|
/**
|
|
@@ -14335,6 +14393,17 @@ class ApiMachineClient {
|
|
|
14335
14393
|
return { claudeAuth };
|
|
14336
14394
|
});
|
|
14337
14395
|
}
|
|
14396
|
+
/**
|
|
14397
|
+
* B-506: the five `sessions.*` methods a CLI on ANOTHER machine of this
|
|
14398
|
+
* account calls to read / message sessions this daemon spawned. Plaintext
|
|
14399
|
+
* on purpose (no shared key with the caller); see
|
|
14400
|
+
* `api/rpc/RpcHandlerManager.ts` `registerPlainHandler`.
|
|
14401
|
+
*/
|
|
14402
|
+
setRemoteSessionOpsHandlers(handlers) {
|
|
14403
|
+
for (const [method, handler] of Object.entries(handlers)) {
|
|
14404
|
+
this.rpcHandlerManager.registerPlainHandler(method, handler);
|
|
14405
|
+
}
|
|
14406
|
+
}
|
|
14338
14407
|
setRPCHandlers({
|
|
14339
14408
|
spawnSession,
|
|
14340
14409
|
resumeSession,
|
|
@@ -15163,7 +15232,11 @@ class ApiMachineClient {
|
|
|
15163
15232
|
}
|
|
15164
15233
|
bindRpcRequestHandler(socket, transport) {
|
|
15165
15234
|
socket.on("rpc-request", async (data, callback) => {
|
|
15166
|
-
|
|
15235
|
+
if (typeof data?.method === "string" && this.rpcHandlerManager.isPlainMethod(data.method)) {
|
|
15236
|
+
logger.debug(`[API MACHINE] Received plain RPC request via ${transport}: ${data.method}`);
|
|
15237
|
+
} else {
|
|
15238
|
+
logger.debugLargeJson(`[API MACHINE] Received RPC request via ${transport}:`, data);
|
|
15239
|
+
}
|
|
15167
15240
|
await answerSocketRequest(
|
|
15168
15241
|
callback,
|
|
15169
15242
|
() => this.rpcHandlerManager.handleRequest(data),
|
|
@@ -16201,17 +16274,56 @@ class ApiClient {
|
|
|
16201
16274
|
}
|
|
16202
16275
|
}
|
|
16203
16276
|
/**
|
|
16204
|
-
*
|
|
16205
|
-
*
|
|
16206
|
-
*
|
|
16207
|
-
*
|
|
16277
|
+
* B-505: mark a session OFFLINE on the server (active=false, `archivedAt`
|
|
16278
|
+
* untouched) — the same row state a missed keepalive produces, so the web
|
|
16279
|
+
* shows it offline and resumable instead of archived.
|
|
16280
|
+
*
|
|
16281
|
+
* Used when the wrapper exits for an infrastructure reason (SIGTERM /
|
|
16282
|
+
* SIGINT: daemon stop, supervisor restart, machine shutdown) as a
|
|
16283
|
+
* synchronous fallback for the socket `session-end` signal: even if the
|
|
16284
|
+
* socket emit doesn't drain before the process exits, the HTTP response
|
|
16285
|
+
* confirms the row went inactive.
|
|
16208
16286
|
*
|
|
16209
|
-
*
|
|
16210
|
-
*
|
|
16211
|
-
*
|
|
16212
|
-
*
|
|
16287
|
+
* This MUST NOT hit `/archive`: since B-265 that route writes the
|
|
16288
|
+
* server-owned `archivedAt` tombstone (the only thing that distinguishes
|
|
16289
|
+
* "archived" from "offline"), and the server answers it with a
|
|
16290
|
+
* `session-archive` command that makes the wrapper archive itself. That is
|
|
16291
|
+
* exactly how every dev-sg upgrade archived the sessions mid-turn on
|
|
16292
|
+
* 2026-09-25. A server without `/deactivate` returns 404, which is a
|
|
16293
|
+
* compatible no-op: `session-end` already sets active=false there.
|
|
16213
16294
|
*/
|
|
16214
16295
|
async deactivateSession(sessionId) {
|
|
16296
|
+
try {
|
|
16297
|
+
const response = await axios.post(
|
|
16298
|
+
`${configuration.serverUrl}/v1/sessions/${sessionId}/deactivate`,
|
|
16299
|
+
{},
|
|
16300
|
+
{
|
|
16301
|
+
headers: {
|
|
16302
|
+
"Authorization": `Bearer ${this.credential.token}`,
|
|
16303
|
+
"X-Happy-Client": `cli-coding-session/${configuration.currentCliVersion}`
|
|
16304
|
+
},
|
|
16305
|
+
timeout: 3e3,
|
|
16306
|
+
validateStatus: (status) => status >= 200 && status < 300 || status === 404
|
|
16307
|
+
}
|
|
16308
|
+
);
|
|
16309
|
+
if (response.status === 404) {
|
|
16310
|
+
logger.debug("[API] deactivateSession: server has no /deactivate route; relying on session-end");
|
|
16311
|
+
return false;
|
|
16312
|
+
}
|
|
16313
|
+
return true;
|
|
16314
|
+
} catch (error) {
|
|
16315
|
+
logger.debug("[API] deactivateSession failed:", error);
|
|
16316
|
+
return false;
|
|
16317
|
+
}
|
|
16318
|
+
}
|
|
16319
|
+
/**
|
|
16320
|
+
* Archive a session on the server: writes the `archivedAt` tombstone and
|
|
16321
|
+
* makes the server tell every wrapper of this session to exit. Only for an
|
|
16322
|
+
* intentional end of life — the user's Archive / kill, a crash the wrapper
|
|
16323
|
+
* cannot recover from, a terminal mirror whose pane went back to a shell.
|
|
16324
|
+
* Never call this from a signal handler; see `deactivateSession`.
|
|
16325
|
+
*/
|
|
16326
|
+
async archiveSession(sessionId) {
|
|
16215
16327
|
try {
|
|
16216
16328
|
const response = await axios.post(
|
|
16217
16329
|
`${configuration.serverUrl}/v1/sessions/${sessionId}/archive`,
|
|
@@ -16226,7 +16338,7 @@ class ApiClient {
|
|
|
16226
16338
|
);
|
|
16227
16339
|
return response.status >= 200 && response.status < 300;
|
|
16228
16340
|
} catch (error) {
|
|
16229
|
-
logger.debug("[API]
|
|
16341
|
+
logger.debug("[API] archiveSession failed:", error);
|
|
16230
16342
|
return false;
|
|
16231
16343
|
}
|
|
16232
16344
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "very-happy-cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.157",
|
|
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": [
|
|
@@ -117,12 +117,12 @@
|
|
|
117
117
|
"zod": "^4.0.0"
|
|
118
118
|
},
|
|
119
119
|
"optionalDependencies": {
|
|
120
|
-
"very-happy-tools-arm64-darwin": "0.2.
|
|
121
|
-
"very-happy-tools-x64-darwin": "0.2.
|
|
122
|
-
"very-happy-tools-arm64-linux": "0.2.
|
|
123
|
-
"very-happy-tools-x64-linux": "0.2.
|
|
124
|
-
"very-happy-tools-arm64-win32": "0.2.
|
|
125
|
-
"very-happy-tools-x64-win32": "0.2.
|
|
120
|
+
"very-happy-tools-arm64-darwin": "0.2.157",
|
|
121
|
+
"very-happy-tools-x64-darwin": "0.2.157",
|
|
122
|
+
"very-happy-tools-arm64-linux": "0.2.157",
|
|
123
|
+
"very-happy-tools-x64-linux": "0.2.157",
|
|
124
|
+
"very-happy-tools-arm64-win32": "0.2.157",
|
|
125
|
+
"very-happy-tools-x64-win32": "0.2.157"
|
|
126
126
|
},
|
|
127
127
|
"devDependencies": {
|
|
128
128
|
"@slopus/happy-wire": "workspace:*",
|