opencode-lcm 0.13.5 → 0.13.6

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
@@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.13.6] - 2026-04-11
11
+
12
+ ### Fixed
13
+ - Bun on Windows now leaves the plugin in a pre-SQLite safe mode by default and requires an explicit override to enable the full archive hooks
14
+
10
15
  ## [0.13.5] - 2026-04-11
11
16
 
12
17
  ### Fixed
package/README.md CHANGED
@@ -105,6 +105,9 @@ Add `opencode-lcm` to your `opencode.json` (project or global `~/.config/opencod
105
105
  > [!IMPORTANT]
106
106
  > All defaults are applied automatically. Expand below only if you need to override settings.
107
107
 
108
+ > [!IMPORTANT]
109
+ > On Bun for Windows, `opencode-lcm` now starts in a pre-SQLite safe mode and only exposes `lcm_status` by default. This avoids a Bun/Windows native-crash path seen in the field. To force-enable the full plugin anyway, set `"runtimeSafety": { "allowUnsafeBunWindows": true }` in the plugin config or export `OPENCODE_LCM_ALLOW_UNSAFE_BUN_WINDOWS=1` before starting OpenCode.
110
+
108
111
  <details>
109
112
  <summary><strong>Full Configuration</strong> (click to expand)</summary>
110
113
 
package/dist/index.js CHANGED
@@ -1,8 +1,59 @@
1
1
  import { tool } from '@opencode-ai/plugin';
2
2
  import { resolveOptions } from './options.js';
3
3
  import { SqliteLcmStore } from './store.js';
4
+ const ALLOW_UNSAFE_BUN_WINDOWS_ENV = 'OPENCODE_LCM_ALLOW_UNSAFE_BUN_WINDOWS';
5
+ function isTruthyEnvFlag(value) {
6
+ if (!value)
7
+ return false;
8
+ const normalized = value.trim().toLowerCase();
9
+ return normalized === '1' || normalized === 'true' || normalized === 'yes' || normalized === 'on';
10
+ }
11
+ function isBunRuntime() {
12
+ return typeof globalThis === 'object' && globalThis !== null && 'Bun' in globalThis;
13
+ }
14
+ function isUnsafeBunWindowsRuntime() {
15
+ return isBunRuntime() && process.platform === 'win32';
16
+ }
17
+ function resolveAllowUnsafeBunWindows(options) {
18
+ return (options.runtimeSafety.allowUnsafeBunWindows ||
19
+ isTruthyEnvFlag(process.env[ALLOW_UNSAFE_BUN_WINDOWS_ENV]));
20
+ }
21
+ function buildSafeModeStatus(allowUnsafeBunWindows) {
22
+ return [
23
+ 'status=disabled',
24
+ 'reason=bun_windows_runtime_guard',
25
+ 'available_tools=lcm_status',
26
+ `platform=${process.platform}`,
27
+ `bun_runtime=${isBunRuntime()}`,
28
+ `runtime_safety_allow_unsafe_bun_windows=${allowUnsafeBunWindows}`,
29
+ 'override_config=runtimeSafety.allowUnsafeBunWindows=true',
30
+ `override_env=${ALLOW_UNSAFE_BUN_WINDOWS_ENV}=1`,
31
+ 'message=opencode-lcm disabled itself before opening SQLite because Bun on Windows has reported native crashes in this path',
32
+ ].join('\n');
33
+ }
34
+ function createSafeModeHooks(allowUnsafeBunWindows) {
35
+ return {
36
+ event: async () => { },
37
+ tool: {
38
+ lcm_status: tool({
39
+ description: 'Show archived LCM capture stats',
40
+ args: {},
41
+ async execute() {
42
+ return buildSafeModeStatus(allowUnsafeBunWindows);
43
+ },
44
+ }),
45
+ },
46
+ 'experimental.chat.messages.transform': async () => { },
47
+ 'experimental.chat.system.transform': async () => { },
48
+ 'experimental.session.compacting': async () => { },
49
+ };
50
+ }
4
51
  export const OpencodeLcmPlugin = async (ctx, rawOptions) => {
5
52
  const options = resolveOptions(rawOptions);
53
+ const allowUnsafeBunWindows = resolveAllowUnsafeBunWindows(options);
54
+ if (isUnsafeBunWindowsRuntime() && !allowUnsafeBunWindows) {
55
+ return createSafeModeHooks(allowUnsafeBunWindows);
56
+ }
6
57
  const store = new SqliteLcmStore(ctx.directory, options);
7
58
  await store.init();
8
59
  return {
@@ -49,6 +100,7 @@ export const OpencodeLcmPlugin = async (ctx, rawOptions) => {
49
100
  `fresh_tail_messages=${options.freshTailMessages}`,
50
101
  `min_messages_for_transform=${options.minMessagesForTransform}`,
51
102
  `large_content_threshold=${options.largeContentThreshold}`,
103
+ `runtime_safety_allow_unsafe_bun_windows=${allowUnsafeBunWindows}`,
52
104
  `binary_preview_providers=${options.binaryPreviewProviders.join(',')}`,
53
105
  `preview_byte_peek=${options.previewBytePeek}`,
54
106
  `privacy_exclude_tool_prefixes=${options.privacy.excludeToolPrefixes.join(',')}`,
package/dist/options.js CHANGED
@@ -40,6 +40,9 @@ export const DEFAULT_SUMMARY_V2 = {
40
40
  strategy: 'deterministic-v2',
41
41
  perMessageBudget: 110,
42
42
  };
43
+ const DEFAULT_RUNTIME_SAFETY = {
44
+ allowUnsafeBunWindows: false,
45
+ };
43
46
  export const DEFAULT_OPTIONS = {
44
47
  interop: DEFAULT_INTEROP,
45
48
  scopeDefaults: DEFAULT_SCOPE_DEFAULTS,
@@ -65,6 +68,7 @@ export const DEFAULT_OPTIONS = {
65
68
  ],
66
69
  previewBytePeek: 16,
67
70
  summaryV2: DEFAULT_SUMMARY_V2,
71
+ runtimeSafety: DEFAULT_RUNTIME_SAFETY,
68
72
  };
69
73
  function asRecord(value) {
70
74
  if (!value || typeof value !== 'object' || Array.isArray(value))
@@ -199,6 +203,12 @@ function asSummaryV2Options(value, fallback) {
199
203
  perMessageBudget: asNumber(record?.perMessageBudget, fallback.perMessageBudget),
200
204
  };
201
205
  }
206
+ function asRuntimeSafetyOptions(value, fallback) {
207
+ const record = asRecord(value);
208
+ return {
209
+ allowUnsafeBunWindows: asBoolean(record?.allowUnsafeBunWindows, fallback.allowUnsafeBunWindows),
210
+ };
211
+ }
202
212
  export function resolveOptions(raw) {
203
213
  const options = asRecord(raw);
204
214
  const interop = asRecord(options?.interop);
@@ -229,5 +239,6 @@ export function resolveOptions(raw) {
229
239
  binaryPreviewProviders: asStringArray(options?.binaryPreviewProviders, DEFAULT_OPTIONS.binaryPreviewProviders),
230
240
  previewBytePeek: asNumber(options?.previewBytePeek, DEFAULT_OPTIONS.previewBytePeek),
231
241
  summaryV2: asSummaryV2Options(options?.summaryV2, DEFAULT_SUMMARY_V2),
242
+ runtimeSafety: asRuntimeSafetyOptions(options?.runtimeSafety, DEFAULT_RUNTIME_SAFETY),
232
243
  };
233
244
  }
package/dist/types.d.ts CHANGED
@@ -50,6 +50,9 @@ export type SummaryV2Options = {
50
50
  strategy: SummaryStrategyName;
51
51
  perMessageBudget: number;
52
52
  };
53
+ export type RuntimeSafetyOptions = {
54
+ allowUnsafeBunWindows: boolean;
55
+ };
53
56
  export type OpencodeLcmOptions = {
54
57
  interop: InteropOptions;
55
58
  scopeDefaults: ScopeDefaults;
@@ -70,6 +73,7 @@ export type OpencodeLcmOptions = {
70
73
  binaryPreviewProviders: string[];
71
74
  previewBytePeek: number;
72
75
  summaryV2: SummaryV2Options;
76
+ runtimeSafety: RuntimeSafetyOptions;
73
77
  };
74
78
  export type CapturedEvent = {
75
79
  id: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-lcm",
3
- "version": "0.13.5",
3
+ "version": "0.13.6",
4
4
  "description": "Long-memory plugin for OpenCode with context-mode interop",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
package/src/index.ts CHANGED
@@ -2,11 +2,75 @@ import { type Hooks, type PluginInput, tool } from '@opencode-ai/plugin';
2
2
 
3
3
  import { resolveOptions } from './options.js';
4
4
  import { SqliteLcmStore } from './store.js';
5
+ import type { OpencodeLcmOptions } from './types.js';
5
6
 
6
7
  type PluginWithOptions = (ctx: PluginInput, rawOptions?: unknown) => Promise<Hooks>;
7
8
 
9
+ const ALLOW_UNSAFE_BUN_WINDOWS_ENV = 'OPENCODE_LCM_ALLOW_UNSAFE_BUN_WINDOWS';
10
+
11
+ function isTruthyEnvFlag(value: string | undefined): boolean {
12
+ if (!value) return false;
13
+ const normalized = value.trim().toLowerCase();
14
+ return normalized === '1' || normalized === 'true' || normalized === 'yes' || normalized === 'on';
15
+ }
16
+
17
+ function isBunRuntime(): boolean {
18
+ return typeof globalThis === 'object' && globalThis !== null && 'Bun' in globalThis;
19
+ }
20
+
21
+ function isUnsafeBunWindowsRuntime(): boolean {
22
+ return isBunRuntime() && process.platform === 'win32';
23
+ }
24
+
25
+ function resolveAllowUnsafeBunWindows(options: OpencodeLcmOptions): boolean {
26
+ return (
27
+ options.runtimeSafety.allowUnsafeBunWindows ||
28
+ isTruthyEnvFlag(process.env[ALLOW_UNSAFE_BUN_WINDOWS_ENV])
29
+ );
30
+ }
31
+
32
+ function buildSafeModeStatus(allowUnsafeBunWindows: boolean): string {
33
+ return [
34
+ 'status=disabled',
35
+ 'reason=bun_windows_runtime_guard',
36
+ 'available_tools=lcm_status',
37
+ `platform=${process.platform}`,
38
+ `bun_runtime=${isBunRuntime()}`,
39
+ `runtime_safety_allow_unsafe_bun_windows=${allowUnsafeBunWindows}`,
40
+ 'override_config=runtimeSafety.allowUnsafeBunWindows=true',
41
+ `override_env=${ALLOW_UNSAFE_BUN_WINDOWS_ENV}=1`,
42
+ 'message=opencode-lcm disabled itself before opening SQLite because Bun on Windows has reported native crashes in this path',
43
+ ].join('\n');
44
+ }
45
+
46
+ function createSafeModeHooks(allowUnsafeBunWindows: boolean): Hooks {
47
+ return {
48
+ event: async () => {},
49
+
50
+ tool: {
51
+ lcm_status: tool({
52
+ description: 'Show archived LCM capture stats',
53
+ args: {},
54
+ async execute() {
55
+ return buildSafeModeStatus(allowUnsafeBunWindows);
56
+ },
57
+ }),
58
+ },
59
+
60
+ 'experimental.chat.messages.transform': async () => {},
61
+ 'experimental.chat.system.transform': async () => {},
62
+ 'experimental.session.compacting': async () => {},
63
+ };
64
+ }
65
+
8
66
  export const OpencodeLcmPlugin: PluginWithOptions = async (ctx, rawOptions) => {
9
67
  const options = resolveOptions(rawOptions);
68
+ const allowUnsafeBunWindows = resolveAllowUnsafeBunWindows(options);
69
+
70
+ if (isUnsafeBunWindowsRuntime() && !allowUnsafeBunWindows) {
71
+ return createSafeModeHooks(allowUnsafeBunWindows);
72
+ }
73
+
10
74
  const store = new SqliteLcmStore(ctx.directory, options);
11
75
 
12
76
  await store.init();
@@ -56,6 +120,7 @@ export const OpencodeLcmPlugin: PluginWithOptions = async (ctx, rawOptions) => {
56
120
  `fresh_tail_messages=${options.freshTailMessages}`,
57
121
  `min_messages_for_transform=${options.minMessagesForTransform}`,
58
122
  `large_content_threshold=${options.largeContentThreshold}`,
123
+ `runtime_safety_allow_unsafe_bun_windows=${allowUnsafeBunWindows}`,
59
124
  `binary_preview_providers=${options.binaryPreviewProviders.join(',')}`,
60
125
  `preview_byte_peek=${options.previewBytePeek}`,
61
126
  `privacy_exclude_tool_prefixes=${options.privacy.excludeToolPrefixes.join(',')}`,
package/src/options.ts CHANGED
@@ -6,6 +6,7 @@ import type {
6
6
  OpencodeLcmOptions,
7
7
  PrivacyOptions,
8
8
  RetentionPolicyOptions,
9
+ RuntimeSafetyOptions,
9
10
  ScopeDefaults,
10
11
  ScopeName,
11
12
  ScopeProfile,
@@ -61,6 +62,10 @@ export const DEFAULT_SUMMARY_V2: SummaryV2Options = {
61
62
  perMessageBudget: 110,
62
63
  };
63
64
 
65
+ const DEFAULT_RUNTIME_SAFETY: RuntimeSafetyOptions = {
66
+ allowUnsafeBunWindows: false,
67
+ };
68
+
64
69
  export const DEFAULT_OPTIONS: OpencodeLcmOptions = {
65
70
  interop: DEFAULT_INTEROP,
66
71
  scopeDefaults: DEFAULT_SCOPE_DEFAULTS,
@@ -86,6 +91,7 @@ export const DEFAULT_OPTIONS: OpencodeLcmOptions = {
86
91
  ],
87
92
  previewBytePeek: 16,
88
93
  summaryV2: DEFAULT_SUMMARY_V2,
94
+ runtimeSafety: DEFAULT_RUNTIME_SAFETY,
89
95
  };
90
96
 
91
97
  function asRecord(value: unknown): Record<string, unknown> | undefined {
@@ -258,6 +264,16 @@ function asSummaryV2Options(value: unknown, fallback: SummaryV2Options): Summary
258
264
  };
259
265
  }
260
266
 
267
+ function asRuntimeSafetyOptions(
268
+ value: unknown,
269
+ fallback: RuntimeSafetyOptions,
270
+ ): RuntimeSafetyOptions {
271
+ const record = asRecord(value);
272
+ return {
273
+ allowUnsafeBunWindows: asBoolean(record?.allowUnsafeBunWindows, fallback.allowUnsafeBunWindows),
274
+ };
275
+ }
276
+
261
277
  export function resolveOptions(raw: unknown): OpencodeLcmOptions {
262
278
  const options = asRecord(raw);
263
279
  const interop = asRecord(options?.interop);
@@ -314,5 +330,6 @@ export function resolveOptions(raw: unknown): OpencodeLcmOptions {
314
330
  ),
315
331
  previewBytePeek: asNumber(options?.previewBytePeek, DEFAULT_OPTIONS.previewBytePeek),
316
332
  summaryV2: asSummaryV2Options(options?.summaryV2, DEFAULT_SUMMARY_V2),
333
+ runtimeSafety: asRuntimeSafetyOptions(options?.runtimeSafety, DEFAULT_RUNTIME_SAFETY),
317
334
  };
318
335
  }
package/src/types.ts CHANGED
@@ -62,6 +62,10 @@ export type SummaryV2Options = {
62
62
  perMessageBudget: number;
63
63
  };
64
64
 
65
+ export type RuntimeSafetyOptions = {
66
+ allowUnsafeBunWindows: boolean;
67
+ };
68
+
65
69
  export type OpencodeLcmOptions = {
66
70
  interop: InteropOptions;
67
71
  scopeDefaults: ScopeDefaults;
@@ -82,6 +86,7 @@ export type OpencodeLcmOptions = {
82
86
  binaryPreviewProviders: string[];
83
87
  previewBytePeek: number;
84
88
  summaryV2: SummaryV2Options;
89
+ runtimeSafety: RuntimeSafetyOptions;
85
90
  };
86
91
 
87
92
  export type CapturedEvent = {