opencode-raven 1.2.1 → 1.2.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.
Files changed (3) hide show
  1. package/README.md +4 -4
  2. package/index.ts +14 -61
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -45,7 +45,7 @@ Restart opencode.
45
45
  | `/raven off` | Disable interception — all agents can use search tools directly |
46
46
  | `/raven model <name>` | Change Raven's model (requires restart) |
47
47
  | `/raven effort <value>` | Change Raven's reasoning effort (requires restart) |
48
- | `/raven stats` | Show blocked calls, context saved (session + global) |
48
+ | `/raven stats` | Show context processed (session + all-time, bytes + tokens) |
49
49
 
50
50
  Config persists across restarts in `~/.config/opencode/raven-config.json` (global, shared across all projects). Auto-created on first run.
51
51
 
@@ -124,10 +124,10 @@ To disable an MCP entirely:
124
124
  | Hook | What it does |
125
125
  |------|--------------|
126
126
  | `config` | Registers Raven agent, adds Context7/Exa/Grep.app MCPs, loads MCP guidance |
127
- | `tool` | Registers `raven_seek` — hidden Raven sessions with error recovery for API failures |
127
+ | `tool` | Registers `raven_seek` — hidden Raven sessions with error recovery for API failures. Tracks context processed for stats. |
128
128
  | `chat.message` | Tracks agent ↔ session mapping for allowlist and Raven exclusion |
129
129
  | `command.execute.before` | Handles `/raven on\|off\|model\|effort\|status` |
130
- | `tool.execute.before` | Blocks search tools for non-Raven, non-excluded agents. Injects `<raven_guidance>` into subagent prompts. Throttled: full message once per session, silent after. Tracks blocked calls + context saved. |
130
+ | `tool.execute.before` | Blocks search tools for non-Raven, non-excluded agents (respects `excludeTools`). Injects `<raven_guidance>` into subagent prompts. |
131
131
 
132
132
  ### Blocked tools (redirected except for Raven and any agents in `excludeAgents`)
133
133
 
@@ -135,7 +135,7 @@ To disable an MCP entirely:
135
135
 
136
136
  | Tool | Source |
137
137
  |------|--------|
138
- | `grep`, `glob`, `webfetch`, `fetch` | Built-in |
138
+ | `grep`, `glob`, `webfetch`, `fetch`, `websearch` | Built-in |
139
139
  | `websearch_web_search_exa` | WebSearch MCP |
140
140
  | `context7_resolve-library-id`, `context7_query-docs` | Context7 MCP |
141
141
  | `exa_web_search_exa`, `exa_web_fetch_exa`, `exa_web_search_advanced_exa` | Exa AI MCP |
package/index.ts CHANGED
@@ -17,6 +17,7 @@ const SEARCH_TOOLS = [
17
17
  "glob",
18
18
  "webfetch",
19
19
  "fetch",
20
+ "websearch",
20
21
  // WebSearch MCP
21
22
  "websearch_web_search_exa",
22
23
  // Context7 MCP
@@ -66,7 +67,7 @@ interface RavenConfig {
66
67
  reasoning_effort?: string
67
68
  excludeAgents?: string[]
68
69
  excludeTools?: string[]
69
- stats?: { blocked: number; bytesSaved: number; tools: Record<string, number> }
70
+ stats?: { bytes: number }
70
71
  }
71
72
 
72
73
  // ── Parse Raven.md frontmatter ──
@@ -201,55 +202,16 @@ export default ((input: PluginInput) => {
201
202
  return config.excludeAgents.some((a) => a.toLowerCase() === lower)
202
203
  }
203
204
 
204
- // Throttle: show the full error message once per session, then silent
205
- const throttledSessions = new Set<string>()
206
205
  const REROUTE_MSG = "Search tools are blocked. Use raven_seek(query=\"...\") to search through Raven."
207
206
 
208
- // ── Session stats: track blocked calls + estimated context saved ──
209
- const sessionStats = new Map<string, { blocked: number; tools: Map<string, number>; bytesSaved: number }>()
210
- const globalStats = { blocked: 0, tools: new Map<string, number>(), bytesSaved: 0 }
211
- // Restore global stats from config
212
- if (config.stats) {
213
- globalStats.blocked = config.stats.blocked ?? 0
214
- globalStats.bytesSaved = config.stats.bytesSaved ?? 0
215
- for (const [t, n] of Object.entries(config.stats.tools ?? {})) {
216
- globalStats.tools.set(t, n)
217
- }
218
- }
219
-
220
- function getBytesEstimate(tool: string): number {
221
- const ESTIMATES: Record<string, number> = {
222
- grep: 2000, glob: 2000,
223
- webfetch: 16000, fetch: 16000,
224
- websearch_web_search_exa: 8000,
225
- context7_resolve_library_id: 1000, context7_query_docs: 4000,
226
- exa_web_search_exa: 8000, exa_web_fetch_exa: 16000,
227
- exa_web_search_advanced_exa: 8000, exa_company_research_exa: 8000,
228
- exa_crawling_exa: 12000, exa_people_search_exa: 6000,
229
- exa_linkedin_search_exa: 6000, exa_get_code_context_exa: 4000,
230
- exa_deep_researcher_start: 8000, exa_deep_researcher_check: 2000,
231
- exa_deep_search_exa: 8000,
232
- grep_app_searchGitHub: 4000,
233
- }
234
- return ESTIMATES[tool] ?? 2000 // default for bash search / unknown
235
- }
207
+ // ── Context processed by raven_seek ──
208
+ let sessionBytes = 0
209
+ let totalBytes = config.stats?.bytes ?? 0
236
210
 
237
- function trackBlock(sessionID: string, tool: string) {
238
- // Session stats
239
- let stats = sessionStats.get(sessionID)
240
- if (!stats) {
241
- stats = { blocked: 0, tools: new Map(), bytesSaved: 0 }
242
- sessionStats.set(sessionID, stats)
243
- }
244
- stats.blocked++
245
- stats.tools.set(tool, (stats.tools.get(tool) ?? 0) + 1)
246
- stats.bytesSaved += getBytesEstimate(tool)
247
- // Global stats
248
- globalStats.blocked++
249
- globalStats.tools.set(tool, (globalStats.tools.get(tool) ?? 0) + 1)
250
- globalStats.bytesSaved += getBytesEstimate(tool)
251
- // Persist to config
252
- config.stats = { blocked: globalStats.blocked, bytesSaved: globalStats.bytesSaved, tools: Object.fromEntries(globalStats.tools) }
211
+ function addBytes(bytes: number) {
212
+ sessionBytes += bytes
213
+ totalBytes += bytes
214
+ config.stats = { bytes: totalBytes }
253
215
  saveConfig(config)
254
216
  }
255
217
 
@@ -349,6 +311,9 @@ export default ((input: PluginInput) => {
349
311
  await client.session.delete({ path: { id: sessionId } })
350
312
  } catch { /* non-fatal */ }
351
313
 
314
+ // Track context saved
315
+ addBytes(output.length)
316
+
352
317
  return { title: "Raven Seek", output }
353
318
  } catch (err: any) {
354
319
  const msg = String(err?.message ?? err ?? "").toLowerCase()
@@ -391,14 +356,7 @@ export default ((input: PluginInput) => {
391
356
  saveConfig(config)
392
357
  output.parts.push({ type: "text", text: "Raven search interception disabled. All agents can use search tools directly." })
393
358
  } else if (arg === "stats") {
394
- const session = sessionStats.get(input.sessionID)
395
- const sessionStr = !session || session.blocked === 0
396
- ? " No blocks this session."
397
- : ` Session: ${session.blocked} blocked, ~${formatBytes(session.bytesSaved)} (~${formatTokens(session.bytesSaved)} tokens)`
398
- const globalStr = globalStats.blocked === 0
399
- ? " Global: no blocks yet."
400
- : ` Global: ${globalStats.blocked} blocked, ~${formatBytes(globalStats.bytesSaved)} (~${formatTokens(globalStats.bytesSaved)} tokens)`
401
- output.parts.push({ type: "text", text: `Raven stats:\n${sessionStr}\n${globalStr}` })
359
+ output.parts.push({ type: "text", text: `Raven context processed:\n This session: ${formatBytes(sessionBytes)} (~${formatTokens(sessionBytes)} tokens)\n All time: ${formatBytes(totalBytes)} (~${formatTokens(totalBytes)} tokens)` })
402
360
  } else if (arg.startsWith("model ")) {
403
361
  const model = raw.slice(6).trim()
404
362
  if (!model) {
@@ -447,17 +405,12 @@ export default ((input: PluginInput) => {
447
405
  const isSearchBashCmd = isSearchBash(input.tool, output.args || input.args)
448
406
 
449
407
  if (isSearchTool || isSearchBashCmd) {
450
- trackBlock(input.sessionID, isSearchBashCmd ? "bash(search)" : input.tool)
451
- if (throttledSessions.has(input.sessionID)) {
452
- throw new Error("")
453
- }
454
- throttledSessions.add(input.sessionID)
455
408
  throw new Error(REROUTE_MSG)
456
409
  }
457
410
  },
458
411
 
459
412
  "tool.execute.after"(input: any, output: any) {
460
- // Reserved for future analytics / redirect tracking (#5)
413
+ // Context saved is tracked in raven_seek instead
461
414
  },
462
415
  }
463
416
  }) satisfies Plugin
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-raven",
3
- "version": "1.2.1",
3
+ "version": "1.2.2",
4
4
  "description": "Search-first subagent for opencode — intercepts search tools and routes them through a hidden Raven agent with Context7, Exa AI, and Grep.app MCPs",
5
5
  "main": "./index.ts",
6
6
  "exports": {