@memorax/memorax-code 0.1.9 → 0.1.11

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 (101) hide show
  1. package/README.md +10 -0
  2. package/bin/memorax-code-codebuddy.mjs +4 -0
  3. package/bin/memorax-code-setup.mjs +182 -65
  4. package/bin/memorax-code.mjs +74 -45
  5. package/docs/configuration.md +85 -22
  6. package/docs/troubleshooting.md +72 -5
  7. package/lib/automatic-update.mjs +233 -0
  8. package/lib/memorax-code-adapter-common/src/automatic-update-state.d.mts +24 -0
  9. package/lib/memorax-code-adapter-common/src/automatic-update-state.mjs +73 -0
  10. package/lib/memorax-code-adapter-common/src/clients/codebuddy-command.mjs +82 -0
  11. package/lib/memorax-code-adapter-common/src/config-utils.d.mts +2 -0
  12. package/lib/memorax-code-adapter-common/src/config-utils.mjs +11 -0
  13. package/lib/memorax-code-adapter-common/src/hooks/client-hook-launcher.mjs +3 -0
  14. package/lib/memorax-code-adapter-common/src/hooks/ensure-backend-runner.mjs +45 -10
  15. package/lib/memorax-code-adapter-common/src/repo-memory/repo-memory-job-worker.mjs +88 -3
  16. package/lib/memorax-code-adapter-common/src/windows-cli-invocation.mjs +11 -1
  17. package/lib/memorax-code-backend/dist/app/backend-server.js +2 -2
  18. package/lib/memorax-code-backend/dist/app/memory-observability.js +2 -2
  19. package/lib/memorax-code-backend/dist/clients/codebuddy/jsonl-history.js +208 -0
  20. package/lib/memorax-code-backend/dist/clients/codebuddy/lifecycle.js +44 -0
  21. package/lib/memorax-code-backend/dist/clients/codebuddy/memory-hook-runtime.js +250 -0
  22. package/lib/memorax-code-backend/dist/clients/codebuddy/turn-id.js +16 -0
  23. package/lib/memorax-code-backend/dist/clients/codex/plugin-install.js +129 -7
  24. package/lib/memorax-code-backend/dist/config/memorax-code.js +14 -1
  25. package/lib/memorax-code-backend/dist/entrypoints/backend-cli.js +43 -6
  26. package/lib/memorax-code-backend/dist/lifecycle/active-clients.js +3 -0
  27. package/lib/memorax-code-backend/dist/lifecycle/automatic-update-scheduler.js +133 -0
  28. package/lib/memorax-code-backend/dist/lifecycle/client-plugin-removal.js +14 -2
  29. package/lib/memorax-code-backend/dist/lifecycle/client-selection.js +6 -4
  30. package/lib/memorax-code-backend/dist/lifecycle/orchestrator.js +48 -9
  31. package/lib/memorax-code-backend/dist/memory/cli.js +4 -2
  32. package/lib/memorax-code-backend/dist/memory/hook-command.js +31 -1
  33. package/lib/memorax-code-backend/dist/memory/reminder-trace-recorder.js +5 -1
  34. package/lib/memorax-code-backend/dist/memory/service.js +12 -0
  35. package/lib/memorax-code-backend/dist/shared/windows-cli-invocation.js +10 -1
  36. package/lib/memorax-code-backend/dist/trace/config.js +12 -0
  37. package/lib/memorax-code-backend/dist/trace/context.js +21 -1
  38. package/lib/memorax-code-backend/dist/trace/store.js +12 -1
  39. package/lib/memorax-code-backend/package.json +1 -1
  40. package/lib/memorax-code-claude-adapter/.claude-plugin/plugin.json +1 -1
  41. package/lib/memorax-code-claude-adapter/hooks/runtime-shell.json +1 -1
  42. package/lib/memorax-code-claude-adapter/package.json +1 -1
  43. package/lib/memorax-code-claude-adapter/runtime-hooks/ensure-backend.mjs +2 -0
  44. package/lib/memorax-code-claude-adapter/src/plugin-install.mjs +3 -0
  45. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/.claude-plugin/plugin.json +1 -1
  46. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/hooks/runtime-shell.json +1 -1
  47. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/automatic-update-state.d.mts +24 -0
  48. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/automatic-update-state.mjs +73 -0
  49. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/clients/codebuddy-command.mjs +82 -0
  50. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/config-utils.d.mts +2 -0
  51. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/config-utils.mjs +11 -0
  52. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/hooks/client-hook-launcher.mjs +3 -0
  53. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/hooks/ensure-backend-runner.mjs +45 -10
  54. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/repo-memory/repo-memory-job-worker.mjs +88 -3
  55. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/windows-cli-invocation.mjs +11 -1
  56. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/package.json +1 -1
  57. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/runtime-hooks/ensure-backend.mjs +2 -0
  58. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/src/plugin-install.mjs +3 -0
  59. package/lib/memorax-code-codebuddy-adapter/.codebuddy-plugin/plugin.json +7 -0
  60. package/lib/memorax-code-codebuddy-adapter/hooks/common-runtime.mjs +13 -0
  61. package/lib/memorax-code-codebuddy-adapter/hooks/hooks.json +38 -0
  62. package/lib/memorax-code-codebuddy-adapter/hooks/repo-memory-job.mjs +40 -0
  63. package/lib/memorax-code-codebuddy-adapter/hooks/runtime-hook.mjs +289 -0
  64. package/lib/memorax-code-codebuddy-adapter/package.json +9 -0
  65. package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/SKILL.md +85 -0
  66. package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/agents/claude.yaml +10 -0
  67. package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/agents/openai.yaml +7 -0
  68. package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/defaults.json +12 -0
  69. package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/references/memorax-add.md +88 -0
  70. package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/references/memorax-search.md +93 -0
  71. package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/references/personal-read.md +46 -0
  72. package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/references/personal-write.md +120 -0
  73. package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/references/repo-build.md +319 -0
  74. package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/references/repo-read.md +103 -0
  75. package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/references/repo-templates.md +390 -0
  76. package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/references/repo-update.md +127 -0
  77. package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/scripts/collect_all.py +579 -0
  78. package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/scripts/detect_updates.py +919 -0
  79. package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/scripts/git_commit_facets.py +222 -0
  80. package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/scripts/github_resource_facets.py +512 -0
  81. package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/scripts/gitlab_resource_facets.py +517 -0
  82. package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/scripts/prepare_repo_memory.py +411 -0
  83. package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/scripts/user_profile_memory.py +528 -0
  84. package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/scripts/validate_memory.py +248 -0
  85. package/lib/memorax-code-codebuddy-adapter/src/cli.mjs +58 -0
  86. package/lib/memorax-code-codebuddy-adapter/src/config.mjs +340 -0
  87. package/lib/memorax-code-codebuddy-adapter/src/hook-manifest.mjs +47 -0
  88. package/lib/memorax-code-codebuddy-adapter/src/runtime-observation.mjs +65 -0
  89. package/lib/memorax-code-codex-adapter/.codex-plugin/plugin.json +1 -1
  90. package/lib/memorax-code-codex-adapter/hooks/runtime-shell.json +1 -1
  91. package/lib/memorax-code-codex-adapter/package.json +1 -1
  92. package/lib/memorax-code-codex-adapter/runtime-hooks/ensure-backend.mjs +4 -1
  93. package/lib/memorax-code-dsh-adapter/package.json +2 -1
  94. package/lib/memorax-code-dsh-adapter/src/profile-lifecycle.mjs +1 -0
  95. package/lib/memorax-code-opencode-adapter/package.json +1 -1
  96. package/lib/npm-invocation.mjs +51 -8
  97. package/lib/resolve-codebuddy-command.mjs +123 -0
  98. package/lib/run-entrypoint.mjs +25 -1
  99. package/lib/windows-cli-invocation.mjs +11 -1
  100. package/lib/windows-user-path.mjs +218 -0
  101. package/package.json +5 -3
@@ -0,0 +1,133 @@
1
+ import { spawn } from "node:child_process";
2
+ import { existsSync } from "node:fs";
3
+ import { join, resolve } from "node:path";
4
+ import { AUTOMATIC_UPDATE_RETRY_INTERVAL_MS, readAutomaticUpdateState, } from "../../../memorax-code-adapter-common/src/automatic-update-state.mjs";
5
+ import { readSetupCompletionRecord, } from "../../../memorax-code-adapter-common/src/setup-completion.mjs";
6
+ const MAX_TIMER_DELAY_MS = 2_147_483_647;
7
+ export function startBackendAutomaticUpdateScheduler(options, runtime = {}) {
8
+ const env = options.env ?? process.env;
9
+ if (automaticUpdateDisabled(env.MEMORAX_CODE_AUTO_UPDATE))
10
+ return undefined;
11
+ const packageRoot = nonEmptyString(options.packageRoot);
12
+ const installedVersion = nonEmptyString(options.packageVersion);
13
+ if (!packageRoot || !installedVersion)
14
+ return undefined;
15
+ const memoraxCodeHome = resolve(options.memoraxCodeHome);
16
+ const memoraxCodeCommand = join(resolve(packageRoot), "bin", "memorax-code.mjs");
17
+ if (!(runtime.existsSync ?? existsSync)(memoraxCodeCommand))
18
+ return undefined;
19
+ const readCompletion = runtime.readSetupCompletionRecord ?? readSetupCompletionRecord;
20
+ const readUpdateState = runtime.readAutomaticUpdateState ?? readAutomaticUpdateState;
21
+ const spawnProcess = runtime.spawnProcess ?? spawn;
22
+ const now = runtime.now ?? Date.now;
23
+ const setTimer = runtime.setTimeout
24
+ ?? ((callback, delayMs) => setTimeout(callback, delayMs));
25
+ const clearTimer = runtime.clearTimeout
26
+ ?? ((handle) => clearTimeout(handle));
27
+ let closed = false;
28
+ let childRunning = false;
29
+ let timer;
30
+ let suppressImmediate = env.MEMORAX_CODE_AUTOMATIC_UPDATE_PROCESS === "1";
31
+ const debug = (message) => options.debug?.(message);
32
+ const scheduleAfter = (delayMs) => {
33
+ if (closed)
34
+ return;
35
+ if (timer !== undefined)
36
+ clearTimer(timer);
37
+ timer = setTimer(() => {
38
+ timer = undefined;
39
+ reconcile();
40
+ }, Math.max(1, Math.min(MAX_TIMER_DELAY_MS, Math.trunc(delayMs))));
41
+ timer.unref?.();
42
+ };
43
+ const scheduleRetry = () => scheduleAfter(AUTOMATIC_UPDATE_RETRY_INTERVAL_MS);
44
+ const launch = () => {
45
+ let child;
46
+ try {
47
+ child = spawnProcess(process.execPath, [memoraxCodeCommand, "update", "--automatic", "--home", memoraxCodeHome], {
48
+ detached: true,
49
+ env: {
50
+ ...env,
51
+ MEMORAX_CODE_AUTOMATIC_UPDATE_PROCESS: "1",
52
+ MEMORAX_CODE_HOME: memoraxCodeHome,
53
+ },
54
+ stdio: "ignore",
55
+ windowsHide: true,
56
+ });
57
+ }
58
+ catch (error) {
59
+ debug(`MemoraX Code automatic update could not start: ${errorMessage(error)}`);
60
+ scheduleRetry();
61
+ return;
62
+ }
63
+ childRunning = true;
64
+ let settled = false;
65
+ const finish = (successful) => {
66
+ if (settled)
67
+ return;
68
+ settled = true;
69
+ childRunning = false;
70
+ if (!closed)
71
+ reconcile(!successful);
72
+ };
73
+ child.once("error", (error) => {
74
+ debug(`MemoraX Code automatic update could not start: ${error.message}`);
75
+ finish(false);
76
+ });
77
+ child.once("close", (code, signal) => finish(signal === null && code === 0));
78
+ child.unref();
79
+ };
80
+ function reconcile(deferDue = false) {
81
+ if (closed || childRunning)
82
+ return;
83
+ const suppressDue = suppressImmediate;
84
+ suppressImmediate = false;
85
+ try {
86
+ const completion = readCompletion(memoraxCodeHome);
87
+ const state = readUpdateState(memoraxCodeHome);
88
+ const nowMs = Number(now());
89
+ if (!Number.isFinite(nowMs))
90
+ throw new TypeError("automatic update scheduler clock is invalid");
91
+ if (completion.status !== "valid") {
92
+ scheduleRetry();
93
+ return;
94
+ }
95
+ if (state.status === "valid" && state.record.installedVersion === installedVersion) {
96
+ const remainingMs = Date.parse(state.record.nextCheckAt) - nowMs;
97
+ if (remainingMs > 0) {
98
+ scheduleAfter(remainingMs);
99
+ return;
100
+ }
101
+ }
102
+ }
103
+ catch (error) {
104
+ debug(`MemoraX Code automatic update scheduling failed: ${errorMessage(error)}`);
105
+ scheduleRetry();
106
+ return;
107
+ }
108
+ if (suppressDue || deferDue)
109
+ scheduleRetry();
110
+ else
111
+ launch();
112
+ }
113
+ reconcile();
114
+ return {
115
+ close() {
116
+ if (closed)
117
+ return;
118
+ closed = true;
119
+ if (timer !== undefined)
120
+ clearTimer(timer);
121
+ timer = undefined;
122
+ },
123
+ };
124
+ }
125
+ function automaticUpdateDisabled(value) {
126
+ return ["0", "false", "no", "off", "disabled"].includes(String(value ?? "").trim().toLowerCase());
127
+ }
128
+ function nonEmptyString(value) {
129
+ return typeof value === "string" && value.trim() ? value.trim() : undefined;
130
+ }
131
+ function errorMessage(error) {
132
+ return error instanceof Error ? error.message : String(error);
133
+ }
@@ -8,6 +8,7 @@ export async function prepareClientPluginRemovalCleanup(options = {}) {
8
8
  const claudePluginInstaller = await loadClaudePluginInstaller();
9
9
  const dshProfileLifecycle = await loadDshProfileLifecycle();
10
10
  const openCodePluginInstaller = await loadOpenCodePluginInstaller();
11
+ const codeBuddyPluginInstaller = await loadCodeBuddyPluginInstaller();
11
12
  const home = resolveHome(options.homeDir);
12
13
  const memoraxCodeHome = resolve(options.memoraxCodeHome ?? process.env.MEMORAX_CODE_HOME ?? join(home, ".memorax-code"));
13
14
  const claudeState = await readJsonRecord(join(memoraxCodeHome, "adapters", "claude-code", "state.json"));
@@ -15,7 +16,7 @@ export async function prepareClientPluginRemovalCleanup(options = {}) {
15
16
  return async () => {
16
17
  try {
17
18
  return await withBackendLifecycleLock({ home: memoraxCodeHome }, async () => {
18
- const [codexPlugin, claudePlugin, dshPlugin, opencodePlugin] = await Promise.all([
19
+ const [codexPlugin, claudePlugin, dshPlugin, opencodePlugin, codebuddyPlugin] = await Promise.all([
19
20
  cleanupCodexAfterBackendRemoval({
20
21
  memoraxCodeHome,
21
22
  homeDir: home,
@@ -45,14 +46,21 @@ export async function prepareClientPluginRemovalCleanup(options = {}) {
45
46
  : {}),
46
47
  }))
47
48
  .catch((error) => removalFailure("opencode-plugin-remove", error)),
49
+ Promise.resolve()
50
+ .then(() => codeBuddyPluginInstaller.removeCodeBuddyPluginInstallation({
51
+ memoraxCodeHome,
52
+ ...(options.codeBuddyHome ? { codeBuddyHome: options.codeBuddyHome } : {}),
53
+ }))
54
+ .catch((error) => removalFailure("codebuddy-plugin-remove", error)),
48
55
  ]);
49
56
  return {
50
- ok: codexPlugin.ok && claudePlugin.ok && dshPlugin.ok && opencodePlugin.ok,
57
+ ok: codexPlugin.ok && claudePlugin.ok && dshPlugin.ok && opencodePlugin.ok && codebuddyPlugin.ok,
51
58
  action: "client-plugin-removal-cleanup",
52
59
  codexPlugin,
53
60
  claudePlugin,
54
61
  dshPlugin,
55
62
  opencodePlugin,
63
+ codebuddyPlugin,
56
64
  };
57
65
  });
58
66
  }
@@ -65,6 +73,7 @@ export async function prepareClientPluginRemovalCleanup(options = {}) {
65
73
  claudePlugin: failure,
66
74
  dshPlugin: failure,
67
75
  opencodePlugin: failure,
76
+ codebuddyPlugin: failure,
68
77
  };
69
78
  }
70
79
  };
@@ -75,6 +84,9 @@ async function loadClaudePluginInstaller() {
75
84
  async function loadOpenCodePluginInstaller() {
76
85
  return await import(new URL("../../../memorax-code-opencode-adapter/src/plugin-install.mjs", import.meta.url).href);
77
86
  }
87
+ async function loadCodeBuddyPluginInstaller() {
88
+ return await import(new URL("../../../memorax-code-codebuddy-adapter/src/config.mjs", import.meta.url).href);
89
+ }
78
90
  async function loadDshProfileLifecycle() {
79
91
  return await import(new URL("../../../memorax-code-dsh-adapter/src/profile-lifecycle.mjs", import.meta.url).href);
80
92
  }
@@ -1,5 +1,5 @@
1
1
  import { loadLifecycleMemoraxCodeConfig } from "../config/memorax-code.js";
2
- const allClients = Object.freeze({ codex: true, claude: true, dsh: true, opencode: true });
2
+ const allClients = Object.freeze({ codex: true, claude: true, dsh: true, opencode: true, codebuddy: true });
3
3
  export function resolveManagedClients(argv, config = {}) {
4
4
  const explicit = argValue(argv, "--clients");
5
5
  if (explicit !== undefined)
@@ -12,9 +12,10 @@ export function resolveManagedClients(argv, config = {}) {
12
12
  // automatic local-Harness discovery enabled for existing installations.
13
13
  dsh: config.clients.dsh !== false,
14
14
  opencode: config.clients.opencode === true,
15
+ ...(config.clients.codebuddy === true ? { codebuddy: true } : {}),
15
16
  };
16
17
  }
17
- return allClients;
18
+ return { codex: true, claude: true, dsh: true, opencode: true };
18
19
  }
19
20
  export function parseManagedClients(value) {
20
21
  const normalized = value.trim().toLowerCase();
@@ -23,14 +24,15 @@ export function parseManagedClients(value) {
23
24
  if (normalized === "none")
24
25
  return { codex: false, claude: false, dsh: false, opencode: false };
25
26
  const names = normalized.split(",").map((name) => name.trim()).filter(Boolean);
26
- if (names.length === 0 || names.some((name) => (name !== "codex" && name !== "claude" && name !== "dsh" && name !== "opencode"))) {
27
- throw new Error(`invalid --clients value: ${value}; expected a comma-separated subset of codex, claude, dsh, opencode, or all or none`);
27
+ if (names.length === 0 || names.some((name) => (name !== "codex" && name !== "claude" && name !== "dsh" && name !== "opencode" && name !== "codebuddy"))) {
28
+ throw new Error(`invalid --clients value: ${value}; expected a comma-separated subset of codex, claude, dsh, opencode, codebuddy, or all or none`);
28
29
  }
29
30
  return {
30
31
  codex: names.includes("codex"),
31
32
  claude: names.includes("claude"),
32
33
  dsh: names.includes("dsh"),
33
34
  opencode: names.includes("opencode"),
35
+ ...(names.includes("codebuddy") ? { codebuddy: true } : {}),
34
36
  };
35
37
  }
36
38
  export function loadManagedClientsConfig(memoraxCodeHome) {
@@ -17,6 +17,7 @@ import { codexAdapterLifecycle } from "../clients/codex/lifecycle.js";
17
17
  import { claudeAdapterLifecycle } from "../clients/claude/lifecycle.js";
18
18
  import { collectDshAdapterLifecycleStatus, withDshAdapterLifecycleLock, } from "../clients/dsh/lifecycle.js";
19
19
  import { openCodeAdapterLifecycle } from "../clients/opencode/lifecycle.js";
20
+ import { codeBuddyAdapterLifecycle } from "../clients/codebuddy/lifecycle.js";
20
21
  const PACKAGE_REPLACEMENT_ENV = "MEMORAX_CODE_PACKAGE_REPLACEMENT";
21
22
  const DSH_OPTIONAL_ENV = "MEMORAX_CODE_DSH_ADAPTER_OPTIONAL";
22
23
  const DSH_RECOVERY_ENV = "MEMORAX_CODE_DSH_ADAPTER_RECOVERY";
@@ -58,15 +59,20 @@ export async function collectMemoraxCodeStatus(backendUrl, backendToken, service
58
59
  const opencodeAdapter = clients.opencode
59
60
  ? await openCodeAdapterLifecycle.status({ argv, serviceOptions, backendUrl })
60
61
  : undefined;
62
+ const codebuddyAdapter = clients.codebuddy
63
+ ? await codeBuddyAdapterLifecycle.status({ argv, serviceOptions, backendUrl })
64
+ : undefined;
61
65
  const codexReady = codexAdapter ? isAdapterReady(codexAdapter) : true;
62
66
  const claudeReady = claudeAdapter ? isAdapterReady(claudeAdapter) : true;
63
67
  const dshReady = dshAdapter ? isAdapterReady(dshAdapter) : true;
64
68
  const opencodeReady = opencodeAdapter ? isAdapterReady(opencodeAdapter) : true;
69
+ const codebuddyReady = codebuddyAdapter ? isAdapterReady(codebuddyAdapter) : true;
65
70
  const optionalDshUnavailable = isOptionalUnavailableDshAdapter(dshAdapter);
66
71
  return {
67
72
  ok: backend.ok
68
73
  && codexReady
69
74
  && opencodeReady
75
+ && codebuddyReady
70
76
  && (isOptionalUnconfiguredClaudeAdapter(claudeAdapter, codexAdapter) || claudeReady)
71
77
  && (optionalDshUnavailable || dshReady),
72
78
  action: "status",
@@ -76,6 +82,7 @@ export async function collectMemoraxCodeStatus(backendUrl, backendToken, service
76
82
  ...(claudeAdapter ? { claudeAdapter } : {}),
77
83
  ...(dshAdapter ? { dshAdapter } : {}),
78
84
  ...(opencodeAdapter ? { opencodeAdapter } : {}),
85
+ ...(codebuddyAdapter ? { codebuddyAdapter } : {}),
79
86
  };
80
87
  }
81
88
  export async function startMemoraxCodeService(serviceOptions, argv, commitReadyState) {
@@ -193,10 +200,14 @@ async function executeMemoraxCodeStart(serviceOptions, argv, backendUrl, memorax
193
200
  const deselectedOpenCode = previousClients?.opencode && !clients.opencode
194
201
  ? await openCodeAdapterLifecycle.disable({ argv, serviceOptions })
195
202
  : undefined;
203
+ const deselectedCodeBuddy = previousClients?.codebuddy && !clients.codebuddy
204
+ ? await codeBuddyAdapterLifecycle.disable({ argv, serviceOptions })
205
+ : undefined;
196
206
  if (deselectedCodex?.ok === false
197
207
  || deselectedClaude?.ok === false
198
208
  || deselectedDsh?.ok === false
199
- || deselectedOpenCode?.ok === false) {
209
+ || deselectedOpenCode?.ok === false
210
+ || deselectedCodeBuddy?.ok === false) {
200
211
  const recovery = await recoverPreparationFailure("adapter_disable_failed");
201
212
  return {
202
213
  ok: false,
@@ -208,6 +219,7 @@ async function executeMemoraxCodeStart(serviceOptions, argv, backendUrl, memorax
208
219
  ? { dshAdapter: recovery.dshAdapter ?? deselectedDsh }
209
220
  : {}),
210
221
  ...(deselectedOpenCode ? { opencodeAdapter: deselectedOpenCode } : {}),
222
+ ...(deselectedCodeBuddy ? { codebuddyAdapter: deselectedCodeBuddy } : {}),
211
223
  };
212
224
  }
213
225
  // The marker is a conservative cleanup scope, not a readiness signal. Persist
@@ -247,6 +259,9 @@ async function executeMemoraxCodeStart(serviceOptions, argv, backendUrl, memorax
247
259
  const opencodeAdapter = clients.opencode
248
260
  ? await openCodeAdapterLifecycle.prepareEnable({ argv, serviceOptions, backendUrl })
249
261
  : undefined;
262
+ const codebuddyAdapter = clients.codebuddy
263
+ ? await codeBuddyAdapterLifecycle.prepareEnable({ argv, serviceOptions, backendUrl })
264
+ : undefined;
250
265
  if (opencodeAdapter?.ok === false) {
251
266
  const recovery = await recoverPreparationFailure("opencode_adapter_enable_failed");
252
267
  return {
@@ -257,6 +272,7 @@ async function executeMemoraxCodeStart(serviceOptions, argv, backendUrl, memorax
257
272
  ...(claudeAdapter ? { claudeAdapter } : {}),
258
273
  ...(recovery.dshAdapter ? { dshAdapter: recovery.dshAdapter } : {}),
259
274
  opencodeAdapter,
275
+ ...(codebuddyAdapter ? { codebuddyAdapter } : {}),
260
276
  };
261
277
  }
262
278
  const preparedDshAdapter = markOptionalDshAdapter(quiescedDsh?.ok === false
@@ -274,6 +290,7 @@ async function executeMemoraxCodeStart(serviceOptions, argv, backendUrl, memorax
274
290
  ...(claudeAdapter ? { claudeAdapter } : {}),
275
291
  dshAdapter: recovery.dshAdapter ?? preparedDshAdapter,
276
292
  ...(opencodeAdapter ? { opencodeAdapter } : {}),
293
+ ...(codebuddyAdapter ? { codebuddyAdapter } : {}),
277
294
  };
278
295
  }
279
296
  const backend = await startBackendService(serviceOptions);
@@ -284,6 +301,9 @@ async function executeMemoraxCodeStart(serviceOptions, argv, backendUrl, memorax
284
301
  const disabledOpenCode = opencodeAdapter
285
302
  ? await openCodeAdapterLifecycle.disable({ argv, serviceOptions })
286
303
  : undefined;
304
+ const disabledCodeBuddy = codebuddyAdapter
305
+ ? await codeBuddyAdapterLifecycle.disable({ argv, serviceOptions })
306
+ : undefined;
287
307
  return {
288
308
  ok: false,
289
309
  action: "start",
@@ -292,6 +312,7 @@ async function executeMemoraxCodeStart(serviceOptions, argv, backendUrl, memorax
292
312
  ...(claudeAdapter ? { claudeAdapter } : {}),
293
313
  ...(preparedDshAdapter ? { dshAdapter: preparedDshAdapter } : {}),
294
314
  ...(disabledOpenCode ? { opencodeAdapter: disabledOpenCode } : {}),
315
+ ...(disabledCodeBuddy ? { codebuddyAdapter: disabledCodeBuddy } : {}),
295
316
  };
296
317
  }
297
318
  const dshAdapter = markOptionalDshAdapter(preparedDshAdapter?.installed === true
@@ -308,6 +329,7 @@ async function executeMemoraxCodeStart(serviceOptions, argv, backendUrl, memorax
308
329
  ...(claudeAdapter ? { claudeAdapter } : {}),
309
330
  ...(dshAdapter ? { dshAdapter } : {}),
310
331
  ...(opencodeAdapter ? { opencodeAdapter } : {}),
332
+ ...(codebuddyAdapter ? { codebuddyAdapter } : {}),
311
333
  };
312
334
  }
313
335
  function expectedBackendUrl(serviceOptions) {
@@ -350,13 +372,15 @@ async function executeMemoraxCodeStop(serviceOptions, argv, context, dshLifecycl
350
372
  claude: activeClients.claude && !clients.claude,
351
373
  dsh: activeClients.dsh && !clients.dsh,
352
374
  opencode: activeClients.opencode && !clients.opencode,
375
+ codebuddy: activeClients.codebuddy && !clients.codebuddy,
353
376
  }
354
377
  : undefined;
355
378
  const hasRemainingClients = remaining?.codex === true
356
379
  || remaining?.claude === true
357
380
  || remaining?.dsh === true
358
- || remaining?.opencode === true;
359
- const backendOnlyStop = !clients.codex && !clients.claude && !clients.dsh && !clients.opencode;
381
+ || remaining?.opencode === true
382
+ || remaining?.codebuddy === true;
383
+ const backendOnlyStop = !clients.codex && !clients.claude && !clients.dsh && !clients.opencode && !clients.codebuddy;
360
384
  const packageReplacement = isPackageReplacement();
361
385
  const needsBackendStop = packageReplacement || backendOnlyStop || !hasRemainingClients;
362
386
  const quiescedDsh = clients.dsh && dshLifecycle
@@ -405,10 +429,14 @@ async function executeMemoraxCodeStop(serviceOptions, argv, context, dshLifecycl
405
429
  const opencodeAdapter = clients.opencode
406
430
  ? await openCodeAdapterLifecycle.disable({ argv, serviceOptions })
407
431
  : undefined;
432
+ const codebuddyAdapter = clients.codebuddy
433
+ ? await codeBuddyAdapterLifecycle.disable({ argv, serviceOptions })
434
+ : undefined;
408
435
  const adaptersOk = codexAdapter?.ok !== false
409
436
  && claudeAdapter?.ok !== false
410
437
  && dshAdapter?.ok !== false
411
- && opencodeAdapter?.ok !== false;
438
+ && opencodeAdapter?.ok !== false
439
+ && codebuddyAdapter?.ok !== false;
412
440
  const backend = stoppedBackend
413
441
  ?? (adaptersOk
414
442
  ? preservedBackendResult(serviceOptions, "active_clients_remaining")
@@ -417,7 +445,8 @@ async function executeMemoraxCodeStop(serviceOptions, argv, context, dshLifecycl
417
445
  && codexAdapter?.ok !== false
418
446
  && claudeAdapter?.ok !== false
419
447
  && dshAdapter?.ok !== false
420
- && opencodeAdapter?.ok !== false;
448
+ && opencodeAdapter?.ok !== false
449
+ && codebuddyAdapter?.ok !== false;
421
450
  return {
422
451
  report: {
423
452
  ok,
@@ -427,6 +456,7 @@ async function executeMemoraxCodeStop(serviceOptions, argv, context, dshLifecycl
427
456
  ...(claudeAdapter ? { claudeAdapter } : {}),
428
457
  ...(dshAdapter ? { dshAdapter } : {}),
429
458
  ...(opencodeAdapter ? { opencodeAdapter } : {}),
459
+ ...(codebuddyAdapter ? { codebuddyAdapter } : {}),
430
460
  },
431
461
  remainingClients: packageReplacement
432
462
  ? activeClients
@@ -535,10 +565,14 @@ async function executeMemoraxCodeUninstall(serviceOptions, argv, context, dshLif
535
565
  const opencodePlugin = clients.opencode
536
566
  ? await openCodeAdapterLifecycle.remove({ argv, serviceOptions })
537
567
  : undefined;
568
+ const codebuddyPlugin = clients.codebuddy
569
+ ? await codeBuddyAdapterLifecycle.remove({ argv, serviceOptions })
570
+ : undefined;
538
571
  const pluginCleanupOk = codexPlugin?.ok !== false
539
572
  && claudePlugin?.ok !== false
540
573
  && dshPlugin?.ok !== false
541
- && opencodePlugin?.ok !== false;
574
+ && opencodePlugin?.ok !== false
575
+ && codebuddyPlugin?.ok !== false;
542
576
  const npmPackageRemoval = !pluginCleanupOk
543
577
  ? skippedNpmPackageRemoval("plugin_cleanup_failed")
544
578
  : canRemoveSharedPackage
@@ -562,11 +596,13 @@ async function executeMemoraxCodeUninstall(serviceOptions, argv, context, dshLif
562
596
  ...(claudeAdapter ? { claudeAdapter } : {}),
563
597
  ...(dshAdapter ? { dshAdapter } : {}),
564
598
  ...(opencodeAdapter ? { opencodeAdapter } : {}),
599
+ ...(codebuddyPlugin ? { codebuddyPlugin } : {}),
565
600
  npmPackageRemoval,
566
601
  removesPlugin: codexPlugin?.ok === true
567
602
  || claudePlugin?.ok === true
568
603
  || (dshPlugin?.ok === true && dshPlugin.skipped !== true)
569
- || opencodePlugin?.ok === true,
604
+ || opencodePlugin?.ok === true
605
+ || codebuddyPlugin?.ok === true,
570
606
  removesUserState: false,
571
607
  };
572
608
  }
@@ -691,7 +727,8 @@ function includesManagedClients(selection, required) {
691
727
  return (!required.codex || selection.codex)
692
728
  && (!required.claude || selection.claude)
693
729
  && (!required.dsh || selection.dsh)
694
- && (!required.opencode || selection.opencode);
730
+ && (!required.opencode || selection.opencode)
731
+ && (!required.codebuddy || selection.codebuddy === true);
695
732
  }
696
733
  function isPackageReplacement() {
697
734
  return truthyEnv(process.env[PACKAGE_REPLACEMENT_ENV]);
@@ -819,7 +856,9 @@ export function isAdapterReady(report) {
819
856
  && report.backendUrlMatches !== false
820
857
  && report.codexSkills?.ok !== false
821
858
  && report.claudeSkills?.ok !== false
822
- && report.opencodeSkills?.ok !== false;
859
+ && report.opencodeSkills?.ok !== false
860
+ && report.codebuddyHooks?.ok !== false
861
+ && report.codebuddySkills?.ok !== false;
823
862
  }
824
863
  export function isOptionalUnavailableDshAdapter(report) {
825
864
  return Boolean(report?.optional === true && !isAdapterReady(report));
@@ -238,9 +238,9 @@ async function resolveMemoryCliRepositoryMemory(options) {
238
238
  }
239
239
  function memoryCliRepositoryFailure(action, failure, fields = {}) {
240
240
  const userAction = failure.reason === "workspace_scope_mismatch"
241
- ? "Start a new Codex, Claude Code, DSH, or OpenCode session from the target repository or local workspace."
241
+ ? "Start a new Codex, Claude Code, WorkBuddy, DSH, or OpenCode session from the target repository or local workspace."
242
242
  : failure.reason === "workspace_scope_unavailable"
243
- ? "Start a new Codex, Claude Code, DSH, or OpenCode session from the target repository or local workspace. If the problem continues, make sure its .git metadata is readable and valid."
243
+ ? "Start a new Codex, Claude Code, WorkBuddy, DSH, or OpenCode session from the target repository or local workspace. If the problem continues, make sure its .git metadata is readable and valid."
244
244
  : undefined;
245
245
  return {
246
246
  ok: false,
@@ -336,6 +336,8 @@ function traceClientLabel(client) {
336
336
  return "DSH";
337
337
  if (client === "opencode")
338
338
  return "OpenCode";
339
+ if (client === "codebuddy")
340
+ return "WorkBuddy";
339
341
  return client === "codex" ? "Codex" : "coding agent";
340
342
  }
341
343
  function memoryAddContentOptions(args) {
@@ -1,4 +1,5 @@
1
1
  import { isRecord } from "../shared/record.js";
2
+ import { codeBuddyPromptDigest, parseCodeBuddyTurnId } from "../clients/codebuddy/turn-id.js";
2
3
  export const MEMORY_HOOK_COMMAND_VERSION = 1;
3
4
  export const INVALID_MEMORY_HOOK_COMMAND = "invalid memory Hook command";
4
5
  const BASE_COMMAND_KEYS = [
@@ -13,6 +14,7 @@ const TURN_START_KEYS = {
13
14
  "claude-code": new Set([...BASE_COMMAND_KEYS, "promptId", "prompt", "transcriptPath"]),
14
15
  opencode: new Set([...BASE_COMMAND_KEYS, "userMessageId", "prompt"]),
15
16
  dsh: new Set(["version", "client", "sessionId", "turn", "startSeq", "cwd", "prompt"]),
17
+ codebuddy: new Set([...BASE_COMMAND_KEYS, "turnId", "prompt", "transcriptPath"]),
16
18
  };
17
19
  const WRITEBACK_KEYS = {
18
20
  codex: new Set([...BASE_COMMAND_KEYS, "turnId", "lastAssistantMessage", "transcriptPath"]),
@@ -39,12 +41,14 @@ const WRITEBACK_KEYS = {
39
41
  "sessionHeader",
40
42
  "events",
41
43
  ]),
44
+ codebuddy: new Set([...BASE_COMMAND_KEYS, "turnId", "transcriptPath"]),
42
45
  };
43
46
  const SKILL_REMINDER_KEYS = {
44
47
  codex: new Set([...BASE_COMMAND_KEYS, "turnId", "transcriptPath", "content", "triggers"]),
45
48
  "claude-code": new Set([...BASE_COMMAND_KEYS, "promptId", "transcriptPath", "content", "triggers"]),
46
49
  dsh: new Set(["version", "client", "sessionId", "turn", "cwd", "content", "triggers"]),
47
50
  opencode: new Set([...BASE_COMMAND_KEYS, "userMessageId", "content", "triggers"]),
51
+ codebuddy: new Set([...BASE_COMMAND_KEYS, "turnId", "transcriptPath", "content", "triggers"]),
48
52
  };
49
53
  export function parseTurnStartCommand(value) {
50
54
  if (!isRecord(value))
@@ -102,6 +106,13 @@ export function parseTurnStartCommand(value) {
102
106
  },
103
107
  };
104
108
  }
109
+ if (base.client === "codebuddy") {
110
+ const turnId = requiredStringField(value, "turnId");
111
+ const transcriptPath = requiredStringField(value, "transcriptPath");
112
+ if (!turnId || !transcriptPath || !validCodeBuddyTurnId(turnId, base.sessionId, prompt))
113
+ return invalidCommand();
114
+ return { ok: true, command: { ...base, client: "codebuddy", turnId, prompt, transcriptPath } };
115
+ }
105
116
  const promptId = requiredStringField(value, "promptId");
106
117
  const transcriptPath = requiredStringField(value, "transcriptPath");
107
118
  if (!promptId || !transcriptPath)
@@ -166,6 +177,13 @@ export function parseWritebackCommand(value) {
166
177
  },
167
178
  };
168
179
  }
180
+ if (base.client === "codebuddy") {
181
+ const turnId = requiredStringField(value, "turnId");
182
+ const transcriptPath = requiredStringField(value, "transcriptPath");
183
+ if (!turnId || !transcriptPath || !validCodeBuddyTurnId(turnId, base.sessionId))
184
+ return invalidCommand();
185
+ return { ok: true, command: { ...base, client: "codebuddy", turnId, transcriptPath } };
186
+ }
169
187
  const lastAssistantMessage = requiredStringField(value, "lastAssistantMessage");
170
188
  if (!lastAssistantMessage)
171
189
  return invalidCommand();
@@ -241,6 +259,13 @@ export function parseSkillReminderCommand(value) {
241
259
  },
242
260
  };
243
261
  }
262
+ if (base.client === "codebuddy") {
263
+ const turnId = requiredStringField(value, "turnId");
264
+ const transcriptPath = requiredStringField(value, "transcriptPath");
265
+ if (!turnId || !transcriptPath || !validCodeBuddyTurnId(turnId, base.sessionId))
266
+ return invalidCommand();
267
+ return { ok: true, command: { ...base, client: "codebuddy", turnId, transcriptPath, content, triggers } };
268
+ }
244
269
  const transcriptPath = requiredStringField(value, "transcriptPath");
245
270
  if (!transcriptPath)
246
271
  return invalidCommand();
@@ -284,7 +309,8 @@ function parseCommandBase(value, allowedKeys) {
284
309
  if (client !== "codex"
285
310
  && client !== "claude-code"
286
311
  && client !== "opencode"
287
- && client !== "dsh")
312
+ && client !== "dsh"
313
+ && client !== "codebuddy")
288
314
  return undefined;
289
315
  const clientKeys = allowedKeys[client];
290
316
  if (!clientKeys || Object.keys(value).some((key) => !clientKeys.has(key)))
@@ -342,6 +368,10 @@ function optionalStringField(value, key) {
342
368
  const field = requiredStringField(value, key);
343
369
  return field ? { ok: true, value: field } : { ok: false };
344
370
  }
371
+ function validCodeBuddyTurnId(turnId, sessionId, prompt) {
372
+ const identity = parseCodeBuddyTurnId({ sessionId, turnId });
373
+ return Boolean(identity && (prompt === undefined || identity.promptDigest === codeBuddyPromptDigest(prompt)));
374
+ }
345
375
  function invalidCommand() {
346
376
  return { ok: false, error: INVALID_MEMORY_HOOK_COMMAND };
347
377
  }
@@ -1,5 +1,5 @@
1
1
  import { parseSkillReminderCommand, } from "./hook-command.js";
2
- import { traceContextFromClaudeHookBody, traceContextFromDshSkillReminder, traceContextFromHookBody, traceContextFromOpenCodeHookBody, } from "../trace/context.js";
2
+ import { traceContextFromClaudeHookBody, traceContextFromDshSkillReminder, traceContextFromHookBody, traceContextFromOpenCodeHookBody, traceContextFromCodeBuddyHookBody, } from "../trace/context.js";
3
3
  import { recordTraceEvent } from "../trace/store.js";
4
4
  export function createMemoryReminderTraceRecorder(options = {}) {
5
5
  return {
@@ -47,6 +47,8 @@ function traceContextForReminder(command) {
47
47
  return traceContextFromClaudeHookBody(command);
48
48
  if (command.client === "dsh")
49
49
  return traceContextFromDshSkillReminder(command);
50
+ if (command.client === "codebuddy")
51
+ return traceContextFromCodeBuddyHookBody(command);
50
52
  return traceContextFromOpenCodeHookBody(command);
51
53
  }
52
54
  function reminderSource(command) {
@@ -56,6 +58,8 @@ function reminderSource(command) {
56
58
  return "claude-hook";
57
59
  if (command.client === "dsh")
58
60
  return "dsh-cordis";
61
+ if (command.client === "codebuddy")
62
+ return "codebuddy-hook";
59
63
  return "opencode-plugin";
60
64
  }
61
65
  async function recordTraceBestEffort(label, promise, diagnosticLogger) {
@@ -3,6 +3,7 @@ import { createCodexMemoryHookRuntime, } from "../clients/codex/memory-hook-runt
3
3
  import { createClaudeMemoryHookRuntime, } from "../clients/claude/memory-hook-runtime.js";
4
4
  import { createDshMemoryHookRuntime, } from "../clients/dsh/memory-hook-runtime.js";
5
5
  import { createOpenCodeMemoryHookRuntime, } from "../clients/opencode/memory-hook-runtime.js";
6
+ import { createCodeBuddyMemoryHookRuntime } from "../clients/codebuddy/memory-hook-runtime.js";
6
7
  import { createMemoryTurnCoordinator } from "./turn-coordinator.js";
7
8
  import { createRepositoryMemorySessionRuntime, } from "./repository-session.js";
8
9
  import { createPendingQuotaNoticeRuntime } from "./quota-notice.js";
@@ -49,6 +50,12 @@ export function createMemoryService(options = {}) {
49
50
  repositoryMemorySession,
50
51
  turnCoordinator,
51
52
  });
53
+ const codeBuddyHook = createCodeBuddyMemoryHookRuntime({
54
+ ...options,
55
+ pendingQuotaNotice,
56
+ repositoryMemorySession,
57
+ turnCoordinator,
58
+ });
52
59
  let closed = false;
53
60
  return {
54
61
  async recordTurnStart(command) {
@@ -61,6 +68,8 @@ export function createMemoryService(options = {}) {
61
68
  return await openCodeHook.recordTurnStart(command);
62
69
  case "dsh":
63
70
  return await dshHook.recordTurnStart(command);
71
+ case "codebuddy":
72
+ return await codeBuddyHook.recordTurnStart(command);
64
73
  }
65
74
  return unsupportedMemoryHookCommand(command);
66
75
  },
@@ -74,6 +83,8 @@ export function createMemoryService(options = {}) {
74
83
  return await openCodeHook.writeback(command);
75
84
  case "dsh":
76
85
  return await dshHook.writeback(command);
86
+ case "codebuddy":
87
+ return await codeBuddyHook.writeback(command);
77
88
  }
78
89
  return unsupportedMemoryHookCommand(command);
79
90
  },
@@ -88,6 +99,7 @@ export function createMemoryService(options = {}) {
88
99
  claudeHook.close();
89
100
  openCodeHook.close();
90
101
  dshHook.close();
102
+ codeBuddyHook.close();
91
103
  turnCoordinator.close();
92
104
  repositoryMemorySession.close();
93
105
  automaticWriteback.close();
@@ -25,6 +25,9 @@ export function resolveWindowsCliInvocation(command, args, options = {}) {
25
25
  resolvedCommand ??= command;
26
26
  if (/\.(?:exe|com)$/i.test(resolvedCommand))
27
27
  return { command: resolvedCommand, args };
28
+ if (isBareCodeBuddyEntrypoint(resolvedCommand, pathApi) && fileExists(resolvedCommand)) {
29
+ return { command: options.nodePath ?? process.execPath, args: [resolvedCommand, ...args] };
30
+ }
28
31
  if (!/\.(?:cmd|bat)$/i.test(resolvedCommand)) {
29
32
  throw new Error(`refusing to execute unsupported Windows command shim: ${resolvedCommand}`);
30
33
  }
@@ -87,12 +90,18 @@ export function selectWindowsCommandCandidate(command, output) {
87
90
  .map((value) => value.trim())
88
91
  .filter(Boolean);
89
92
  const selected = candidates.find((value) => /\.(?:cmd|bat)$/i.test(value))
90
- ?? candidates.find((value) => /\.(?:exe|com)$/i.test(value));
93
+ ?? candidates.find((value) => /\.(?:exe|com)$/i.test(value))
94
+ ?? (path.win32.basename(command).toLowerCase() === "codebuddy"
95
+ ? candidates.find((value) => isBareCodeBuddyEntrypoint(value, path.win32))
96
+ : undefined);
91
97
  if (!selected) {
92
98
  throw new Error(`where.exe did not return a safe executable or command shim for ${command}`);
93
99
  }
94
100
  return selected;
95
101
  }
102
+ function isBareCodeBuddyEntrypoint(value, pathApi) {
103
+ return pathApi.basename(value).toLowerCase() === "codebuddy" && pathApi.extname(value) === "";
104
+ }
96
105
  export function resolveWindowsNpmInvocation(args, env, options = {}) {
97
106
  const platform = options.platform ?? process.platform;
98
107
  if (platform !== "win32")