opencode-discord 1.8.0 → 1.8.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/index.ts +138 -50
- package/package.json +1 -1
package/index.ts
CHANGED
|
@@ -2,8 +2,10 @@ import type { Plugin } from "@opencode-ai/plugin"
|
|
|
2
2
|
import { Client } from "@xhayper/discord-rpc"
|
|
3
3
|
|
|
4
4
|
const CLIENT_ID = process.env.DISCORD_CLIENT_ID ?? "1486144419940929676"
|
|
5
|
+
const RECONNECT_DELAY = 5_000
|
|
6
|
+
const MAX_RECONNECT_ATTEMPTS = 5
|
|
5
7
|
|
|
6
|
-
type Activity = "idle" | "thinking" | "editing" | "running" | "reading"
|
|
8
|
+
type Activity = "idle" | "thinking" | "editing" | "running" | "reading" | "approving" | "commanding"
|
|
7
9
|
type LogLevel = "debug" | "info" | "error" | "warn"
|
|
8
10
|
|
|
9
11
|
function basename(filePath?: string): string | undefined {
|
|
@@ -18,28 +20,33 @@ export const DiscordStatus: Plugin = async ({ client: sdk, directory }) => {
|
|
|
18
20
|
let connected = false
|
|
19
21
|
let sessionActive = false
|
|
20
22
|
const currentProject = basename(directory) ?? "Unknown Project"
|
|
21
|
-
let startTimestamp = Date
|
|
23
|
+
let startTimestamp = new Date()
|
|
22
24
|
let lastEditedFile: string | undefined
|
|
23
25
|
let activity: Activity = "idle"
|
|
24
26
|
let sessionCount = 0
|
|
27
|
+
let filesChanged = 0
|
|
28
|
+
let lastCommand: string | undefined
|
|
29
|
+
let reconnectAttempts = 0
|
|
30
|
+
let reconnectTimer: ReturnType<typeof setTimeout> | undefined
|
|
31
|
+
let pendingPresence = false
|
|
32
|
+
let destroyed = false
|
|
25
33
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
})
|
|
36
|
-
} catch { }
|
|
34
|
+
function log(level: LogLevel, message: string, extra?: Record<string, unknown>) {
|
|
35
|
+
sdk.app.log({
|
|
36
|
+
body: {
|
|
37
|
+
service: "discord-status",
|
|
38
|
+
level,
|
|
39
|
+
message,
|
|
40
|
+
extra,
|
|
41
|
+
},
|
|
42
|
+
}).catch(() => {})
|
|
37
43
|
}
|
|
38
44
|
|
|
39
|
-
|
|
45
|
+
log("info", "Plugin initializing", { clientId: CLIENT_ID, project: currentProject })
|
|
40
46
|
|
|
41
|
-
|
|
42
|
-
if (!connected) return
|
|
47
|
+
function updatePresence() {
|
|
48
|
+
if (!connected || pendingPresence) return
|
|
49
|
+
pendingPresence = true
|
|
43
50
|
|
|
44
51
|
let details: string
|
|
45
52
|
let state: string
|
|
@@ -65,70 +72,117 @@ export const DiscordStatus: Plugin = async ({ client: sdk, directory }) => {
|
|
|
65
72
|
case "reading":
|
|
66
73
|
details = lastEditedFile ? `Reading ${lastEditedFile}` : "Reading files"
|
|
67
74
|
break
|
|
75
|
+
case "approving":
|
|
76
|
+
details = "Waiting for approval..."
|
|
77
|
+
break
|
|
78
|
+
case "commanding":
|
|
79
|
+
details = lastCommand ? `Running /${lastCommand}` : "Running command"
|
|
80
|
+
break
|
|
68
81
|
default:
|
|
69
82
|
details = "Session active"
|
|
70
83
|
}
|
|
71
84
|
|
|
72
|
-
state =
|
|
73
|
-
|
|
74
|
-
|
|
85
|
+
state = filesChanged > 0
|
|
86
|
+
? `${currentProject} · ${filesChanged} file${filesChanged !== 1 ? "s" : ""} changed`
|
|
87
|
+
: currentProject
|
|
88
|
+
smallImageKey = activity === "thinking" ? "thinking" : activity === "approving" ? "idle" : "active"
|
|
89
|
+
smallImageText = activity === "thinking" ? "Generating" : activity === "approving" ? "Needs approval" : "Active"
|
|
75
90
|
}
|
|
76
91
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
})
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
92
|
+
rpc.user?.setActivity({
|
|
93
|
+
details,
|
|
94
|
+
state,
|
|
95
|
+
startTimestamp,
|
|
96
|
+
largeImageKey: "opencode",
|
|
97
|
+
largeImageText: `OpenCode · ${sessionCount} session${sessionCount !== 1 ? "s" : ""}`,
|
|
98
|
+
smallImageKey,
|
|
99
|
+
smallImageText,
|
|
100
|
+
})
|
|
101
|
+
.then(() => log("debug", "Presence updated", { details, state }))
|
|
102
|
+
.catch((e) => log("error", "Failed to set presence", { error: String(e) }))
|
|
103
|
+
.finally(() => { pendingPresence = false })
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function scheduleReconnect() {
|
|
107
|
+
if (destroyed || reconnectTimer) return
|
|
108
|
+
if (reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {
|
|
109
|
+
log("error", "Max reconnect attempts reached, giving up")
|
|
110
|
+
return
|
|
90
111
|
}
|
|
112
|
+
reconnectAttempts++
|
|
113
|
+
log("info", "Scheduling reconnect", { attempt: reconnectAttempts })
|
|
114
|
+
reconnectTimer = setTimeout(() => {
|
|
115
|
+
reconnectTimer = undefined
|
|
116
|
+
rpc.login()
|
|
117
|
+
.then(() => {
|
|
118
|
+
log("info", "Reconnected to Discord RPC")
|
|
119
|
+
reconnectAttempts = 0
|
|
120
|
+
})
|
|
121
|
+
.catch((e) => {
|
|
122
|
+
log("error", "Reconnect failed", { error: String(e) })
|
|
123
|
+
scheduleReconnect()
|
|
124
|
+
})
|
|
125
|
+
}, RECONNECT_DELAY)
|
|
91
126
|
}
|
|
92
127
|
|
|
93
|
-
rpc.on("ready",
|
|
128
|
+
rpc.on("ready", () => {
|
|
94
129
|
connected = true
|
|
95
|
-
startTimestamp = Date
|
|
96
|
-
|
|
97
|
-
|
|
130
|
+
startTimestamp = new Date()
|
|
131
|
+
log("info", "Discord RPC connected")
|
|
132
|
+
updatePresence()
|
|
98
133
|
})
|
|
99
134
|
|
|
100
|
-
rpc.on("
|
|
101
|
-
|
|
135
|
+
rpc.on("disconnected", () => {
|
|
136
|
+
connected = false
|
|
137
|
+
log("warn", "Discord RPC disconnected")
|
|
138
|
+
scheduleReconnect()
|
|
102
139
|
})
|
|
103
140
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
141
|
+
process.on("exit", () => {
|
|
142
|
+
destroyed = true
|
|
143
|
+
if (reconnectTimer) clearTimeout(reconnectTimer)
|
|
144
|
+
if (connected) rpc.user?.clearActivity().catch(() => {})
|
|
145
|
+
rpc.destroy().catch(() => {})
|
|
107
146
|
})
|
|
108
147
|
|
|
109
148
|
try {
|
|
110
|
-
|
|
149
|
+
log("info", "Logging in to Discord RPC...")
|
|
111
150
|
await rpc.login()
|
|
112
|
-
|
|
151
|
+
reconnectAttempts = 0
|
|
152
|
+
log("info", "Discord RPC login successful")
|
|
113
153
|
} catch (e) {
|
|
114
154
|
connected = false
|
|
115
|
-
|
|
155
|
+
log("error", "Discord RPC login failed", { error: String(e) })
|
|
156
|
+
scheduleReconnect()
|
|
116
157
|
}
|
|
117
158
|
|
|
118
159
|
return {
|
|
119
160
|
event: async ({ event }) => {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
switch (eventType) {
|
|
161
|
+
switch (event.type) {
|
|
123
162
|
case "session.created":
|
|
124
163
|
sessionActive = true
|
|
125
164
|
sessionCount += 1
|
|
126
|
-
startTimestamp = Date
|
|
165
|
+
startTimestamp = new Date()
|
|
127
166
|
lastEditedFile = undefined
|
|
167
|
+
lastCommand = undefined
|
|
168
|
+
filesChanged = 0
|
|
128
169
|
activity = "thinking"
|
|
129
170
|
break
|
|
130
|
-
case "session.
|
|
171
|
+
case "session.updated":
|
|
131
172
|
sessionActive = true
|
|
173
|
+
if (activity === "idle") activity = "thinking"
|
|
174
|
+
break
|
|
175
|
+
case "session.status": {
|
|
176
|
+
const status = (event as { properties?: { status?: string } }).properties?.status
|
|
177
|
+
if (status === "busy" || status === "retry") {
|
|
178
|
+
sessionActive = true
|
|
179
|
+
activity = "thinking"
|
|
180
|
+
} else if (status === "idle") {
|
|
181
|
+
activity = "idle"
|
|
182
|
+
}
|
|
183
|
+
break
|
|
184
|
+
}
|
|
185
|
+
case "session.idle":
|
|
132
186
|
activity = "idle"
|
|
133
187
|
break
|
|
134
188
|
case "session.error":
|
|
@@ -138,8 +192,13 @@ export const DiscordStatus: Plugin = async ({ client: sdk, directory }) => {
|
|
|
138
192
|
case "session.deleted":
|
|
139
193
|
sessionActive = false
|
|
140
194
|
lastEditedFile = undefined
|
|
195
|
+
lastCommand = undefined
|
|
196
|
+
filesChanged = 0
|
|
141
197
|
activity = "idle"
|
|
142
|
-
|
|
198
|
+
if (connected) {
|
|
199
|
+
rpc.user?.clearActivity().catch(() => {})
|
|
200
|
+
}
|
|
201
|
+
return
|
|
143
202
|
case "session.compacted":
|
|
144
203
|
activity = "thinking"
|
|
145
204
|
break
|
|
@@ -149,6 +208,35 @@ export const DiscordStatus: Plugin = async ({ client: sdk, directory }) => {
|
|
|
149
208
|
lastEditedFile = basename(editedFile) ?? lastEditedFile
|
|
150
209
|
break
|
|
151
210
|
}
|
|
211
|
+
case "session.diff": {
|
|
212
|
+
const diffs = (event as { properties?: { diff?: unknown[] } }).properties?.diff
|
|
213
|
+
if (diffs) filesChanged = diffs.length
|
|
214
|
+
break
|
|
215
|
+
}
|
|
216
|
+
case "message.part.updated": {
|
|
217
|
+
if (sessionActive && activity !== "editing" && activity !== "running") {
|
|
218
|
+
activity = "thinking"
|
|
219
|
+
}
|
|
220
|
+
break
|
|
221
|
+
}
|
|
222
|
+
case "pty.created":
|
|
223
|
+
activity = "running"
|
|
224
|
+
break
|
|
225
|
+
case "pty.exited":
|
|
226
|
+
activity = sessionActive ? "thinking" : "idle"
|
|
227
|
+
break
|
|
228
|
+
case "permission.updated":
|
|
229
|
+
activity = "approving"
|
|
230
|
+
break
|
|
231
|
+
case "permission.replied":
|
|
232
|
+
activity = sessionActive ? "thinking" : "idle"
|
|
233
|
+
break
|
|
234
|
+
case "command.executed": {
|
|
235
|
+
const cmdName = (event as { properties?: { name?: string } }).properties?.name
|
|
236
|
+
lastCommand = cmdName
|
|
237
|
+
activity = "commanding"
|
|
238
|
+
break
|
|
239
|
+
}
|
|
152
240
|
}
|
|
153
241
|
|
|
154
242
|
updatePresence()
|