pubblue 0.4.11 → 0.4.12
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.
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import {
|
|
2
|
+
TunnelApiError
|
|
3
|
+
} from "./chunk-7NFHPJ76.js";
|
|
1
4
|
import {
|
|
2
5
|
CHANNELS,
|
|
3
6
|
CONTROL_CHANNEL,
|
|
@@ -55,8 +58,9 @@ function generateOffer(peer, timeoutMs) {
|
|
|
55
58
|
|
|
56
59
|
// src/lib/tunnel-daemon-shared.ts
|
|
57
60
|
var OFFER_TIMEOUT_MS = 1e4;
|
|
58
|
-
var SIGNAL_POLL_WAITING_MS =
|
|
59
|
-
var SIGNAL_POLL_CONNECTED_MS =
|
|
61
|
+
var SIGNAL_POLL_WAITING_MS = 5e3;
|
|
62
|
+
var SIGNAL_POLL_CONNECTED_MS = 15e3;
|
|
63
|
+
var LOCAL_CANDIDATE_FLUSH_MS = 2e3;
|
|
60
64
|
var RECOVERY_DELAY_MS = 1e3;
|
|
61
65
|
var WRITE_ACK_TIMEOUT_MS = 5e3;
|
|
62
66
|
var NOT_CONNECTED_WRITE_ERROR = "No browser connected. Ask the user to open the tunnel URL first, then retry.";
|
|
@@ -69,6 +73,14 @@ function shouldRecoverForBrowserAnswerChange(params) {
|
|
|
69
73
|
if (!incomingBrowserAnswer) return false;
|
|
70
74
|
return incomingBrowserAnswer !== lastAppliedBrowserAnswer;
|
|
71
75
|
}
|
|
76
|
+
function getSignalPollDelayMs(params) {
|
|
77
|
+
const baseDelay = params.remoteDescriptionApplied ? SIGNAL_POLL_CONNECTED_MS : SIGNAL_POLL_WAITING_MS;
|
|
78
|
+
if (params.retryAfterSeconds === void 0) return baseDelay;
|
|
79
|
+
if (!Number.isFinite(params.retryAfterSeconds) || params.retryAfterSeconds <= 0) {
|
|
80
|
+
return baseDelay;
|
|
81
|
+
}
|
|
82
|
+
return Math.max(baseDelay, Math.ceil(params.retryAfterSeconds * 1e3));
|
|
83
|
+
}
|
|
72
84
|
|
|
73
85
|
// src/lib/tunnel-daemon.ts
|
|
74
86
|
async function startDaemon(config) {
|
|
@@ -321,7 +333,7 @@ async function startDaemon(config) {
|
|
|
321
333
|
await apiClient.signal(tunnelId, { candidates: newOnes }).catch((error) => {
|
|
322
334
|
debugLog("failed to publish local ICE candidates", error);
|
|
323
335
|
});
|
|
324
|
-
},
|
|
336
|
+
}, LOCAL_CANDIDATE_FLUSH_MS);
|
|
325
337
|
localCandidateStopTimer = setTimeout(() => {
|
|
326
338
|
clearLocalCandidateTimers();
|
|
327
339
|
}, 3e4);
|
|
@@ -441,12 +453,16 @@ async function startDaemon(config) {
|
|
|
441
453
|
}
|
|
442
454
|
async function runPollingLoop() {
|
|
443
455
|
if (stopped) return;
|
|
456
|
+
let retryAfterSeconds;
|
|
444
457
|
try {
|
|
445
458
|
await pollSignalingOnce();
|
|
446
459
|
} catch (error) {
|
|
460
|
+
if (error instanceof TunnelApiError && error.status === 429) {
|
|
461
|
+
retryAfterSeconds = error.retryAfterSeconds;
|
|
462
|
+
}
|
|
447
463
|
markError("signaling poll failed", error);
|
|
448
464
|
}
|
|
449
|
-
scheduleNextPoll(remoteDescriptionApplied
|
|
465
|
+
scheduleNextPoll(getSignalPollDelayMs({ remoteDescriptionApplied, retryAfterSeconds }));
|
|
450
466
|
}
|
|
451
467
|
async function runNegotiationCycle() {
|
|
452
468
|
if (!peer) throw new Error("PeerConnection not initialized");
|
|
@@ -656,5 +672,6 @@ async function startDaemon(config) {
|
|
|
656
672
|
export {
|
|
657
673
|
getTunnelWriteReadinessError,
|
|
658
674
|
shouldRecoverForBrowserAnswerChange,
|
|
675
|
+
getSignalPollDelayMs,
|
|
659
676
|
startDaemon
|
|
660
677
|
};
|
package/dist/index.js
CHANGED
|
@@ -111,6 +111,11 @@ function getConfig(homeDir) {
|
|
|
111
111
|
bridge: saved.bridge
|
|
112
112
|
};
|
|
113
113
|
}
|
|
114
|
+
function getTelegramMiniAppUrl(type, id) {
|
|
115
|
+
const saved = loadConfig();
|
|
116
|
+
if (!saved?.telegram?.botUsername) return null;
|
|
117
|
+
return `https://t.me/${saved.telegram.botUsername}?startapp=${type === "pub" ? "p" : "t"}_${id}`;
|
|
118
|
+
}
|
|
114
119
|
|
|
115
120
|
// src/commands/shared.ts
|
|
116
121
|
import * as fs2 from "fs";
|
|
@@ -278,7 +283,21 @@ function parsePositiveInteger(raw, key) {
|
|
|
278
283
|
}
|
|
279
284
|
return parsed;
|
|
280
285
|
}
|
|
281
|
-
|
|
286
|
+
var SUPPORTED_KEYS = [
|
|
287
|
+
"bridge.mode",
|
|
288
|
+
"openclaw.path",
|
|
289
|
+
"openclaw.sessionId",
|
|
290
|
+
"openclaw.threadId",
|
|
291
|
+
"openclaw.canvasReminderEvery",
|
|
292
|
+
"openclaw.deliver",
|
|
293
|
+
"openclaw.deliverChannel",
|
|
294
|
+
"openclaw.replyTo",
|
|
295
|
+
"openclaw.deliverTimeoutMs",
|
|
296
|
+
"openclaw.attachmentDir",
|
|
297
|
+
"openclaw.attachmentMaxBytes",
|
|
298
|
+
"telegram.botToken"
|
|
299
|
+
];
|
|
300
|
+
function applyConfigSet(bridge, telegram, key, value) {
|
|
282
301
|
switch (key) {
|
|
283
302
|
case "bridge.mode":
|
|
284
303
|
bridge.mode = parseBridgeModeValue(value);
|
|
@@ -292,6 +311,9 @@ function applyBridgeSet(bridge, key, value) {
|
|
|
292
311
|
case "openclaw.threadId":
|
|
293
312
|
bridge.threadId = value;
|
|
294
313
|
return;
|
|
314
|
+
case "openclaw.canvasReminderEvery":
|
|
315
|
+
bridge.canvasReminderEvery = parsePositiveInteger(value, key);
|
|
316
|
+
return;
|
|
295
317
|
case "openclaw.deliver":
|
|
296
318
|
bridge.deliver = parseBooleanValue(value, key);
|
|
297
319
|
return;
|
|
@@ -310,26 +332,20 @@ function applyBridgeSet(bridge, key, value) {
|
|
|
310
332
|
case "openclaw.attachmentMaxBytes":
|
|
311
333
|
bridge.attachmentMaxBytes = parsePositiveInteger(value, key);
|
|
312
334
|
return;
|
|
335
|
+
case "telegram.botToken":
|
|
336
|
+
telegram.botToken = value;
|
|
337
|
+
return;
|
|
313
338
|
default:
|
|
314
339
|
throw new Error(
|
|
315
340
|
[
|
|
316
341
|
`Unknown config key: ${key}`,
|
|
317
342
|
"Supported keys:",
|
|
318
|
-
|
|
319
|
-
" openclaw.path",
|
|
320
|
-
" openclaw.sessionId",
|
|
321
|
-
" openclaw.threadId",
|
|
322
|
-
" openclaw.deliver",
|
|
323
|
-
" openclaw.deliverChannel",
|
|
324
|
-
" openclaw.replyTo",
|
|
325
|
-
" openclaw.deliverTimeoutMs",
|
|
326
|
-
" openclaw.attachmentDir",
|
|
327
|
-
" openclaw.attachmentMaxBytes"
|
|
343
|
+
...SUPPORTED_KEYS.map((k) => ` ${k}`)
|
|
328
344
|
].join("\n")
|
|
329
345
|
);
|
|
330
346
|
}
|
|
331
347
|
}
|
|
332
|
-
function
|
|
348
|
+
function applyConfigUnset(bridge, telegram, key) {
|
|
333
349
|
switch (key) {
|
|
334
350
|
case "bridge.mode":
|
|
335
351
|
delete bridge.mode;
|
|
@@ -343,6 +359,9 @@ function applyBridgeUnset(bridge, key) {
|
|
|
343
359
|
case "openclaw.threadId":
|
|
344
360
|
delete bridge.threadId;
|
|
345
361
|
return;
|
|
362
|
+
case "openclaw.canvasReminderEvery":
|
|
363
|
+
delete bridge.canvasReminderEvery;
|
|
364
|
+
return;
|
|
346
365
|
case "openclaw.deliver":
|
|
347
366
|
delete bridge.deliver;
|
|
348
367
|
return;
|
|
@@ -361,16 +380,45 @@ function applyBridgeUnset(bridge, key) {
|
|
|
361
380
|
case "openclaw.attachmentMaxBytes":
|
|
362
381
|
delete bridge.attachmentMaxBytes;
|
|
363
382
|
return;
|
|
383
|
+
case "telegram.botToken":
|
|
384
|
+
delete telegram.botToken;
|
|
385
|
+
delete telegram.botUsername;
|
|
386
|
+
delete telegram.hasMainWebApp;
|
|
387
|
+
return;
|
|
364
388
|
default:
|
|
365
389
|
throw new Error(`Unknown config key for --unset: ${key}`);
|
|
366
390
|
}
|
|
367
391
|
}
|
|
368
|
-
function
|
|
369
|
-
return Object.values(
|
|
392
|
+
function hasValues(obj) {
|
|
393
|
+
return Object.values(obj).some((value) => value !== void 0);
|
|
394
|
+
}
|
|
395
|
+
function maskSecret(value) {
|
|
396
|
+
if (value.length <= 8) return "********";
|
|
397
|
+
return `${value.slice(0, 4)}...${value.slice(-4)}`;
|
|
398
|
+
}
|
|
399
|
+
async function telegramGetMe(token) {
|
|
400
|
+
const resp = await fetch(`https://api.telegram.org/bot${token}/getMe`);
|
|
401
|
+
const data = await resp.json();
|
|
402
|
+
if (!data.ok || !data.result?.username) {
|
|
403
|
+
throw new Error(data.description ?? "Invalid bot token");
|
|
404
|
+
}
|
|
405
|
+
return {
|
|
406
|
+
username: data.result.username,
|
|
407
|
+
hasMainWebApp: data.result.has_main_web_app === true
|
|
408
|
+
};
|
|
370
409
|
}
|
|
371
|
-
function
|
|
372
|
-
|
|
373
|
-
|
|
410
|
+
async function telegramSetMenuButton(token, url) {
|
|
411
|
+
const resp = await fetch(`https://api.telegram.org/bot${token}/setChatMenuButton`, {
|
|
412
|
+
method: "POST",
|
|
413
|
+
headers: { "Content-Type": "application/json" },
|
|
414
|
+
body: JSON.stringify({
|
|
415
|
+
menu_button: { type: "web_app", text: "Open", web_app: { url } }
|
|
416
|
+
})
|
|
417
|
+
});
|
|
418
|
+
const data = await resp.json();
|
|
419
|
+
if (!data.ok) {
|
|
420
|
+
throw new Error(data.description ?? "setChatMenuButton failed");
|
|
421
|
+
}
|
|
374
422
|
}
|
|
375
423
|
function printConfigSummary(saved) {
|
|
376
424
|
if (!saved) {
|
|
@@ -378,34 +426,50 @@ function printConfigSummary(saved) {
|
|
|
378
426
|
return;
|
|
379
427
|
}
|
|
380
428
|
console.log("Saved config:");
|
|
381
|
-
console.log(` apiKey: ${
|
|
382
|
-
if (
|
|
429
|
+
console.log(` apiKey: ${maskSecret(saved.apiKey)}`);
|
|
430
|
+
if (saved.bridge && hasValues(saved.bridge)) {
|
|
431
|
+
console.log(` bridge.mode: ${saved.bridge.mode ?? "(unset)"}`);
|
|
432
|
+
if (saved.bridge.openclawPath) console.log(` openclaw.path: ${saved.bridge.openclawPath}`);
|
|
433
|
+
if (saved.bridge.sessionId) console.log(` openclaw.sessionId: ${saved.bridge.sessionId}`);
|
|
434
|
+
if (saved.bridge.threadId) console.log(` openclaw.threadId: ${saved.bridge.threadId}`);
|
|
435
|
+
if (saved.bridge.canvasReminderEvery !== void 0)
|
|
436
|
+
console.log(` openclaw.canvasReminderEvery: ${saved.bridge.canvasReminderEvery}`);
|
|
437
|
+
if (saved.bridge.deliver !== void 0)
|
|
438
|
+
console.log(` openclaw.deliver: ${saved.bridge.deliver ? "true" : "false"}`);
|
|
439
|
+
if (saved.bridge.deliverChannel)
|
|
440
|
+
console.log(` openclaw.deliverChannel: ${saved.bridge.deliverChannel}`);
|
|
441
|
+
if (saved.bridge.replyTo) console.log(` openclaw.replyTo: ${saved.bridge.replyTo}`);
|
|
442
|
+
if (saved.bridge.deliverTimeoutMs !== void 0)
|
|
443
|
+
console.log(` openclaw.deliverTimeoutMs: ${saved.bridge.deliverTimeoutMs}`);
|
|
444
|
+
if (saved.bridge.attachmentDir)
|
|
445
|
+
console.log(` openclaw.attachmentDir: ${saved.bridge.attachmentDir}`);
|
|
446
|
+
if (saved.bridge.attachmentMaxBytes !== void 0)
|
|
447
|
+
console.log(` openclaw.attachmentMaxBytes: ${saved.bridge.attachmentMaxBytes}`);
|
|
448
|
+
} else {
|
|
383
449
|
console.log(" bridge: none");
|
|
384
|
-
return;
|
|
385
450
|
}
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
if (saved.
|
|
393
|
-
console.log(`
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
console.log(
|
|
397
|
-
|
|
398
|
-
console.log(
|
|
399
|
-
|
|
400
|
-
console.log(` openclaw.attachmentMaxBytes: ${saved.bridge.attachmentMaxBytes}`);
|
|
451
|
+
if (saved.telegram?.botToken && saved.telegram.botUsername) {
|
|
452
|
+
console.log(` telegram.botToken: ${maskSecret(saved.telegram.botToken)}`);
|
|
453
|
+
console.log(` telegram.botUsername: @${saved.telegram.botUsername}`);
|
|
454
|
+
if (!saved.telegram.hasMainWebApp) {
|
|
455
|
+
console.log(" INFO: Register Mini App in @BotFather for deep links to open in Telegram");
|
|
456
|
+
}
|
|
457
|
+
} else if (saved.telegram?.botToken) {
|
|
458
|
+
console.log(` telegram.botToken: ${maskSecret(saved.telegram.botToken)}`);
|
|
459
|
+
console.log(" telegram.botUsername: (not resolved)");
|
|
460
|
+
} else {
|
|
461
|
+
console.log(" telegram: not configured");
|
|
462
|
+
console.log(" INFO: Set telegram.botToken to enable Telegram Mini App links");
|
|
463
|
+
console.log(" Example: pubblue configure --set telegram.botToken=<BOT_TOKEN>");
|
|
464
|
+
}
|
|
401
465
|
}
|
|
402
466
|
function registerConfigureCommand(program2) {
|
|
403
467
|
program2.command("configure").description("Configure the CLI with your API key").option("--api-key <key>", "Your API key (less secure: appears in shell history)").option("--api-key-stdin", "Read API key from stdin").option(
|
|
404
468
|
"--set <key=value>",
|
|
405
|
-
"Set
|
|
469
|
+
"Set config key (repeatable). Example: --set telegram.botToken=<token>",
|
|
406
470
|
collectValues,
|
|
407
471
|
[]
|
|
408
|
-
).option("--unset <key>", "Unset
|
|
472
|
+
).option("--unset <key>", "Unset config key (repeatable)", collectValues, []).option("--show", "Show saved configuration").action(
|
|
409
473
|
async (opts) => {
|
|
410
474
|
const saved = loadConfig();
|
|
411
475
|
const hasApiUpdate = Boolean(opts.apiKey || opts.apiKeyStdin);
|
|
@@ -431,16 +495,35 @@ function registerConfigureCommand(program2) {
|
|
|
431
495
|
}
|
|
432
496
|
}
|
|
433
497
|
const nextBridge = { ...saved?.bridge ?? {} };
|
|
498
|
+
const nextTelegram = { ...saved?.telegram ?? {} };
|
|
499
|
+
let telegramTokenChanged = false;
|
|
434
500
|
for (const entry of opts.set) {
|
|
435
501
|
const { key, value } = parseSetInput(entry);
|
|
436
|
-
|
|
502
|
+
applyConfigSet(nextBridge, nextTelegram, key, value);
|
|
503
|
+
if (key === "telegram.botToken") telegramTokenChanged = true;
|
|
437
504
|
}
|
|
438
505
|
for (const key of opts.unset) {
|
|
439
|
-
|
|
506
|
+
applyConfigUnset(nextBridge, nextTelegram, key.trim());
|
|
507
|
+
}
|
|
508
|
+
if (telegramTokenChanged && nextTelegram.botToken) {
|
|
509
|
+
console.log("Verifying Telegram bot token...");
|
|
510
|
+
const bot = await telegramGetMe(nextTelegram.botToken);
|
|
511
|
+
nextTelegram.botUsername = bot.username;
|
|
512
|
+
nextTelegram.hasMainWebApp = bot.hasMainWebApp;
|
|
513
|
+
console.log(` Bot: @${bot.username}`);
|
|
514
|
+
await telegramSetMenuButton(nextTelegram.botToken, "https://pub.blue");
|
|
515
|
+
console.log(" Menu button set to https://pub.blue");
|
|
516
|
+
if (!bot.hasMainWebApp) {
|
|
517
|
+
console.log("");
|
|
518
|
+
console.log(" INFO: For deep links to open inside Telegram, register the Mini App:");
|
|
519
|
+
console.log(" @BotFather \u2192 /mybots \u2192 your bot \u2192 Bot Settings \u2192 Configure Mini App");
|
|
520
|
+
console.log(" Set Web App URL to: https://pub.blue");
|
|
521
|
+
}
|
|
440
522
|
}
|
|
441
523
|
const nextConfig = {
|
|
442
524
|
apiKey,
|
|
443
|
-
bridge:
|
|
525
|
+
bridge: hasValues(nextBridge) ? nextBridge : void 0,
|
|
526
|
+
telegram: hasValues(nextTelegram) ? nextTelegram : void 0
|
|
444
527
|
};
|
|
445
528
|
saveConfig(nextConfig);
|
|
446
529
|
console.log("Configuration saved.");
|
|
@@ -479,6 +562,8 @@ function registerPublicationCommands(program2) {
|
|
|
479
562
|
expiresIn: opts.expires
|
|
480
563
|
});
|
|
481
564
|
console.log(`Created: ${result.url}`);
|
|
565
|
+
const tmaUrl = getTelegramMiniAppUrl("pub", result.slug);
|
|
566
|
+
if (tmaUrl) console.log(`Telegram: ${tmaUrl}`);
|
|
482
567
|
if (result.expiresAt) {
|
|
483
568
|
console.log(` Expires: ${new Date(result.expiresAt).toISOString()}`);
|
|
484
569
|
}
|
|
@@ -643,6 +728,9 @@ function buildBridgeProcessEnv(bridgeConfig) {
|
|
|
643
728
|
setIfMissing("OPENCLAW_PATH", bridgeConfig.openclawPath);
|
|
644
729
|
setIfMissing("OPENCLAW_SESSION_ID", bridgeConfig.sessionId);
|
|
645
730
|
setIfMissing("OPENCLAW_THREAD_ID", bridgeConfig.threadId);
|
|
731
|
+
if (bridgeConfig.canvasReminderEvery !== void 0) {
|
|
732
|
+
setIfMissing("OPENCLAW_CANVAS_REMINDER_EVERY", bridgeConfig.canvasReminderEvery);
|
|
733
|
+
}
|
|
646
734
|
if (bridgeConfig.deliver !== void 0) {
|
|
647
735
|
setIfMissing("OPENCLAW_DELIVER", bridgeConfig.deliver ? "1" : "0");
|
|
648
736
|
}
|
|
@@ -1292,7 +1380,7 @@ import * as path5 from "path";
|
|
|
1292
1380
|
// package.json
|
|
1293
1381
|
var package_default = {
|
|
1294
1382
|
name: "pubblue",
|
|
1295
|
-
version: "0.4.
|
|
1383
|
+
version: "0.4.12",
|
|
1296
1384
|
description: "CLI tool for publishing static content via pub.blue",
|
|
1297
1385
|
type: "module",
|
|
1298
1386
|
bin: {
|
|
@@ -1434,8 +1522,10 @@ function registerTunnelStartCommand(tunnel) {
|
|
|
1434
1522
|
"Foreground mode disables managed bridge process. Use background mode for --bridge openclaw."
|
|
1435
1523
|
);
|
|
1436
1524
|
}
|
|
1437
|
-
const { startDaemon } = await import("./tunnel-daemon-
|
|
1525
|
+
const { startDaemon } = await import("./tunnel-daemon-RKWEA5BV.js");
|
|
1438
1526
|
console.log(`Tunnel started: ${target.url}`);
|
|
1527
|
+
const fgTma = getTelegramMiniAppUrl("tunnel", target.tunnelId);
|
|
1528
|
+
if (fgTma) console.log(`Telegram: ${fgTma}`);
|
|
1439
1529
|
console.log(`Tunnel ID: ${target.tunnelId}`);
|
|
1440
1530
|
console.log(`Expires: ${new Date(target.expiresAt).toISOString()}`);
|
|
1441
1531
|
if (target.mode === "existing") console.log("Mode: attached existing tunnel");
|
|
@@ -1530,6 +1620,8 @@ function registerTunnelStartCommand(tunnel) {
|
|
|
1530
1620
|
}
|
|
1531
1621
|
}
|
|
1532
1622
|
console.log(`Tunnel started: ${target.url}`);
|
|
1623
|
+
const runTma = getTelegramMiniAppUrl("tunnel", target.tunnelId);
|
|
1624
|
+
if (runTma) console.log(`Telegram: ${runTma}`);
|
|
1533
1625
|
console.log(`Tunnel ID: ${target.tunnelId}`);
|
|
1534
1626
|
console.log(`Expires: ${new Date(target.expiresAt).toISOString()}`);
|
|
1535
1627
|
console.log("Daemon already running for this tunnel.");
|
|
@@ -1630,6 +1722,8 @@ function registerTunnelStartCommand(tunnel) {
|
|
|
1630
1722
|
}
|
|
1631
1723
|
}
|
|
1632
1724
|
console.log(`Tunnel started: ${target.url}`);
|
|
1725
|
+
const tma = getTelegramMiniAppUrl("tunnel", target.tunnelId);
|
|
1726
|
+
if (tma) console.log(`Telegram: ${tma}`);
|
|
1633
1727
|
console.log(`Tunnel ID: ${target.tunnelId}`);
|
|
1634
1728
|
console.log(`Expires: ${new Date(target.expiresAt).toISOString()}`);
|
|
1635
1729
|
if (target.mode === "existing") console.log("Mode: attached existing tunnel");
|
|
@@ -34,6 +34,7 @@ var MONITORED_ATTACHMENT_CHANNELS = /* @__PURE__ */ new Set([
|
|
|
34
34
|
CHANNELS.MEDIA
|
|
35
35
|
]);
|
|
36
36
|
var DEFAULT_ATTACHMENT_MAX_BYTES = 25 * 1024 * 1024;
|
|
37
|
+
var DEFAULT_CANVAS_REMINDER_EVERY = 10;
|
|
37
38
|
var MAX_SEEN_IDS = 1e4;
|
|
38
39
|
function sleep(ms) {
|
|
39
40
|
return new Promise((resolve) => {
|
|
@@ -58,6 +59,11 @@ function resolveAttachmentMaxBytes() {
|
|
|
58
59
|
if (!Number.isFinite(raw) || raw <= 0) return DEFAULT_ATTACHMENT_MAX_BYTES;
|
|
59
60
|
return raw;
|
|
60
61
|
}
|
|
62
|
+
function resolveCanvasReminderEvery() {
|
|
63
|
+
const raw = Number.parseInt(process.env.OPENCLAW_CANVAS_REMINDER_EVERY || "", 10);
|
|
64
|
+
if (!Number.isFinite(raw) || raw <= 0) return DEFAULT_CANVAS_REMINDER_EVERY;
|
|
65
|
+
return raw;
|
|
66
|
+
}
|
|
61
67
|
function inferExtensionFromMime(mime) {
|
|
62
68
|
const normalized = mime.split(";")[0]?.trim().toLowerCase();
|
|
63
69
|
if (!normalized) return ".bin";
|
|
@@ -123,8 +129,24 @@ function stageAttachment(params) {
|
|
|
123
129
|
streamStatus: params.streamStatus
|
|
124
130
|
};
|
|
125
131
|
}
|
|
126
|
-
function
|
|
132
|
+
function buildCanvasPolicyReminderBlock() {
|
|
133
|
+
return [
|
|
134
|
+
"[Canvas policy reminder: do not reply to this reminder block]",
|
|
135
|
+
"- Prefer canvas-first responses for substantive output.",
|
|
136
|
+
"- Use chat only for short clarifications, confirmations, or blockers.",
|
|
137
|
+
"- Keep chat replies concise.",
|
|
138
|
+
""
|
|
139
|
+
].join("\n");
|
|
140
|
+
}
|
|
141
|
+
function shouldIncludeCanvasPolicyReminder(forwardedMessageCount, reminderEvery) {
|
|
142
|
+
if (!Number.isFinite(reminderEvery) || reminderEvery <= 0) return false;
|
|
143
|
+
if (forwardedMessageCount <= 0) return false;
|
|
144
|
+
return forwardedMessageCount % reminderEvery === 0;
|
|
145
|
+
}
|
|
146
|
+
function buildInboundPrompt(tunnelId2, userText, includeCanvasReminder) {
|
|
147
|
+
const policyReminder = includeCanvasReminder ? buildCanvasPolicyReminderBlock() : "";
|
|
127
148
|
return [
|
|
149
|
+
policyReminder,
|
|
128
150
|
`[Pubblue Tunnel ${tunnelId2}] Incoming user message:`,
|
|
129
151
|
"",
|
|
130
152
|
userText,
|
|
@@ -132,10 +154,12 @@ function buildInboundPrompt(tunnelId2, userText) {
|
|
|
132
154
|
"---",
|
|
133
155
|
`Reply with: pubblue tunnel write --tunnel ${tunnelId2} "<your reply>"`,
|
|
134
156
|
`Canvas update: pubblue tunnel write --tunnel ${tunnelId2} -c canvas -f /path/to/file.html`
|
|
135
|
-
].join("\n");
|
|
157
|
+
].filter(Boolean).join("\n");
|
|
136
158
|
}
|
|
137
|
-
function buildAttachmentPrompt(tunnelId2, staged) {
|
|
159
|
+
function buildAttachmentPrompt(tunnelId2, staged, includeCanvasReminder) {
|
|
160
|
+
const policyReminder = includeCanvasReminder ? buildCanvasPolicyReminderBlock() : "";
|
|
138
161
|
return [
|
|
162
|
+
policyReminder,
|
|
139
163
|
`[Pubblue Tunnel ${tunnelId2}] Incoming user attachment:`,
|
|
140
164
|
`- channel: ${staged.channel}`,
|
|
141
165
|
`- type: attachment`,
|
|
@@ -336,7 +360,11 @@ async function handleAttachmentEntry(params) {
|
|
|
336
360
|
const { entry, activeStreams } = params;
|
|
337
361
|
const { channel, msg } = entry;
|
|
338
362
|
const stageAndDeliver = async (staged2) => {
|
|
339
|
-
const attachmentPrompt = buildAttachmentPrompt(
|
|
363
|
+
const attachmentPrompt = buildAttachmentPrompt(
|
|
364
|
+
params.tunnelId,
|
|
365
|
+
staged2,
|
|
366
|
+
params.includeCanvasReminder
|
|
367
|
+
);
|
|
340
368
|
await deliverMessageToOpenClaw({
|
|
341
369
|
openclawPath: params.openclawPath,
|
|
342
370
|
sessionId: params.sessionId,
|
|
@@ -345,6 +373,7 @@ async function handleAttachmentEntry(params) {
|
|
|
345
373
|
};
|
|
346
374
|
if (msg.type === "stream-start") {
|
|
347
375
|
const existing = activeStreams.get(channel);
|
|
376
|
+
let deliveredInterrupted = false;
|
|
348
377
|
if (existing && existing.bytes > 0) {
|
|
349
378
|
const interruptedBytes = Buffer.concat(existing.chunks);
|
|
350
379
|
const stagedInterrupted = stageAttachment({
|
|
@@ -359,6 +388,7 @@ async function handleAttachmentEntry(params) {
|
|
|
359
388
|
bytes: interruptedBytes
|
|
360
389
|
});
|
|
361
390
|
await stageAndDeliver(stagedInterrupted);
|
|
391
|
+
deliveredInterrupted = true;
|
|
362
392
|
}
|
|
363
393
|
activeStreams.set(channel, {
|
|
364
394
|
bytes: 0,
|
|
@@ -367,15 +397,15 @@ async function handleAttachmentEntry(params) {
|
|
|
367
397
|
mime: typeof msg.meta?.mime === "string" ? msg.meta.mime : void 0,
|
|
368
398
|
streamId: msg.id
|
|
369
399
|
});
|
|
370
|
-
return;
|
|
400
|
+
return deliveredInterrupted;
|
|
371
401
|
}
|
|
372
402
|
if (msg.type === "stream-end") {
|
|
373
403
|
const stream2 = activeStreams.get(channel);
|
|
374
|
-
if (!stream2) return;
|
|
404
|
+
if (!stream2) return false;
|
|
375
405
|
const requestedStreamId = typeof msg.meta?.streamId === "string" ? msg.meta.streamId : void 0;
|
|
376
|
-
if (requestedStreamId && requestedStreamId !== stream2.streamId) return;
|
|
406
|
+
if (requestedStreamId && requestedStreamId !== stream2.streamId) return false;
|
|
377
407
|
activeStreams.delete(channel);
|
|
378
|
-
if (stream2.bytes === 0) return;
|
|
408
|
+
if (stream2.bytes === 0) return false;
|
|
379
409
|
const bytes = Buffer.concat(stream2.chunks);
|
|
380
410
|
const staged2 = stageAttachment({
|
|
381
411
|
attachmentRoot: params.attachmentRoot,
|
|
@@ -389,14 +419,14 @@ async function handleAttachmentEntry(params) {
|
|
|
389
419
|
bytes
|
|
390
420
|
});
|
|
391
421
|
await stageAndDeliver(staged2);
|
|
392
|
-
return;
|
|
422
|
+
return true;
|
|
393
423
|
}
|
|
394
424
|
if (msg.type === "stream-data") {
|
|
395
|
-
if (typeof msg.data !== "string" || msg.data.length === 0) return;
|
|
425
|
+
if (typeof msg.data !== "string" || msg.data.length === 0) return false;
|
|
396
426
|
const stream2 = activeStreams.get(channel);
|
|
397
|
-
if (!stream2) return;
|
|
427
|
+
if (!stream2) return false;
|
|
398
428
|
const requestedStreamId = readStreamIdFromMeta(msg.meta);
|
|
399
|
-
if (requestedStreamId && requestedStreamId !== stream2.streamId) return;
|
|
429
|
+
if (requestedStreamId && requestedStreamId !== stream2.streamId) return false;
|
|
400
430
|
const chunk = decodeBinaryPayload(msg.data, `${channel}/${msg.id}`);
|
|
401
431
|
const nextBytes = stream2.bytes + chunk.length;
|
|
402
432
|
if (nextBytes > params.attachmentMaxBytes) {
|
|
@@ -407,16 +437,16 @@ async function handleAttachmentEntry(params) {
|
|
|
407
437
|
}
|
|
408
438
|
stream2.bytes = nextBytes;
|
|
409
439
|
stream2.chunks.push(chunk);
|
|
410
|
-
return;
|
|
440
|
+
return false;
|
|
411
441
|
}
|
|
412
442
|
if (msg.type !== "binary" || typeof msg.data !== "string") {
|
|
413
|
-
return;
|
|
443
|
+
return false;
|
|
414
444
|
}
|
|
415
445
|
const payload = decodeBinaryPayload(msg.data, `${channel}/${msg.id}`);
|
|
416
446
|
const stream = activeStreams.get(channel);
|
|
417
447
|
if (stream) {
|
|
418
448
|
const requestedStreamId = readStreamIdFromMeta(msg.meta);
|
|
419
|
-
if (requestedStreamId && requestedStreamId !== stream.streamId) return;
|
|
449
|
+
if (requestedStreamId && requestedStreamId !== stream.streamId) return false;
|
|
420
450
|
const nextBytes = stream.bytes + payload.length;
|
|
421
451
|
if (nextBytes > params.attachmentMaxBytes) {
|
|
422
452
|
activeStreams.delete(channel);
|
|
@@ -426,7 +456,7 @@ async function handleAttachmentEntry(params) {
|
|
|
426
456
|
}
|
|
427
457
|
stream.bytes = nextBytes;
|
|
428
458
|
stream.chunks.push(payload);
|
|
429
|
-
return;
|
|
459
|
+
return false;
|
|
430
460
|
}
|
|
431
461
|
if (payload.length > params.attachmentMaxBytes) {
|
|
432
462
|
throw new Error(
|
|
@@ -444,6 +474,7 @@ async function handleAttachmentEntry(params) {
|
|
|
444
474
|
bytes: payload
|
|
445
475
|
});
|
|
446
476
|
await stageAndDeliver(staged);
|
|
477
|
+
return true;
|
|
447
478
|
}
|
|
448
479
|
async function startOpenClawBridge(params) {
|
|
449
480
|
const startedAt = Date.now();
|
|
@@ -514,6 +545,8 @@ async function startOpenClawBridge(params) {
|
|
|
514
545
|
});
|
|
515
546
|
const seenIds = /* @__PURE__ */ new Set();
|
|
516
547
|
const activeStreams = /* @__PURE__ */ new Map();
|
|
548
|
+
const canvasReminderEvery = resolveCanvasReminderEvery();
|
|
549
|
+
let forwardedMessageCount = 0;
|
|
517
550
|
let consecutiveReadFailures = 0;
|
|
518
551
|
while (!shuttingDown) {
|
|
519
552
|
let messages = [];
|
|
@@ -542,7 +575,7 @@ async function startOpenClawBridge(params) {
|
|
|
542
575
|
continue;
|
|
543
576
|
}
|
|
544
577
|
if (messages.length === 0) {
|
|
545
|
-
await sleep(
|
|
578
|
+
await sleep(500);
|
|
546
579
|
continue;
|
|
547
580
|
}
|
|
548
581
|
for (const rawEntry of messages) {
|
|
@@ -555,25 +588,34 @@ async function startOpenClawBridge(params) {
|
|
|
555
588
|
seenIds.clear();
|
|
556
589
|
}
|
|
557
590
|
try {
|
|
591
|
+
const includeCanvasReminder = shouldIncludeCanvasPolicyReminder(
|
|
592
|
+
forwardedMessageCount + 1,
|
|
593
|
+
canvasReminderEvery
|
|
594
|
+
);
|
|
558
595
|
const chat = readTextChatMessage(entry);
|
|
559
596
|
if (chat) {
|
|
560
597
|
await deliverMessageToOpenClaw({
|
|
561
598
|
openclawPath,
|
|
562
599
|
sessionId,
|
|
563
|
-
text: buildInboundPrompt(params.tunnelId, chat)
|
|
600
|
+
text: buildInboundPrompt(params.tunnelId, chat, includeCanvasReminder)
|
|
564
601
|
});
|
|
602
|
+
forwardedMessageCount += 1;
|
|
565
603
|
continue;
|
|
566
604
|
}
|
|
567
605
|
if (!MONITORED_ATTACHMENT_CHANNELS.has(entry.channel)) continue;
|
|
568
|
-
await handleAttachmentEntry({
|
|
606
|
+
const deliveredAttachment = await handleAttachmentEntry({
|
|
569
607
|
activeStreams,
|
|
570
608
|
attachmentMaxBytes,
|
|
571
609
|
attachmentRoot,
|
|
572
610
|
entry,
|
|
611
|
+
includeCanvasReminder,
|
|
573
612
|
openclawPath,
|
|
574
613
|
sessionId,
|
|
575
614
|
tunnelId: params.tunnelId
|
|
576
615
|
});
|
|
616
|
+
if (deliveredAttachment) {
|
|
617
|
+
forwardedMessageCount += 1;
|
|
618
|
+
}
|
|
577
619
|
} catch (error) {
|
|
578
620
|
const message = error instanceof Error ? error.message : String(error);
|
|
579
621
|
console.error(`[pubblue bridge ${params.tunnelId}] ${message}`);
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
|
+
getSignalPollDelayMs,
|
|
2
3
|
getTunnelWriteReadinessError,
|
|
3
4
|
shouldRecoverForBrowserAnswerChange,
|
|
4
5
|
startDaemon
|
|
5
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-UW7JILRJ.js";
|
|
7
|
+
import "./chunk-7NFHPJ76.js";
|
|
6
8
|
import "./chunk-4YTJ2WKF.js";
|
|
7
9
|
export {
|
|
10
|
+
getSignalPollDelayMs,
|
|
8
11
|
getTunnelWriteReadinessError,
|
|
9
12
|
shouldRecoverForBrowserAnswerChange,
|
|
10
13
|
startDaemon
|