-
Notifications
You must be signed in to change notification settings - Fork 823
fix(opencode): comprehensive compatibility fix for opencode 1.18.x #604
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
d97783b
157682a
352d6f7
d4cca14
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -33,6 +33,62 @@ const path = require('node:path'); | |
|
|
||
| const HOOKS_DIR = ${JSON.stringify(hooksDirAbsolute)}; | ||
|
|
||
| // Require shared modules from hooks directory (side-effect-free, deployed by setup-hooks) | ||
| var runtimePaths = require(path.join(HOOKS_DIR, '_runtimePaths')); | ||
| var memoryFiltering = require(path.join(HOOKS_DIR, '_memoryFiltering')); | ||
|
|
||
| // DIAG: temporary diagnostic markers (remove after maintainer verification) | ||
| var evolverMarkPath = require('path').join(require('path').dirname(hooksDirAbsolute), '.evolver-diag.log'); | ||
| function evolverMark(id, data) { | ||
| try { require('fs').appendFileSync(evolverMarkPath, new Date().toISOString() + ' [' + id + '] ' + (data||'') + '\\n'); } catch(e) {} | ||
| } | ||
| evolverMark('A-module-loaded', 'Bun=' + (typeof Bun !== 'undefined')); | ||
|
|
||
| // Read recent entries from jsonl tail (avoid full-file parse for large graphs) | ||
| function readRecentEntries(graphPath, maxEntries) { | ||
| try { | ||
| var stat = require('fs').statSync(graphPath); | ||
| var tailBytes = Math.min(stat.size, 64 * 1024); | ||
| var fd = require('fs').openSync(graphPath, 'r'); | ||
| var buf = Buffer.alloc(tailBytes); | ||
| require('fs').readSync(fd, buf, 0, tailBytes, Math.max(0, stat.size - tailBytes)); | ||
| require('fs').closeSync(fd); | ||
| return buf.toString('utf8').trim().split('\\n').slice(-maxEntries).map(function(l) { | ||
| try { return JSON.parse(l); } catch(e) { return null; } | ||
| }).filter(Boolean); | ||
| } catch(e) { return []; } | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. File descriptor leak on read errorsMedium Severity
Reviewed by Cursor Bugbot for commit d4cca14. Configure here. |
||
| } | ||
|
|
||
| // Format outcome with correct [+]/[-] prefix and score handling | ||
| function formatOutcome(e) { | ||
| var ok = e.outcome && e.outcome.status === 'success'; | ||
| var score = (e.outcome && e.outcome.score !== undefined && e.outcome.score !== null) | ||
| ? e.outcome.score | ||
| : (e.score !== undefined && e.score !== null ? e.score : 'N/A'); | ||
| var signals = e.signals || (e.outcome && e.outcome.signals) || []; | ||
| var note = e.outcome && e.outcome.note ? ' ' + e.outcome.note : ''; | ||
| return (ok ? '[+]' : '[-]') + ' ' + (e.timestamp || 'unknown') + ' score=' + score + ' signals=' + JSON.stringify(signals) + note; | ||
| } | ||
|
|
||
| // Inline memory reader using shared modules for workspace-aware filtering | ||
| function readEvolverMemoryInline() { | ||
| try { | ||
| var evolverRoot = runtimePaths.findEvolverRoot(); | ||
| if (!evolverRoot) return null; | ||
| var graphPath = runtimePaths.findMemoryGraph(evolverRoot); | ||
| if (!graphPath || !require('fs').existsSync(graphPath)) return null; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Null root skips memory fallbackHigh Severity The Reviewed by Cursor Bugbot for commit d4cca14. Configure here. |
||
| var currentDir = runtimePaths.resolveProjectDir(); | ||
| var entries = readRecentEntries(graphPath, 20); | ||
| var filtered = memoryFiltering.filterRelevantOutcomes(entries); | ||
| if (!filtered || filtered.length === 0) return null; | ||
| var sc = filtered.filter(function(e) { return e.outcome && e.outcome.status === 'success'; }).length; | ||
| var fc = filtered.filter(function(e) { return !(e.outcome && e.outcome.status === 'success'); }).length; | ||
| return '[Evolution Memory] Recent ' + filtered.length + ' outcomes (' + sc + ' success, ' + fc + ' failed):\\n' + | ||
| filtered.map(formatOutcome).join('\\n') + | ||
| '\\n\\nUse successful approaches. Avoid repeating failed patterns.'; | ||
| } catch(e) { return null; } | ||
| } | ||
|
cursor[bot] marked this conversation as resolved.
|
||
|
|
||
| function runHook(scriptName, payload, timeoutMs) { | ||
| try { | ||
| const result = spawnSync('node', [path.join(HOOKS_DIR, scriptName)], { | ||
|
|
@@ -47,33 +103,57 @@ function runHook(scriptName, payload, timeoutMs) { | |
| } | ||
| } | ||
|
|
||
| var memoryInjected = false; | ||
|
|
||
| const Evolver = async () => ({ | ||
| // Memory injection via chat.message (can mutate output.parts for the LLM) | ||
| "chat.message": async (input, output) => { | ||
| if (memoryInjected) return; | ||
| evolverMark('C-chat-message', 'parts=' + (output && output.parts ? output.parts.length : 'N/A')); | ||
| // 1) Inline read (0ms, workspace-aware via shared modules) | ||
| var text = readEvolverMemoryInline(); | ||
| evolverMark('D-inline', 'text=' + (text ? text.length : 0)); | ||
| // 2) Fallback: full spawnSync (for platforms where it works) | ||
| if (!text) { | ||
| var result = runHook('evolver-session-start.js', { | ||
| session_id: input && input.sessionID, | ||
| }, 10000); | ||
| text = result && (result.agent_message || result.additionalContext); | ||
| evolverMark('D1-spawnSync', 'text=' + (text ? text.length : 0)); | ||
| } else { | ||
| // 3) Best-effort spawnSync for side effects (recordMarkedSession, daemon restart) | ||
| try { runHook('evolver-session-start.js', { session_id: input && input.sessionID }, 2000); } catch(e) {} | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Side-effect spawn blocks first messageMedium Severity The Reviewed by Cursor Bugbot for commit d4cca14. Configure here. |
||
| } | ||
| evolverMark('E-inject', 'text=' + (text ? text.length : 0)); | ||
| if (text && output && Array.isArray(output.parts)) { | ||
| output.parts.unshift({ type: "text", text: text }); | ||
| memoryInjected = true; | ||
| evolverMark('E1-injected', 'parts=' + output.parts.length); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Deferred flag retries spawn every messageHigh Severity The Reviewed by Cursor Bugbot for commit d4cca14. Configure here. |
||
| } | ||
| }, | ||
| // session.idle -> record outcome | ||
| event: async ({ event }) => { | ||
| if (!event || typeof event.type !== 'string') return; | ||
| if (event.type === 'session.created') { | ||
| runHook('evolver-session-start.js', { | ||
| session_id: event.properties && event.properties.info && event.properties.info.id, | ||
| }, 3000); | ||
| return; | ||
| } | ||
| if (event.type === 'session.idle') { | ||
| runHook('evolver-session-end.js', { | ||
| session_id: event.properties && event.properties.sessionID, | ||
| }, 8000); | ||
| }, 10000); | ||
| } | ||
| }, | ||
| 'tool.execute.after': async (input, output) => { | ||
| if (!input || typeof input.tool !== 'string') return; | ||
| if (input.tool !== 'write' && input.tool !== 'edit') return; | ||
| evolverMark('G-tool-after', 'tool=' + input.tool); | ||
| runHook('evolver-signal-detect.js', { | ||
| tool_input: (output && output.args) || {}, | ||
| tool_response: (output && output.output) || (output && output.result) || {}, | ||
| }, 2000); | ||
| }, 5000); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Verify omits required helper scriptsMedium Severity The generated plugin now hard-requires Additional Locations (1)Reviewed by Cursor Bugbot for commit d4cca14. Configure here. |
||
| }, | ||
| }); | ||
|
|
||
| module.exports = { Evolver }; | ||
| module.exports.default = Evolver; | ||
| const evolverPluginModule = { id: "evolver", server: Evolver }; | ||
| module.exports = evolverPluginModule; | ||
| module.exports.default = evolverPluginModule; | ||
|
cursor[bot] marked this conversation as resolved.
|
||
| `; | ||
| } | ||
|
|
||
|
|
@@ -220,7 +300,7 @@ function verify({ configRoot }) { | |
| // failure here is a guaranteed failure under opencode too. | ||
| delete require.cache[require.resolve(pluginPath)]; | ||
| const mod = require(pluginPath); | ||
| const fn = mod && (mod.Evolver || mod.default); | ||
| const fn = mod && (mod.server || mod.Evolver || mod.default); | ||
| pluginLoadable = typeof fn === 'function'; | ||
| if (!pluginLoadable) pluginLoadError = 'no Evolver/default function export'; | ||
| } catch (err) { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -113,7 +113,7 @@ describe('opencode adapter: buildPluginSource', () => { | |
| it('exports both named Evolver and default for opencode loader compat', () => { | ||
| const src = opencodeAdapter.buildPluginSource('/x'); | ||
| assert.match(src, /module\.exports\s*=\s*\{\s*Evolver\s*\}/); | ||
| assert.match(src, /module\.exports\.default\s*=\s*Evolver/); | ||
| assert.match(src, /module\.exports\.default\s*=\s*evolverPluginModule/); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Stale session.created test still passesMedium Severity This commit removes the Additional Locations (1)Reviewed by Cursor Bugbot for commit d4cca14. Configure here. |
||
| }); | ||
|
|
||
| it('produces source that parses as valid JavaScript', () => { | ||
|
|
@@ -329,7 +329,7 @@ describe('opencode adapter: verify (issue #531)', () => { | |
| fs.mkdirSync(pluginsDir, { recursive: true }); | ||
| // user-authored plugin: parses, exports a function, but no managed marker | ||
| fs.writeFileSync(path.join(pluginsDir, 'evolver.js'), | ||
| 'const Evolver = async () => ({});\nmodule.exports = { Evolver };\nmodule.exports.default = Evolver;\n', | ||
| 'const Evolver = async () => ({});\nconst evolverPluginModule = { id: \"evolver\", server: Evolver };\nmodule.exports = evolverPluginModule;\nmodule.exports.default = evolverPluginModule;\n', | ||
| 'utf8', | ||
| ); | ||
|
|
||
|
|
||


There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Plugin crashes on undefined identifier
High Severity
The generated plugin references
hooksDirAbsolutein its diagnostic logging path, but this variable is not defined in the emitted file. This causes aReferenceErrorduring module evaluation, preventing the plugin from loading correctly.Reviewed by Cursor Bugbot for commit d4cca14. Configure here.