opencode-raven 2.3.0 → 2.3.1
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.md +90 -81
- package/README.md +7 -1
- package/package.json +26 -20
- package/raven-core.ts +1703 -1679
- package/rpc.ts +18 -0
- package/tui.ts +65 -3
- package/v2.ts +345 -323
package/rpc.ts
CHANGED
|
@@ -5,6 +5,10 @@ import { z } from "zod"
|
|
|
5
5
|
export const RavenRpc = {
|
|
6
6
|
id: "opencode-raven",
|
|
7
7
|
methods: {
|
|
8
|
+
refreshMcps: {
|
|
9
|
+
input: z.object({ serverName: z.string().optional() }),
|
|
10
|
+
output: z.object({ refreshed: z.array(z.string()), failed: z.array(z.string()) }),
|
|
11
|
+
},
|
|
8
12
|
command: {
|
|
9
13
|
input: z.object({ arguments: z.string() }),
|
|
10
14
|
output: z.object({ text: z.string() }),
|
|
@@ -25,3 +29,17 @@ export const RavenRpc = {
|
|
|
25
29
|
} as const satisfies Rpc.PortableDefinition
|
|
26
30
|
|
|
27
31
|
export default RavenRpc
|
|
32
|
+
|
|
33
|
+
export function mcpRefreshArguments(raw: string): { serverName?: string } | undefined {
|
|
34
|
+
const match = raw.trim().match(/^mcp\s+refresh(?:\s+(.+))?$/i)
|
|
35
|
+
return match ? (match[1] ? { serverName: match[1].trim() } : {}) : undefined
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function mcpRefreshToast(result: { refreshed: string[]; failed: string[] }) {
|
|
39
|
+
const refreshed = result.refreshed.length ? result.refreshed.join(", ") : "none"
|
|
40
|
+
const failed = result.failed.length ? result.failed.join(", ") : "none"
|
|
41
|
+
return {
|
|
42
|
+
title: "Raven MCP", message: `Refresh complete.\nRefreshed: ${refreshed}\nFailed: ${failed}`,
|
|
43
|
+
variant: result.failed.length ? "warning" as const : "success" as const, duration: 6000,
|
|
44
|
+
}
|
|
45
|
+
}
|
package/tui.ts
CHANGED
|
@@ -1,16 +1,70 @@
|
|
|
1
1
|
import type { Plugin } from "@opencode/plugin/tui"
|
|
2
|
-
import { RavenRpc } from "./rpc"
|
|
2
|
+
import { RavenRpc, mcpRefreshArguments, mcpRefreshToast } from "./rpc"
|
|
3
3
|
|
|
4
4
|
export default {
|
|
5
5
|
id: "opencode-raven.tui",
|
|
6
6
|
setup(ctx) {
|
|
7
7
|
const rpc = ctx.client.rpc(RavenRpc)
|
|
8
|
+
const refreshController = new AbortController()
|
|
9
|
+
const refreshes = new Map<string, Promise<void>>()
|
|
10
|
+
const refreshMcps = (input: { serverName?: string } = {}) => {
|
|
11
|
+
const key = input.serverName?.toLowerCase() ?? "*"
|
|
12
|
+
const pending = refreshes.get(key)
|
|
13
|
+
if (pending) return pending
|
|
14
|
+
ctx.ui.toast.show({ title: "Raven MCP", message: `Refreshing ${input.serverName ?? "on-demand MCPs"}…`, variant: "info", duration: 3000 })
|
|
15
|
+
const run = rpc.refreshMcps(input, {
|
|
16
|
+
location: ctx.location ?? ctx.data.location.default(), signal: refreshController.signal,
|
|
17
|
+
}).then((result) => {
|
|
18
|
+
if (!refreshController.signal.aborted) ctx.ui.toast.show(mcpRefreshToast(result))
|
|
19
|
+
}).catch((error) => {
|
|
20
|
+
if (refreshController.signal.aborted) return
|
|
21
|
+
ctx.ui.toast.show({ title: "Raven MCP", message: `Refresh failed: ${String(error)}. Retry /raven mcp refresh.`, variant: "error", duration: 6000 })
|
|
22
|
+
}).finally(() => refreshes.delete(key))
|
|
23
|
+
refreshes.set(key, run)
|
|
24
|
+
return run
|
|
25
|
+
}
|
|
8
26
|
const show = async (text: string) => {
|
|
9
|
-
|
|
27
|
+
// Load the host's OpenTUI peer only when the terminal needs it.
|
|
28
|
+
const { BoxRenderable, TextRenderable, ScrollBoxRenderable, RGBA } = await import("@opentui/core")
|
|
29
|
+
ctx.ui.dialog.show(() => {
|
|
30
|
+
const theme = ctx.theme.surface("dialog")
|
|
31
|
+
const color = theme.background.base
|
|
32
|
+
const backgroundColor = RGBA.fromValues(color.r, color.g, color.b, 1)
|
|
33
|
+
const height = () => Math.max(1, Math.min(32, ctx.renderer.height - 4))
|
|
34
|
+
const box = new BoxRenderable(ctx.renderer, {
|
|
35
|
+
width: "100%", height: height(), flexDirection: "column",
|
|
36
|
+
paddingX: 2, paddingY: 1, gap: 1, backgroundColor, overflow: "hidden",
|
|
37
|
+
})
|
|
38
|
+
box.add(new TextRenderable(ctx.renderer, {
|
|
39
|
+
content: "Raven", fg: theme.text.base, flexShrink: 0,
|
|
40
|
+
}))
|
|
41
|
+
const scroll = new ScrollBoxRenderable(ctx.renderer, {
|
|
42
|
+
flexGrow: 1, minHeight: 1, scrollX: false, scrollY: true,
|
|
43
|
+
contentOptions: { paddingRight: 1 },
|
|
44
|
+
})
|
|
45
|
+
scroll.add(new TextRenderable(ctx.renderer, {
|
|
46
|
+
content: text, fg: theme.text.base, width: "100%", wrapMode: "word", flexShrink: 0,
|
|
47
|
+
}))
|
|
48
|
+
box.add(scroll)
|
|
49
|
+
box.add(new TextRenderable(ctx.renderer, {
|
|
50
|
+
content: "↑/↓ PgUp/PgDn wheel · esc close",
|
|
51
|
+
fg: theme.text.muted, flexShrink: 0, wrapMode: "word",
|
|
52
|
+
}))
|
|
53
|
+
const resize = () => { box.height = height() }
|
|
54
|
+
ctx.renderer.on("resize", resize)
|
|
55
|
+
box.once("destroyed", () => ctx.renderer.off("resize", resize))
|
|
56
|
+
scroll.focus()
|
|
57
|
+
return box
|
|
58
|
+
})
|
|
59
|
+
ctx.ui.dialog.set({ size: "large", centered: true })
|
|
10
60
|
}
|
|
11
61
|
const unsubscribe = rpc.events.on("notice", async (event) => {
|
|
12
62
|
const location = ctx.location ?? ctx.data.location.default()
|
|
13
63
|
if (event.location?.directory !== location.directory) return
|
|
64
|
+
if (event.data.sessionID) {
|
|
65
|
+
const route = ctx.ui.router.current()
|
|
66
|
+
if (route.type !== "session" || route.sessionID !== event.data.sessionID) return
|
|
67
|
+
}
|
|
14
68
|
if (event.data.kind === "command") {
|
|
15
69
|
const route = ctx.ui.router.current()
|
|
16
70
|
if (route.type !== "session" || route.sessionID !== event.data.sessionID) return
|
|
@@ -19,12 +73,17 @@ export default {
|
|
|
19
73
|
ctx.ui.toast.show(event.data)
|
|
20
74
|
}
|
|
21
75
|
})
|
|
76
|
+
// V2's service outlives the TUI. Check MCP health on each client attachment,
|
|
77
|
+
// not just once when the server plugin is initialized.
|
|
78
|
+
void refreshMcps()
|
|
22
79
|
ctx.keymap.layer(() => ({ commands: [{
|
|
23
80
|
id: "raven", title: "Raven", description: "Manage Raven routing, MCPs, models, and status",
|
|
24
81
|
slash: { name: "raven", arguments: true },
|
|
25
82
|
async run(input = "") {
|
|
26
83
|
try {
|
|
27
84
|
const args = input.replace(/^\s*\/raven(?:\s+|$)/i, "").trim()
|
|
85
|
+
const refresh = mcpRefreshArguments(args)
|
|
86
|
+
if (refresh) { await refreshMcps(refresh); return }
|
|
28
87
|
const result = await rpc.command({ arguments: args }, { location: ctx.location ?? ctx.data.location.default() })
|
|
29
88
|
await show(result.text)
|
|
30
89
|
} catch (error) {
|
|
@@ -32,6 +91,9 @@ export default {
|
|
|
32
91
|
}
|
|
33
92
|
},
|
|
34
93
|
}] }))
|
|
35
|
-
return
|
|
94
|
+
return () => {
|
|
95
|
+
refreshController.abort()
|
|
96
|
+
unsubscribe()
|
|
97
|
+
}
|
|
36
98
|
},
|
|
37
99
|
} satisfies Plugin.Definition
|