opencode-raven 2.1.7 → 2.1.8

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 CHANGED
@@ -1,5 +1,14 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.1.8
4
+
5
+ ### Added
6
+ - `raven_seek` now returns a model-visible session ID and accepts it through the optional `sessionId` argument, allowing follow-up research to continue in the same Raven session.
7
+ - Resumed sessions are validated as Raven children of the current main session before they can be prompted again.
8
+
9
+ ### Fixed
10
+ - Context estimates for resumed sessions count only the newly handled context instead of counting earlier Raven turns again.
11
+
3
12
  ## 2.1.7
4
13
 
5
14
  ### Fixed
package/README.md CHANGED
@@ -54,10 +54,10 @@ In this run, Raven handled the search-heavy web research in a child session and
54
54
 
55
55
  1. Your agent attempts to use a routed search, fetch, shell-discovery, or MCP tool.
56
56
  2. Raven blocks the noisy direct call and gives the agent the exact `raven_seek` retry.
57
- 3. The agent continues automatically through a linked Raven child session.
57
+ 3. The agent creates a linked Raven child session or resumes one for a related follow-up.
58
58
  4. Raven performs the tool-heavy work and returns only compact findings to the main session.
59
59
 
60
- The main model receives Raven's final answer instead of its raw tool output. Child sessions remain available in OpenCode's session/subagent view for inspection.
60
+ The main model receives Raven's final answer instead of its raw tool output. Each result includes a Raven session ID that the main agent can pass back for a context-aware follow-up. Child sessions remain available in OpenCode's session/subagent view for inspection.
61
61
 
62
62
  For direct control, mention `@Raven` in any chat. This sends the request straight to the Raven agent, but it is optional and not required for automatic routing.
63
63
 
@@ -65,9 +65,18 @@ Under the hood, Raven registers two tools:
65
65
 
66
66
  | Tool | Purpose |
67
67
  |------|---------|
68
- | `raven_seek` | Creates a linked Raven child session and returns its compact result. |
68
+ | `raven_seek` | Creates or resumes a linked Raven child session and returns its compact result. |
69
69
  | `raven_mcp` | Raven-only bridge to MCPs configured under `onDemandMcpServers`. |
70
70
 
71
+ For a related follow-up, the main agent can reuse the ID returned by the earlier call:
72
+
73
+ ```text
74
+ raven_seek(query="Research the available options")
75
+ raven_seek(query="Compare the first two in more detail", sessionId="ses_...")
76
+ ```
77
+
78
+ Raven accepts a resumed session only when it is a Raven child of the current main session. Unrelated work should omit `sessionId` and start a fresh child.
79
+
71
80
  OpenCode's built-in tools already exist; Raven routes selected calls instead of replacing those tools. Global MCPs also remain globally registered, so their schemas still enter the main session even when Raven routes their calls.
72
81
 
73
82
  On-demand MCPs are different: Raven discovers and calls them internally, so their full schemas are not registered globally. Enabled servers are probed after startup to discover tools and health, then per-session connections are opened when Raven uses them. "On-demand" describes schema exposure and normal use, not the absence of a startup health probe.
package/index.ts CHANGED
@@ -532,8 +532,9 @@ export default (async (input: PluginInput) => {
532
532
  return config
533
533
  }
534
534
  let mcpMetadata = loadMcpMetadata()
535
- const ravenSessions = new Set<string>()
536
- const ravenTaskCalls = new Set<string>()
535
+ const ravenSessions = new Set<string>()
536
+ const activeRavenPrompts = new Set<string>()
537
+ const ravenTaskCalls = new Set<string>()
537
538
  const sessionAgents = new Map<string, string>()
538
539
  const ravenSessionParents = new Map<string, string>()
539
540
  const ravenErrorWaiters = new Map<string, (error: Error) => void>()
@@ -1346,12 +1347,10 @@ Then restart opencode for the model change to take effect. If the task can be co
1346
1347
  return { current: PACKAGE_VERSION, latest, available: !!latest && compareVersions(latest, PACKAGE_VERSION) > 0 }
1347
1348
  }
1348
1349
 
1349
- async function countRavenSavedCandidateBytes(sessionId: string): Promise<number> {
1350
- const messagesResp = await client.session.messages({ path: { id: sessionId }, query: { limit: 200 } })
1351
- const messages = (messagesResp as any)?.data ?? []
1352
- const assistantMessages = messages.filter((m: any) =>
1353
- m?.info?.role === "assistant" && m?.info?.tokens?.output > 0
1354
- )
1350
+ function estimateRavenSavedCandidateBytes(messages: any[]): number {
1351
+ const assistantMessages = messages.filter((m: any) =>
1352
+ m?.info?.role === "assistant" && m?.info?.tokens?.output > 0
1353
+ )
1355
1354
  const first = assistantMessages[0]
1356
1355
  const last = assistantMessages[assistantMessages.length - 1]
1357
1356
  const t = last?.info?.tokens
@@ -1363,9 +1362,15 @@ Then restart opencode for the model change to take effect. If the task can be co
1363
1362
  const baselineTokens = firstTokens
1364
1363
  ? (firstTokens.input ?? 0) + (firstTokens.cache?.read ?? 0) + (firstTokens.cache?.write ?? 0)
1365
1364
  : 0
1366
- const savedCandidateTokens = Math.max(0, totalTokens - baselineTokens)
1367
- return savedCandidateTokens * 4
1368
- }
1365
+ const savedCandidateTokens = Math.max(0, totalTokens - baselineTokens)
1366
+ return savedCandidateTokens * 4
1367
+ }
1368
+
1369
+ async function countRavenSavedCandidateBytes(sessionId: string, signal?: AbortSignal): Promise<number> {
1370
+ const messagesResp = await client.session.messages({ path: { id: sessionId }, signal })
1371
+ const messages = (messagesResp as any)?.data ?? []
1372
+ return estimateRavenSavedCandidateBytes(messages)
1373
+ }
1369
1374
 
1370
1375
  async function getUpdateInfo(): Promise<{ current: string; latest?: string; available: boolean }> {
1371
1376
  if (updateInfo) return updateInfo
@@ -1529,33 +1534,85 @@ Then restart opencode for the model change to take effect. If the task can be co
1529
1534
  duplicateMcpToastPending = duplicateMcpServers.length > 0
1530
1535
  },
1531
1536
 
1532
- // Register raven_seek tool — lets agents with task:false still delegate through Raven
1533
- tool: {
1534
- "raven_seek": tool({
1535
- description: "Unified Raven delegation tool. Use this whenever a tool/MCP is blocked by Raven, or when grep, glob, WebFetch/fetch, websearch, docs lookup, GitHub search, on-demand MCP capabilities, or search-like bash would be used. Handles routed MCP requests, on-demand MCPs, local codebase search, filesystem discovery, specific URL/page reads, web/docs research, GitHub examples, and command-output/system inspection via Raven.",
1536
- args: {
1537
- query: tool.schema.string().describe("What Raven should do. Include the original blocked tool/MCP request and relevant args, exact URLs when replacing WebFetch, and commands/output checks when replacing grep/rg/head over command output."),
1538
- },
1539
- async execute(args, context) {
1540
- const started = Date.now()
1541
- const timeout = (config.timeout ?? 600) * 1000
1542
- try {
1543
- // Create a Raven session
1544
- const session = await client.session.create({
1545
- body: {
1546
- parentID: context.sessionID,
1547
- title: `raven_seek: ${args.query.slice(0, 80)}`,
1548
- },
1549
- signal: context.abort,
1550
- })
1551
-
1552
- const sessionId = (session as any)?.data?.id ?? (session as any)?.id
1553
- if (!sessionId) {
1554
- return { title: "Raven Seek", output: "Failed to create Raven session." }
1555
- }
1556
-
1557
- ravenSessions.add(sessionId)
1558
-
1537
+ // Register raven_seek tool — lets agents with task:false still delegate through Raven
1538
+ tool: {
1539
+ "raven_seek": tool({
1540
+ description: "Unified Raven delegation tool. Creates a Raven child session or resumes one for follow-up research. Use this whenever a tool/MCP is blocked by Raven, or when grep, glob, WebFetch/fetch, websearch, docs lookup, GitHub search, on-demand MCP capabilities, or search-like bash would be used. Handles routed MCP requests, on-demand MCPs, local codebase search, filesystem discovery, specific URL/page reads, web/docs research, GitHub examples, and command-output/system inspection via Raven.",
1541
+ args: {
1542
+ query: tool.schema.string().describe("What Raven should do. Include the original blocked tool/MCP request and relevant args, exact URLs when replacing WebFetch, and commands/output checks when replacing grep/rg/head over command output."),
1543
+ sessionId: tool.schema.string().optional().describe("Raven session ID returned by an earlier raven_seek call. Provide it only when this request should continue that session's research context."),
1544
+ },
1545
+ async execute(args, context) {
1546
+ const started = Date.now()
1547
+ const timeout = (config.timeout ?? 600) * 1000
1548
+ let sessionId: string | undefined
1549
+ let resumed = false
1550
+ let previousSavedCandidate = 0
1551
+ try {
1552
+ const requestedSessionId = args.sessionId?.trim()
1553
+ if (args.sessionId !== undefined && !requestedSessionId) {
1554
+ return { title: "Raven Seek", output: "Cannot resume Raven: sessionId must not be empty." }
1555
+ }
1556
+
1557
+ if (requestedSessionId) {
1558
+ const knownRavenSession = ravenSessions.has(requestedSessionId) || sessionAgents.get(requestedSessionId) === "raven"
1559
+ let existingSession: any
1560
+ try {
1561
+ const sessionResp = await client.session.get({ path: { id: requestedSessionId }, signal: context.abort })
1562
+ existingSession = (sessionResp as any)?.data ?? sessionResp
1563
+ } catch (err) {
1564
+ if (context.abort.aborted) throw err
1565
+ return { title: "Raven Seek", output: `Cannot resume Raven session ${requestedSessionId}: the session was not found or could not be read.` }
1566
+ }
1567
+
1568
+ if (!existingSession?.id || existingSession.id !== requestedSessionId) {
1569
+ return { title: "Raven Seek", output: `Cannot resume Raven session ${requestedSessionId}: the session was not found or could not be read.` }
1570
+ }
1571
+ if (existingSession.parentID !== context.sessionID || existingSession.projectID !== input.project.id) {
1572
+ return { title: "Raven Seek", output: "Cannot resume Raven: sessionId is not a Raven child of this main session." }
1573
+ }
1574
+
1575
+ let messages: any[]
1576
+ try {
1577
+ const messagesResp = await client.session.messages({ path: { id: requestedSessionId }, signal: context.abort })
1578
+ const messageData = (messagesResp as any)?.data
1579
+ if (!Array.isArray(messageData)) {
1580
+ return { title: "Raven Seek", output: `Cannot resume Raven session ${requestedSessionId}: its message history could not be read.` }
1581
+ }
1582
+ messages = messageData
1583
+ } catch (err) {
1584
+ if (context.abort.aborted) throw err
1585
+ return { title: "Raven Seek", output: `Cannot resume Raven session ${requestedSessionId}: its message history could not be read.` }
1586
+ }
1587
+
1588
+ const userMessages = messages.filter((message: any) => message?.info?.role === "user")
1589
+ const persistedRavenSession = userMessages.length > 0
1590
+ && userMessages.every((message: any) => message.info.agent === "raven")
1591
+ if (!persistedRavenSession && !(knownRavenSession && userMessages.length === 0)) {
1592
+ return { title: "Raven Seek", output: "Cannot resume Raven: sessionId is not a Raven child of this main session." }
1593
+ }
1594
+
1595
+ sessionId = requestedSessionId
1596
+ resumed = true
1597
+ previousSavedCandidate = estimateRavenSavedCandidateBytes(messages)
1598
+ } else {
1599
+ const session = await client.session.create({
1600
+ body: {
1601
+ parentID: context.sessionID,
1602
+ title: `raven_seek: ${args.query.slice(0, 80)}`,
1603
+ },
1604
+ signal: context.abort,
1605
+ })
1606
+
1607
+ sessionId = (session as any)?.data?.id ?? (session as any)?.id
1608
+ if (!sessionId) {
1609
+ return { title: "Raven Seek", output: "Failed to create Raven session." }
1610
+ }
1611
+ }
1612
+
1613
+ ravenSessions.add(sessionId)
1614
+ const activeSessionId = sessionId
1615
+
1559
1616
  // Preserve the child session link for TUI clients that render it.
1560
1617
  context.metadata({ metadata: { sessionId } })
1561
1618
 
@@ -1564,15 +1621,35 @@ Then restart opencode for the model change to take effect. If the task can be co
1564
1621
  const logFile = join(tmpdir(), "raven-sessions.log")
1565
1622
  const ts = new Date().toISOString()
1566
1623
  writeFileSync(logFile, `${ts} ${sessionId}\n`)
1567
- } catch { /* non-fatal */ }
1624
+ } catch { /* non-fatal */ }
1625
+
1626
+ if (activeRavenPrompts.has(activeSessionId)) {
1627
+ return {
1628
+ title: "Raven Seek",
1629
+ metadata: { sessionId: activeSessionId },
1630
+ output: `Raven session \`${activeSessionId}\` is already handling a request. Wait for it to finish before sending another follow-up.`,
1631
+ }
1632
+ }
1633
+ activeRavenPrompts.add(activeSessionId)
1568
1634
 
1569
1635
  // Send the query to Raven with timeout. Provider/API errors are also
1570
1636
  // surfaced through message.updated events; fail as soon as one lands
1571
1637
  // so the caller does not wait for the full timeout on quota/auth issues.
1572
1638
  let timer: ReturnType<typeof setTimeout> | undefined
1573
1639
  let promptCompleted = false
1640
+ let sessionAbort: Promise<unknown> | undefined
1574
1641
  const promptController = new AbortController()
1575
- const abortPrompt = () => promptController.abort(context.abort.reason)
1642
+ const abortPrompt = () => {
1643
+ if (promptController.signal.aborted) return
1644
+ promptController.abort(context.abort.reason)
1645
+ const abortController = new AbortController()
1646
+ const abortTimer = setTimeout(() => abortController.abort(), Math.min(timeout, 5_000))
1647
+ sessionAbort = client.session.abort({
1648
+ path: { id: activeSessionId },
1649
+ signal: abortController.signal,
1650
+ throwOnError: true,
1651
+ }).catch(() => undefined).finally(() => clearTimeout(abortTimer))
1652
+ }
1576
1653
  if (context.abort.aborted) abortPrompt()
1577
1654
  else context.abort.addEventListener("abort", abortPrompt, { once: true })
1578
1655
  const providerError = waitForRavenProviderError(sessionId)
@@ -1586,12 +1663,13 @@ Then restart opencode for the model change to take effect. If the task can be co
1586
1663
  parts: [{ type: "text", text: args.query }],
1587
1664
  },
1588
1665
  signal: promptController.signal,
1666
+ throwOnError: true,
1589
1667
  }),
1590
1668
  providerError,
1591
1669
  firstProgress,
1592
1670
  new Promise<never>((_, reject) => {
1593
1671
  timer = setTimeout(() => {
1594
- promptController.abort()
1672
+ abortPrompt()
1595
1673
  reject(new Error(`Raven timed out after ${timeout / 1000}s — child session retained: ${sessionId}`))
1596
1674
  }, timeout)
1597
1675
  }),
@@ -1599,8 +1677,9 @@ Then restart opencode for the model change to take effect. If the task can be co
1599
1677
  promptCompleted = true
1600
1678
  if (timer) clearTimeout(timer)
1601
1679
  clearRavenWaiters(sessionId)
1680
+ if ((result as any)?.error) throw new Error(providerErrorMessage((result as any).error))
1602
1681
 
1603
- const elapsed = ((Date.now() - started) / 1000).toFixed(1)
1682
+ const elapsed = ((Date.now() - started) / 1000).toFixed(1)
1604
1683
 
1605
1684
  // Extract text from the response
1606
1685
  const parts = (result as any)?.data?.parts ?? []
@@ -1609,46 +1688,64 @@ Then restart opencode for the model change to take effect. If the task can be co
1609
1688
  .map((p: any) => p.text)
1610
1689
  const output = textParts.join("\n") || "Raven returned no results."
1611
1690
 
1612
- // Context saved = Raven output/reasoning/cache context - compact answer returned to main session.
1613
- let savedCandidate = 0
1614
- try {
1615
- savedCandidate = await countRavenSavedCandidateBytes(sessionId)
1616
- } catch { /* best-effort */ }
1691
+ // Context saved = Raven output/reasoning/cache context - compact answer returned to main session.
1692
+ let savedCandidate = 0
1693
+ const statsController = new AbortController()
1694
+ const abortStats = () => statsController.abort(context.abort.reason)
1695
+ const statsTimer = setTimeout(() => statsController.abort(), Math.min(timeout, 5_000))
1696
+ if (context.abort.aborted) abortStats()
1697
+ else context.abort.addEventListener("abort", abortStats, { once: true })
1698
+ try {
1699
+ const totalSavedCandidate = await countRavenSavedCandidateBytes(sessionId, statsController.signal)
1700
+ savedCandidate = Math.max(0, totalSavedCandidate - previousSavedCandidate)
1701
+ } catch { /* best-effort */ }
1702
+ finally {
1703
+ clearTimeout(statsTimer)
1704
+ context.abort.removeEventListener("abort", abortStats)
1705
+ }
1617
1706
  if (savedCandidate <= 0) {
1618
1707
  for (const part of parts) {
1619
1708
  if (part.args) savedCandidate += JSON.stringify(part.args).length
1620
1709
  if (part.content) savedCandidate += typeof part.content === "string" ? part.content.length : JSON.stringify(part.content).length
1621
1710
  }
1622
1711
  }
1623
- const saved = Math.max(0, savedCandidate - output.length)
1624
- addBytes(saved)
1625
- ravenProviderIssue = undefined
1626
-
1627
- return { title: "Raven Seek", metadata: { sessionId }, output: `${output}\n\n*Raven searched for ${elapsed}s — ${formatBytes(savedCandidate)} handled, ${formatTokens(savedCandidate)} tokens*` }
1712
+ const continuation = `Raven session: \`${sessionId}\`${resumed ? " (continued)" : ""}. Pass this as \`sessionId\` in a later \`raven_seek\` call to continue the same research.`
1713
+ const resultOutput = `${output}\n\n*Raven searched for ${elapsed}s — ${formatBytes(savedCandidate)} handled, ${formatTokens(savedCandidate)} tokens*\n\n${continuation}`
1714
+ addBytes(Math.max(0, savedCandidate - resultOutput.length))
1715
+ ravenProviderIssue = undefined
1716
+
1717
+ return { title: "Raven Seek", metadata: { sessionId }, output: resultOutput }
1628
1718
  } finally {
1629
1719
  context.abort.removeEventListener("abort", abortPrompt)
1630
- if (!promptCompleted) promptController.abort()
1720
+ if (!promptCompleted) abortPrompt()
1721
+ if (sessionAbort) await sessionAbort
1631
1722
  if (timer) clearTimeout(timer)
1632
1723
  clearRavenWaiters(sessionId)
1724
+ activeRavenPrompts.delete(activeSessionId)
1633
1725
  }
1634
- } catch (err: any) {
1635
- const elapsed = ((Date.now() - started) / 1000).toFixed(1)
1726
+ } catch (err: any) {
1727
+ const elapsed = ((Date.now() - started) / 1000).toFixed(1)
1636
1728
  const msg = String(err?.message ?? err ?? "").toLowerCase()
1729
+ const missingSession = /session.*not found|not found.*session/i.test(msg)
1637
1730
  const hint =
1638
1731
  context.abort.aborted ? "Raven search cancelled."
1639
1732
  : /rate.?limit|too many requests|429/i.test(msg) ? "Raven rate limited — wait 30s then retry with a narrower query."
1640
- : /quota|usage.?limit|usage exceeded|free usage|subscribe to go|subscription|billing|insufficient.*(?:credit|balance|quota)/i.test(msg) ? "Raven API quota/subscription exhausted — proceed without Raven, tell the user Raven could not run, and mention what information may be missing."
1641
- : /auth|unauthorized|forbidden|401|403|api key|login/i.test(msg) ? "Raven provider authentication failed — proceed without Raven and tell the user to check the Raven model/provider login or API key."
1642
- : /token|context.?length|too large|too long/i.test(msg) ? "Raven query too large — shorten your query and retry."
1643
- : /model|unavailable|down|not found/i.test(msg) ? "Raven model unavailable — retry later, or proceed without search."
1644
- : /did not produce an initial response|retry dialog/i.test(msg) ? err.message
1645
- : /timeout|timed.?out|session kept/i.test(msg) ? err.message
1646
- : `Raven search failed. Proceed without search — note gaps for the user. [${err.message || err}]`
1647
- if (/quota|usage.?limit|usage exceeded|free usage|subscribe to go|subscription|billing|auth|unauthorized|forbidden|401|403|api key|login|model|unavailable|down|not found|did not produce an initial response|retry dialog/i.test(msg)) {
1648
- ravenProviderIssue = hint
1649
- }
1650
- const recovery = ravenProviderIssue ? `\n\n${ravenRecoveryInstructions()}` : ""
1651
- return { title: "Raven Seek", output: `${hint}${recovery}\n\n*Attempt took ${elapsed}s*` }
1733
+ : /quota|usage.?limit|usage exceeded|free usage|subscribe to go|subscription|billing|insufficient.*(?:credit|balance|quota)/i.test(msg) ? "Raven API quota/subscription exhausted — proceed without Raven, tell the user Raven could not run, and mention what information may be missing."
1734
+ : /auth|unauthorized|forbidden|401|403|api key|login/i.test(msg) ? "Raven provider authentication failed — proceed without Raven and tell the user to check the Raven model/provider login or API key."
1735
+ : /token|context.?length|too large|too long/i.test(msg) ? "Raven query too large — shorten your query and retry."
1736
+ : missingSession ? "The Raven session is no longer available — retry without sessionId to start a new Raven search."
1737
+ : /model|unavailable|down|not found/i.test(msg) ? "Raven model unavailable — retry later, or proceed without search."
1738
+ : /did not produce an initial response|retry dialog/i.test(msg) ? err.message
1739
+ : /timeout|timed.?out|session kept/i.test(msg) ? err.message
1740
+ : `Raven search failed. Proceed without search — note gaps for the user. [${err.message || err}]`
1741
+ if (!missingSession && /quota|usage.?limit|usage exceeded|free usage|subscribe to go|subscription|billing|auth|unauthorized|forbidden|401|403|api key|login|model|unavailable|down|not found|did not produce an initial response|retry dialog/i.test(msg)) {
1742
+ ravenProviderIssue = hint
1743
+ }
1744
+ const recovery = ravenProviderIssue ? `\n\n${ravenRecoveryInstructions()}` : ""
1745
+ const continuation = sessionId && !missingSession
1746
+ ? `\n\nRaven session: \`${sessionId}\`. Pass this as \`sessionId\` in a later \`raven_seek\` call to retry or continue.`
1747
+ : ""
1748
+ return { title: "Raven Seek", metadata: sessionId ? { sessionId } : undefined, output: `${hint}${recovery}\n\n*Attempt took ${elapsed}s*${continuation}` }
1652
1749
  }
1653
1750
  },
1654
1751
  }),
@@ -1768,6 +1865,7 @@ Then restart opencode for the model change to take effect. If the task can be co
1768
1865
  const sessionId = evt.properties?.info?.id
1769
1866
  if (sessionId) {
1770
1867
  ravenSessions.delete(sessionId)
1868
+ activeRavenPrompts.delete(sessionId)
1771
1869
  sessionAgents.delete(sessionId)
1772
1870
  clearRavenWaiters(sessionId)
1773
1871
  for (const [parentId, childId] of ravenSessionParents) {
package/mcp-guidance.md CHANGED
@@ -2,7 +2,9 @@
2
2
 
3
3
  Do not call tools or MCPs that Raven blocks directly. Raven may route search/fetch tools, docs/web/GitHub MCPs, user-configured tool/MCP prefixes, or on-demand MCP capabilities through `raven_seek` to save context.
4
4
 
5
- Use `raven_seek(query="...")` as the next tool call for blocked tool/MCP requests. Include the original tool/MCP name, intent, and relevant arguments.
5
+ Use `raven_seek(query="...")` as the next tool call for blocked tool/MCP requests. Include the original tool/MCP name, intent, and relevant arguments.
6
+
7
+ When a request follows up on an earlier Raven result, pass the Raven session ID returned by that call as `sessionId` so Raven retains its research context. Omit `sessionId` for unrelated work.
6
8
 
7
9
  For on-demand MCP requests, include the target MCP server and likely tool name in the `raven_seek` query when the listed capabilities make that obvious. Do not call `raven_mcp` directly unless you are Raven.
8
10
 
@@ -26,6 +28,8 @@ Examples:
26
28
 
27
29
  `raven_seek(query="Check whether archivemount/libarchive supports ISO or UDF. Use docs, web, or command output as needed.")`
28
30
 
29
- `raven_seek(query="Use the Linear MCP to find open bugs assigned to me and summarize the top 3")`
31
+ `raven_seek(query="Use the Linear MCP to find open bugs assigned to me and summarize the top 3")`
32
+
33
+ `raven_seek(query="Compare the first two bugs in more detail", sessionId="ses_...")`
30
34
 
31
35
  Simple piped output filters like `command | grep ...`, `command | rg ...`, `command | findstr ...`, or `command | head ...` are allowed when they only filter bounded output from the immediately preceding command.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-raven",
3
- "version": "2.1.7",
3
+ "version": "2.1.8",
4
4
  "description": "Context-saving search and on-demand MCP routing for OpenCode through a focused Raven subagent.",
5
5
  "main": "./index.ts",
6
6
  "exports": {
@@ -18,6 +18,7 @@
18
18
  "LICENSE"
19
19
  ],
20
20
  "scripts": {
21
+ "test": "bun test",
21
22
  "typecheck": "tsc --noEmit"
22
23
  },
23
24
  "keywords": [