@grinev/opencode-telegram-bot 0.20.1 → 0.20.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/opencode/events.js +95 -5
- package/package.json +1 -1
package/dist/opencode/events.js
CHANGED
|
@@ -32,6 +32,65 @@ function waitWithAbort(ms, signal) {
|
|
|
32
32
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
33
33
|
});
|
|
34
34
|
}
|
|
35
|
+
function isRecord(value) {
|
|
36
|
+
return typeof value === "object" && value !== null;
|
|
37
|
+
}
|
|
38
|
+
function isEventLike(value) {
|
|
39
|
+
return isRecord(value) && typeof value.type === "string" && isRecord(value.properties);
|
|
40
|
+
}
|
|
41
|
+
function normalizeDirectoryForComparison(directory) {
|
|
42
|
+
const normalized = directory.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
43
|
+
return /^[a-z]:/i.test(normalized) ? normalized.toLowerCase() : normalized;
|
|
44
|
+
}
|
|
45
|
+
function isSameDirectory(left, right) {
|
|
46
|
+
return normalizeDirectoryForComparison(left) === normalizeDirectoryForComparison(right);
|
|
47
|
+
}
|
|
48
|
+
function normalizeGlobalEvent(rawEvent, directory) {
|
|
49
|
+
if (isEventLike(rawEvent)) {
|
|
50
|
+
return rawEvent;
|
|
51
|
+
}
|
|
52
|
+
if (!isRecord(rawEvent) || !("payload" in rawEvent)) {
|
|
53
|
+
logger.debug("[Events] Ignoring global event with unknown shape");
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
const eventDirectory = typeof rawEvent.directory === "string" ? rawEvent.directory : null;
|
|
57
|
+
if (eventDirectory && !isSameDirectory(eventDirectory, directory)) {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
if (!isEventLike(rawEvent.payload)) {
|
|
61
|
+
logger.debug("[Events] Ignoring global event with unknown payload shape");
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
return rawEvent.payload;
|
|
65
|
+
}
|
|
66
|
+
function normalizeEvent(rawEvent, source, directory) {
|
|
67
|
+
if (source === "global") {
|
|
68
|
+
return normalizeGlobalEvent(rawEvent, directory);
|
|
69
|
+
}
|
|
70
|
+
if (!isEventLike(rawEvent)) {
|
|
71
|
+
logger.debug("[Events] Ignoring legacy event with unknown shape");
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
return rawEvent;
|
|
75
|
+
}
|
|
76
|
+
async function subscribeToGlobalEventStream(signal) {
|
|
77
|
+
const globalEvents = opencodeClient.global;
|
|
78
|
+
if (!globalEvents?.event) {
|
|
79
|
+
throw new Error("Global event subscription is not available");
|
|
80
|
+
}
|
|
81
|
+
const result = await globalEvents.event({ signal });
|
|
82
|
+
if (!result.stream) {
|
|
83
|
+
throw new Error(FATAL_NO_STREAM_ERROR);
|
|
84
|
+
}
|
|
85
|
+
return { source: "global", stream: result.stream };
|
|
86
|
+
}
|
|
87
|
+
async function subscribeToLegacyEventStream(directory, signal) {
|
|
88
|
+
const result = await opencodeClient.event.subscribe({ directory }, { signal });
|
|
89
|
+
if (!result.stream) {
|
|
90
|
+
throw new Error(FATAL_NO_STREAM_ERROR);
|
|
91
|
+
}
|
|
92
|
+
return { source: "legacy", stream: result.stream };
|
|
93
|
+
}
|
|
35
94
|
export async function subscribeToEvents(directory, callback) {
|
|
36
95
|
if (isListening && activeDirectory === directory) {
|
|
37
96
|
eventCallback = callback;
|
|
@@ -53,14 +112,33 @@ export async function subscribeToEvents(directory, callback) {
|
|
|
53
112
|
streamAbortController = controller;
|
|
54
113
|
try {
|
|
55
114
|
let reconnectAttempt = 0;
|
|
115
|
+
let useLegacyEventsOnce = false;
|
|
56
116
|
while (isListening && activeDirectory === directory && !controller.signal.aborted) {
|
|
57
117
|
try {
|
|
58
|
-
|
|
59
|
-
if (
|
|
60
|
-
|
|
118
|
+
let subscription;
|
|
119
|
+
if (useLegacyEventsOnce) {
|
|
120
|
+
useLegacyEventsOnce = false;
|
|
121
|
+
subscription = await subscribeToLegacyEventStream(directory, controller.signal);
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
try {
|
|
125
|
+
subscription = await subscribeToGlobalEventStream(controller.signal);
|
|
126
|
+
logger.debug(`Using global OpenCode event stream for ${directory}`);
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
if (controller.signal.aborted || !isListening || activeDirectory !== directory) {
|
|
130
|
+
throw error;
|
|
131
|
+
}
|
|
132
|
+
if (isExpectedOpencodeUnavailableError(error)) {
|
|
133
|
+
throw error;
|
|
134
|
+
}
|
|
135
|
+
logger.warn(`Global event stream unavailable for ${directory}, falling back to project event stream`, error);
|
|
136
|
+
subscription = await subscribeToLegacyEventStream(directory, controller.signal);
|
|
137
|
+
}
|
|
61
138
|
}
|
|
62
139
|
reconnectAttempt = 0;
|
|
63
|
-
eventStream =
|
|
140
|
+
eventStream = subscription.stream;
|
|
141
|
+
let usefulEventCount = 0;
|
|
64
142
|
for await (const event of eventStream) {
|
|
65
143
|
if (!isListening || activeDirectory !== directory || controller.signal.aborted) {
|
|
66
144
|
logger.debug(`Event listener stopped or changed directory, breaking loop`);
|
|
@@ -69,6 +147,13 @@ export async function subscribeToEvents(directory, callback) {
|
|
|
69
147
|
// CRITICAL: Explicitly yield to the event loop BEFORE processing the event
|
|
70
148
|
// This allows grammY to handle getUpdates between SSE events
|
|
71
149
|
await new Promise((resolve) => setImmediate(resolve));
|
|
150
|
+
const normalizedEvent = normalizeEvent(event, subscription.source, directory);
|
|
151
|
+
if (!normalizedEvent) {
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
if (normalizedEvent.type !== "server.connected") {
|
|
155
|
+
usefulEventCount++;
|
|
156
|
+
}
|
|
72
157
|
if (eventCallback) {
|
|
73
158
|
// Use setImmediate to avoid blocking the event loop
|
|
74
159
|
// and let grammY process incoming Telegram updates
|
|
@@ -81,7 +166,7 @@ export async function subscribeToEvents(directory, callback) {
|
|
|
81
166
|
listenerGeneration !== generation) {
|
|
82
167
|
return;
|
|
83
168
|
}
|
|
84
|
-
callbackSnapshot(
|
|
169
|
+
callbackSnapshot(normalizedEvent);
|
|
85
170
|
});
|
|
86
171
|
}
|
|
87
172
|
}
|
|
@@ -89,6 +174,11 @@ export async function subscribeToEvents(directory, callback) {
|
|
|
89
174
|
if (!isListening || activeDirectory !== directory || controller.signal.aborted) {
|
|
90
175
|
break;
|
|
91
176
|
}
|
|
177
|
+
if (subscription.source === "global" && usefulEventCount === 0) {
|
|
178
|
+
useLegacyEventsOnce = true;
|
|
179
|
+
logger.warn(`Global event stream ended without project events for ${directory}, falling back to project event stream`);
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
92
182
|
reconnectAttempt++;
|
|
93
183
|
const reconnectDelay = getReconnectDelayMs(reconnectAttempt);
|
|
94
184
|
logger.warn(`Event stream ended for ${directory}, reconnecting in ${reconnectDelay}ms (attempt=${reconnectAttempt})`);
|