opencode-raven 2.1.6 → 2.1.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/CHANGELOG.md +6 -0
  2. package/index.ts +82 -71
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.1.7
4
+
5
+ ### Fixed
6
+ - Stats and `/raven` setting updates now patch the latest on-disk configuration instead of rewriting stale in-memory state, preserving externally edited MCP servers and headers.
7
+ - Existing malformed or partially written configuration files are preserved for repair instead of being replaced with defaults.
8
+
3
9
  ## 2.1.6
4
10
 
5
11
  ### Added
package/index.ts CHANGED
@@ -469,28 +469,27 @@ export default (async (input: PluginInput) => {
469
469
  }
470
470
 
471
471
  function loadConfig(): RavenConfig {
472
- try {
473
- if (existsSync(configFile)) {
472
+ if (existsSync(configFile)) {
473
+ try {
474
474
  const raw = JSON.parse(readFileSync(configFile, "utf-8"))
475
- const normalized = normalizeConfig(raw)
476
- if (JSON.stringify(raw) !== JSON.stringify(normalized)) {
477
- saveConfig(normalized)
478
- }
479
- return normalized
475
+ return normalizeConfig(raw)
476
+ } catch {
477
+ // Preserve malformed or partially-written config so the user can repair it.
478
+ return { ...DEFAULT_CONFIG }
480
479
  }
481
- } catch { /* ignore corruption, use defaults */ }
482
- // Auto-create config file with defaults on first run
483
- saveConfig(DEFAULT_CONFIG)
484
- return { ...DEFAULT_CONFIG }
485
- }
486
-
487
- function saveConfig(config: RavenConfig) {
488
- try {
489
- mkdirSync(ravenConfigDir, { recursive: true })
490
- writeFileSync(configFile, JSON.stringify(config, null, 2) + "\n")
491
- } catch { /* non-fatal: config won't persist but toggle still works in-session */ }
492
- }
493
-
480
+ }
481
+ // Auto-create config file with defaults on first run
482
+ writeConfig(DEFAULT_CONFIG)
483
+ return { ...DEFAULT_CONFIG }
484
+ }
485
+
486
+ function writeConfig(config: RavenConfig) {
487
+ try {
488
+ mkdirSync(ravenConfigDir, { recursive: true })
489
+ writeFileSync(configFile, JSON.stringify(config, null, 2) + "\n")
490
+ } catch { /* non-fatal: config won't persist but toggle still works in-session */ }
491
+ }
492
+
494
493
  function loadMcpMetadata(): Record<string, OnDemandMcpMetadata> {
495
494
  const defaults = cloneOnDemandMcpMetadata(DEFAULT_ON_DEMAND_MCP_METADATA)
496
495
  try {
@@ -515,8 +514,23 @@ export default (async (input: PluginInput) => {
515
514
  writeFileSync(dynamicGuidanceFile, `## Raven on-demand MCP capabilities\n\n${onDemandMcpGuidance("delegate", dynamicGuidanceMcpOrder())}\n`, "utf-8")
516
515
  } catch { /* non-fatal: guidance still exists in tool/subagent paths */ }
517
516
  }
518
-
517
+
519
518
  let config = loadConfig()
519
+
520
+ function updateConfig(update: Partial<RavenConfig> | ((latest: RavenConfig) => Partial<RavenConfig>)): RavenConfig {
521
+ try {
522
+ const raw = existsSync(configFile) ? JSON.parse(readFileSync(configFile, "utf-8")) : config
523
+ const latest = normalizeConfig(raw)
524
+ const patch = typeof update === "function" ? update(latest) : update
525
+ config = normalizeConfig({ ...latest, ...patch })
526
+ writeConfig({ ...raw, ...patch })
527
+ } catch {
528
+ // Keep malformed or partially-written external edits intact instead of replacing them.
529
+ const patch = typeof update === "function" ? update(config) : update
530
+ config = normalizeConfig({ ...config, ...patch })
531
+ }
532
+ return config
533
+ }
520
534
  let mcpMetadata = loadMcpMetadata()
521
535
  const ravenSessions = new Set<string>()
522
536
  const ravenTaskCalls = new Set<string>()
@@ -1274,16 +1288,17 @@ Then restart opencode for the model change to take effect. If the task can be co
1274
1288
  return parsed
1275
1289
  }
1276
1290
 
1277
- // ── Context saved by Raven delegation ──
1278
- let sessionBytes = 0
1279
- let totalBytes = config.stats?.bytes ?? 0
1280
-
1281
- function addBytes(bytes: number) {
1282
- sessionBytes += bytes
1283
- totalBytes += bytes
1284
- config.stats = { bytes: totalBytes }
1285
- saveConfig(config)
1286
- }
1291
+ // ── Context saved by Raven delegation ──
1292
+ let sessionBytes = 0
1293
+ let totalBytes = config.stats?.bytes ?? 0
1294
+
1295
+ function addBytes(bytes: number) {
1296
+ sessionBytes += bytes
1297
+ const updated = updateConfig((latest) => ({
1298
+ stats: { bytes: Math.max(totalBytes, latest.stats?.bytes ?? 0) + bytes },
1299
+ }))
1300
+ totalBytes = updated.stats?.bytes ?? totalBytes + bytes
1301
+ }
1287
1302
 
1288
1303
  function formatBytes(bytes: number): string {
1289
1304
  return bytes >= 1_000_000 ? `${(bytes / 1_000_000).toFixed(1)}MB`
@@ -1814,16 +1829,14 @@ Then restart opencode for the model change to take effect. If the task can be co
1814
1829
  const raw = input.arguments.trim()
1815
1830
  const arg = raw.toLowerCase()
1816
1831
 
1817
- if (arg === "on") {
1818
- config.enabled = true
1819
- saveConfig(config)
1820
- output.parts.push({ type: "text", text: "Raven tool/MCP routing enabled. Non-Raven agents will be redirected to raven_seek for configured tools." })
1832
+ if (arg === "on") {
1833
+ updateConfig({ enabled: true })
1834
+ output.parts.push({ type: "text", text: "Raven tool/MCP routing enabled. Non-Raven agents will be redirected to raven_seek for configured tools." })
1821
1835
  } else if (arg === "help") {
1822
1836
  output.parts.push({ type: "text", text: helpText() })
1823
- } else if (arg === "off") {
1824
- config.enabled = false
1825
- saveConfig(config)
1826
- output.parts.push({ type: "text", text: "Raven tool/MCP routing disabled. All agents can use tools directly." })
1837
+ } else if (arg === "off") {
1838
+ updateConfig({ enabled: false })
1839
+ output.parts.push({ type: "text", text: "Raven tool/MCP routing disabled. All agents can use tools directly." })
1827
1840
  } else if (arg === "stats") {
1828
1841
  output.parts.push({ type: "text", text: statsText() })
1829
1842
  } else if (arg === "route") {
@@ -1835,16 +1848,18 @@ Then restart opencode for the model change to take effect. If the task can be co
1835
1848
  const name = parts.slice(3).join(" ").trim()
1836
1849
  const key = kind === "tool" ? "routeTools" : kind === "keyword" ? "routeToolKeywords" : undefined
1837
1850
 
1838
- if (!key || !["add", "remove", "rm"].includes(action) || !name) {
1839
- output.parts.push({ type: "text", text: "Usage:\n /raven route tool add <tool_name>\n /raven route tool remove <tool_name>\n /raven route keyword add <keyword>\n /raven route keyword remove <keyword>\n\nGlobal MCPs are auto-routed by detected OpenCode MCP server name." })
1840
- } else {
1841
- const values = uniqueStrings(config[key])
1842
- const exists = values.some((value) => value.toLowerCase() === name.toLowerCase())
1843
- config[key] = action === "add"
1844
- ? exists ? values : [...values, name]
1845
- : values.filter((value) => value.toLowerCase() !== name.toLowerCase())
1846
- saveConfig(config)
1847
- output.parts.push({ type: "text", text: routeSummary() })
1851
+ if (!key || !["add", "remove", "rm"].includes(action) || !name) {
1852
+ output.parts.push({ type: "text", text: "Usage:\n /raven route tool add <tool_name>\n /raven route tool remove <tool_name>\n /raven route keyword add <keyword>\n /raven route keyword remove <keyword>\n\nGlobal MCPs are auto-routed by detected OpenCode MCP server name." })
1853
+ } else {
1854
+ updateConfig((latest) => {
1855
+ const values = uniqueStrings(latest[key])
1856
+ const exists = values.some((value) => value.toLowerCase() === name.toLowerCase())
1857
+ const next = action === "add"
1858
+ ? exists ? values : [...values, name]
1859
+ : values.filter((value) => value.toLowerCase() !== name.toLowerCase())
1860
+ return key === "routeTools" ? { routeTools: next } : { routeToolKeywords: next }
1861
+ })
1862
+ output.parts.push({ type: "text", text: routeSummary() })
1848
1863
  }
1849
1864
  } else if (arg === "mcp") {
1850
1865
  output.parts.push({ type: "text", text: `${onDemandMcpStatus()}\n\nUsage:\n /raven mcp refresh [server_name]\n /raven mcp detail full\n /raven mcp detail minimized` })
@@ -1861,12 +1876,11 @@ Then restart opencode for the model change to take effect. If the task can be co
1861
1876
  })
1862
1877
  } else if (action === "detail") {
1863
1878
  const detail = parts[2]?.toLowerCase()
1864
- if (detail !== "full" && detail !== "minimized") {
1865
- output.parts.push({ type: "text", text: `Usage: /raven mcp detail full|minimized\nCurrent: ${config.onDemandMcpDescriptionDetail ?? "full"}` })
1866
- } else {
1867
- config.onDemandMcpDescriptionDetail = detail
1868
- saveConfig(config)
1869
- output.parts.push({ type: "text", text: `On-demand MCP guidance detail set to: ${detail}\nRestart opencode for startup instructions/tool descriptions to fully refresh.` })
1879
+ if (detail !== "full" && detail !== "minimized") {
1880
+ output.parts.push({ type: "text", text: `Usage: /raven mcp detail full|minimized\nCurrent: ${config.onDemandMcpDescriptionDetail ?? "full"}` })
1881
+ } else {
1882
+ updateConfig({ onDemandMcpDescriptionDetail: detail })
1883
+ output.parts.push({ type: "text", text: `On-demand MCP guidance detail set to: ${detail}\nRestart opencode for startup instructions/tool descriptions to fully refresh.` })
1870
1884
  }
1871
1885
  } else {
1872
1886
  output.parts.push({ type: "text", text: "Usage:\n /raven mcp\n /raven mcp refresh [server_name]\n /raven mcp detail full\n /raven mcp detail minimized" })
@@ -1887,31 +1901,28 @@ Then restart opencode for the model change to take effect. If the task can be co
1887
1901
  }
1888
1902
  } else if (arg.startsWith("model ")) {
1889
1903
  const model = raw.slice(6).trim()
1890
- if (!model) {
1891
- output.parts.push({ type: "text", text: `Usage: /raven model <name>\nCurrent model: ${config.model || fm.model || "(default)"}` })
1892
- } else {
1893
- config.model = model
1894
- saveConfig(config)
1895
- ravenProviderIssue = undefined
1904
+ if (!model) {
1905
+ output.parts.push({ type: "text", text: `Usage: /raven model <name>\nCurrent model: ${config.model || fm.model || "(default)"}` })
1906
+ } else {
1907
+ updateConfig({ model })
1908
+ ravenProviderIssue = undefined
1896
1909
  output.parts.push({ type: "text", text: `Raven model set to: ${model}\nRestart opencode for the change to take effect.` })
1897
1910
  }
1898
1911
  } else if (arg.startsWith("effort ")) {
1899
1912
  const effort = raw.slice(7).trim()
1900
- if (!effort) {
1901
- output.parts.push({ type: "text", text: `Usage: /raven effort <value>\nCurrent: ${config.reasoning_effort || fm.reasoning_effort || "(default)"}` })
1902
- } else {
1903
- config.reasoning_effort = effort
1904
- saveConfig(config)
1905
- output.parts.push({ type: "text", text: `Raven reasoning effort set to: ${effort}\nRestart opencode for the change to take effect.` })
1913
+ if (!effort) {
1914
+ output.parts.push({ type: "text", text: `Usage: /raven effort <value>\nCurrent: ${config.reasoning_effort || fm.reasoning_effort || "(default)"}` })
1915
+ } else {
1916
+ updateConfig({ reasoning_effort: effort })
1917
+ output.parts.push({ type: "text", text: `Raven reasoning effort set to: ${effort}\nRestart opencode for the change to take effect.` })
1906
1918
  }
1907
1919
  } else if (arg.startsWith("timeout ")) {
1908
1920
  const secs = Number(raw.slice(8).trim())
1909
1921
  if (!Number.isInteger(secs) || secs < 10 || secs > MAX_TIMEOUT_SECONDS) {
1910
1922
  output.parts.push({ type: "text", text: `Usage: /raven timeout <seconds>\nMust be between 10 and ${MAX_TIMEOUT_SECONDS}. Current: ${config.timeout ?? 600}s` })
1911
- } else {
1912
- config.timeout = secs
1913
- saveConfig(config)
1914
- output.parts.push({ type: "text", text: `Raven timeout set to ${secs}s. Takes effect immediately.` })
1923
+ } else {
1924
+ updateConfig({ timeout: secs })
1925
+ output.parts.push({ type: "text", text: `Raven timeout set to ${secs}s. Takes effect immediately.` })
1915
1926
  }
1916
1927
  } else {
1917
1928
  const enabled = config.enabled ? "enabled" : "disabled"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-raven",
3
- "version": "2.1.6",
3
+ "version": "2.1.7",
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": {