zback 1.4.1 → 1.4.2

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/README.md CHANGED
@@ -82,3 +82,16 @@ The npm package declares its remote administration capabilities as dual-use soft
82
82
  ```sh
83
83
  npm test
84
84
  ```
85
+
86
+ ## Connection health (1.2.6)
87
+
88
+ Normal WebSocket ping/pong checks update the local health timestamp silently. A
89
+ `Connection restored` message is printed **only after a real WebSocket
90
+ disconnection and a successful new handshake**, never after a routine pong.
91
+ A delayed pong has a 60-second grace period before the client reconnects.
92
+ The private session identifier remains derived from the local agent key and
93
+ does not change on reconnection. If the connection truly closes, the terminal
94
+ now reports the WebSocket close code and available server reason.
95
+
96
+ Update the backend to 1.2.4 for relaxed server heartbeat handling too. This
97
+ does not change `--timeout 0`, MCP tools, or interactive consent behavior.
package/bin/zback.js CHANGED
@@ -10,9 +10,9 @@ import { createTerminal, createEventReporter } from '../src/terminal.js';
10
10
  import { activeSession, agentKey, consentFile, hasConsent, initStateDir, logFile, readJson, resetKey, sameSession, saveJson, settingsFile, stateFile, alive } from '../src/runtime.js';
11
11
 
12
12
  const ENTRY = fileURLToPath(import.meta.url);
13
- const VERSION = '1.2.5';
13
+ const VERSION = '1.2.6';
14
14
  const DEFAULT_URL = 'wss://zb.soymaycol.icu/ws';
15
- const STALE_AFTER_MS = 60000;
15
+ const STALE_AFTER_MS = 120000;
16
16
 
17
17
  function connectionStatus(state) {
18
18
  if (state.status === 'connected' && (!Number.isFinite(Date.parse(state.updated)) || Date.now() - Date.parse(state.updated) > STALE_AFTER_MS)) return 'unresponsive';
@@ -113,8 +113,9 @@ async function worker() {
113
113
  const mark = data => {
114
114
  const previous = readJson(stateFile);
115
115
  if (previous && previous.status !== 'stopped' && previous.pid !== process.pid && alive(previous.pid)) return;
116
- saveJson(stateFile, { pid: process.pid, started, version: VERSION, updated: new Date().toISOString(), ...data });
117
- if (data.status === 'connected') {
116
+ const { heartbeat, ...state } = data;
117
+ saveJson(stateFile, { pid: process.pid, started, version: VERSION, updated: new Date().toISOString(), ...state });
118
+ if (data.status === 'connected' && !heartbeat) {
118
119
  reporter.clearProblem();
119
120
  if (!lastId) terminal.connected(data.id, mcpEndpoint(settings.url));
120
121
  else terminal.restored();
@@ -242,8 +243,9 @@ async function start(options) {
242
243
  let lastId = null;
243
244
  const reporter = createEventReporter(terminal);
244
245
  const mark = data => {
245
- saveJson(stateFile, { pid: process.pid, started, version: VERSION, updated: new Date().toISOString(), ...data });
246
- if (data.status === 'connected') {
246
+ const { heartbeat, ...state } = data;
247
+ saveJson(stateFile, { pid: process.pid, started, version: VERSION, updated: new Date().toISOString(), ...state });
248
+ if (data.status === 'connected' && !heartbeat) {
247
249
  reporter.clearProblem();
248
250
  if (!lastId) terminal.connected(data.id, mcpEndpoint(options.url));
249
251
  else terminal.restored();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zback",
3
- "version": "1.4.1",
3
+ "version": "1.4.2",
4
4
  "description": "Opt-in foreground remote shell administration with a persistent WebSocket agent and private session credential",
5
5
  "type": "module",
6
6
  "bin": {
package/src/agent.js CHANGED
@@ -63,7 +63,7 @@ export async function connectAgent(options, onState, token, onEvent = () => {},
63
63
  }
64
64
  }
65
65
  const heartbeatInterval = options.heartbeatIntervalMs ?? 20000;
66
- const pongTimeout = options.pongTimeoutMs ?? 12000;
66
+ const pongTimeout = options.pongTimeoutMs ?? 60000;
67
67
  let stopped = false;
68
68
  let retries = 0;
69
69
  let socket = null;
@@ -136,7 +136,7 @@ export async function connectAgent(options, onState, token, onEvent = () => {},
136
136
  const pong = () => {
137
137
  clearTimeout(pongTimer);
138
138
  pongTimer = null;
139
- if (welcomed && !stopped) update('connected', { id: lastId });
139
+ if (welcomed && !stopped) update('connected', { id: lastId, heartbeat: true });
140
140
  };
141
141
  const checkConnection = () => {
142
142
  if (current.readyState !== WebSocketClient.OPEN || pongTimer || stopped) return;
@@ -193,7 +193,7 @@ export async function connectAgent(options, onState, token, onEvent = () => {},
193
193
  const failed = error => disconnected(error?.message || 'WebSocket connection failed');
194
194
  const closed = (code, reason) => {
195
195
  const detail = reason?.toString();
196
- disconnected(welcomed ? 'Connection lost' : `Backend closed the connection (WebSocket ${code}${detail ? `: ${detail}` : ''})`);
196
+ disconnected(welcomed ? `Connection lost (WebSocket ${code}${detail ? `: ${detail}` : ''})` : `Backend closed the connection (WebSocket ${code}${detail ? `: ${detail}` : ''})`);
197
197
  };
198
198
  current.on('open', opened);
199
199
  current.on('message', received);