opencode-telegram-mirror 0.4.0 → 0.4.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/README.md +1 -1
- package/package.json +1 -1
- package/src/main.ts +91 -9
package/README.md
CHANGED
package/package.json
CHANGED
package/src/main.ts
CHANGED
|
@@ -98,6 +98,10 @@ interface BotState {
|
|
|
98
98
|
assistantMessageIds: Set<string>;
|
|
99
99
|
pendingParts: Map<string, Part[]>;
|
|
100
100
|
sentPartIds: Set<string>;
|
|
101
|
+
typingIndicators: Map<
|
|
102
|
+
string,
|
|
103
|
+
{ stop: () => void; timeout: ReturnType<typeof setTimeout> | null; mode: "idle" | "tool" }
|
|
104
|
+
>;
|
|
101
105
|
}
|
|
102
106
|
|
|
103
107
|
async function main() {
|
|
@@ -259,6 +263,7 @@ async function main() {
|
|
|
259
263
|
assistantMessageIds: new Set(),
|
|
260
264
|
pendingParts: new Map(),
|
|
261
265
|
sentPartIds: new Set(),
|
|
266
|
+
typingIndicators: new Map(),
|
|
262
267
|
}
|
|
263
268
|
|
|
264
269
|
if (initialThreadTitle && config.threadId) {
|
|
@@ -644,7 +649,7 @@ async function handleTelegramMessage(
|
|
|
644
649
|
directory: state.directory,
|
|
645
650
|
})
|
|
646
651
|
if (abortResult.data) {
|
|
647
|
-
|
|
652
|
+
log("info", "Abort request sent", { sessionId: state.sessionId })
|
|
648
653
|
} else {
|
|
649
654
|
log("error", "Failed to abort session", {
|
|
650
655
|
sessionId: state.sessionId,
|
|
@@ -690,7 +695,7 @@ async function handleTelegramMessage(
|
|
|
690
695
|
directory: state.directory,
|
|
691
696
|
})
|
|
692
697
|
if (abortResult.data) {
|
|
693
|
-
|
|
698
|
+
log("info", "Abort request sent", { sessionId: state.sessionId })
|
|
694
699
|
} else {
|
|
695
700
|
log("error", "Failed to abort session", {
|
|
696
701
|
sessionId: state.sessionId,
|
|
@@ -1014,16 +1019,36 @@ async function handleOpenCodeEvent(state: BotState, ev: OpenCodeEvent) {
|
|
|
1014
1019
|
const sessionId =
|
|
1015
1020
|
ev.properties?.sessionID ??
|
|
1016
1021
|
ev.properties?.info?.sessionID ??
|
|
1017
|
-
ev.properties?.part?.sessionID
|
|
1022
|
+
ev.properties?.part?.sessionID ??
|
|
1023
|
+
ev.properties?.session?.id
|
|
1018
1024
|
const sessionTitle = ev.properties?.session?.title
|
|
1019
1025
|
|
|
1020
1026
|
// Log errors in full and send to Telegram
|
|
1021
1027
|
if (ev.type === "session.error") {
|
|
1022
1028
|
const errorMsg = JSON.stringify(ev.properties, null, 2)
|
|
1029
|
+
const error = ev.properties?.error as
|
|
1030
|
+
| { name?: string; data?: { message?: string } }
|
|
1031
|
+
| undefined
|
|
1032
|
+
const errorName = error?.name
|
|
1033
|
+
const errorText = error?.data?.message
|
|
1034
|
+
const isInterrupted =
|
|
1035
|
+
errorName === "MessageAbortedError" || errorText === "The operation was aborted."
|
|
1036
|
+
|
|
1023
1037
|
log("error", "OpenCode session error", {
|
|
1024
1038
|
sessionId,
|
|
1025
1039
|
error: ev.properties,
|
|
1026
1040
|
})
|
|
1041
|
+
|
|
1042
|
+
if (isInterrupted) {
|
|
1043
|
+
const sendResult = await state.telegram.sendMessage("Interrupted.")
|
|
1044
|
+
if (sendResult.status === "error") {
|
|
1045
|
+
log("error", "Failed to send interrupt message", {
|
|
1046
|
+
error: sendResult.error.message,
|
|
1047
|
+
})
|
|
1048
|
+
}
|
|
1049
|
+
return
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1027
1052
|
// Send error to Telegram for visibility
|
|
1028
1053
|
const sendResult = await state.telegram.sendMessage(
|
|
1029
1054
|
`OpenCode Error:\n${errorMsg.slice(0, 3500)}`
|
|
@@ -1076,6 +1101,12 @@ async function handleOpenCodeEvent(state: BotState, ev: OpenCodeEvent) {
|
|
|
1076
1101
|
const key = `${info.sessionID}:${info.id}`
|
|
1077
1102
|
state.assistantMessageIds.add(key)
|
|
1078
1103
|
log("debug", "Registered assistant message", { key })
|
|
1104
|
+
const entry = state.typingIndicators.get(key)
|
|
1105
|
+
if (entry && entry.mode === "tool") {
|
|
1106
|
+
if (entry.timeout) clearTimeout(entry.timeout)
|
|
1107
|
+
entry.stop()
|
|
1108
|
+
state.typingIndicators.delete(key)
|
|
1109
|
+
}
|
|
1079
1110
|
}
|
|
1080
1111
|
}
|
|
1081
1112
|
|
|
@@ -1093,6 +1124,39 @@ async function handleOpenCodeEvent(state: BotState, ev: OpenCodeEvent) {
|
|
|
1093
1124
|
return
|
|
1094
1125
|
}
|
|
1095
1126
|
|
|
1127
|
+
const stopTypingIndicator = (targetKey: string) => {
|
|
1128
|
+
const entry = state.typingIndicators.get(targetKey)
|
|
1129
|
+
if (!entry) return
|
|
1130
|
+
if (entry.timeout) clearTimeout(entry.timeout)
|
|
1131
|
+
entry.stop()
|
|
1132
|
+
state.typingIndicators.delete(targetKey)
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
const startTypingIndicator = (targetKey: string, mode: "idle" | "tool") => {
|
|
1136
|
+
const existing = state.typingIndicators.get(targetKey)
|
|
1137
|
+
if (existing && existing.mode === mode) return
|
|
1138
|
+
if (existing) {
|
|
1139
|
+
if (existing.timeout) clearTimeout(existing.timeout)
|
|
1140
|
+
existing.stop()
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
const stop = state.telegram.startTyping(mode === "tool" ? 2000 : 4000)
|
|
1144
|
+
state.typingIndicators.set(targetKey, { stop, timeout: null, mode })
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
const bumpTypingIndicator = (targetKey: string, mode: "idle" | "tool") => {
|
|
1148
|
+
const existing = state.typingIndicators.get(targetKey)
|
|
1149
|
+
if (!existing || existing.mode !== mode) {
|
|
1150
|
+
startTypingIndicator(targetKey, mode)
|
|
1151
|
+
return
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
if (existing.timeout) clearTimeout(existing.timeout)
|
|
1155
|
+
existing.timeout = setTimeout(() => {
|
|
1156
|
+
stopTypingIndicator(targetKey)
|
|
1157
|
+
}, 12000)
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1096
1160
|
log("debug", "Processing message part", {
|
|
1097
1161
|
key,
|
|
1098
1162
|
partType: part.type,
|
|
@@ -1106,12 +1170,11 @@ async function handleOpenCodeEvent(state: BotState, ev: OpenCodeEvent) {
|
|
|
1106
1170
|
state.pendingParts.set(key, existing)
|
|
1107
1171
|
|
|
1108
1172
|
if (part.type !== "step-finish") {
|
|
1109
|
-
const
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
}
|
|
1173
|
+
const typingMode =
|
|
1174
|
+
part.type === "tool" && (part.tool === "edit" || part.tool === "write")
|
|
1175
|
+
? "tool"
|
|
1176
|
+
: "idle"
|
|
1177
|
+
bumpTypingIndicator(key, typingMode)
|
|
1115
1178
|
}
|
|
1116
1179
|
|
|
1117
1180
|
// Send tools/reasoning immediately (except edit/write tools - wait for completion to get diff data)
|
|
@@ -1139,6 +1202,7 @@ async function handleOpenCodeEvent(state: BotState, ev: OpenCodeEvent) {
|
|
|
1139
1202
|
|
|
1140
1203
|
// On step-finish, send remaining parts
|
|
1141
1204
|
if (part.type === "step-finish") {
|
|
1205
|
+
stopTypingIndicator(key)
|
|
1142
1206
|
for (const p of existing) {
|
|
1143
1207
|
if (p.type === "step-start" || p.type === "step-finish") continue
|
|
1144
1208
|
if (state.sentPartIds.has(p.id)) continue
|
|
@@ -1241,6 +1305,24 @@ async function handleOpenCodeEvent(state: BotState, ev: OpenCodeEvent) {
|
|
|
1241
1305
|
}
|
|
1242
1306
|
}
|
|
1243
1307
|
|
|
1308
|
+
if (ev.type === "message.updated") {
|
|
1309
|
+
const info = ev.properties.info
|
|
1310
|
+
if (info?.role === "assistant") {
|
|
1311
|
+
const key = `${info.sessionID}:${info.id}`
|
|
1312
|
+
const entry = state.typingIndicators.get(key)
|
|
1313
|
+
if (entry && entry.mode === "tool") {
|
|
1314
|
+
const stopTypingIndicator = (targetKey: string) => {
|
|
1315
|
+
const existing = state.typingIndicators.get(targetKey)
|
|
1316
|
+
if (!existing) return
|
|
1317
|
+
if (existing.timeout) clearTimeout(existing.timeout)
|
|
1318
|
+
existing.stop()
|
|
1319
|
+
state.typingIndicators.delete(targetKey)
|
|
1320
|
+
}
|
|
1321
|
+
stopTypingIndicator(key)
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1244
1326
|
const threadId = state.threadId ?? 0
|
|
1245
1327
|
|
|
1246
1328
|
if (ev.type === "question.asked") {
|