moshi-opencode-hooks 1.0.1 → 1.0.9
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 +92 -16
- package/package.json +1 -1
package/index.ts
CHANGED
|
@@ -4,20 +4,19 @@ import type { Plugin } from "@opencode-ai/plugin"
|
|
|
4
4
|
|
|
5
5
|
const TOKEN_PATH = `${homedir()}/.config/moshi/token`
|
|
6
6
|
const API_URL = "https://api.getmoshi.app/api/v1/agent-events"
|
|
7
|
-
const INTERESTING_TOOLS = new Set(["bash", "edit", "write", "read", "glob", "grep", "task"])
|
|
7
|
+
const INTERESTING_TOOLS = new Set(["bash", "edit", "write", "read", "glob", "grep", "task", "question", "apply_patch"])
|
|
8
8
|
|
|
9
9
|
interface HookState {
|
|
10
10
|
model?: string
|
|
11
11
|
lastToolName?: string
|
|
12
12
|
lastStopTime?: number
|
|
13
|
-
sessionStartTime?: number
|
|
14
13
|
}
|
|
15
14
|
|
|
16
15
|
interface AgentEvent {
|
|
17
16
|
source: "opencode"
|
|
18
|
-
eventType: "
|
|
17
|
+
eventType: "pre_tool" | "post_tool" | "notification" | "stop"
|
|
19
18
|
sessionId: string
|
|
20
|
-
category: "approval_required" | "task_complete" | "tool_running" | "tool_finished"
|
|
19
|
+
category: "approval_required" | "task_complete" | "tool_running" | "tool_finished"
|
|
21
20
|
title: string
|
|
22
21
|
message: string
|
|
23
22
|
eventId: string
|
|
@@ -55,7 +54,27 @@ async function loadToken(): Promise<string | null> {
|
|
|
55
54
|
}
|
|
56
55
|
}
|
|
57
56
|
|
|
58
|
-
async function
|
|
57
|
+
async function logPluginEvent(
|
|
58
|
+
client: Parameters<Plugin>[0]["client"],
|
|
59
|
+
level: "warn" | "error",
|
|
60
|
+
message: string,
|
|
61
|
+
extra?: Record<string, unknown>,
|
|
62
|
+
): Promise<void> {
|
|
63
|
+
try {
|
|
64
|
+
await client.app.log({
|
|
65
|
+
body: {
|
|
66
|
+
service: "moshi-hooks",
|
|
67
|
+
level,
|
|
68
|
+
message,
|
|
69
|
+
extra,
|
|
70
|
+
},
|
|
71
|
+
})
|
|
72
|
+
} catch (err) {
|
|
73
|
+
console.error(`[moshi-hooks] ${message}`, err)
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async function sendEvent(token: string, event: AgentEvent): Promise<Response> {
|
|
59
78
|
const body = JSON.stringify(event)
|
|
60
79
|
const headers = {
|
|
61
80
|
"Content-Type": "application/json",
|
|
@@ -69,11 +88,32 @@ async function sendEvent(token: string, event: AgentEvent): Promise<void> {
|
|
|
69
88
|
body,
|
|
70
89
|
signal: AbortSignal.timeout(5000),
|
|
71
90
|
})
|
|
91
|
+
return res
|
|
92
|
+
} catch (err) {
|
|
93
|
+
throw err
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async function sendAgentEvent(
|
|
98
|
+
client: Parameters<Plugin>[0]["client"],
|
|
99
|
+
token: string,
|
|
100
|
+
event: AgentEvent,
|
|
101
|
+
): Promise<void> {
|
|
102
|
+
try {
|
|
103
|
+
const res = await sendEvent(token, event)
|
|
72
104
|
if (!res.ok && res.status >= 500) {
|
|
73
|
-
|
|
105
|
+
await logPluginEvent(client, "error", "Moshi API returned server error", {
|
|
106
|
+
status: res.status,
|
|
107
|
+
eventType: event.eventType,
|
|
108
|
+
sessionId: event.sessionId,
|
|
109
|
+
})
|
|
74
110
|
}
|
|
75
111
|
} catch (err) {
|
|
76
|
-
|
|
112
|
+
await logPluginEvent(client, "error", "Failed to send Moshi event", {
|
|
113
|
+
error: err instanceof Error ? err.message : String(err),
|
|
114
|
+
eventType: event.eventType,
|
|
115
|
+
sessionId: event.sessionId,
|
|
116
|
+
})
|
|
77
117
|
}
|
|
78
118
|
}
|
|
79
119
|
|
|
@@ -87,7 +127,7 @@ export const MoshiHooks: Plugin = async ({ client, directory }) => {
|
|
|
87
127
|
const events = await client.event.subscribe()
|
|
88
128
|
for await (const event of events.stream) {
|
|
89
129
|
const token = await loadToken()
|
|
90
|
-
if (!token)
|
|
130
|
+
if (!token) continue
|
|
91
131
|
|
|
92
132
|
const sessionId = (event as any).sessionId ?? "unknown"
|
|
93
133
|
const projectName = directory ? basename(directory) : undefined
|
|
@@ -95,7 +135,6 @@ export const MoshiHooks: Plugin = async ({ client, directory }) => {
|
|
|
95
135
|
|
|
96
136
|
if (event.type === "session.created") {
|
|
97
137
|
await writeState(sessionId, {
|
|
98
|
-
sessionStartTime: Date.now() / 1000,
|
|
99
138
|
model: (event as any).properties?.model,
|
|
100
139
|
})
|
|
101
140
|
continue
|
|
@@ -119,29 +158,63 @@ export const MoshiHooks: Plugin = async ({ client, directory }) => {
|
|
|
119
158
|
modelName: state.model,
|
|
120
159
|
toolName: state.lastToolName,
|
|
121
160
|
}
|
|
122
|
-
await
|
|
161
|
+
await sendAgentEvent(client, token, evt)
|
|
123
162
|
}
|
|
124
163
|
}
|
|
125
164
|
} catch (err) {
|
|
126
|
-
|
|
165
|
+
await logPluginEvent(client, "error", "Event subscription error", {
|
|
166
|
+
error: err instanceof Error ? err.message : String(err),
|
|
167
|
+
})
|
|
127
168
|
}
|
|
128
169
|
}
|
|
129
170
|
|
|
130
171
|
setupEventSubscription()
|
|
131
172
|
|
|
173
|
+
const isSubagentSession = async (sessionID: string): Promise<boolean> => {
|
|
174
|
+
try {
|
|
175
|
+
const res = await (client.session.get as any)({ sessionID })
|
|
176
|
+
return !!(res.data?.parentID)
|
|
177
|
+
} catch {
|
|
178
|
+
return false
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
132
182
|
return {
|
|
133
|
-
"tool.execute.before": async (input,
|
|
183
|
+
"tool.execute.before": async (input, output) => {
|
|
134
184
|
const token = await loadToken()
|
|
135
185
|
if (!token) return
|
|
136
186
|
|
|
137
187
|
const { tool, sessionID } = input
|
|
138
188
|
if (!tool || !INTERESTING_TOOLS.has(tool.toLowerCase())) return
|
|
189
|
+
if (await isSubagentSession(sessionID)) return
|
|
139
190
|
|
|
140
191
|
await writeState(sessionID, { lastToolName: tool })
|
|
141
192
|
|
|
142
193
|
const state = await readState(sessionID)
|
|
143
194
|
const projectName = directory ? basename(directory) : undefined
|
|
144
195
|
|
|
196
|
+
if (tool === "question") {
|
|
197
|
+
const questions: any[] = output.args?.questions ?? []
|
|
198
|
+
const lines = questions.map((q) => {
|
|
199
|
+
const opts = q.options?.map((o: any) => ` - ${o.label}`).join("\n") ?? ""
|
|
200
|
+
return `${q.header}: ${q.question}\n${opts}`
|
|
201
|
+
})
|
|
202
|
+
const evt: AgentEvent = {
|
|
203
|
+
source: "opencode",
|
|
204
|
+
eventType: "notification",
|
|
205
|
+
sessionId: sessionID,
|
|
206
|
+
category: "approval_required",
|
|
207
|
+
title: "Question",
|
|
208
|
+
message: lines.join("\n---\n").slice(0, 512),
|
|
209
|
+
eventId: crypto.randomUUID(),
|
|
210
|
+
projectName,
|
|
211
|
+
modelName: state.model,
|
|
212
|
+
toolName: tool,
|
|
213
|
+
}
|
|
214
|
+
await sendAgentEvent(client, token, evt)
|
|
215
|
+
return
|
|
216
|
+
}
|
|
217
|
+
|
|
145
218
|
const evt: AgentEvent = {
|
|
146
219
|
source: "opencode",
|
|
147
220
|
eventType: "pre_tool",
|
|
@@ -154,7 +227,7 @@ export const MoshiHooks: Plugin = async ({ client, directory }) => {
|
|
|
154
227
|
modelName: state.model,
|
|
155
228
|
toolName: tool,
|
|
156
229
|
}
|
|
157
|
-
await
|
|
230
|
+
await sendAgentEvent(client, token, evt)
|
|
158
231
|
},
|
|
159
232
|
|
|
160
233
|
"tool.execute.after": async (input, output) => {
|
|
@@ -163,6 +236,9 @@ export const MoshiHooks: Plugin = async ({ client, directory }) => {
|
|
|
163
236
|
|
|
164
237
|
const { tool, sessionID } = input
|
|
165
238
|
if (!tool || !INTERESTING_TOOLS.has(tool.toLowerCase())) return
|
|
239
|
+
if (await isSubagentSession(sessionID)) return
|
|
240
|
+
|
|
241
|
+
if (tool === "question") return
|
|
166
242
|
|
|
167
243
|
const state = await readState(sessionID)
|
|
168
244
|
const projectName = directory ? basename(directory) : undefined
|
|
@@ -179,10 +255,10 @@ export const MoshiHooks: Plugin = async ({ client, directory }) => {
|
|
|
179
255
|
modelName: state.model,
|
|
180
256
|
toolName: tool,
|
|
181
257
|
}
|
|
182
|
-
await
|
|
258
|
+
await sendAgentEvent(client, token, evt)
|
|
183
259
|
},
|
|
184
260
|
|
|
185
|
-
"permission.
|
|
261
|
+
"permission.asked": async (input: any, _output: any) => {
|
|
186
262
|
const token = await loadToken()
|
|
187
263
|
if (!token) return
|
|
188
264
|
|
|
@@ -203,7 +279,7 @@ export const MoshiHooks: Plugin = async ({ client, directory }) => {
|
|
|
203
279
|
projectName,
|
|
204
280
|
modelName: state.model,
|
|
205
281
|
}
|
|
206
|
-
await
|
|
282
|
+
await sendAgentEvent(client, token, evt)
|
|
207
283
|
},
|
|
208
284
|
}
|
|
209
285
|
}
|