@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,250 @@
1
+ import { readCodeBuddyInterruptedTranscriptTurn, readCodeBuddyTranscriptTurn, } from "./jsonl-history.js";
2
+ import { retrieveAutomaticMemoryContext } from "../../memory/automatic-retrieval.js";
3
+ import { createAutomaticMemoryWritebackRuntime } from "../../memory/automatic-writeback.js";
4
+ import { claimQuotaNotice, createPendingQuotaNoticeRuntime, } from "../../memory/quota-notice.js";
5
+ import { createMemoryTurnCoordinator } from "../../memory/turn-coordinator.js";
6
+ import { createRepositoryMemorySessionRuntime, resolvedRepoMemoryWorktree } from "../../memory/repository-session.js";
7
+ import { traceContextFromCodeBuddyHookBody } from "../../trace/context.js";
8
+ import { markCurrentCodeBuddyTurnOutcome, readOpenTraceTurn, recordCodeBuddyTraceEvent, traceTurnEventId, writeCurrentCodeBuddyTurn, } from "../../trace/store.js";
9
+ export function createCodeBuddyMemoryHookRuntime(options = {}) {
10
+ const now = options.now ?? (() => Date.now());
11
+ const pendingQuotaNotice = options.pendingQuotaNotice ?? createPendingQuotaNoticeRuntime({
12
+ claimQuotaNotice: options.claimQuotaNotice,
13
+ diagnosticLogger: options.diagnosticLogger,
14
+ env: options.env,
15
+ });
16
+ const ownsPendingQuotaNotice = options.pendingQuotaNotice === undefined;
17
+ const automatic = options.turnCoordinator ? undefined : options.automaticWriteback ? { enqueue: options.automaticWriteback, discardForScopeUpgrade: () => 0, drain: async () => undefined, close: () => undefined } : createAutomaticMemoryWritebackRuntime({ diagnosticLogger: options.diagnosticLogger, queueQuotaNotice: pendingQuotaNotice.queue });
18
+ const coordinator = options.turnCoordinator ?? createMemoryTurnCoordinator({ automaticWriteback: automatic.enqueue, now, ttlMs: options.ttlMs, maxEntries: options.maxEntries, cleanupIntervalMs: options.cleanupIntervalMs });
19
+ const repository = options.repositoryMemorySession ?? createRepositoryMemorySessionRuntime({ onScopeUpgrade: automatic?.discardForScopeUpgrade });
20
+ return {
21
+ async recordTurnStart(command) {
22
+ await reconcilePreviousInterruptedTurn(coordinator, command, options, now);
23
+ const memory = await resolveRepository(command, options, repository);
24
+ const traceContext = traceContextFromCodeBuddyHookBody(command, new Date(now()).toISOString());
25
+ coordinator.recordTurnStart({ client: "codebuddy", sessionId: command.sessionId, clientTurnId: command.turnId, cwd: command.cwd, workspaceKind: command.workspaceKind, transcriptPath: command.transcriptPath, createdAt: now(), traceContext, repositoryMemory: memory });
26
+ await recordTraceBestEffort("codebuddy_memory_hook.turn_start_event", recordCodeBuddyTraceEvent({
27
+ eventId: traceTurnEventId(traceContext, "turn_start"),
28
+ memoraxCodeHome: options.memoraxCodeHome,
29
+ env: options.env,
30
+ traceContext,
31
+ type: "turn_start",
32
+ source: "unknown",
33
+ operation: "query",
34
+ ok: true,
35
+ request: { prompt: command.prompt, cwd: command.cwd, transcriptPath: command.transcriptPath },
36
+ }), options.diagnosticLogger);
37
+ await recordTraceBestEffort("codebuddy_memory_hook.current_turn_write", writeCurrentCodeBuddyTurn(traceContext, {
38
+ memoraxCodeHome: options.memoraxCodeHome,
39
+ env: options.env,
40
+ now: () => new Date(now()),
41
+ }), options.diagnosticLogger);
42
+ const repoMemoryWorktree = resolvedRepoMemoryWorktree(memory);
43
+ const pendingUserNotice = memory.ok
44
+ ? await pendingQuotaNotice.claim(memory.memory.config)
45
+ : undefined;
46
+ const retrieval = await retrieveAutomaticMemoryContext({ diagnosticLogger: options.diagnosticLogger, claimQuotaNotice: options.claimQuotaNotice ?? claimQuotaNotice, env: options.env ?? process.env, fetchImpl: options.fetchImpl, memoryObservability: options.memoryObservability, memoryObservabilitySource: "codebuddy_hook_retrieval", query: command.prompt, repositoryMemory: memory, sessionKey: command.sessionId, traceContext: traceContextFromCodeBuddyHookBody(command) });
47
+ const userNotice = [pendingUserNotice, retrieval.userNotice].filter(Boolean).join("\n");
48
+ return { ok: true, ...(repoMemoryWorktree ? { repoMemoryWorktree } : {}), ...(retrieval.context ? { additionalContext: retrieval.context } : {}), ...(userNotice ? { userNotice } : {}) };
49
+ },
50
+ async writeback(command) {
51
+ const entry = coordinator.getTurn({ client: "codebuddy", sessionId: command.sessionId, clientTurnId: command.turnId });
52
+ if (!command.sessionId)
53
+ return { ok: true, scheduled: false, reason: "missing_session_id" };
54
+ if (!command.turnId)
55
+ return { ok: true, scheduled: false, reason: "turn_id_missing" };
56
+ if (entry?.transcriptPath && entry.transcriptPath !== command.transcriptPath) {
57
+ await recordCodeBuddyTurnEnd(options, traceContextForWriteback(command, entry), undefined, "transcript_path_mismatch");
58
+ return { ok: true, scheduled: false, reason: "transcript_path_mismatch" };
59
+ }
60
+ const traceContext = traceContextForWriteback(command, entry);
61
+ const transcript = await readWithRetry({ transcriptPath: command.transcriptPath, sessionId: command.sessionId, turnId: command.turnId }, options);
62
+ if (!transcript.ok) {
63
+ await recordCodeBuddyTurnEnd(options, traceContext, undefined, transcript.reason);
64
+ return { ok: true, scheduled: false, reason: transcript.reason };
65
+ }
66
+ await recordCodeBuddyTurnEnd(options, traceContext, transcript.turn);
67
+ const memory = await resolveRepository({ ...command, cwd: command.cwd ?? entry?.cwd, workspaceKind: command.workspaceKind ?? entry?.workspaceKind }, options, repository);
68
+ const completed = await coordinator.completeMaterializedTurn({ key: { client: "codebuddy", sessionId: command.sessionId, clientTurnId: command.turnId }, metadata: entry, resolveRepositoryMemory: async () => memory, userText: transcript.turn.userPrompt, assistantText: transcript.turn.assistantReply, writeback: { client: "codebuddy", sessionKey: command.sessionId, env: options.env ?? process.env, fetchImpl: options.fetchImpl, memoryObservability: options.memoryObservability, memoryObservabilitySource: "codebuddy_hook_writeback", traceContext: traceContextFromCodeBuddyHookBody(command) } });
69
+ await recordCodeBuddyTurnMaterialization(options, traceContext, transcript.turn);
70
+ return completed.scheduled ? { ok: true, scheduled: true } : { ok: true, scheduled: false, reason: completed.reason };
71
+ },
72
+ size() { return coordinator.size("codebuddy"); },
73
+ close() { if (!options.turnCoordinator)
74
+ coordinator.close(); if (!options.repositoryMemorySession)
75
+ repository.close(); automatic?.close?.(); if (ownsPendingQuotaNotice)
76
+ pendingQuotaNotice.close(); },
77
+ };
78
+ }
79
+ async function resolveRepository(command, options, repository) {
80
+ return repository.resolve({ client: "codebuddy", sessionId: command.sessionId, workspaceRoot: command.cwd, workspaceKind: command.workspaceKind, memoraxCodeHome: options.memoraxCodeHome ?? options.env?.MEMORAX_CODE_HOME, env: options.env });
81
+ }
82
+ async function readWithRetry(input, options) {
83
+ const attempts = options.transcriptReadAttempts ?? 6;
84
+ let result = await readCodeBuddyTranscriptTurn(input);
85
+ for (let i = 1; i < attempts && !result.ok && ["transcript_unavailable", "turn_not_found", "user_prompt_missing", "assistant_message_missing"].includes(result.reason); i += 1) {
86
+ await new Promise((resolve) => setTimeout(resolve, options.transcriptRetryDelayMs ?? 100));
87
+ result = await readCodeBuddyTranscriptTurn(input);
88
+ }
89
+ return result;
90
+ }
91
+ function traceContextForWriteback(command, entry) {
92
+ return traceContextFromCodeBuddyHookBody({
93
+ ...command,
94
+ cwd: command.cwd ?? entry?.cwd,
95
+ workspaceKind: command.workspaceKind ?? entry?.workspaceKind,
96
+ }) ?? entry?.traceContext;
97
+ }
98
+ async function recordCodeBuddyTurnEnd(options, traceContext, turn, failureReason) {
99
+ const outcome = turn ? "completed" : failureReason === "assistant_message_missing" ? "interrupted" : undefined;
100
+ const recorded = await recordTraceBestEffort("codebuddy_memory_hook.turn_end_event", recordCodeBuddyTraceEvent({
101
+ eventId: traceTurnEventId(traceContext, "turn_end"),
102
+ memoraxCodeHome: options.memoraxCodeHome,
103
+ env: options.env,
104
+ traceContext,
105
+ type: "turn_end",
106
+ source: turn ? "codebuddy-hook" : "codebuddy-transcript",
107
+ operation: "reply",
108
+ ok: Boolean(turn),
109
+ ...(outcome ? { outcome } : {}),
110
+ ...(turn ? {
111
+ activities: turn.activities,
112
+ sessionTurnIndex: turn.sessionTurnIndex,
113
+ request: { prompt: turn.userPrompt },
114
+ response: { assistantMessage: turn.assistantReply },
115
+ } : {
116
+ error: failureReason,
117
+ }),
118
+ }), options.diagnosticLogger);
119
+ if (outcome) {
120
+ await recordTraceBestEffort("codebuddy_memory_hook.current_turn_close", markCurrentCodeBuddyTurnOutcome(traceContext, outcome, { memoraxCodeHome: options.memoraxCodeHome, env: options.env }), options.diagnosticLogger);
121
+ }
122
+ return recorded;
123
+ }
124
+ async function recordCodeBuddyTurnMaterialization(options, traceContext, turn) {
125
+ const originalEventId = traceTurnEventId(traceContext, "turn_end");
126
+ if (!originalEventId)
127
+ return;
128
+ await recordTraceBestEffort("codebuddy_memory_hook.turn_materialized_event", recordCodeBuddyTraceEvent({
129
+ eventId: traceTurnEventId(traceContext, "turn_materialized"),
130
+ memoraxCodeHome: options.memoraxCodeHome,
131
+ env: options.env,
132
+ traceContext,
133
+ type: "turn_materialized",
134
+ source: "codebuddy-transcript",
135
+ operation: "reply",
136
+ ok: true,
137
+ outcome: "completed",
138
+ activities: turn.activities,
139
+ sessionTurnIndex: turn.sessionTurnIndex,
140
+ request: { original_event_id: originalEventId, prompt: turn.userPrompt },
141
+ response: { assistantMessage: turn.assistantReply },
142
+ }), options.diagnosticLogger);
143
+ }
144
+ async function reconcilePreviousInterruptedTurn(coordinator, currentTurn, options, now) {
145
+ const candidate = await previousInterruptedTurnCandidate(coordinator, currentTurn, options, now);
146
+ if (!candidate)
147
+ return;
148
+ const transcript = await readCodeBuddyInterruptedTranscriptTurn({
149
+ transcriptPath: candidate.transcriptPath,
150
+ sessionId: candidate.sessionId,
151
+ turnId: candidate.turnId,
152
+ });
153
+ if (!transcript.ok)
154
+ return;
155
+ await recordCodeBuddyInterruptedTurnEnd(options, candidate.traceContext, transcript.turn);
156
+ coordinator.discardTurn({
157
+ client: "codebuddy",
158
+ sessionId: candidate.sessionId,
159
+ clientTurnId: candidate.turnId,
160
+ }, "interrupted");
161
+ options.diagnosticLogger?.("codebuddy_memory_hook.interrupted_turn_reconciled", {
162
+ sessionId: candidate.sessionId,
163
+ turnId: candidate.turnId,
164
+ assistantChars: transcript.turn.assistantReply.length,
165
+ activityCount: transcript.turn.activities.length,
166
+ sessionTurnIndex: transcript.turn.sessionTurnIndex,
167
+ });
168
+ }
169
+ async function previousInterruptedTurnCandidate(coordinator, currentTurn, options, now) {
170
+ const open = await readOpenTraceTurn({
171
+ client: "codebuddy",
172
+ memoraxCodeHome: options.memoraxCodeHome,
173
+ env: options.env,
174
+ expectedSessionId: currentTurn.sessionId,
175
+ allowStale: true,
176
+ now: () => new Date(now()),
177
+ });
178
+ if (!open.ok && open.reason === "closed")
179
+ return undefined;
180
+ if (open.ok && open.traceContext.turnId && open.traceContext.turnId !== currentTurn.turnId) {
181
+ const turnId = open.traceContext.turnId;
182
+ const cached = coordinator.getTurn({ client: "codebuddy", sessionId: currentTurn.sessionId, clientTurnId: turnId });
183
+ return {
184
+ sessionId: currentTurn.sessionId,
185
+ turnId,
186
+ transcriptPath: cached?.transcriptPath
187
+ ?? open.traceContext.transcriptPath
188
+ ?? currentTurn.transcriptPath,
189
+ traceContext: cached?.traceContext ?? open.traceContext,
190
+ };
191
+ }
192
+ const cached = coordinator.latestTurn({
193
+ client: "codebuddy",
194
+ sessionId: currentTurn.sessionId,
195
+ excludeClientTurnId: currentTurn.turnId,
196
+ });
197
+ if (!cached)
198
+ return undefined;
199
+ const transcriptPath = cached.transcriptPath ?? currentTurn.transcriptPath;
200
+ const traceContext = cached.traceContext ?? traceContextFromCodeBuddyHookBody({
201
+ sessionId: cached.sessionId,
202
+ turnId: cached.clientTurnId,
203
+ cwd: cached.cwd,
204
+ workspaceKind: cached.workspaceKind,
205
+ transcriptPath,
206
+ }, new Date(cached.createdAt).toISOString());
207
+ if (!traceContext)
208
+ return undefined;
209
+ return {
210
+ sessionId: cached.sessionId,
211
+ turnId: cached.clientTurnId,
212
+ transcriptPath,
213
+ traceContext,
214
+ };
215
+ }
216
+ async function recordCodeBuddyInterruptedTurnEnd(options, traceContext, turn) {
217
+ const recorded = await recordTraceBestEffort("codebuddy_memory_hook.interrupted_turn_end_event", recordCodeBuddyTraceEvent({
218
+ eventId: traceTurnEventId(traceContext, "turn_end"),
219
+ memoraxCodeHome: options.memoraxCodeHome,
220
+ env: options.env,
221
+ traceContext,
222
+ type: "turn_end",
223
+ source: "codebuddy-transcript",
224
+ operation: "reply",
225
+ ok: true,
226
+ outcome: "interrupted",
227
+ activities: turn.activities,
228
+ sessionTurnIndex: turn.sessionTurnIndex,
229
+ request: { prompt: turn.userPrompt },
230
+ response: { assistantMessage: turn.assistantReply },
231
+ }), options.diagnosticLogger);
232
+ if (!recorded || (!recorded.written && recorded.reason !== "duplicate_event"))
233
+ return;
234
+ await recordTraceBestEffort("codebuddy_memory_hook.interrupted_current_turn_close", markCurrentCodeBuddyTurnOutcome(traceContext, "interrupted", {
235
+ memoraxCodeHome: options.memoraxCodeHome,
236
+ env: options.env,
237
+ }), options.diagnosticLogger);
238
+ }
239
+ async function recordTraceBestEffort(label, promise, diagnosticLogger) {
240
+ try {
241
+ return await promise;
242
+ }
243
+ catch (error) {
244
+ diagnosticLogger?.("codebuddy_trace.write_failed", {
245
+ label,
246
+ error: error instanceof Error ? error.message : String(error),
247
+ });
248
+ return undefined;
249
+ }
250
+ }
@@ -0,0 +1,16 @@
1
+ import { createHash } from "node:crypto";
2
+ export function parseCodeBuddyTurnId(input) {
3
+ const prefix = `${input.sessionId}:`;
4
+ if (!input.turnId.startsWith(prefix))
5
+ return undefined;
6
+ const match = /^(0|[1-9]\d*):([0-9a-f]{64})$/.exec(input.turnId.slice(prefix.length));
7
+ if (!match)
8
+ return undefined;
9
+ const boundary = Number(match[1]);
10
+ if (!Number.isSafeInteger(boundary))
11
+ return undefined;
12
+ return { boundary, promptDigest: match[2] };
13
+ }
14
+ export function codeBuddyPromptDigest(prompt) {
15
+ return createHash("sha256").update(prompt.trim()).digest("hex");
16
+ }
@@ -11,7 +11,9 @@ import { confirmHookTrust, listMemoraxCodeHooks, trustCodexPluginHookSelection,
11
11
  import { resolveWindowsCliInvocation } from "../../shared/windows-cli-invocation.js";
12
12
  const PLUGIN_NAME = "memorax-code-codex-adapter";
13
13
  const CLI_MARKETPLACE_NAME = "memorax-code";
14
+ const PLUGIN_ID = `${PLUGIN_NAME}@${CLI_MARKETPLACE_NAME}`;
14
15
  const ADAPTER_COMMON_NAME = "memorax-code-adapter-common";
16
+ const PLUGIN_LIST_TIMEOUT_MS = 10_000;
15
17
  export async function installCodexPlugin(options = {}) {
16
18
  const home = resolveHome(options.homeDir);
17
19
  const codexHome = resolveCodexHome(options.codexHome, home);
@@ -124,14 +126,25 @@ export async function activateCodexPlugin(options = {}) {
124
126
  if (!existsSync(join(cliMarketplaceRoot, ".agents", "plugins", "marketplace.json"))) {
125
127
  await stageCodexCliMarketplace(install);
126
128
  }
129
+ const registrationBefore = await inspectCodexPluginRegistration({
130
+ ...options,
131
+ codexHome: install.codexHome,
132
+ codexCommand,
133
+ workspace,
134
+ });
127
135
  let marketplaceAdd;
128
136
  let pluginAdd;
129
- if (install.registrationMode === "bootstrap") {
130
- marketplaceAdd = await runCommand(codexCommand, ["plugin", "marketplace", "add", cliMarketplaceRoot, "--json"], { cwd: workspace, env });
131
- if (!marketplaceAdd.ok) {
132
- throw new Error(`codex plugin marketplace add failed: ${marketplaceAdd.stderr || marketplaceAdd.stdout || "unknown error"}`);
137
+ if (!registrationBefore.registered || !registrationBefore.enabled) {
138
+ if (registrationBefore.available) {
139
+ marketplaceAdd = skippedPluginCommand("marketplace_registration_preserved");
140
+ }
141
+ else {
142
+ marketplaceAdd = await runCommand(codexCommand, ["plugin", "marketplace", "add", cliMarketplaceRoot, "--json"], { cwd: workspace, env });
143
+ if (!marketplaceAdd.ok) {
144
+ throw new Error(`codex plugin marketplace add failed: ${marketplaceAdd.stderr || marketplaceAdd.stdout || "unknown error"}`);
145
+ }
133
146
  }
134
- pluginAdd = await runCommand(codexCommand, ["plugin", "add", `${PLUGIN_NAME}@${CLI_MARKETPLACE_NAME}`, "--json"], { cwd: workspace, env });
147
+ pluginAdd = await runCommand(codexCommand, ["plugin", "add", PLUGIN_ID, "--json"], { cwd: workspace, env });
135
148
  if (!pluginAdd.ok) {
136
149
  throw new Error(`codex plugin add failed: ${pluginAdd.stderr || pluginAdd.stdout || "unknown error"}`);
137
150
  }
@@ -140,6 +153,19 @@ export async function activateCodexPlugin(options = {}) {
140
153
  marketplaceAdd = skippedPluginCommand("versioned_installation_preserved");
141
154
  pluginAdd = skippedPluginCommand("versioned_installation_preserved");
142
155
  }
156
+ const registration = await inspectCodexPluginRegistration({
157
+ ...options,
158
+ codexHome: install.codexHome,
159
+ codexCommand,
160
+ workspace,
161
+ });
162
+ const expectedVersion = stringField(await readJsonRecord(join(install.pluginSourcePath, ".codex-plugin", "plugin.json")), "version");
163
+ if (!registration.registered || !registration.enabled) {
164
+ throw new Error("Codex plugin registration was not enabled after activation");
165
+ }
166
+ if (!expectedVersion || registration.version !== expectedVersion) {
167
+ throw new Error(`Codex plugin registration version does not match ${expectedVersion ?? "the installed plugin"}`);
168
+ }
143
169
  await removePersonalMarketplaceEntry(bootstrapMarketplacePath);
144
170
  const hooks = await listMemoraxCodeHooks(codexCommand, workspace, env);
145
171
  if (hooks.length === 0) {
@@ -163,12 +189,37 @@ export async function activateCodexPlugin(options = {}) {
163
189
  workspace,
164
190
  marketplaceAdd,
165
191
  pluginAdd,
192
+ registrationBefore,
193
+ registration,
166
194
  hooks,
167
195
  trustedHooks: hooks.length,
168
196
  configPath,
169
197
  startsBackend: false,
170
198
  };
171
199
  }
200
+ export async function inspectCodexPluginRegistration(options = {}) {
201
+ const home = resolveHome(options.homeDir);
202
+ const codexHome = resolveCodexHome(options.codexHome, home);
203
+ const codexCommand = options.codexCommand ?? process.env.CODEX_CLI_PATH ?? "codex";
204
+ const workspace = resolve(options.workspace ?? process.cwd());
205
+ const result = await runCommand(codexCommand, ["plugin", "list", "--available", "--json"], {
206
+ cwd: workspace,
207
+ env: { ...process.env, HOME: home, CODEX_HOME: codexHome },
208
+ timeoutMs: PLUGIN_LIST_TIMEOUT_MS,
209
+ });
210
+ if (!result.ok) {
211
+ throw new Error(`codex plugin list failed: ${result.stderr || result.stdout || "unknown error"}`);
212
+ }
213
+ const state = parseCodexPluginList(result.stdout);
214
+ return {
215
+ ok: true,
216
+ action: "codex-plugin-registration",
217
+ codexHome,
218
+ codexCommand,
219
+ workspace,
220
+ ...state,
221
+ };
222
+ }
172
223
  export async function removeCodexPlugin(options = {}) {
173
224
  const home = resolveHome(options.homeDir);
174
225
  const codexHome = resolveCodexHome(options.codexHome, home);
@@ -368,10 +419,12 @@ function mjsFiles(root) {
368
419
  }
369
420
  async function writePluginMetadata(pluginSourcePath, codexCommand) {
370
421
  const normalizedCodexCommand = nonEmpty(codexCommand);
422
+ const npmExecPath = nonEmpty(process.env.MEMORAX_CODE_NPM_EXEC_PATH);
371
423
  const metadata = {
372
424
  version: 1,
373
425
  memoraxCodeCommand: process.argv[1],
374
426
  ...(normalizedCodexCommand ? { codexCommand: normalizedCodexCommand } : {}),
427
+ ...(npmExecPath ? { npmExecPath } : {}),
375
428
  writtenAt: new Date().toISOString(),
376
429
  };
377
430
  await writeFile(join(pluginSourcePath, ".memorax-code-package.json"), `${JSON.stringify(metadata, null, 2)}\n`, "utf8");
@@ -447,6 +500,55 @@ async function readMarketplace(marketplacePath) {
447
500
  function skippedPluginCommand(reason) {
448
501
  return { ok: true, stdout: "", stderr: "", skipped: true, reason };
449
502
  }
503
+ function parseCodexPluginList(stdout) {
504
+ let parsed;
505
+ try {
506
+ parsed = JSON.parse(stdout);
507
+ }
508
+ catch {
509
+ throw new Error("codex plugin list returned invalid JSON");
510
+ }
511
+ if (!isRecord(parsed) || !Array.isArray(parsed.installed) || !Array.isArray(parsed.available)) {
512
+ throw new Error("codex plugin list returned an invalid registry payload");
513
+ }
514
+ const installed = matchingPluginEntry(parsed.installed, "installed");
515
+ const available = matchingPluginEntry(parsed.available, "available");
516
+ if (!installed) {
517
+ return {
518
+ available: available !== undefined,
519
+ registered: false,
520
+ enabled: false,
521
+ };
522
+ }
523
+ const version = typeof installed.version === "string" ? nonEmpty(installed.version) : undefined;
524
+ if (installed.installed !== true
525
+ || typeof installed.enabled !== "boolean"
526
+ || !version) {
527
+ throw new Error("codex plugin list returned an invalid MemoraX Code registration");
528
+ }
529
+ return {
530
+ available: true,
531
+ registered: true,
532
+ enabled: installed.enabled,
533
+ version,
534
+ };
535
+ }
536
+ function matchingPluginEntry(entries, collection) {
537
+ const matches = entries.filter((entry) => isRecord(entry) && (entry.pluginId === PLUGIN_ID
538
+ || (entry.name === PLUGIN_NAME && entry.marketplaceName === CLI_MARKETPLACE_NAME)));
539
+ if (matches.length > 1) {
540
+ throw new Error(`codex plugin list returned duplicate MemoraX Code ${collection} entries`);
541
+ }
542
+ const entry = matches[0];
543
+ if (!entry)
544
+ return undefined;
545
+ if (entry.pluginId !== PLUGIN_ID
546
+ || entry.name !== PLUGIN_NAME
547
+ || entry.marketplaceName !== CLI_MARKETPLACE_NAME) {
548
+ throw new Error(`codex plugin list returned an invalid MemoraX Code ${collection} entry`);
549
+ }
550
+ return entry;
551
+ }
450
552
  function pluginEntry(sourcePath) {
451
553
  return {
452
554
  name: PLUGIN_NAME,
@@ -495,13 +597,33 @@ function runCommand(command, args, options) {
495
597
  });
496
598
  let stdout = "";
497
599
  let stderr = "";
600
+ let settled = false;
601
+ let timeout;
602
+ const finish = (result) => {
603
+ if (settled)
604
+ return;
605
+ settled = true;
606
+ if (timeout)
607
+ clearTimeout(timeout);
608
+ resolveResult(result);
609
+ };
610
+ if (options.timeoutMs) {
611
+ timeout = setTimeout(() => {
612
+ child.kill();
613
+ finish({
614
+ ok: false,
615
+ stdout,
616
+ stderr: stderr || `command timed out after ${options.timeoutMs}ms`,
617
+ });
618
+ }, options.timeoutMs);
619
+ }
498
620
  child.stdout.on("data", (chunk) => { stdout += String(chunk); });
499
621
  child.stderr.on("data", (chunk) => { stderr += String(chunk); });
500
622
  child.on("error", (error) => {
501
- resolveResult({ ok: false, stdout, stderr: stderr || error.message });
623
+ finish({ ok: false, stdout, stderr: stderr || error.message });
502
624
  });
503
625
  child.on("close", (code) => {
504
- resolveResult({ ok: code === 0, stdout, stderr });
626
+ finish({ ok: code === 0, stdout, stderr });
505
627
  });
506
628
  });
507
629
  }
@@ -70,6 +70,10 @@ export function renderDefaultMemoraxCodeConfig() {
70
70
  "enabled = true # Enable local OpenCode session memory trace collection.",
71
71
  "capture_content = true # Store content in local OpenCode trace events.",
72
72
  "",
73
+ "[trace.codebuddy]",
74
+ "enabled = true # Enable local CodeBuddy session memory trace collection.",
75
+ "capture_content = true # Store content in local CodeBuddy trace events.",
76
+ "",
73
77
  ].join("\n");
74
78
  }
75
79
  export async function seedMissingMemoraxCodeConfig(memoraxCodeHome = defaultMemoraxCodeHome(process.env)) {
@@ -146,12 +150,14 @@ function normalizeMemoraxCodeConfig(value) {
146
150
  const traceClaude = recordValue(trace?.claude);
147
151
  const traceDsh = recordValue(trace?.dsh);
148
152
  const traceOpenCode = recordValue(trace?.opencode);
153
+ const traceCodeBuddy = recordValue(trace?.codebuddy);
149
154
  return (prune({
150
155
  clients: prune({
151
156
  codex: booleanField(clients, "codex"),
152
157
  claude: booleanField(clients, "claude"),
153
158
  dsh: booleanField(clients, "dsh"),
154
159
  opencode: booleanField(clients, "opencode"),
160
+ codebuddy: booleanField(clients, "codebuddy"),
155
161
  }),
156
162
  memorax: prune({
157
163
  endpoint: stringField(memorax, "endpoint"),
@@ -230,6 +236,13 @@ function normalizeMemoraxCodeConfig(value) {
230
236
  max_event_chars: numberField(traceOpenCode, "max_event_chars"),
231
237
  max_file_bytes: numberField(traceOpenCode, "max_file_bytes"),
232
238
  }),
239
+ codebuddy: prune({
240
+ enabled: booleanField(traceCodeBuddy, "enabled"),
241
+ capture_content: booleanField(traceCodeBuddy, "capture_content"),
242
+ retention_days: numberField(traceCodeBuddy, "retention_days"),
243
+ max_event_chars: numberField(traceCodeBuddy, "max_event_chars"),
244
+ max_file_bytes: numberField(traceCodeBuddy, "max_file_bytes"),
245
+ }),
233
246
  }),
234
247
  }) ?? {});
235
248
  }
@@ -242,7 +255,7 @@ function validateRawLifecycleConfig(value, path) {
242
255
  const clients = tableValue(rawClients);
243
256
  if (!clients)
244
257
  throw invalidLifecycleConfig(path, "clients must be a table");
245
- for (const field of ["codex", "claude", "dsh", "opencode"]) {
258
+ for (const field of ["codex", "claude", "dsh", "opencode", "codebuddy"]) {
246
259
  if (clients[field] !== undefined && typeof clients[field] !== "boolean") {
247
260
  throw invalidLifecycleConfig(path, `clients.${field} must be a boolean`);
248
261
  }
@@ -8,7 +8,8 @@ import { createBackendServer } from "../app/backend-server.js";
8
8
  import { backendDebug } from "../shared/debug-log.js";
9
9
  import { prepareClientPluginRemovalCleanup } from "../lifecycle/client-plugin-removal.js";
10
10
  import { resolveBackendInstallWatchdogConfig, startBackendInstallWatchdog, } from "../lifecycle/install-watchdog.js";
11
- import { activateCodexPlugin, installCodexPlugin } from "../clients/codex/plugin-install.js";
11
+ import { startBackendAutomaticUpdateScheduler, } from "../lifecycle/automatic-update-scheduler.js";
12
+ import { activateCodexPlugin, inspectCodexPluginRegistration, installCodexPlugin, } from "../clients/codex/plugin-install.js";
12
13
  import { inspectCodexPluginHooks, trustCodexPluginHooks } from "../clients/codex/plugin-hooks.js";
13
14
  import { collectMemoraxCodeStatus, isAdapterReady, isOptionalUnavailableDshAdapter, isOptionalUnconfiguredClaudeAdapter, restartMemoraxCodeService, startMemoraxCodeService, stopMemoraxCodeService, uninstallMemoraxCodeService, } from "../lifecycle/orchestrator.js";
14
15
  import { backendEnv } from "../config/backend-env.js";
@@ -29,9 +30,9 @@ export function runBackendCli(argv = process.argv) {
29
30
  `Usage: ${usageName} [${commands}] [--backend-url URL] [--backend-token TOKEN] [--home DIR]`,
30
31
  "[--host HOST] [--port PORT] [--rotate] [--show]",
31
32
  "[--codex-command CMD]",
32
- "[--codex-home DIR] [--claude-home DIR] [--dsh-home DIR] [--opencode-config-dir DIR]",
33
+ "[--codex-home DIR] [--claude-home DIR] [--dsh-home DIR] [--opencode-config-dir DIR] [--codebuddy-home DIR]",
33
34
  "[--dsh-command CMD] [--dsh-adapter-root DIR] [--memorax-code-command CMD]",
34
- "[--clients codex|claude|dsh|opencode|CLIENT,...|all|none]",
35
+ "[--clients codex|claude|dsh|opencode|codebuddy|CLIENT,...|all|none]",
35
36
  "[--json]",
36
37
  "[--marketplace-path FILE] [--plugin-source-path DIR] [--claude-command CMD] [--help]",
37
38
  "[--yes]",
@@ -210,12 +211,14 @@ async function startRawBackendServer(state, host, port, argv) {
210
211
  dshHome: argValue(argv, "--dsh-home"),
211
212
  dshAdapterRoot: argValue(argv, "--dsh-adapter-root"),
212
213
  openCodeConfigDir: argValue(argv, "--opencode-config-dir"),
214
+ codeBuddyHome: argValue(argv, "--codebuddy-home"),
213
215
  codexCommand: argValue(argv, "--codex-command"),
214
216
  claudeCommand: argValue(argv, "--claude-command"),
215
217
  dshCommand: argValue(argv, "--dsh-command"),
216
218
  })
217
219
  : undefined;
218
220
  const server = createBackendServer(state);
221
+ let automaticUpdateScheduler;
219
222
  let installRemovalStarted = false;
220
223
  const installWatchdog = startBackendInstallWatchdog(installWatchdogConfig, async (event) => {
221
224
  if (installRemovalStarted)
@@ -245,7 +248,10 @@ async function startRawBackendServer(state, host, port, argv) {
245
248
  });
246
249
  });
247
250
  installBackendShutdownControls(server, state);
248
- server.once("close", () => installWatchdog?.close());
251
+ server.once("close", () => {
252
+ automaticUpdateScheduler?.close();
253
+ installWatchdog?.close();
254
+ });
249
255
  server.on("error", (error) => {
250
256
  if (error.code === "EADDRINUSE") {
251
257
  console.error(`memorax-code-backend failed to listen on http://${host}:${port}: address already in use`);
@@ -258,6 +264,15 @@ async function startRawBackendServer(state, host, port, argv) {
258
264
  });
259
265
  server.listen(port, host, () => {
260
266
  console.log(`memorax-code-backend listening on http://${host}:${port}`);
267
+ if (backendEnv("INSTANCE_ID")) {
268
+ automaticUpdateScheduler = startBackendAutomaticUpdateScheduler({
269
+ env: process.env,
270
+ memoraxCodeHome: state.sessionHome,
271
+ packageRoot: process.env.MEMORAX_CODE_NPM_PACKAGE_ROOT,
272
+ packageVersion: process.env.MEMORAX_CODE_NPM_PACKAGE_VERSION,
273
+ debug: (message) => backendDebug("automatic_update.scheduler_failed", { message }),
274
+ });
275
+ }
261
276
  });
262
277
  }
263
278
  function installBackendShutdownControls(server, state) {
@@ -382,6 +397,13 @@ async function runCodexPluginCommand(argv) {
382
397
  yes: argv.includes("--yes"),
383
398
  });
384
399
  }
400
+ if (subcommand === "registration") {
401
+ return await inspectCodexPluginRegistration({
402
+ codexHome: argValue(argv, "--codex-home"),
403
+ codexCommand: argValue(argv, "--codex-command"),
404
+ workspace: argValue(argv, "--workspace"),
405
+ });
406
+ }
385
407
  if (subcommand === "hooks") {
386
408
  return await inspectCodexPluginHooks({
387
409
  codexHome: argValue(argv, "--codex-home"),
@@ -427,6 +449,15 @@ function printCodexPluginInstallResult(report) {
427
449
  console.log("backend: not started");
428
450
  return;
429
451
  }
452
+ if (report.action === "codex-plugin-registration") {
453
+ console.log(`codex home: ${report.codexHome}`);
454
+ console.log(`registered: ${report.registered ? "yes" : "no"}`);
455
+ console.log(`enabled: ${report.enabled ? "yes" : "no"}`);
456
+ if (report.version)
457
+ console.log(`version: ${report.version}`);
458
+ console.log("backend: not started");
459
+ return;
460
+ }
430
461
  const install = report.action === "codex-plugin-activate" ? report.install : report;
431
462
  console.log(`codex home: ${install.codexHome}`);
432
463
  console.log(`plugin source: ${install.pluginSourcePath}`);
@@ -490,6 +521,8 @@ function printMemoraxCodeStatus(report) {
490
521
  backendLog(`DSH adapter: ${dshAdapterStatusLine(report.dshAdapter)}`);
491
522
  if (report.opencodeAdapter)
492
523
  backendLog(`OpenCode adapter: ${adapterStatusLine(report.opencodeAdapter)}`);
524
+ if (report.codebuddyAdapter)
525
+ backendLog(`CodeBuddy adapter: ${adapterStatusLine(report.codebuddyAdapter)}`);
493
526
  if (!suppressBackendGuidance()) {
494
527
  for (const line of statusGuidance(report))
495
528
  backendLog(line);
@@ -591,6 +624,8 @@ function printLifecycleResult(report) {
591
624
  backendLog(`DSH adapter: ${dshAdapterStatusLine(report.dshAdapter)}`);
592
625
  if (report.opencodeAdapter)
593
626
  backendLog(`OpenCode adapter: ${adapterStatusLine(report.opencodeAdapter)}`);
627
+ if (report.codebuddyAdapter)
628
+ backendLog(`CodeBuddy adapter: ${adapterStatusLine(report.codebuddyAdapter)}`);
594
629
  if (report.codexPlugin) {
595
630
  const removed = report.codexPlugin.removedPaths.length;
596
631
  const marketplace = report.codexPlugin.marketplaceChanged ? " marketplace=updated" : " marketplace=unchanged";
@@ -848,11 +883,13 @@ function adapterStatusLine(report) {
848
883
  const enabled = isAdapterReady(report);
849
884
  const skillStatus = report.codexSkills?.status
850
885
  ?? report.claudeSkills?.status
851
- ?? report.opencodeSkills?.status;
886
+ ?? report.opencodeSkills?.status
887
+ ?? report.codebuddySkills?.status;
852
888
  const skills = skillStatus ? ` skills=${skillStatus}` : "";
889
+ const hooks = report.codebuddyHooks?.status ? ` hook-runtime=${report.codebuddyHooks.status}` : "";
853
890
  const changed = report.changed === true ? " changed" : "";
854
891
  const integration = report.integration ?? report.state?.integration ?? "hooks";
855
- return `${enabled ? "ok" : "not enabled"} integration=${integration}${skills}${changed}`;
892
+ return `${enabled ? "ok" : "not enabled"} integration=${integration}${skills}${hooks}${changed}`;
856
893
  }
857
894
  function selectedLifecycleClientNames(report) {
858
895
  return [
@@ -10,6 +10,8 @@ export function readActiveManagedClients(memoraxCodeHome) {
10
10
  return undefined;
11
11
  if (value.opencode !== undefined && typeof value.opencode !== "boolean")
12
12
  return undefined;
13
+ if (value.codebuddy !== undefined && typeof value.codebuddy !== "boolean")
14
+ return undefined;
13
15
  if (value.dsh !== undefined && typeof value.dsh !== "boolean")
14
16
  return undefined;
15
17
  return {
@@ -19,6 +21,7 @@ export function readActiveManagedClients(memoraxCodeHome) {
19
21
  // that the Backend was serving DSH.
20
22
  dsh: value.dsh === true,
21
23
  opencode: value.opencode === true,
24
+ ...(value.codebuddy === true ? { codebuddy: true } : {}),
22
25
  };
23
26
  }
24
27
  catch {