memorix 1.9.7 → 1.9.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
@@ -6,6 +6,13 @@ All notable changes to this project will be documented in this file.
6
6
 
7
7
  No unreleased changes.
8
8
 
9
+ ## [1.9.8] - 2026-10-02
10
+
11
+ ### Fixed
12
+ - **OpenCode v2 plugin compatibility** -- generated plugins retain the v1
13
+ named export while also exposing the v2 default `id`/`setup` contract,
14
+ including event subscriptions, tool hooks, cleanup, and host session IDs.
15
+
9
16
  ## [1.9.7] - 2026-09-28
10
17
 
11
18
  ### Fixed
package/dist/cli/index.js CHANGED
@@ -3232,7 +3232,7 @@ ${import_picocolors.default.gray(m3)} ${s4}
3232
3232
 
3233
3233
  // src/cli/version.ts
3234
3234
  function getCliVersion() {
3235
- return true ? "1.9.7" : pkg.version;
3235
+ return true ? "1.9.8" : pkg.version;
3236
3236
  }
3237
3237
  var init_version = __esm({
3238
3238
  "src/cli/version.ts"() {
@@ -195667,6 +195667,161 @@ export const MemorixPlugin = async ({ project, client, $, directory, worktree })
195667
195667
  },
195668
195668
  };
195669
195669
  };
195670
+
195671
+ /**
195672
+ * OpenCode v2 compatibility layer. Keep this beside the v1 export so one
195673
+ * generated file can be loaded by both host generations during migration.
195674
+ */
195675
+ const v2HookCommand = ${hookCommand};
195676
+
195677
+ function v2ReportHookFailure(eventName, detail) {
195678
+ if (process.env.MEMORIX_HOOK_DEBUG !== '1') return;
195679
+ console.error('[memorix-plugin] v2 hook delivery failed:', eventName, detail);
195680
+ }
195681
+
195682
+ function v2EventData(event) {
195683
+ return event && typeof event.data === 'object' && event.data !== null ? event.data : {};
195684
+ }
195685
+
195686
+ function v2SessionId(event) {
195687
+ const data = v2EventData(event);
195688
+ const value = data.sessionID ?? data.sessionId ?? data.session_id
195689
+ ?? event?.sessionID ?? event?.sessionId;
195690
+ return typeof value === 'string' ? value : '';
195691
+ }
195692
+
195693
+ function v2RunHook(payload, directory, sessionId) {
195694
+ const nextPayload = { agent: 'opencode', cwd: directory, ...payload };
195695
+ if (sessionId) nextPayload.session_id = sessionId;
195696
+ const eventName = nextPayload.hook_event_name || 'unknown';
195697
+ try {
195698
+ const result = spawnSync(v2HookCommand, ['hook'], {
195699
+ input: JSON.stringify(nextPayload),
195700
+ timeout: 10_000,
195701
+ encoding: 'utf-8',
195702
+ stdio: ['pipe', 'pipe', 'pipe'],
195703
+ shell: process.platform === 'win32',
195704
+ windowsHide: true,
195705
+ });
195706
+ if (result.status !== 0) {
195707
+ v2ReportHookFailure(eventName, {
195708
+ exit: result.status,
195709
+ stderr: (result.stderr || '').slice(0, 200),
195710
+ error: result.error?.message,
195711
+ });
195712
+ }
195713
+ } catch (error) {
195714
+ v2ReportHookFailure(eventName, error?.message ?? error);
195715
+ }
195716
+ }
195717
+
195718
+ function v2SessionState(states, sessionId) {
195719
+ const key = sessionId || '__unknown__';
195720
+ let state = states.get(key);
195721
+ if (!state) {
195722
+ state = { pendingText: '', lastDeliveredKey: '' };
195723
+ states.set(key, state);
195724
+ }
195725
+ return state;
195726
+ }
195727
+
195728
+ function v2FlushAssistant(state, directory, sessionId) {
195729
+ const text = state.pendingText.trim();
195730
+ if (!text || text === state.lastDeliveredKey) return;
195731
+ v2RunHook({ hook_event_name: 'message.updated', ai_response: text }, directory, sessionId);
195732
+ state.lastDeliveredKey = text;
195733
+ state.pendingText = '';
195734
+ }
195735
+
195736
+ function v2FilePath(file) {
195737
+ if (typeof file === 'string') return file;
195738
+ if (!file || typeof file !== 'object') return '';
195739
+ return file.path ?? file.file ?? file.name ?? '';
195740
+ }
195741
+
195742
+ function v2HandleEvent(event, directory, states) {
195743
+ if (!event || typeof event !== 'object') return;
195744
+ const type = typeof event.type === 'string' ? event.type : '';
195745
+ const data = v2EventData(event);
195746
+ const sessionId = v2SessionId(event);
195747
+ const state = v2SessionState(states, sessionId);
195748
+ const run = (payload) => v2RunHook(payload, directory, sessionId);
195749
+
195750
+ switch (type) {
195751
+ case 'session.created':
195752
+ run({ hook_event_name: 'session.created' });
195753
+ break;
195754
+ case 'session.text.delta':
195755
+ state.pendingText += typeof data.delta === 'string' ? data.delta : (typeof data.text === 'string' ? data.text : '');
195756
+ break;
195757
+ case 'session.message.updated':
195758
+ case 'message.updated':
195759
+ if (data.role === 'assistant' && typeof data.text === 'string') state.pendingText = data.text;
195760
+ break;
195761
+ case 'session.step.ended': {
195762
+ const files = Array.isArray(data.files) ? data.files : [];
195763
+ for (const file of files) {
195764
+ const filePath = v2FilePath(file);
195765
+ if (filePath) run({ hook_event_name: 'file.edited', file_path: filePath });
195766
+ }
195767
+ if (typeof data.text === 'string') state.pendingText += data.text;
195768
+ break;
195769
+ }
195770
+ case 'session.idle':
195771
+ v2FlushAssistant(state, directory, sessionId);
195772
+ run({ hook_event_name: 'session.idle' });
195773
+ break;
195774
+ case 'session.compacted':
195775
+ run({ hook_event_name: 'session.compacted' });
195776
+ break;
195777
+ default:
195778
+ break;
195779
+ }
195780
+ }
195781
+
195782
+ function setup(ctx) {
195783
+ const directory = ctx?.location?.directory || ctx?.location?.worktree || process.cwd();
195784
+ const controller = new AbortController();
195785
+ const states = new Map();
195786
+
195787
+ if (ctx?.tool?.hook) {
195788
+ try {
195789
+ Promise.resolve(ctx.tool.hook('execute.after', async (event) => {
195790
+ const sessionId = v2SessionId(event);
195791
+ const data = event && typeof event.input === 'object' ? event.input : {};
195792
+ const tool = event?.tool ?? event?.name ?? '';
195793
+ v2RunHook({
195794
+ hook_event_name: 'tool.execute.after',
195795
+ tool_name: tool,
195796
+ tool_input: data,
195797
+ }, directory, sessionId);
195798
+ if (tool === 'shell') {
195799
+ v2RunHook({
195800
+ hook_event_name: 'command.executed',
195801
+ command: data.command ?? data.cmd ?? '',
195802
+ }, directory, sessionId);
195803
+ }
195804
+ })).catch((error) => v2ReportHookFailure('execute.after', error?.message ?? error));
195805
+ } catch (error) {
195806
+ v2ReportHookFailure('execute.after', error?.message ?? error);
195807
+ }
195808
+ }
195809
+
195810
+ if (ctx?.event?.subscribe) {
195811
+ void (async () => {
195812
+ try {
195813
+ const stream = ctx.event.subscribe({ signal: controller.signal });
195814
+ for await (const event of stream) v2HandleEvent(event, directory, states);
195815
+ } catch (error) {
195816
+ if (!controller.signal.aborted) v2ReportHookFailure('event.subscribe', error?.message ?? error);
195817
+ }
195818
+ })();
195819
+ }
195820
+
195821
+ return () => controller.abort();
195822
+ }
195823
+
195824
+ export default { id: 'memorix', setup };
195670
195825
  `;
195671
195826
  }
195672
195827
  async function installOpenCodeSkills(projectRoot, global2 = false) {
@@ -196492,7 +196647,7 @@ var init_installers = __esm({
196492
196647
  "use strict";
196493
196648
  init_esm_shims();
196494
196649
  init_official_skills();
196495
- OPENCODE_PLUGIN_VERSION = 7;
196650
+ OPENCODE_PLUGIN_VERSION = 8;
196496
196651
  AGENT_SKILL_DIRS = {
196497
196652
  cursor: { project: path31.join(".cursor", "skills"), global: path31.join(".cursor", "skills") },
196498
196653
  windsurf: { project: path31.join(".windsurf", "skills"), global: path31.join(".windsurf", "skills") },
@@ -209019,7 +209174,7 @@ var init_tool_profile = __esm({
209019
209174
  function getMcpServerInfo() {
209020
209175
  return {
209021
209176
  name: "memorix",
209022
- version: true ? "1.9.7" : "1.0.1"
209177
+ version: true ? "1.9.8" : "1.0.1"
209023
209178
  };
209024
209179
  }
209025
209180
  function createMcpDiscoverResult(capabilities, serverInfo) {
package/dist/index.js CHANGED
@@ -160104,7 +160104,7 @@ var init_request_context = __esm({
160104
160104
  function getMcpServerInfo() {
160105
160105
  return {
160106
160106
  name: "memorix",
160107
- version: true ? "1.9.7" : "1.0.1"
160107
+ version: true ? "1.9.8" : "1.0.1"
160108
160108
  };
160109
160109
  }
160110
160110
  function createMcpDiscoverResult(capabilities, serverInfo) {
@@ -170093,6 +170093,161 @@ export const MemorixPlugin = async ({ project, client, $, directory, worktree })
170093
170093
  },
170094
170094
  };
170095
170095
  };
170096
+
170097
+ /**
170098
+ * OpenCode v2 compatibility layer. Keep this beside the v1 export so one
170099
+ * generated file can be loaded by both host generations during migration.
170100
+ */
170101
+ const v2HookCommand = ${hookCommand};
170102
+
170103
+ function v2ReportHookFailure(eventName, detail) {
170104
+ if (process.env.MEMORIX_HOOK_DEBUG !== '1') return;
170105
+ console.error('[memorix-plugin] v2 hook delivery failed:', eventName, detail);
170106
+ }
170107
+
170108
+ function v2EventData(event) {
170109
+ return event && typeof event.data === 'object' && event.data !== null ? event.data : {};
170110
+ }
170111
+
170112
+ function v2SessionId(event) {
170113
+ const data = v2EventData(event);
170114
+ const value = data.sessionID ?? data.sessionId ?? data.session_id
170115
+ ?? event?.sessionID ?? event?.sessionId;
170116
+ return typeof value === 'string' ? value : '';
170117
+ }
170118
+
170119
+ function v2RunHook(payload, directory, sessionId) {
170120
+ const nextPayload = { agent: 'opencode', cwd: directory, ...payload };
170121
+ if (sessionId) nextPayload.session_id = sessionId;
170122
+ const eventName = nextPayload.hook_event_name || 'unknown';
170123
+ try {
170124
+ const result = spawnSync(v2HookCommand, ['hook'], {
170125
+ input: JSON.stringify(nextPayload),
170126
+ timeout: 10_000,
170127
+ encoding: 'utf-8',
170128
+ stdio: ['pipe', 'pipe', 'pipe'],
170129
+ shell: process.platform === 'win32',
170130
+ windowsHide: true,
170131
+ });
170132
+ if (result.status !== 0) {
170133
+ v2ReportHookFailure(eventName, {
170134
+ exit: result.status,
170135
+ stderr: (result.stderr || '').slice(0, 200),
170136
+ error: result.error?.message,
170137
+ });
170138
+ }
170139
+ } catch (error) {
170140
+ v2ReportHookFailure(eventName, error?.message ?? error);
170141
+ }
170142
+ }
170143
+
170144
+ function v2SessionState(states, sessionId) {
170145
+ const key = sessionId || '__unknown__';
170146
+ let state = states.get(key);
170147
+ if (!state) {
170148
+ state = { pendingText: '', lastDeliveredKey: '' };
170149
+ states.set(key, state);
170150
+ }
170151
+ return state;
170152
+ }
170153
+
170154
+ function v2FlushAssistant(state, directory, sessionId) {
170155
+ const text = state.pendingText.trim();
170156
+ if (!text || text === state.lastDeliveredKey) return;
170157
+ v2RunHook({ hook_event_name: 'message.updated', ai_response: text }, directory, sessionId);
170158
+ state.lastDeliveredKey = text;
170159
+ state.pendingText = '';
170160
+ }
170161
+
170162
+ function v2FilePath(file) {
170163
+ if (typeof file === 'string') return file;
170164
+ if (!file || typeof file !== 'object') return '';
170165
+ return file.path ?? file.file ?? file.name ?? '';
170166
+ }
170167
+
170168
+ function v2HandleEvent(event, directory, states) {
170169
+ if (!event || typeof event !== 'object') return;
170170
+ const type = typeof event.type === 'string' ? event.type : '';
170171
+ const data = v2EventData(event);
170172
+ const sessionId = v2SessionId(event);
170173
+ const state = v2SessionState(states, sessionId);
170174
+ const run = (payload) => v2RunHook(payload, directory, sessionId);
170175
+
170176
+ switch (type) {
170177
+ case 'session.created':
170178
+ run({ hook_event_name: 'session.created' });
170179
+ break;
170180
+ case 'session.text.delta':
170181
+ state.pendingText += typeof data.delta === 'string' ? data.delta : (typeof data.text === 'string' ? data.text : '');
170182
+ break;
170183
+ case 'session.message.updated':
170184
+ case 'message.updated':
170185
+ if (data.role === 'assistant' && typeof data.text === 'string') state.pendingText = data.text;
170186
+ break;
170187
+ case 'session.step.ended': {
170188
+ const files = Array.isArray(data.files) ? data.files : [];
170189
+ for (const file of files) {
170190
+ const filePath = v2FilePath(file);
170191
+ if (filePath) run({ hook_event_name: 'file.edited', file_path: filePath });
170192
+ }
170193
+ if (typeof data.text === 'string') state.pendingText += data.text;
170194
+ break;
170195
+ }
170196
+ case 'session.idle':
170197
+ v2FlushAssistant(state, directory, sessionId);
170198
+ run({ hook_event_name: 'session.idle' });
170199
+ break;
170200
+ case 'session.compacted':
170201
+ run({ hook_event_name: 'session.compacted' });
170202
+ break;
170203
+ default:
170204
+ break;
170205
+ }
170206
+ }
170207
+
170208
+ function setup(ctx) {
170209
+ const directory = ctx?.location?.directory || ctx?.location?.worktree || process.cwd();
170210
+ const controller = new AbortController();
170211
+ const states = new Map();
170212
+
170213
+ if (ctx?.tool?.hook) {
170214
+ try {
170215
+ Promise.resolve(ctx.tool.hook('execute.after', async (event) => {
170216
+ const sessionId = v2SessionId(event);
170217
+ const data = event && typeof event.input === 'object' ? event.input : {};
170218
+ const tool = event?.tool ?? event?.name ?? '';
170219
+ v2RunHook({
170220
+ hook_event_name: 'tool.execute.after',
170221
+ tool_name: tool,
170222
+ tool_input: data,
170223
+ }, directory, sessionId);
170224
+ if (tool === 'shell') {
170225
+ v2RunHook({
170226
+ hook_event_name: 'command.executed',
170227
+ command: data.command ?? data.cmd ?? '',
170228
+ }, directory, sessionId);
170229
+ }
170230
+ })).catch((error) => v2ReportHookFailure('execute.after', error?.message ?? error));
170231
+ } catch (error) {
170232
+ v2ReportHookFailure('execute.after', error?.message ?? error);
170233
+ }
170234
+ }
170235
+
170236
+ if (ctx?.event?.subscribe) {
170237
+ void (async () => {
170238
+ try {
170239
+ const stream = ctx.event.subscribe({ signal: controller.signal });
170240
+ for await (const event of stream) v2HandleEvent(event, directory, states);
170241
+ } catch (error) {
170242
+ if (!controller.signal.aborted) v2ReportHookFailure('event.subscribe', error?.message ?? error);
170243
+ }
170244
+ })();
170245
+ }
170246
+
170247
+ return () => controller.abort();
170248
+ }
170249
+
170250
+ export default { id: 'memorix', setup };
170096
170251
  `;
170097
170252
  }
170098
170253
  async function installOpenCodeSkills(projectRoot, global2 = false) {
@@ -170918,7 +171073,7 @@ var init_installers = __esm({
170918
171073
  "use strict";
170919
171074
  init_esm_shims();
170920
171075
  init_official_skills();
170921
- OPENCODE_PLUGIN_VERSION = 7;
171076
+ OPENCODE_PLUGIN_VERSION = 8;
170922
171077
  AGENT_SKILL_DIRS = {
170923
171078
  cursor: { project: path34.join(".cursor", "skills"), global: path34.join(".cursor", "skills") },
170924
171079
  windsurf: { project: path34.join(".windsurf", "skills"), global: path34.join(".windsurf", "skills") },
@@ -176442,7 +176597,7 @@ var init_serve_shared = __esm({
176442
176597
 
176443
176598
  // src/cli/version.ts
176444
176599
  function getCliVersion() {
176445
- return true ? "1.9.7" : pkg.version;
176600
+ return true ? "1.9.8" : pkg.version;
176446
176601
  }
176447
176602
  var init_version3 = __esm({
176448
176603
  "src/cli/version.ts"() {
@@ -6,6 +6,13 @@ All notable changes to this project will be documented in this file.
6
6
 
7
7
  No unreleased changes.
8
8
 
9
+ ## [1.9.8] - 2026-10-02
10
+
11
+ ### Fixed
12
+ - **OpenCode v2 plugin compatibility** -- generated plugins retain the v1
13
+ named export while also exposing the v2 default `id`/`setup` contract,
14
+ including event subscriptions, tool hooks, cleanup, and host session IDs.
15
+
9
16
  ## [1.9.7] - 2026-09-28
10
17
 
11
18
  ### Fixed
package/dist/sdk.js CHANGED
@@ -166086,6 +166086,161 @@ export const MemorixPlugin = async ({ project, client, $, directory, worktree })
166086
166086
  },
166087
166087
  };
166088
166088
  };
166089
+
166090
+ /**
166091
+ * OpenCode v2 compatibility layer. Keep this beside the v1 export so one
166092
+ * generated file can be loaded by both host generations during migration.
166093
+ */
166094
+ const v2HookCommand = ${hookCommand};
166095
+
166096
+ function v2ReportHookFailure(eventName, detail) {
166097
+ if (process.env.MEMORIX_HOOK_DEBUG !== '1') return;
166098
+ console.error('[memorix-plugin] v2 hook delivery failed:', eventName, detail);
166099
+ }
166100
+
166101
+ function v2EventData(event) {
166102
+ return event && typeof event.data === 'object' && event.data !== null ? event.data : {};
166103
+ }
166104
+
166105
+ function v2SessionId(event) {
166106
+ const data = v2EventData(event);
166107
+ const value = data.sessionID ?? data.sessionId ?? data.session_id
166108
+ ?? event?.sessionID ?? event?.sessionId;
166109
+ return typeof value === 'string' ? value : '';
166110
+ }
166111
+
166112
+ function v2RunHook(payload, directory, sessionId) {
166113
+ const nextPayload = { agent: 'opencode', cwd: directory, ...payload };
166114
+ if (sessionId) nextPayload.session_id = sessionId;
166115
+ const eventName = nextPayload.hook_event_name || 'unknown';
166116
+ try {
166117
+ const result = spawnSync(v2HookCommand, ['hook'], {
166118
+ input: JSON.stringify(nextPayload),
166119
+ timeout: 10_000,
166120
+ encoding: 'utf-8',
166121
+ stdio: ['pipe', 'pipe', 'pipe'],
166122
+ shell: process.platform === 'win32',
166123
+ windowsHide: true,
166124
+ });
166125
+ if (result.status !== 0) {
166126
+ v2ReportHookFailure(eventName, {
166127
+ exit: result.status,
166128
+ stderr: (result.stderr || '').slice(0, 200),
166129
+ error: result.error?.message,
166130
+ });
166131
+ }
166132
+ } catch (error) {
166133
+ v2ReportHookFailure(eventName, error?.message ?? error);
166134
+ }
166135
+ }
166136
+
166137
+ function v2SessionState(states, sessionId) {
166138
+ const key = sessionId || '__unknown__';
166139
+ let state = states.get(key);
166140
+ if (!state) {
166141
+ state = { pendingText: '', lastDeliveredKey: '' };
166142
+ states.set(key, state);
166143
+ }
166144
+ return state;
166145
+ }
166146
+
166147
+ function v2FlushAssistant(state, directory, sessionId) {
166148
+ const text = state.pendingText.trim();
166149
+ if (!text || text === state.lastDeliveredKey) return;
166150
+ v2RunHook({ hook_event_name: 'message.updated', ai_response: text }, directory, sessionId);
166151
+ state.lastDeliveredKey = text;
166152
+ state.pendingText = '';
166153
+ }
166154
+
166155
+ function v2FilePath(file) {
166156
+ if (typeof file === 'string') return file;
166157
+ if (!file || typeof file !== 'object') return '';
166158
+ return file.path ?? file.file ?? file.name ?? '';
166159
+ }
166160
+
166161
+ function v2HandleEvent(event, directory, states) {
166162
+ if (!event || typeof event !== 'object') return;
166163
+ const type = typeof event.type === 'string' ? event.type : '';
166164
+ const data = v2EventData(event);
166165
+ const sessionId = v2SessionId(event);
166166
+ const state = v2SessionState(states, sessionId);
166167
+ const run = (payload) => v2RunHook(payload, directory, sessionId);
166168
+
166169
+ switch (type) {
166170
+ case 'session.created':
166171
+ run({ hook_event_name: 'session.created' });
166172
+ break;
166173
+ case 'session.text.delta':
166174
+ state.pendingText += typeof data.delta === 'string' ? data.delta : (typeof data.text === 'string' ? data.text : '');
166175
+ break;
166176
+ case 'session.message.updated':
166177
+ case 'message.updated':
166178
+ if (data.role === 'assistant' && typeof data.text === 'string') state.pendingText = data.text;
166179
+ break;
166180
+ case 'session.step.ended': {
166181
+ const files = Array.isArray(data.files) ? data.files : [];
166182
+ for (const file of files) {
166183
+ const filePath = v2FilePath(file);
166184
+ if (filePath) run({ hook_event_name: 'file.edited', file_path: filePath });
166185
+ }
166186
+ if (typeof data.text === 'string') state.pendingText += data.text;
166187
+ break;
166188
+ }
166189
+ case 'session.idle':
166190
+ v2FlushAssistant(state, directory, sessionId);
166191
+ run({ hook_event_name: 'session.idle' });
166192
+ break;
166193
+ case 'session.compacted':
166194
+ run({ hook_event_name: 'session.compacted' });
166195
+ break;
166196
+ default:
166197
+ break;
166198
+ }
166199
+ }
166200
+
166201
+ function setup(ctx) {
166202
+ const directory = ctx?.location?.directory || ctx?.location?.worktree || process.cwd();
166203
+ const controller = new AbortController();
166204
+ const states = new Map();
166205
+
166206
+ if (ctx?.tool?.hook) {
166207
+ try {
166208
+ Promise.resolve(ctx.tool.hook('execute.after', async (event) => {
166209
+ const sessionId = v2SessionId(event);
166210
+ const data = event && typeof event.input === 'object' ? event.input : {};
166211
+ const tool = event?.tool ?? event?.name ?? '';
166212
+ v2RunHook({
166213
+ hook_event_name: 'tool.execute.after',
166214
+ tool_name: tool,
166215
+ tool_input: data,
166216
+ }, directory, sessionId);
166217
+ if (tool === 'shell') {
166218
+ v2RunHook({
166219
+ hook_event_name: 'command.executed',
166220
+ command: data.command ?? data.cmd ?? '',
166221
+ }, directory, sessionId);
166222
+ }
166223
+ })).catch((error) => v2ReportHookFailure('execute.after', error?.message ?? error));
166224
+ } catch (error) {
166225
+ v2ReportHookFailure('execute.after', error?.message ?? error);
166226
+ }
166227
+ }
166228
+
166229
+ if (ctx?.event?.subscribe) {
166230
+ void (async () => {
166231
+ try {
166232
+ const stream = ctx.event.subscribe({ signal: controller.signal });
166233
+ for await (const event of stream) v2HandleEvent(event, directory, states);
166234
+ } catch (error) {
166235
+ if (!controller.signal.aborted) v2ReportHookFailure('event.subscribe', error?.message ?? error);
166236
+ }
166237
+ })();
166238
+ }
166239
+
166240
+ return () => controller.abort();
166241
+ }
166242
+
166243
+ export default { id: 'memorix', setup };
166089
166244
  `;
166090
166245
  }
166091
166246
  async function installOpenCodeSkills(projectRoot, global2 = false) {
@@ -166911,7 +167066,7 @@ var init_installers = __esm({
166911
167066
  "use strict";
166912
167067
  init_esm_shims();
166913
167068
  init_official_skills();
166914
- OPENCODE_PLUGIN_VERSION = 7;
167069
+ OPENCODE_PLUGIN_VERSION = 8;
166915
167070
  AGENT_SKILL_DIRS = {
166916
167071
  cursor: { project: path34.join(".cursor", "skills"), global: path34.join(".cursor", "skills") },
166917
167072
  windsurf: { project: path34.join(".windsurf", "skills"), global: path34.join(".windsurf", "skills") },
@@ -170816,7 +170971,7 @@ var MCP_SERVER_INFO_META_KEY = "io.modelcontextprotocol/serverInfo";
170816
170971
  function getMcpServerInfo() {
170817
170972
  return {
170818
170973
  name: "memorix",
170819
- version: true ? "1.9.7" : "1.0.1"
170974
+ version: true ? "1.9.8" : "1.0.1"
170820
170975
  };
170821
170976
  }
170822
170977
  function createMcpDiscoverResult(capabilities, serverInfo) {
@@ -13,7 +13,7 @@ Memorix is a TypeScript project built around:
13
13
 
14
14
  ## Current Development Baseline
15
15
 
16
- The current release line is **1.9.7** and package metadata is kept synchronized
16
+ The current release line is **1.9.8** and package metadata is kept synchronized
17
17
  with the root package and MCP Registry manifest.
18
18
 
19
19
  Contributors should assume the following areas are part of the current release line:
@@ -55,7 +55,7 @@ Generated guidance also has scope:
55
55
  | Cursor | `memorix setup --agent cursor --global` | Cursor MCP and rules config | Cursor MCP config, `.cursor/rules/memorix.mdc`, skills, and hook guidance | Reload Cursor after setup so it can pick up project config changes. |
56
56
  | Grok Build | `memorix setup --agent grok --global` | `~/.grok/hooks/memorix.json` + `~/.grok/AGENTS.md` | Lifecycle hooks on SessionStart, SessionEnd, UserPromptSubmit, PostToolUse, PostToolUseFailure, PreCompact, PostCompact, Stop, and StopFailure, plus Grok project/global guidance | Uses Grok's own JSON hook directory. MCP stays host-owned unless you pass `--mcp http`, which writes `[mcp_servers.memorix]` with `url = "http://localhost:3211/mcp"` in Grok `config.toml`. Global hooks are trusted; project hooks require Grok `/hooks-trust`. Set `GROK_HOME` to use a custom Grok home. The command is `memorix hook --agent grok`. |
57
57
  | Gemini CLI | `memorix setup --agent gemini-cli --global` | Gemini CLI extension | Extension under `~/.gemini/extensions/memorix` with MCP, `GEMINI.md` context, hooks, commands, and skills | Antigravity CLI has an official Gemini CLI migration path, but Gemini CLI remains an active standalone Google CLI target. |
58
- | OpenCode | `memorix setup --agent opencode --global` | OpenCode local plugin file | Global setup writes `~/.config/opencode/plugins/memorix.js`, `~/.config/opencode/opencode.json`, skills, and `AGENTS.md` guidance; repo-local setup writes the `.opencode/` equivalents. | OpenCode loads local plugin and skill files from its config scope. |
58
+ | OpenCode | `memorix setup --agent opencode --global` | OpenCode local plugin file | Global setup writes `~/.config/opencode/plugins/memorix.js`, `~/.config/opencode/opencode.json`, skills, and `AGENTS.md` guidance; repo-local setup writes the `.opencode/` equivalents. | The generated plugin keeps the v1 named export and also exposes the v2 default `id`/`setup` contract. v2 event subscriptions use the host session ID when forwarding hooks. |
59
59
  | OpenClaw | `memorix setup --agent openclaw --global` | OpenClaw compatible bundle | `~/.openclaw/extensions/memorix` with `.mcp.json`, skills, and OpenClaw `HOOK.md`/`handler.ts` hook pack | Setup attempts `openclaw plugins install <path> --force` and `openclaw hooks enable memorix` when OpenClaw is available. |
60
60
  | Hermes Agent | `memorix setup --agent hermes --global` | Hermes plugin | Hermes home (`%LOCALAPPDATA%\hermes` on native Windows, `~/.hermes` elsewhere, or `HERMES_HOME`), plugin enablement in `config.yaml`, and `mcp_servers.memorix` | The plugin registers hooks, a slash command, a CLI command, and skills through Hermes' plugin context. Existing YAML keys are preserved. |
61
61
  | Oh-my-Pi | `memorix setup --agent omp --global` | Oh-my-Pi `omp.extensions` package | `.omp/packages/memorix` or `~/.omp/agent/packages/memorix`, plus `.omp/mcp.json` or `~/.omp/agent/mcp.json` | Setup attempts `omp plugin link <path>`. This target uses Oh-my-Pi's package/extension manifest, not Pi's legacy package manifest. |
package/docs/README.md CHANGED
@@ -126,7 +126,7 @@ Historical/deep-reference documents may describe older designs. If they conflict
126
126
 
127
127
  ## Current Product Line
128
128
 
129
- The current product line is **1.9.7**. Multi-device store sync is opt-in and
129
+ The current product line is **1.9.8**. Multi-device store sync is opt-in and
130
130
  keeps local SQLite canonical; existing users do not upload anything until they
131
131
  configure a relay provider explicitly.
132
132
  The authoritative acceptance contract for the current implementation is
package/docs/SETUP.md CHANGED
@@ -502,7 +502,7 @@ OpenCode can also use a local Memorix plugin generated by setup:
502
502
  memorix setup --agent opencode --global
503
503
  ```
504
504
 
505
- This writes the OpenCode plugin, skills, MCP config, and `AGENTS.md` guidance in the selected scope.
505
+ This writes the OpenCode plugin, skills, MCP config, and `AGENTS.md` guidance in the selected scope. The generated plugin exports both the legacy v1 hook function and the v2 default `id`/`setup` contract, so OpenCode v1 and v2 can load the same file during the migration window.
506
506
 
507
507
  Antigravity setup installs a native plugin at `~/.gemini/config/plugins/memorix` for global setup or `.agents/plugins/memorix` for workspace setup. The plugin includes stdio MCP, official hooks, rules, and skills:
508
508
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "memorix",
3
3
  "mcpName": "io.github.AVIDS2/memorix",
4
- "version": "1.9.7",
4
+ "version": "1.9.8",
5
5
  "description": "Local-first shared memory layer for AI coding agents across MCP clients, Git history, reasoning context, and project sessions.",
6
6
  "type": "module",
7
7
  "sideEffects": false,
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "name": "memorix",
3
- "version": "1.9.7"
3
+ "version": "1.9.8"
4
4
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://anthropic.com/claude-code/plugin.schema.json",
3
3
  "name": "memorix",
4
- "version": "1.9.7",
4
+ "version": "1.9.8",
5
5
  "description": "Shared workspace memory for Claude Code and other AI coding agents.",
6
6
  "author": {
7
7
  "name": "AVIDS2",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memorix",
3
- "version": "1.9.7",
3
+ "version": "1.9.8",
4
4
  "description": "Local-first project memory for CodeBuddy Code and other coding agents.",
5
5
  "author": {
6
6
  "name": "AVIDS2",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memorix",
3
- "version": "1.9.7",
3
+ "version": "1.9.8",
4
4
  "description": "Shared workspace memory for Codex and other AI coding agents.",
5
5
  "author": {
6
6
  "name": "AVIDS2",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memorix",
3
- "version": "1.9.7",
3
+ "version": "1.9.8",
4
4
  "description": "Shared local workspace memory for GitHub Copilot CLI and other AI coding agents.",
5
5
  "author": {
6
6
  "name": "AVIDS2",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memorix",
3
- "version": "1.9.7",
3
+ "version": "1.9.8",
4
4
  "mcpServers": {
5
5
  "memorix": {
6
6
  "command": "memorix",
@@ -1,5 +1,5 @@
1
1
  name: memorix
2
- version: "1.9.7"
2
+ version: "1.9.8"
3
3
  description: Shared workspace memory bridge for Hermes Agent.
4
4
  author: AVIDS2
5
5
  license: Apache-2.0
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memorix-omp-package",
3
- "version": "1.9.7",
3
+ "version": "1.9.8",
4
4
  "description": "Memorix shared workspace memory package for Oh-my-Pi.",
5
5
  "license": "Apache-2.0",
6
6
  "keywords": [
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memorix",
3
- "version": "1.9.7",
3
+ "version": "1.9.8",
4
4
  "description": "Shared workspace memory for OpenClaw and other AI coding agents.",
5
5
  "author": {
6
6
  "name": "AVIDS2",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memorix-pi-package",
3
- "version": "1.9.7",
3
+ "version": "1.9.8",
4
4
  "description": "Memorix shared workspace memory package for Pi coding agent.",
5
5
  "license": "Apache-2.0",
6
6
  "keywords": [
package/server.json CHANGED
@@ -7,7 +7,7 @@
7
7
  "url": "https://github.com/AVIDS2/memorix",
8
8
  "source": "github"
9
9
  },
10
- "version": "1.9.7",
10
+ "version": "1.9.8",
11
11
  "websiteUrl": "https://mem.rglens.com",
12
12
  "icons": [
13
13
  {
@@ -22,7 +22,7 @@
22
22
  {
23
23
  "registryType": "npm",
24
24
  "identifier": "memorix",
25
- "version": "1.9.7",
25
+ "version": "1.9.8",
26
26
  "runtimeHint": "npx",
27
27
  "packageArguments": [
28
28
  {
@@ -449,8 +449,10 @@ function generateKiroHookFiles(): Array<{ filename: string; content: string }> {
449
449
  * Format: .opencode/plugins/memorix.js — Bun-compatible JS module
450
450
  * See: https://opencode.ai/docs/plugins/
451
451
  *
452
- * Plugin contract (verified against official docs Apr 2026):
453
- * - Named export: export const MemorixPlugin = async (ctx) => { return { ... } }
452
+ * Plugin contracts:
453
+ * - OpenCode v1: named export `MemorixPlugin` returns an event-hook map.
454
+ * - OpenCode v2: default export contains `id` and `setup(ctx)`, with event
455
+ * subscriptions and tool hooks registered through the context.
454
456
  * - Return object keys are EVENT NAMES (e.g. "session.created", "file.edited")
455
457
  * - Each key maps to an async handler: (input, output) => { ... }
456
458
  * - Session/file/command events: handler receives ({ event }) for event-style hooks
@@ -464,7 +466,7 @@ function generateKiroHookFiles(): Array<{ filename: string; content: string }> {
464
466
  * protocol used by all agents. spawnSync works in both Node.js and Bun
465
467
  * runtimes (OpenCode may fall back to Node.js on Windows).
466
468
  */
467
- const OPENCODE_PLUGIN_VERSION = 7;
469
+ const OPENCODE_PLUGIN_VERSION = 8;
468
470
 
469
471
  const AGENT_SKILL_DIRS: Partial<Record<AgentName, { project: string; global?: string }>> = {
470
472
  cursor: { project: path.join('.cursor', 'skills'), global: path.join('.cursor', 'skills') },
@@ -716,6 +718,161 @@ export const MemorixPlugin = async ({ project, client, $, directory, worktree })
716
718
  },
717
719
  };
718
720
  };
721
+
722
+ /**
723
+ * OpenCode v2 compatibility layer. Keep this beside the v1 export so one
724
+ * generated file can be loaded by both host generations during migration.
725
+ */
726
+ const v2HookCommand = ${hookCommand};
727
+
728
+ function v2ReportHookFailure(eventName, detail) {
729
+ if (process.env.MEMORIX_HOOK_DEBUG !== '1') return;
730
+ console.error('[memorix-plugin] v2 hook delivery failed:', eventName, detail);
731
+ }
732
+
733
+ function v2EventData(event) {
734
+ return event && typeof event.data === 'object' && event.data !== null ? event.data : {};
735
+ }
736
+
737
+ function v2SessionId(event) {
738
+ const data = v2EventData(event);
739
+ const value = data.sessionID ?? data.sessionId ?? data.session_id
740
+ ?? event?.sessionID ?? event?.sessionId;
741
+ return typeof value === 'string' ? value : '';
742
+ }
743
+
744
+ function v2RunHook(payload, directory, sessionId) {
745
+ const nextPayload = { agent: 'opencode', cwd: directory, ...payload };
746
+ if (sessionId) nextPayload.session_id = sessionId;
747
+ const eventName = nextPayload.hook_event_name || 'unknown';
748
+ try {
749
+ const result = spawnSync(v2HookCommand, ['hook'], {
750
+ input: JSON.stringify(nextPayload),
751
+ timeout: 10_000,
752
+ encoding: 'utf-8',
753
+ stdio: ['pipe', 'pipe', 'pipe'],
754
+ shell: process.platform === 'win32',
755
+ windowsHide: true,
756
+ });
757
+ if (result.status !== 0) {
758
+ v2ReportHookFailure(eventName, {
759
+ exit: result.status,
760
+ stderr: (result.stderr || '').slice(0, 200),
761
+ error: result.error?.message,
762
+ });
763
+ }
764
+ } catch (error) {
765
+ v2ReportHookFailure(eventName, error?.message ?? error);
766
+ }
767
+ }
768
+
769
+ function v2SessionState(states, sessionId) {
770
+ const key = sessionId || '__unknown__';
771
+ let state = states.get(key);
772
+ if (!state) {
773
+ state = { pendingText: '', lastDeliveredKey: '' };
774
+ states.set(key, state);
775
+ }
776
+ return state;
777
+ }
778
+
779
+ function v2FlushAssistant(state, directory, sessionId) {
780
+ const text = state.pendingText.trim();
781
+ if (!text || text === state.lastDeliveredKey) return;
782
+ v2RunHook({ hook_event_name: 'message.updated', ai_response: text }, directory, sessionId);
783
+ state.lastDeliveredKey = text;
784
+ state.pendingText = '';
785
+ }
786
+
787
+ function v2FilePath(file) {
788
+ if (typeof file === 'string') return file;
789
+ if (!file || typeof file !== 'object') return '';
790
+ return file.path ?? file.file ?? file.name ?? '';
791
+ }
792
+
793
+ function v2HandleEvent(event, directory, states) {
794
+ if (!event || typeof event !== 'object') return;
795
+ const type = typeof event.type === 'string' ? event.type : '';
796
+ const data = v2EventData(event);
797
+ const sessionId = v2SessionId(event);
798
+ const state = v2SessionState(states, sessionId);
799
+ const run = (payload) => v2RunHook(payload, directory, sessionId);
800
+
801
+ switch (type) {
802
+ case 'session.created':
803
+ run({ hook_event_name: 'session.created' });
804
+ break;
805
+ case 'session.text.delta':
806
+ state.pendingText += typeof data.delta === 'string' ? data.delta : (typeof data.text === 'string' ? data.text : '');
807
+ break;
808
+ case 'session.message.updated':
809
+ case 'message.updated':
810
+ if (data.role === 'assistant' && typeof data.text === 'string') state.pendingText = data.text;
811
+ break;
812
+ case 'session.step.ended': {
813
+ const files = Array.isArray(data.files) ? data.files : [];
814
+ for (const file of files) {
815
+ const filePath = v2FilePath(file);
816
+ if (filePath) run({ hook_event_name: 'file.edited', file_path: filePath });
817
+ }
818
+ if (typeof data.text === 'string') state.pendingText += data.text;
819
+ break;
820
+ }
821
+ case 'session.idle':
822
+ v2FlushAssistant(state, directory, sessionId);
823
+ run({ hook_event_name: 'session.idle' });
824
+ break;
825
+ case 'session.compacted':
826
+ run({ hook_event_name: 'session.compacted' });
827
+ break;
828
+ default:
829
+ break;
830
+ }
831
+ }
832
+
833
+ function setup(ctx) {
834
+ const directory = ctx?.location?.directory || ctx?.location?.worktree || process.cwd();
835
+ const controller = new AbortController();
836
+ const states = new Map();
837
+
838
+ if (ctx?.tool?.hook) {
839
+ try {
840
+ Promise.resolve(ctx.tool.hook('execute.after', async (event) => {
841
+ const sessionId = v2SessionId(event);
842
+ const data = event && typeof event.input === 'object' ? event.input : {};
843
+ const tool = event?.tool ?? event?.name ?? '';
844
+ v2RunHook({
845
+ hook_event_name: 'tool.execute.after',
846
+ tool_name: tool,
847
+ tool_input: data,
848
+ }, directory, sessionId);
849
+ if (tool === 'shell') {
850
+ v2RunHook({
851
+ hook_event_name: 'command.executed',
852
+ command: data.command ?? data.cmd ?? '',
853
+ }, directory, sessionId);
854
+ }
855
+ })).catch((error) => v2ReportHookFailure('execute.after', error?.message ?? error));
856
+ } catch (error) {
857
+ v2ReportHookFailure('execute.after', error?.message ?? error);
858
+ }
859
+ }
860
+
861
+ if (ctx?.event?.subscribe) {
862
+ void (async () => {
863
+ try {
864
+ const stream = ctx.event.subscribe({ signal: controller.signal });
865
+ for await (const event of stream) v2HandleEvent(event, directory, states);
866
+ } catch (error) {
867
+ if (!controller.signal.aborted) v2ReportHookFailure('event.subscribe', error?.message ?? error);
868
+ }
869
+ })();
870
+ }
871
+
872
+ return () => controller.abort();
873
+ }
874
+
875
+ export default { id: 'memorix', setup };
719
876
  `;
720
877
  }
721
878