memorix 1.9.8 → 1.9.10

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 (38) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +2 -0
  3. package/README.zh-CN.md +2 -0
  4. package/dist/cli/index.js +125 -31
  5. package/dist/index.js +117 -31
  6. package/dist/maintenance-runner.js +22 -5
  7. package/dist/media-audio-runner.js +22 -5
  8. package/dist/media-video-runner.js +22 -5
  9. package/dist/memcode-runtime/CHANGELOG.md +17 -0
  10. package/dist/sdk.js +116 -30
  11. package/dist/vector-backfill-runner.js +22 -5
  12. package/docs/CONFIGURATION.md +22 -0
  13. package/docs/DEVELOPMENT.md +1 -1
  14. package/docs/INTEGRATIONS.md +1 -1
  15. package/docs/README.md +1 -1
  16. package/docs/SETUP.md +1 -1
  17. package/package.json +1 -1
  18. package/plugins/antigravity/memorix/plugin.json +1 -1
  19. package/plugins/claude/memorix/.claude-plugin/plugin.json +1 -1
  20. package/plugins/codebuddy/memorix/.codebuddy-plugin/plugin.json +1 -1
  21. package/plugins/codex/memorix/.codex-plugin/plugin.json +1 -1
  22. package/plugins/copilot/memorix/plugin.json +1 -1
  23. package/plugins/gemini/memorix/gemini-extension.json +1 -1
  24. package/plugins/hermes/memorix/plugin.yaml +1 -1
  25. package/plugins/omp/memorix/package.json +1 -1
  26. package/plugins/openclaw/memorix/.codex-plugin/plugin.json +1 -1
  27. package/plugins/pi/memorix/package.json +1 -1
  28. package/server.json +2 -2
  29. package/src/cli/commands/init.ts +7 -1
  30. package/src/cli/tui/ConfigureView.tsx +2 -0
  31. package/src/config/dotenv-loader.ts +1 -0
  32. package/src/config/resolved-config.ts +19 -5
  33. package/src/config/yaml-loader.ts +2 -2
  34. package/src/config.ts +5 -1
  35. package/src/dashboard/server.ts +3 -0
  36. package/src/hooks/installers/index.ts +89 -21
  37. package/src/llm/provider.ts +2 -1
  38. package/src/multimodal/image-loader.ts +2 -2
@@ -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.8** and package metadata is kept synchronized
16
+ The current release line is **1.9.10** 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. | 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. |
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, adds the v1 `server` field to its dual default export, and exposes the v2 `id`/`setup` contract. OpenCode 1.x lifecycle/file/command/message events use the generic `event` hook; v2 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.8**. Multi-device store sync is opt-in and
129
+ The current product line is **1.9.10**. 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. 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.
505
+ This writes the OpenCode plugin, skills, MCP config, and `AGENTS.md` guidance in the selected scope. The generated plugin exports the legacy v1 hook function and a dual default object with the v1 `server` field plus the v2 `id`/`setup` contract, so OpenCode v1 and v2 can load the same file during the migration window. OpenCode 1.x events are received through its generic `event` hook.
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.8",
4
+ "version": "1.9.10",
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.8"
3
+ "version": "1.9.10"
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.8",
4
+ "version": "1.9.10",
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.8",
3
+ "version": "1.9.10",
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.8",
3
+ "version": "1.9.10",
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.8",
3
+ "version": "1.9.10",
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.8",
3
+ "version": "1.9.10",
4
4
  "mcpServers": {
5
5
  "memorix": {
6
6
  "command": "memorix",
@@ -1,5 +1,5 @@
1
1
  name: memorix
2
- version: "1.9.8"
2
+ version: "1.9.10"
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.8",
3
+ "version": "1.9.10",
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.8",
3
+ "version": "1.9.10",
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.8",
3
+ "version": "1.9.10",
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.8",
10
+ "version": "1.9.10",
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.8",
25
+ "version": "1.9.10",
26
26
  "runtimeHint": "npx",
27
27
  "packageArguments": [
28
28
  {
@@ -21,7 +21,7 @@ import {
21
21
  type InitScope,
22
22
  } from './init-shared.js';
23
23
 
24
- type InitLlmProvider = 'none' | 'openai' | 'anthropic' | 'openrouter' | 'atlascloud' | 'custom';
24
+ type InitLlmProvider = 'none' | 'openai' | 'anthropic' | 'openrouter' | 'requesty' | 'atlascloud' | 'custom';
25
25
  type InitEmbeddingProvider = 'off' | 'api' | 'fastembed';
26
26
  type InitInjectMode = 'minimal' | 'full' | 'silent';
27
27
 
@@ -103,6 +103,7 @@ export default defineCommand({
103
103
  { value: 'openai', label: 'OpenAI', hint: 'gpt-4o-mini' },
104
104
  { value: 'anthropic', label: 'Anthropic', hint: 'claude-3-haiku' },
105
105
  { value: 'openrouter', label: 'OpenRouter', hint: 'multi-provider' },
106
+ { value: 'requesty', label: 'Requesty', hint: 'multi-provider' },
106
107
  { value: 'atlascloud', label: 'Atlas Cloud', hint: 'DeepSeek V3.2' },
107
108
  { value: 'custom', label: 'Custom', hint: 'OpenAI-compatible endpoint' },
108
109
  ],
@@ -169,6 +170,8 @@ export default defineCommand({
169
170
  envLines.push('MEMORIX_LLM_API_KEY=sk-ant-your-key-here');
170
171
  } else if (llmProvider === 'openrouter') {
171
172
  envLines.push('MEMORIX_LLM_API_KEY=sk-or-your-key-here');
173
+ } else if (llmProvider === 'requesty') {
174
+ envLines.push('MEMORIX_LLM_API_KEY=rqsty-your-key-here');
172
175
  }
173
176
  if (llmProvider === 'custom') {
174
177
  envLines.push('# MEMORIX_LLM_BASE_URL=http://localhost:11434/v1');
@@ -197,6 +200,7 @@ export default defineCommand({
197
200
  envLines.push('# OPENAI_API_KEY=sk-...');
198
201
  envLines.push('# ANTHROPIC_API_KEY=sk-ant-...');
199
202
  envLines.push('# OPENROUTER_API_KEY=sk-or-...');
203
+ envLines.push('# REQUESTY_API_KEY=rqsty-...');
200
204
  envLines.push('');
201
205
 
202
206
  mkdirSync(targetDir, { recursive: true });
@@ -293,6 +297,8 @@ export function buildInitTomlConfig(options: {
293
297
  lines.push('model = "claude-3-haiku-20240307"');
294
298
  } else if (options.llmProvider === 'openrouter') {
295
299
  lines.push('# model = "openai/gpt-4o-mini"');
300
+ } else if (options.llmProvider === 'requesty') {
301
+ lines.push('# model = "openai/gpt-4o-mini"');
296
302
  } else if (options.llmProvider === 'atlascloud') {
297
303
  lines.push('model = "deepseek-ai/deepseek-v3.2"');
298
304
  }
@@ -166,6 +166,7 @@ export function ConfigureView({ onBack }: ConfigureViewProps): React.ReactElemen
166
166
  { key: 'openai', label: 'OpenAI', hint: 'gpt-4o-mini' },
167
167
  { key: 'anthropic', label: 'Anthropic', hint: 'claude-3-haiku' },
168
168
  { key: 'openrouter', label: 'OpenRouter', hint: 'multi-provider' },
169
+ { key: 'requesty', label: 'Requesty', hint: 'multi-provider' },
169
170
  { key: 'atlascloud', label: 'Atlas Cloud', hint: 'DeepSeek V3.2' },
170
171
  { key: 'custom', label: 'Custom endpoint', hint: 'OpenAI-compatible' },
171
172
  { key: 'disable', label: 'Disable LLM', hint: 'free heuristic mode', color: COLORS.warning },
@@ -447,6 +448,7 @@ export function ConfigureView({ onBack }: ConfigureViewProps): React.ReactElemen
447
448
  function getDefaultModel(provider: string): string {
448
449
  if (provider === 'anthropic') return 'claude-3-haiku-20240307';
449
450
  if (provider === 'openrouter') return 'openai/gpt-4o-mini';
451
+ if (provider === 'requesty') return 'openai/gpt-4o-mini';
450
452
  if (provider === 'atlascloud') return 'deepseek-ai/deepseek-v3.2';
451
453
  return 'gpt-4o-mini';
452
454
  }
@@ -143,6 +143,7 @@ export function getLoadedEnvValue(key: string): string | undefined {
143
143
  // OPENAI_API_KEY — OpenAI compatibility fallback
144
144
  // ANTHROPIC_API_KEY — Anthropic compatibility fallback
145
145
  // OPENROUTER_API_KEY — OpenRouter compatibility fallback
146
+ // REQUESTY_API_KEY - Requesty memory preset and embedding endpoint key
146
147
  // MINIMAX_API_KEY — MiniMax global image/video generation key
147
148
  // MINIMAX_CN_API_KEY — MiniMax China-region image/video generation key
148
149
  // MINIMAX_REGION — MiniMax region: global or cn
@@ -96,6 +96,7 @@ export function getResolvedConfig(options: ResolvedLaneOptions = {}): ResolvedMe
96
96
  const legacy = loadFileConfig(homeDir);
97
97
  const embeddingBaseUrl = first(process.env.MEMORIX_EMBEDDING_BASE_URL, toml.embedding?.base_url, yaml.embedding?.baseUrl, legacy.embeddingApi?.baseUrl);
98
98
  const openRouterEmbeddingApiKey = isOpenRouterUrl(embeddingBaseUrl) ? process.env.OPENROUTER_API_KEY : undefined;
99
+ const requestyEmbeddingApiKey = isRequestyUrl(embeddingBaseUrl) ? process.env.REQUESTY_API_KEY : undefined;
99
100
  const memoryLlmProvider = first(
100
101
  process.env.MEMORIX_LLM_PROVIDER,
101
102
  toml.memory?.llm?.provider,
@@ -118,16 +119,18 @@ export function getResolvedConfig(options: ResolvedLaneOptions = {}): ResolvedMe
118
119
  ? process.env.OPENROUTER_API_KEY
119
120
  : undefined;
120
121
  const isAtlasCloud = memoryLlmProvider === 'atlascloud';
122
+ const isRequesty = memoryLlmProvider === 'requesty';
123
+ const usesPresetKeyOnly = isAtlasCloud || isRequesty;
121
124
  const memoryLlmApiKey = first(
122
125
  process.env.MEMORIX_LLM_API_KEY,
123
126
  process.env.MEMORIX_API_KEY,
124
127
  toml.memory?.llm?.api_key,
125
128
  yaml.llm?.apiKey,
126
129
  legacy.llm?.apiKey,
127
- // Never borrow another provider's credentials for the Atlas Cloud preset.
128
- isAtlasCloud ? process.env.ATLASCLOUD_API_KEY : process.env.OPENAI_API_KEY,
129
- isAtlasCloud ? undefined : process.env.ANTHROPIC_API_KEY,
130
- isAtlasCloud ? undefined : openRouterMemoryLlmApiKey,
130
+ // Never borrow another provider's credentials for the Atlas Cloud or Requesty presets.
131
+ isAtlasCloud ? process.env.ATLASCLOUD_API_KEY : isRequesty ? process.env.REQUESTY_API_KEY : process.env.OPENAI_API_KEY,
132
+ usesPresetKeyOnly ? undefined : process.env.ANTHROPIC_API_KEY,
133
+ usesPresetKeyOnly ? undefined : openRouterMemoryLlmApiKey,
131
134
  );
132
135
 
133
136
  const resolved: ResolvedMemorixConfig = {
@@ -177,7 +180,7 @@ export function getResolvedConfig(options: ResolvedLaneOptions = {}): ResolvedMe
177
180
  provider: first(process.env.MEMORIX_EMBEDDING, toml.embedding?.provider, yaml.embedding?.provider, legacy.embedding, 'off'),
178
181
  model: first(process.env.MEMORIX_EMBEDDING_MODEL, toml.embedding?.model, yaml.embedding?.model, legacy.embeddingApi?.model),
179
182
  baseUrl: embeddingBaseUrl,
180
- apiKey: first(process.env.MEMORIX_EMBEDDING_API_KEY, toml.embedding?.api_key, yaml.embedding?.apiKey, legacy.embeddingApi?.apiKey, openRouterEmbeddingApiKey),
183
+ apiKey: first(process.env.MEMORIX_EMBEDDING_API_KEY, toml.embedding?.api_key, yaml.embedding?.apiKey, legacy.embeddingApi?.apiKey, openRouterEmbeddingApiKey, requestyEmbeddingApiKey),
181
184
  dimensions: firstNumber(parseNumber(process.env.MEMORIX_EMBEDDING_DIMENSIONS), toml.embedding?.dimensions, yaml.embedding?.dimensions, legacy.embeddingApi?.dimensions),
182
185
  },
183
186
  rerank: resolveRerankLane({
@@ -323,6 +326,7 @@ function getEnvSourceNames(): string[] {
323
326
  'MEMORIX_CODEGRAPH_EXTERNAL_COMMAND',
324
327
  'MEMORIX_CODEGRAPH_EXTERNAL_TIMEOUT_MS',
325
328
  'OPENROUTER_API_KEY',
329
+ 'REQUESTY_API_KEY',
326
330
  'ATLASCLOUD_API_KEY',
327
331
  ].filter((name) => process.env[name]);
328
332
  }
@@ -337,6 +341,16 @@ function isOpenRouterUrl(value: string | undefined): boolean {
337
341
  }
338
342
  }
339
343
 
344
+ function isRequestyUrl(value: string | undefined): boolean {
345
+ if (!value) return false;
346
+ try {
347
+ const url = new URL(value);
348
+ return url.hostname.toLowerCase() === 'requesty.ai' || url.hostname.toLowerCase().endsWith('.requesty.ai');
349
+ } catch {
350
+ return /(^|\.)requesty\.ai(?::|\/|$)/i.test(value);
351
+ }
352
+ }
353
+
340
354
  function isOpenRouterMemoryLane(provider: string | undefined, baseUrl: string | undefined): boolean {
341
355
  return provider?.trim().toLowerCase() === 'openrouter' || isOpenRouterUrl(baseUrl);
342
356
  }
@@ -25,7 +25,7 @@ import { getGlobalYamlPath, getProjectYamlPath } from './config-paths.js';
25
25
  export interface MemorixYamlConfig {
26
26
  /** LLM provider configuration */
27
27
  llm?: {
28
- provider?: 'openai' | 'anthropic' | 'openrouter' | string;
28
+ provider?: 'openai' | 'anthropic' | 'openrouter' | 'requesty' | string;
29
29
  model?: string;
30
30
  apiKey?: string;
31
31
  baseUrl?: string;
@@ -33,7 +33,7 @@ export interface MemorixYamlConfig {
33
33
 
34
34
  /** TUI / chat agent LLM provider configuration */
35
35
  agent?: {
36
- provider?: 'openai' | 'anthropic' | 'openrouter' | string;
36
+ provider?: 'openai' | 'anthropic' | 'openrouter' | 'requesty' | string;
37
37
  model?: string;
38
38
  apiKey?: string;
39
39
  baseUrl?: string;
package/src/config.ts CHANGED
@@ -93,7 +93,8 @@ export function getEmbeddingModel(): string {
93
93
  /**
94
94
  * Provider-aware embedding model default: OpenRouter serves many providers,
95
95
  * and the OpenAI-only default does not fit it. Qwen3-Embedding-8B is a
96
- * strong multilingual default there; everywhere else keep the OpenAI model.
96
+ * strong multilingual default there. Requesty expects vendor-prefixed ids,
97
+ * so it gets the prefixed OpenAI model. Everywhere else keep the OpenAI model.
97
98
  * An explicitly configured model always wins.
98
99
  */
99
100
  export function defaultEmbeddingModelFor(
@@ -106,6 +107,9 @@ export function defaultEmbeddingModelFor(
106
107
  if (url && (url.hostname === 'openrouter.ai' || url.hostname.endsWith('.openrouter.ai'))) {
107
108
  return 'qwen/qwen3-embedding-8b';
108
109
  }
110
+ if (url && (url.hostname === 'requesty.ai' || url.hostname.endsWith('.requesty.ai'))) {
111
+ return 'openai/text-embedding-3-small';
112
+ }
109
113
  } catch { /* treat unparsable base URLs as non-OpenRouter */ }
110
114
  return 'text-embedding-3-small';
111
115
  }
@@ -682,6 +682,9 @@ async function handleApi(
682
682
  ...(resolved.memory.llm.provider === 'openrouter' || resolved.memory.llm.baseUrl?.includes('openrouter.ai')
683
683
  ? ['OPENROUTER_API_KEY']
684
684
  : []),
685
+ ...(resolved.memory.llm.provider === 'requesty'
686
+ ? ['REQUESTY_API_KEY']
687
+ : []),
685
688
  ];
686
689
  if (llmKey) {
687
690
  const src = resolveDashboardKeySource({ value: llmKey, envKeys: memoryKeyEnv, configSource });
@@ -466,7 +466,7 @@ function generateKiroHookFiles(): Array<{ filename: string; content: string }> {
466
466
  * protocol used by all agents. spawnSync works in both Node.js and Bun
467
467
  * runtimes (OpenCode may fall back to Node.js on Windows).
468
468
  */
469
- const OPENCODE_PLUGIN_VERSION = 8;
469
+ const OPENCODE_PLUGIN_VERSION = 9;
470
470
 
471
471
  const AGENT_SKILL_DIRS: Partial<Record<AgentName, { project: string; global?: string }>> = {
472
472
  cursor: { project: path.join('.cursor', 'skills'), global: path.join('.cursor', 'skills') },
@@ -537,6 +537,8 @@ function generateOpenCodePlugin(): string {
537
537
  */
538
538
  import { spawnSync } from 'node:child_process';
539
539
 
540
+ const HOOK_TIMEOUT_MS = 30_000;
541
+
540
542
  export const MemorixPlugin = async ({ project, client, $, directory, worktree }) => {
541
543
  // Generate a stable session ID for this plugin lifetime
542
544
  const sessionId = \`opencode-\${Date.now().toString(36)}-\${Math.random().toString(36).slice(2, 8)}\`;
@@ -562,14 +564,14 @@ export const MemorixPlugin = async ({ project, client, $, directory, worktree })
562
564
  * - spawnSync is simpler: no stream lifecycle, no writer.close() bugs
563
565
  * - stdin pipe via input option is reliable cross-platform
564
566
  */
565
- function runHook(payload) {
566
- payload.session_id = sessionId;
567
+ function runHook(payload, activeSessionId = sessionId) {
568
+ payload.session_id = activeSessionId;
567
569
  const data = JSON.stringify(payload);
568
570
  const eventName = payload.hook_event_name || 'unknown';
569
571
  try {
570
572
  const result = spawnSync(hookCommand, ['hook'], {
571
573
  input: data,
572
- timeout: 10_000,
574
+ timeout: HOOK_TIMEOUT_MS,
573
575
  encoding: 'utf-8',
574
576
  stdio: ['pipe', 'pipe', 'pipe'],
575
577
  shell: process.platform === 'win32',
@@ -614,7 +616,87 @@ export const MemorixPlugin = async ({ project, client, $, directory, worktree })
614
616
  .trim();
615
617
  }
616
618
 
619
+ function flushAssistantResponse(activeSessionId = sessionId) {
620
+ if (!pendingAssistantResponse?.text) return;
621
+ const deliveryKey = pendingAssistantResponse.id
622
+ ? \`\${pendingAssistantResponse.id}:\${pendingAssistantResponse.text}\`
623
+ : pendingAssistantResponse.text;
624
+ if (deliveryKey === lastDeliveredAssistantKey) return;
625
+ runHook({
626
+ agent: 'opencode',
627
+ hook_event_name: 'message.updated',
628
+ ai_response: pendingAssistantResponse.text,
629
+ message_id: pendingAssistantResponse.id,
630
+ cwd: directory,
631
+ }, activeSessionId);
632
+ lastDeliveredAssistantKey = deliveryKey;
633
+ }
634
+
635
+ function eventSessionId(event) {
636
+ const properties = event?.properties ?? {};
637
+ const value = properties.sessionID ?? properties.sessionId ?? properties.info?.id;
638
+ return typeof value === 'string' && value ? value : sessionId;
639
+ }
640
+
641
+ async function handleEvent({ event }) {
642
+ const properties = event?.properties ?? {};
643
+ const activeSessionId = eventSessionId(event);
644
+ switch (event?.type) {
645
+ case 'session.created':
646
+ runHook({
647
+ agent: 'opencode',
648
+ hook_event_name: 'session.created',
649
+ cwd: directory,
650
+ }, activeSessionId);
651
+ break;
652
+ case 'session.idle':
653
+ flushAssistantResponse(activeSessionId);
654
+ runHook({
655
+ agent: 'opencode',
656
+ hook_event_name: 'session.idle',
657
+ cwd: directory,
658
+ }, activeSessionId);
659
+ break;
660
+ case 'file.edited':
661
+ runHook({
662
+ agent: 'opencode',
663
+ hook_event_name: 'file.edited',
664
+ file_path: properties.file ?? properties.path ?? '',
665
+ cwd: directory,
666
+ }, activeSessionId);
667
+ break;
668
+ case 'command.executed':
669
+ runHook({
670
+ agent: 'opencode',
671
+ hook_event_name: 'command.executed',
672
+ command: properties.name ?? properties.command ?? '',
673
+ cwd: directory,
674
+ }, activeSessionId);
675
+ break;
676
+ case 'message.updated': {
677
+ const message = properties.info ?? properties.message ?? properties;
678
+ if (message?.role !== 'assistant') break;
679
+ const text = extractMessageText(message);
680
+ if (!text) break;
681
+ pendingAssistantResponse = { id: message.id, text };
682
+ break;
683
+ }
684
+ case 'session.compacted':
685
+ runHook({
686
+ agent: 'opencode',
687
+ hook_event_name: 'session.compacted',
688
+ cwd: directory,
689
+ }, activeSessionId);
690
+ break;
691
+ default:
692
+ break;
693
+ }
694
+ }
695
+
617
696
  return {
697
+ // OpenCode 1.x delivers lifecycle/file/command/message events through this
698
+ // generic hook. The named keys below remain for older compatible hosts.
699
+ event: handleEvent,
618
700
  /** Session created — record session start */
619
701
  'session.created': async ({ session }) => {
620
702
  runHook({
@@ -626,21 +708,7 @@ export const MemorixPlugin = async ({ project, client, $, directory, worktree })
626
708
 
627
709
  /** Session idle — record session end */
628
710
  'session.idle': async ({ session }) => {
629
- if (pendingAssistantResponse?.text) {
630
- const deliveryKey = pendingAssistantResponse.id
631
- ? \`\${pendingAssistantResponse.id}:\${pendingAssistantResponse.text}\`
632
- : pendingAssistantResponse.text;
633
- if (deliveryKey !== lastDeliveredAssistantKey) {
634
- runHook({
635
- agent: 'opencode',
636
- hook_event_name: 'message.updated',
637
- ai_response: pendingAssistantResponse.text,
638
- message_id: pendingAssistantResponse.id,
639
- cwd: directory,
640
- });
641
- lastDeliveredAssistantKey = deliveryKey;
642
- }
643
- }
711
+ flushAssistantResponse();
644
712
  runHook({
645
713
  agent: 'opencode',
646
714
  hook_event_name: 'session.idle',
@@ -748,7 +816,7 @@ function v2RunHook(payload, directory, sessionId) {
748
816
  try {
749
817
  const result = spawnSync(v2HookCommand, ['hook'], {
750
818
  input: JSON.stringify(nextPayload),
751
- timeout: 10_000,
819
+ timeout: HOOK_TIMEOUT_MS,
752
820
  encoding: 'utf-8',
753
821
  stdio: ['pipe', 'pipe', 'pipe'],
754
822
  shell: process.platform === 'win32',
@@ -872,7 +940,7 @@ function setup(ctx) {
872
940
  return () => controller.abort();
873
941
  }
874
942
 
875
- export default { id: 'memorix', setup };
943
+ export default { id: 'memorix', server: MemorixPlugin, setup };
876
944
  `;
877
945
  }
878
946
 
@@ -20,7 +20,7 @@ import {
20
20
  } from '../config.js';
21
21
 
22
22
  export interface LLMConfig {
23
- provider: 'openai' | 'anthropic' | 'openrouter' | 'atlascloud' | 'custom';
23
+ provider: 'openai' | 'anthropic' | 'openrouter' | 'requesty' | 'atlascloud' | 'custom';
24
24
  apiKey: string;
25
25
  model?: string;
26
26
  baseUrl?: string;
@@ -201,6 +201,7 @@ const PROVIDER_DEFAULTS: Record<string, { baseUrl: string; model: string }> = {
201
201
  openai: { baseUrl: 'https://api.openai.com/v1', model: 'gpt-4.1-nano' },
202
202
  anthropic: { baseUrl: 'https://api.anthropic.com/v1', model: 'claude-3-5-haiku-latest' },
203
203
  openrouter: { baseUrl: 'https://openrouter.ai/api/v1', model: 'openai/gpt-4.1-nano' },
204
+ requesty: { baseUrl: 'https://router.requesty.ai/v1', model: 'openai/gpt-4.1-nano' },
204
205
  atlascloud: { baseUrl: 'https://api.atlascloud.ai/v1', model: 'deepseek-ai/deepseek-v3.2' },
205
206
  custom: { baseUrl: 'http://localhost:11434/v1', model: 'llama3' },
206
207
  };
@@ -19,7 +19,7 @@ import {
19
19
  } from './image-payload.js';
20
20
 
21
21
  // Providers that use the OpenAI-compatible /chat/completions Vision endpoint
22
- const OPENAI_COMPATIBLE_PROVIDERS = new Set(['openai', 'openrouter', 'custom']);
22
+ const OPENAI_COMPATIBLE_PROVIDERS = new Set(['openai', 'openrouter', 'requesty', 'custom']);
23
23
  const MAX_VISION_PROMPT_CHARS = 12_000;
24
24
  const MAX_VISION_DESCRIPTION_CHARS = 12_000;
25
25
  const MAX_VISION_LABELS = 50;
@@ -137,7 +137,7 @@ export async function analyzeImage(input: ImageInput): Promise<ImageAnalysisResu
137
137
  const config = getLLMConfig()!;
138
138
  if (!OPENAI_COMPATIBLE_PROVIDERS.has(config.provider)) {
139
139
  throw new Error(
140
- `Image analysis requires an OpenAI-compatible provider (openai, openrouter, or custom). ` +
140
+ `Image analysis requires an OpenAI-compatible provider (openai, openrouter, requesty, or custom). ` +
141
141
  `Current provider "${config.provider}" uses a different API shape. ` +
142
142
  `Set MEMORIX_LLM_PROVIDER=openai or configure an OpenAI-compatible base URL.`,
143
143
  );