opencode-raven 1.2.6 → 1.2.7

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 +3 -2
  2. package/index.ts +63 -10
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -40,7 +40,7 @@ Restart opencode.
40
40
 
41
41
  | Command | Action |
42
42
  |---------|--------|
43
- | `/raven` | Show status — enabled/disabled, model, reasoning effort, timeout |
43
+ | `/raven` | Show status — enabled/disabled, version, update availability, model, reasoning effort, timeout (no args) |
44
44
  | `/raven on` | Enable search tool redirection (default) |
45
45
  | `/raven off` | Disable interception — all agents can use search tools directly |
46
46
  | `/raven update` | Check npm for a newer Raven, clear opencode's plugin cache if needed, then restart opencode |
@@ -55,7 +55,7 @@ Config persists across restarts in `~/.config/opencode/raven-config.json` (globa
55
55
 
56
56
  opencode caches npm plugins, so `"opencode-raven"` / `"opencode-raven@latest"` may not automatically refresh after a new npm release.
57
57
 
58
- Raven checks npm at startup. If an update is available, it shows a TUI notification. To update:
58
+ Raven checks npm after the TUI starts. If an update is available, it shows a notification. `/raven` also shows the current version and update availability. To update:
59
59
 
60
60
  ```txt
61
61
  /raven update
@@ -168,6 +168,7 @@ To disable an MCP entirely:
168
168
  | `config` | Registers Raven agent, merges Context7/Exa/Grep.app MCP defaults, loads MCP guidance |
169
169
  | `tool` | Registers `raven_seek` — creates Raven sessions with timeout, error recovery, timing, and session tree visibility. Tracks context processed for stats (both `raven_seek` and direct `@Raven`). |
170
170
  | `chat.message` | Tracks agent ↔ session mapping for allowlist and Raven exclusion |
171
+ | `event` | Shows startup update notifications after the TUI event stream is ready |
171
172
  | `command.execute.before` | Handles `/raven on\|off\|update\|model\|effort\|timeout\|stats\|status` |
172
173
  | `tool.execute.before` | Blocks search tools for non-Raven, non-excluded agents (respects `excludeTools`). Error output gives the next `raven_seek(query="...")` call. Injects concise `<raven_guidance>` into subagent prompts. |
173
174
  | `tool.execute.after` | Counts output bytes from direct `@Raven` calls for accurate stats. |
package/index.ts CHANGED
@@ -263,6 +263,9 @@ export default ((input: PluginInput) => {
263
263
  const ravenSessions = new Set<string>()
264
264
  const ravenTaskCalls = new Set<string>()
265
265
  const sessionAgents = new Map<string, string>()
266
+ let updateInfo: { current: string; latest?: string; available: boolean } | undefined
267
+ let updateCheckPromise: Promise<{ current: string; latest?: string; available: boolean }> | undefined
268
+ let updateToastPending = false
266
269
 
267
270
  // ── Check if an agent is excluded from Raven enforcement (case-insensitive) ──
268
271
  function isExcluded(agent: string | undefined): boolean {
@@ -320,12 +323,19 @@ export default ((input: PluginInput) => {
320
323
  }
321
324
 
322
325
  async function fetchLatestVersion(): Promise<string | undefined> {
323
- const res = await fetch(`https://registry.npmjs.org/${PACKAGE_NAME}/latest`, {
324
- headers: { accept: "application/json" },
325
- })
326
- if (!res.ok) return undefined
327
- const data = await res.json() as { version?: string }
328
- return data.version
326
+ const controller = new AbortController()
327
+ const timeout = setTimeout(() => controller.abort(), 5000)
328
+ try {
329
+ const res = await fetch(`https://registry.npmjs.org/${PACKAGE_NAME}/latest`, {
330
+ headers: { accept: "application/json" },
331
+ signal: controller.signal,
332
+ })
333
+ if (!res.ok) return undefined
334
+ const data = await res.json() as { version?: string }
335
+ return data.version
336
+ } finally {
337
+ clearTimeout(timeout)
338
+ }
329
339
  }
330
340
 
331
341
  async function checkForUpdate(): Promise<{ current: string; latest?: string; available: boolean }> {
@@ -333,6 +343,36 @@ export default ((input: PluginInput) => {
333
343
  return { current: PACKAGE_VERSION, latest, available: !!latest && compareVersions(latest, PACKAGE_VERSION) > 0 }
334
344
  }
335
345
 
346
+ async function getUpdateInfo(): Promise<{ current: string; latest?: string; available: boolean }> {
347
+ if (updateInfo) return updateInfo
348
+ if (!updateCheckPromise) {
349
+ updateCheckPromise = checkForUpdate()
350
+ .then((info) => {
351
+ updateInfo = info
352
+ return info
353
+ })
354
+ .catch((err) => {
355
+ updateCheckPromise = undefined
356
+ throw err
357
+ })
358
+ }
359
+ return updateCheckPromise
360
+ }
361
+
362
+ async function refreshUpdateInfo(): Promise<{ current: string; latest?: string; available: boolean }> {
363
+ updateInfo = undefined
364
+ updateCheckPromise = checkForUpdate()
365
+ .then((info) => {
366
+ updateInfo = info
367
+ return info
368
+ })
369
+ .catch((err) => {
370
+ updateCheckPromise = undefined
371
+ throw err
372
+ })
373
+ return updateCheckPromise
374
+ }
375
+
336
376
  function clearPluginCache(): string[] {
337
377
  const packagesDir = join(homedir(), ".cache", "opencode", "packages")
338
378
  if (!existsSync(packagesDir)) return []
@@ -353,7 +393,7 @@ export default ((input: PluginInput) => {
353
393
 
354
394
  async function notifyIfUpdateAvailable() {
355
395
  try {
356
- const info = await checkForUpdate()
396
+ const info = await getUpdateInfo()
357
397
  if (!info.available || !info.latest) return
358
398
  await (client as any).tui?.showToast?.({
359
399
  body: {
@@ -417,7 +457,7 @@ export default ((input: PluginInput) => {
417
457
  }
418
458
  }
419
459
 
420
- void notifyIfUpdateAvailable()
460
+ updateToastPending = true
421
461
  },
422
462
 
423
463
  // Register raven_seek tool — lets agents with task:false still search through Raven
@@ -510,6 +550,12 @@ export default ((input: PluginInput) => {
510
550
  }
511
551
  },
512
552
 
553
+ event() {
554
+ if (!updateToastPending) return
555
+ updateToastPending = false
556
+ setTimeout(() => void notifyIfUpdateAvailable(), 500)
557
+ },
558
+
513
559
  // /raven on|off|model <name>|effort <value>|timeout <seconds>|stats|status
514
560
  async "command.execute.before"(input: any, output: any) {
515
561
  if (input.command !== "raven") return
@@ -529,7 +575,7 @@ export default ((input: PluginInput) => {
529
575
  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)` })
530
576
  } else if (arg === "update") {
531
577
  try {
532
- const info = await checkForUpdate()
578
+ const info = await refreshUpdateInfo()
533
579
  if (!info.latest) {
534
580
  output.parts.push({ type: "text", text: `Could not check npm for ${PACKAGE_NAME}. Try again later.\n\n${manualUpdateText()}` })
535
581
  } else if (!info.available) {
@@ -573,7 +619,14 @@ export default ((input: PluginInput) => {
573
619
  const model = config.model || fm.model || "(default)"
574
620
  const effort = config.reasoning_effort || fm.reasoning_effort || "(default)"
575
621
  const timeout = config.timeout ?? 180
576
- output.parts.push({ type: "text", text: `Raven is ${enabled}. Model: ${model}. Reasoning: ${effort}. Timeout: ${timeout}s\n\nCommands:\n /raven on — enable search interception\n /raven off — disable search interception\n /raven update — check npm, clear plugin cache if newer, then restart opencode\n /raven model <name> — change Raven's model (requires restart)\n /raven effort <value> — change Raven's reasoning effort (requires restart)\n /raven timeout <seconds> — change raven_seek timeout\n /raven stats — show blocked calls and context saved` })
622
+ let update = "Update: unable to check npm."
623
+ try {
624
+ const info = await getUpdateInfo()
625
+ update = info.available && info.latest
626
+ ? `Update: ${info.latest} available. Run /raven update, then restart opencode.`
627
+ : `Update: up to date${info.latest ? ` (latest ${info.latest})` : ""}.`
628
+ } catch { /* keep fallback */ }
629
+ output.parts.push({ type: "text", text: `Raven is ${enabled}. Version: ${PACKAGE_VERSION}. Model: ${model}. Reasoning: ${effort}. Timeout: ${timeout}s\n${update}\n\nCommands:\n /raven on — enable search interception\n /raven off — disable search interception\n /raven update — check npm, clear plugin cache if newer, then restart opencode\n /raven model <name> — change Raven's model (requires restart)\n /raven effort <value> — change Raven's reasoning effort (requires restart)\n /raven timeout <seconds> — change raven_seek timeout\n /raven stats — show blocked calls and context saved` })
577
630
  }
578
631
  },
579
632
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-raven",
3
- "version": "1.2.6",
3
+ "version": "1.2.7",
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": {