@ouro.bot/cli 0.1.0-alpha.830 → 0.1.0-alpha.831
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/changelog.json
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"_note": "This changelog is maintained as part of the PR/version-bump workflow. Agent-curated, not auto-generated. Agents read this file directly via read_file to understand what changed between versions.",
|
|
3
3
|
"versions": [
|
|
4
|
+
{
|
|
5
|
+
"version": "0.1.0-alpha.831",
|
|
6
|
+
"changes": [
|
|
7
|
+
"Sanctuary: the Telegram long-poll survives transient transport failures — a dropped authority socket or a Telegram API error is retried with exponential backoff instead of exiting the sense process, so a gateway restart no longer crash-loops the resident. Non-transport (dispatch/durable-audit) failures still surface."
|
|
8
|
+
]
|
|
9
|
+
},
|
|
4
10
|
{
|
|
5
11
|
"version": "0.1.0-alpha.830",
|
|
6
12
|
"changes": [
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<?xml version="1.0"?>
|
|
2
2
|
<Container version="2">
|
|
3
3
|
<Name>ouro-butler</Name>
|
|
4
|
-
<Repository>ghcr.io/ourostack/ouroboros-butler:0.1.0-alpha.
|
|
4
|
+
<Repository>ghcr.io/ourostack/ouroboros-butler:0.1.0-alpha.831</Repository>
|
|
5
5
|
<Registry>https://github.com/ourostack/ouroboros/pkgs/container/ouroboros-butler</Registry>
|
|
6
6
|
<Network>host</Network>
|
|
7
7
|
<Shell>sh</Shell>
|
|
@@ -36,6 +36,15 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
exports.SocketFrontendClient = void 0;
|
|
37
37
|
const net = __importStar(require("node:net"));
|
|
38
38
|
const runtime_1 = require("../nerves/runtime");
|
|
39
|
+
// A dropped or refused frontend socket is a transient transport condition, not a
|
|
40
|
+
// logic failure: the caller (e.g. the Telegram long-poll loop) should reconnect
|
|
41
|
+
// and retry rather than exit. Flag such errors so that layer can classify them.
|
|
42
|
+
function markTransientTransport(error) {
|
|
43
|
+
return Object.assign(error, { isTransientTransportError: true });
|
|
44
|
+
}
|
|
45
|
+
function transientTransportError(message) {
|
|
46
|
+
return markTransientTransport(new Error(message));
|
|
47
|
+
}
|
|
39
48
|
class SocketFrontendClient {
|
|
40
49
|
socketPath;
|
|
41
50
|
pending = new Map();
|
|
@@ -65,7 +74,7 @@ class SocketFrontendClient {
|
|
|
65
74
|
return () => this.closeListeners.delete(listener);
|
|
66
75
|
}
|
|
67
76
|
close() {
|
|
68
|
-
this.failPending(
|
|
77
|
+
this.failPending(transientTransportError("frontend socket closed"));
|
|
69
78
|
this.socket?.destroy();
|
|
70
79
|
this.socket = null;
|
|
71
80
|
}
|
|
@@ -78,19 +87,20 @@ class SocketFrontendClient {
|
|
|
78
87
|
const socket = net.createConnection(this.socketPath);
|
|
79
88
|
const fail = (error) => {
|
|
80
89
|
this.connecting = null;
|
|
81
|
-
reject(error);
|
|
90
|
+
reject(markTransientTransport(error));
|
|
82
91
|
};
|
|
83
92
|
socket.once("error", fail);
|
|
84
93
|
socket.once("connect", () => {
|
|
85
94
|
socket.removeListener("error", fail);
|
|
86
95
|
socket.on("error", (error) => {
|
|
87
|
-
|
|
96
|
+
const transient = markTransientTransport(error);
|
|
97
|
+
this.failPending(transient);
|
|
88
98
|
for (const listener of this.closeListeners)
|
|
89
|
-
listener(
|
|
99
|
+
listener(transient);
|
|
90
100
|
});
|
|
91
101
|
socket.on("data", (chunk) => this.handleData(chunk.toString("utf8")));
|
|
92
102
|
socket.on("close", () => {
|
|
93
|
-
const error =
|
|
103
|
+
const error = transientTransportError("frontend socket closed");
|
|
94
104
|
this.failPending(error);
|
|
95
105
|
for (const listener of this.closeListeners)
|
|
96
106
|
listener(error);
|
|
@@ -80,6 +80,12 @@ class FileTelegramOffsetStore {
|
|
|
80
80
|
exports.FileTelegramOffsetStore = FileTelegramOffsetStore;
|
|
81
81
|
const DEFAULT_TELEGRAM_INDETERMINATE_RETENTION_MS = 24 * 60 * 60 * 1_000;
|
|
82
82
|
const DEFAULT_TELEGRAM_MAX_INDETERMINATE_RECEIPTS = 1_000;
|
|
83
|
+
// Long-poll retry backoff. A transient failure (a Telegram 5xx, a dropped
|
|
84
|
+
// authority socket in the resident, or a relayed gateway 409 during a
|
|
85
|
+
// getUpdates-session reclaim) must not exit the sense process; the loop waits
|
|
86
|
+
// and retries, doubling from the base up to the cap and resetting on success.
|
|
87
|
+
const TELEGRAM_POLL_RETRY_BASE_MS = 1_000;
|
|
88
|
+
const TELEGRAM_POLL_RETRY_MAX_MS = 30_000;
|
|
83
89
|
const TELEGRAM_UPDATE_DIGEST_DOMAIN = "ouroboros.telegram.update.v1";
|
|
84
90
|
const TELEGRAM_UPDATE_SEQUENCE_DOMAIN = "ouroboros.telegram.update-sequence.v1";
|
|
85
91
|
const TELEGRAM_UPDATE_DIGEST = /^tgu_[A-Za-z0-9_-]{43}$/u;
|
|
@@ -469,14 +475,26 @@ class FileTelegramUpdateInboxStore {
|
|
|
469
475
|
}
|
|
470
476
|
}
|
|
471
477
|
exports.FileTelegramUpdateInboxStore = FileTelegramUpdateInboxStore;
|
|
478
|
+
// A poll failure is retryable when it is a transient transport condition: a
|
|
479
|
+
// Telegram Bot API error (network/HTTP), or an authority-socket error flagged
|
|
480
|
+
// transient by the resident's socket client (a dropped or refused connection).
|
|
481
|
+
// Dispatch and durable-audit invariant failures are not transport conditions;
|
|
482
|
+
// they must surface rather than be spun on indefinitely.
|
|
483
|
+
function isRetryablePollError(error) {
|
|
484
|
+
if (error instanceof TelegramApiError)
|
|
485
|
+
return true;
|
|
486
|
+
return error instanceof Error
|
|
487
|
+
&& error.isTransientTransportError === true;
|
|
488
|
+
}
|
|
472
489
|
function createTelegramLongPoll(options) {
|
|
473
490
|
let nextUpdateId = options.offsetStore.load();
|
|
474
491
|
const shutdown = new AbortController();
|
|
475
|
-
const retryAfterPollError = (signal) => new Promise((resolve
|
|
476
|
-
const
|
|
492
|
+
const retryAfterPollError = (signal, attempt) => new Promise((resolve) => {
|
|
493
|
+
const delay = Math.min(TELEGRAM_POLL_RETRY_BASE_MS * 2 ** Math.max(0, attempt - 1), TELEGRAM_POLL_RETRY_MAX_MS);
|
|
494
|
+
const timer = setTimeout(resolve, delay);
|
|
477
495
|
signal.addEventListener("abort", () => {
|
|
478
496
|
clearTimeout(timer);
|
|
479
|
-
|
|
497
|
+
resolve();
|
|
480
498
|
}, { once: true });
|
|
481
499
|
});
|
|
482
500
|
const inboundAttachments = (message) => {
|
|
@@ -690,23 +708,37 @@ function createTelegramLongPoll(options) {
|
|
|
690
708
|
pollOnce,
|
|
691
709
|
async run(signal) {
|
|
692
710
|
const runSignal = signal ? AbortSignal.any([shutdown.signal, signal]) : shutdown.signal;
|
|
711
|
+
let consecutiveFailures = 0;
|
|
693
712
|
while (!runSignal.aborted) {
|
|
694
713
|
try {
|
|
695
714
|
await pollOnce(runSignal);
|
|
715
|
+
consecutiveFailures = 0;
|
|
696
716
|
}
|
|
697
717
|
catch (error) {
|
|
698
718
|
if (shutdown.signal.aborted || signal?.aborted)
|
|
699
719
|
return;
|
|
700
|
-
|
|
720
|
+
// Only transient transport failures are retried here: a Telegram API
|
|
721
|
+
// error, or a dropped/again-failing authority socket in the resident.
|
|
722
|
+
// Exiting the process on those (as an unhandled throw would) only
|
|
723
|
+
// trades an in-process retry for a heavier container/daemon restart
|
|
724
|
+
// cycle, which is what produced the observed ~10s crash-loops. A
|
|
725
|
+
// non-transport failure (a dispatch or durable-audit invariant) still
|
|
726
|
+
// surfaces so it is not silently spun on.
|
|
727
|
+
if (!isRetryablePollError(error))
|
|
701
728
|
throw error;
|
|
729
|
+
consecutiveFailures += 1;
|
|
702
730
|
(0, runtime_1.emitNervesEvent)({
|
|
703
731
|
level: "warn",
|
|
704
732
|
component: "senses",
|
|
705
733
|
event: "telegram.poll_retry",
|
|
706
734
|
message: "Telegram long poll request failed; retrying",
|
|
707
|
-
meta: {
|
|
735
|
+
meta: {
|
|
736
|
+
attempt: consecutiveFailures,
|
|
737
|
+
...(error instanceof TelegramApiError ? { status: error.status, errorCode: error.errorCode } : {}),
|
|
738
|
+
reason: error.message,
|
|
739
|
+
},
|
|
708
740
|
});
|
|
709
|
-
await retryAfterPollError(runSignal);
|
|
741
|
+
await retryAfterPollError(runSignal, consecutiveFailures);
|
|
710
742
|
}
|
|
711
743
|
}
|
|
712
744
|
},
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ouro.bot/cli",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.831",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@ouro.bot/cli",
|
|
9
|
-
"version": "0.1.0-alpha.
|
|
9
|
+
"version": "0.1.0-alpha.831",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@anthropic-ai/sdk": "^0.78.0",
|
|
12
12
|
"@azure/identity": "^4.13.0",
|