@memorax/memorax-code 0.1.8 → 0.1.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 (102) 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 +82 -22
  6. package/docs/troubleshooting.md +69 -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/clients/codex/rollout-turn.js +52 -2
  25. package/lib/memorax-code-backend/dist/config/memorax-code.js +14 -1
  26. package/lib/memorax-code-backend/dist/entrypoints/backend-cli.js +43 -6
  27. package/lib/memorax-code-backend/dist/lifecycle/active-clients.js +3 -0
  28. package/lib/memorax-code-backend/dist/lifecycle/automatic-update-scheduler.js +133 -0
  29. package/lib/memorax-code-backend/dist/lifecycle/client-plugin-removal.js +14 -2
  30. package/lib/memorax-code-backend/dist/lifecycle/client-selection.js +6 -4
  31. package/lib/memorax-code-backend/dist/lifecycle/orchestrator.js +48 -9
  32. package/lib/memorax-code-backend/dist/memory/cli.js +4 -2
  33. package/lib/memorax-code-backend/dist/memory/hook-command.js +31 -1
  34. package/lib/memorax-code-backend/dist/memory/reminder-trace-recorder.js +5 -1
  35. package/lib/memorax-code-backend/dist/memory/service.js +12 -0
  36. package/lib/memorax-code-backend/dist/shared/windows-cli-invocation.js +10 -1
  37. package/lib/memorax-code-backend/dist/trace/config.js +12 -0
  38. package/lib/memorax-code-backend/dist/trace/context.js +21 -1
  39. package/lib/memorax-code-backend/dist/trace/store.js +12 -1
  40. package/lib/memorax-code-backend/package.json +1 -1
  41. package/lib/memorax-code-claude-adapter/.claude-plugin/plugin.json +1 -1
  42. package/lib/memorax-code-claude-adapter/hooks/runtime-shell.json +1 -1
  43. package/lib/memorax-code-claude-adapter/package.json +1 -1
  44. package/lib/memorax-code-claude-adapter/runtime-hooks/ensure-backend.mjs +2 -0
  45. package/lib/memorax-code-claude-adapter/src/plugin-install.mjs +3 -0
  46. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/.claude-plugin/plugin.json +1 -1
  47. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/hooks/runtime-shell.json +1 -1
  48. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/automatic-update-state.d.mts +24 -0
  49. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/automatic-update-state.mjs +73 -0
  50. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/clients/codebuddy-command.mjs +82 -0
  51. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/config-utils.d.mts +2 -0
  52. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/config-utils.mjs +11 -0
  53. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/hooks/client-hook-launcher.mjs +3 -0
  54. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/hooks/ensure-backend-runner.mjs +45 -10
  55. 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
  56. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/windows-cli-invocation.mjs +11 -1
  57. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/package.json +1 -1
  58. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/runtime-hooks/ensure-backend.mjs +2 -0
  59. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/src/plugin-install.mjs +3 -0
  60. package/lib/memorax-code-codebuddy-adapter/.codebuddy-plugin/plugin.json +7 -0
  61. package/lib/memorax-code-codebuddy-adapter/hooks/common-runtime.mjs +13 -0
  62. package/lib/memorax-code-codebuddy-adapter/hooks/hooks.json +38 -0
  63. package/lib/memorax-code-codebuddy-adapter/hooks/repo-memory-job.mjs +40 -0
  64. package/lib/memorax-code-codebuddy-adapter/hooks/runtime-hook.mjs +279 -0
  65. package/lib/memorax-code-codebuddy-adapter/package.json +9 -0
  66. package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/SKILL.md +85 -0
  67. package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/agents/claude.yaml +10 -0
  68. package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/agents/openai.yaml +7 -0
  69. package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/defaults.json +12 -0
  70. package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/references/memorax-add.md +88 -0
  71. package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/references/memorax-search.md +93 -0
  72. package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/references/personal-read.md +46 -0
  73. package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/references/personal-write.md +120 -0
  74. package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/references/repo-build.md +319 -0
  75. package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/references/repo-read.md +103 -0
  76. package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/references/repo-templates.md +390 -0
  77. package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/references/repo-update.md +127 -0
  78. package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/scripts/collect_all.py +579 -0
  79. package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/scripts/detect_updates.py +919 -0
  80. package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/scripts/git_commit_facets.py +222 -0
  81. package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/scripts/github_resource_facets.py +512 -0
  82. package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/scripts/gitlab_resource_facets.py +517 -0
  83. package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/scripts/prepare_repo_memory.py +411 -0
  84. package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/scripts/user_profile_memory.py +528 -0
  85. package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/scripts/validate_memory.py +248 -0
  86. package/lib/memorax-code-codebuddy-adapter/src/cli.mjs +58 -0
  87. package/lib/memorax-code-codebuddy-adapter/src/config.mjs +277 -0
  88. package/lib/memorax-code-codebuddy-adapter/src/hook-manifest.mjs +47 -0
  89. package/lib/memorax-code-codebuddy-adapter/src/runtime-observation.mjs +65 -0
  90. package/lib/memorax-code-codex-adapter/.codex-plugin/plugin.json +1 -1
  91. package/lib/memorax-code-codex-adapter/hooks/runtime-shell.json +1 -1
  92. package/lib/memorax-code-codex-adapter/package.json +1 -1
  93. package/lib/memorax-code-codex-adapter/runtime-hooks/ensure-backend.mjs +4 -1
  94. package/lib/memorax-code-dsh-adapter/package.json +2 -1
  95. package/lib/memorax-code-dsh-adapter/src/profile-lifecycle.mjs +1 -0
  96. package/lib/memorax-code-opencode-adapter/package.json +1 -1
  97. package/lib/npm-invocation.mjs +51 -8
  98. package/lib/resolve-codebuddy-command.mjs +112 -0
  99. package/lib/run-entrypoint.mjs +25 -1
  100. package/lib/windows-cli-invocation.mjs +11 -1
  101. package/lib/windows-user-path.mjs +218 -0
  102. package/package.json +5 -3
@@ -21,7 +21,7 @@ let requestedSignal;
21
21
  for (const signal of ["SIGINT", "SIGTERM"]) {
22
22
  process.on(signal, () => {
23
23
  requestedSignal = signal;
24
- activeChild?.kill(signal);
24
+ if (activeChild) terminateClient(activeChild, signal);
25
25
  });
26
26
  }
27
27
 
@@ -56,12 +56,16 @@ async function main(args) {
56
56
  runId: workerContext.runId,
57
57
  });
58
58
  if (initial.status !== "started") throw new Error(`repo memory job cannot start from status ${initial.status}`);
59
+ const clientTimeoutMs = repoMemoryClientTimeoutMs();
60
+ const clientKillGraceMs = repoMemoryClientKillGraceMs();
59
61
  let state = writeJobState(request.jobPath, {
60
62
  ...initial,
61
63
  status: "running",
62
64
  pid: process.pid,
63
65
  workerPid: process.pid,
64
66
  workerStartedAt: new Date().toISOString(),
67
+ clientTimeoutMs,
68
+ clientKillGraceMs,
65
69
  });
66
70
 
67
71
  if (requestedSignal) {
@@ -85,9 +89,18 @@ async function main(args) {
85
89
  });
86
90
  },
87
91
  finalMessagePath: state.finalMessagePath,
92
+ timeoutMs: clientTimeoutMs,
93
+ killGraceMs: clientKillGraceMs,
88
94
  });
89
95
  state = readJobState(request.jobPath);
90
96
 
97
+ if (childResult.timedOut) {
98
+ return finishFailed(request, state, workerContext, `${runner}_timeout`, {
99
+ timeoutMs: childResult.timeoutMs,
100
+ elapsedMs: childResult.elapsedMs,
101
+ signal: childResult.signal,
102
+ });
103
+ }
91
104
  if (childResult.error) {
92
105
  return finishFailed(request, state, workerContext, `${runner}_spawn_failed`, {
93
106
  error: childResult.error.message,
@@ -213,6 +226,11 @@ async function waitForOwnedMarker(input) {
213
226
  function runClient(command, options) {
214
227
  return new Promise((resolveResult) => {
215
228
  let settled = false;
229
+ let timeoutHandle;
230
+ let killHandle;
231
+ let timedOut = false;
232
+ let timeoutMs;
233
+ const startedAt = Date.now();
216
234
  let invocation;
217
235
  try {
218
236
  invocation = process.platform === "win32" && /\.[cm]?js$/i.test(command[0])
@@ -228,6 +246,7 @@ function runClient(command, options) {
228
246
  const child = spawn(invocation.command, invocation.args, {
229
247
  cwd: options.cwd,
230
248
  env: options.env,
249
+ detached: process.platform !== "win32",
231
250
  stdio: options.captureStdout ? ["ignore", "pipe", "inherit"] : "inherit",
232
251
  });
233
252
  activeChild = child;
@@ -243,8 +262,10 @@ function runClient(command, options) {
243
262
  const finish = (result) => {
244
263
  if (settled) return;
245
264
  settled = true;
265
+ if (timeoutHandle) clearTimeout(timeoutHandle);
266
+ if (killHandle) clearTimeout(killHandle);
246
267
  activeChild = undefined;
247
- resolveResult(result);
268
+ resolveResult({ ...result, timedOut, timeoutMs, elapsedMs: Date.now() - startedAt });
248
269
  };
249
270
  child.once("error", (error) => finish({ code: undefined, signal: undefined, error }));
250
271
  child.once("close", (code, signal) => {
@@ -258,10 +279,74 @@ function runClient(command, options) {
258
279
  }
259
280
  finish({ code, signal, error: undefined });
260
281
  });
261
- if (requestedSignal) child.kill(requestedSignal);
282
+ if (Number.isFinite(options.timeoutMs) && options.timeoutMs > 0) {
283
+ timeoutHandle = setTimeout(() => {
284
+ timeoutMs = options.timeoutMs;
285
+ timedOut = true;
286
+ const terminated = terminateClient(child, "SIGTERM");
287
+ // A broken or ignored CLI must not leave a detached job behind forever.
288
+ // The grace period is deliberately short and only targets this child.
289
+ killHandle = setTimeout(() => {
290
+ if (!settled) terminateClient(child, "SIGKILL");
291
+ }, options.killGraceMs);
292
+ if (!terminated && !settled) {
293
+ finish({
294
+ code: undefined,
295
+ signal: "SIGTERM",
296
+ error: undefined,
297
+ timedOut: true,
298
+ timeoutMs,
299
+ });
300
+ }
301
+ }, options.timeoutMs);
302
+ }
303
+ if (requestedSignal) terminateClient(child, requestedSignal);
262
304
  });
263
305
  }
264
306
 
307
+ function terminateClient(child, signal) {
308
+ if (!child || !Number.isInteger(child.pid) || child.pid <= 0) return false;
309
+ if (process.platform !== "win32") {
310
+ try {
311
+ process.kill(-child.pid, signal);
312
+ return true;
313
+ } catch {
314
+ // The process group may have exited between the timeout and this call.
315
+ }
316
+ }
317
+ if (process.platform === "win32") {
318
+ try {
319
+ const result = spawnSync("taskkill", ["/PID", String(child.pid), "/T", "/F"], {
320
+ encoding: "utf8",
321
+ windowsHide: true,
322
+ stdio: ["ignore", "ignore", "ignore"],
323
+ });
324
+ if (result.status === 0) return true;
325
+ } catch {
326
+ // Fall through to the direct child termination below.
327
+ }
328
+ }
329
+ try {
330
+ return child.kill(signal);
331
+ } catch {
332
+ return false;
333
+ }
334
+ }
335
+
336
+ function repoMemoryClientTimeoutMs(env = process.env) {
337
+ return positiveEnvInteger(env.MEMORAX_CODE_REPO_MEMORY_JOB_TIMEOUT_MS, 10 * 60 * 1000);
338
+ }
339
+
340
+ function repoMemoryClientKillGraceMs(env = process.env) {
341
+ return positiveEnvInteger(env.MEMORAX_CODE_REPO_MEMORY_JOB_KILL_GRACE_MS, 5000);
342
+ }
343
+
344
+ function positiveEnvInteger(value, fallback) {
345
+ if (typeof value !== "string" || !/^\d+$/.test(value.trim())) return fallback;
346
+ const parsed = Number(value.trim());
347
+ return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : fallback;
348
+ }
349
+
265
350
  function validateBundle(repo, validator) {
266
351
  const python = process.env.MEMORAX_CODE_REPO_MEMORY_PYTHON_COMMAND || "python3";
267
352
  process.stdout.write("\n[repo-memory-worker] validating generated repo memory\n");
@@ -24,6 +24,9 @@ export function resolveWindowsCliInvocation(command, args, options = {}) {
24
24
  }
25
25
  resolvedCommand ??= command;
26
26
  if (/\.(?:exe|com)$/i.test(resolvedCommand)) return { command: resolvedCommand, args };
27
+ if (isBareCodeBuddyEntrypoint(resolvedCommand, pathApi) && fileExists(resolvedCommand)) {
28
+ return { command: options.nodePath ?? process.execPath, args: [resolvedCommand, ...args] };
29
+ }
27
30
  if (!/\.(?:cmd|bat)$/i.test(resolvedCommand)) {
28
31
  throw new Error(`refusing to execute unsupported Windows command shim: ${resolvedCommand}`);
29
32
  }
@@ -104,9 +107,16 @@ export function selectWindowsCommandCandidate(command, output) {
104
107
  .map((value) => value.trim())
105
108
  .filter(Boolean);
106
109
  const selected = candidates.find((value) => /\.(?:cmd|bat)$/i.test(value))
107
- ?? candidates.find((value) => /\.(?:exe|com)$/i.test(value));
110
+ ?? candidates.find((value) => /\.(?:exe|com)$/i.test(value))
111
+ ?? (path.win32.basename(command).toLowerCase() === "codebuddy"
112
+ ? candidates.find((value) => isBareCodeBuddyEntrypoint(value, path.win32))
113
+ : undefined);
108
114
  if (!selected) {
109
115
  throw new Error(`where.exe did not return a safe executable or command shim for ${command}`);
110
116
  }
111
117
  return selected;
112
118
  }
119
+
120
+ function isBareCodeBuddyEntrypoint(value, pathApi) {
121
+ return pathApi.basename(value).toLowerCase() === "codebuddy" && pathApi.extname(value) === "";
122
+ }
@@ -8,7 +8,7 @@ import { handleHealthRequest } from "../transport/http/health.js";
8
8
  import { authorized, publicErrorMessage, statusCodeFromError } from "../transport/http/request.js";
9
9
  import { json } from "../transport/http/json.js";
10
10
  import { createBackendState } from "./state.js";
11
- import { TRACE_CLIENTS } from "../trace/config.js";
11
+ import { TRACE_RUNTIME_CLIENTS } from "../trace/config.js";
12
12
  import { pruneExpiredTraceSessionsForClient } from "../trace/store.js";
13
13
  const DEFAULT_BACKEND_SHUTDOWN_TIMEOUT_MS = 4_000;
14
14
  export function createBackendServer(state = createBackendState(), options = {}) {
@@ -28,7 +28,7 @@ export function createBackendServer(state = createBackendState(), options = {})
28
28
  memoraxCodeHome: state.sessionHome,
29
29
  }),
30
30
  };
31
- for (const client of TRACE_CLIENTS) {
31
+ for (const client of TRACE_RUNTIME_CLIENTS) {
32
32
  void pruneExpiredTraceSessionsForClient({
33
33
  client,
34
34
  memoraxCodeHome: state.sessionHome,
@@ -1,6 +1,6 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import { backendDebug } from "../shared/debug-log.js";
3
- import { TRACE_CLIENTS, clientTraceConfigFromEnv, } from "../trace/config.js";
3
+ import { TRACE_RUNTIME_CLIENTS, clientTraceConfigFromEnv, } from "../trace/config.js";
4
4
  import { recordTraceEvent } from "../trace/store.js";
5
5
  export function createBackendMemoryObservability(memoraxCodeHome, existingHook, env = process.env) {
6
6
  if (existingHook)
@@ -47,7 +47,7 @@ export function composeMemoryObservabilityHooks(hooks) {
47
47
  };
48
48
  }
49
49
  function sessionTraceObservabilityHook(memoraxCodeHome, env) {
50
- const enabledClients = new Set(TRACE_CLIENTS.filter((client) => clientTraceConfigFromEnv(client, env).enabled));
50
+ const enabledClients = new Set(TRACE_RUNTIME_CLIENTS.filter((client) => clientTraceConfigFromEnv(client, env).enabled));
51
51
  if (enabledClients.size === 0)
52
52
  return undefined;
53
53
  const pending = new Set();
@@ -0,0 +1,208 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { codeBuddyPromptDigest, parseCodeBuddyTurnId } from "./turn-id.js";
3
+ const RECORD_OFFSET = Symbol("codebuddyRecordOffset");
4
+ export async function readCodeBuddyTranscriptTurn(input) {
5
+ let text;
6
+ try {
7
+ text = await readFile(input.transcriptPath, "utf8");
8
+ }
9
+ catch (error) {
10
+ return { ok: false, reason: "transcript_unavailable", error: error instanceof Error ? error.message : String(error) };
11
+ }
12
+ return codeBuddyTranscriptTurnFromJsonLines(text, input);
13
+ }
14
+ export async function readCodeBuddyInterruptedTranscriptTurn(input) {
15
+ let text;
16
+ try {
17
+ text = await readFile(input.transcriptPath, "utf8");
18
+ }
19
+ catch (error) {
20
+ return { ok: false, reason: "transcript_unavailable", error: error instanceof Error ? error.message : String(error) };
21
+ }
22
+ return codeBuddyInterruptedTranscriptTurnFromJsonLines(text, input);
23
+ }
24
+ export function codeBuddyTranscriptTurnFromJsonLines(text, input) {
25
+ const selected = selectCodeBuddyTurnBranch(text, input);
26
+ if (!selected.ok)
27
+ return selected;
28
+ const branch = selected.records.filter((record) => record.role === "assistant" && record.status === "completed" && belongsTo(record, selected.userId, selected.records));
29
+ if (branch.length !== 1)
30
+ return { ok: false, reason: branch.length > 1 ? "turn_ambiguous" : "assistant_message_missing" };
31
+ const assistant = branch[0];
32
+ const reply = assistantText(assistant);
33
+ if (!reply)
34
+ return { ok: false, reason: "assistant_message_missing" };
35
+ return {
36
+ ok: true,
37
+ turn: {
38
+ sessionId: input.sessionId,
39
+ turnId: input.turnId,
40
+ userPrompt: selected.userPrompt,
41
+ assistantReply: reply,
42
+ activities: activitiesBetween(selected.records, selected.userId, assistant),
43
+ sessionTurnIndex: selected.sessionTurnIndex,
44
+ },
45
+ };
46
+ }
47
+ export function codeBuddyInterruptedTranscriptTurnFromJsonLines(text, input) {
48
+ const selected = selectCodeBuddyTurnBranch(text, input);
49
+ if (!selected.ok)
50
+ return selected;
51
+ const assistants = selected.records.filter((record) => (record.role === "assistant"
52
+ && (record.type === "message" || stringField(record, "status") !== undefined)
53
+ && belongsTo(record, selected.userId, selected.records)));
54
+ if (assistants.length > 1)
55
+ return { ok: false, reason: "turn_ambiguous" };
56
+ const assistant = assistants[0];
57
+ if (assistant && assistant.status !== "incomplete") {
58
+ return { ok: false, reason: "turn_not_interrupted" };
59
+ }
60
+ return {
61
+ ok: true,
62
+ turn: {
63
+ sessionId: input.sessionId,
64
+ turnId: input.turnId,
65
+ userPrompt: selected.userPrompt,
66
+ assistantReply: assistant ? assistantText(assistant) ?? "" : "",
67
+ activities: assistant ? activitiesBetween(selected.records, selected.userId, assistant) : [],
68
+ sessionTurnIndex: selected.sessionTurnIndex,
69
+ },
70
+ };
71
+ }
72
+ function selectCodeBuddyTurnBranch(text, input) {
73
+ const identity = parseCodeBuddyTurnId(input);
74
+ if (!identity)
75
+ return { ok: false, reason: "turn_not_found" };
76
+ const records = parseJsonLines(text);
77
+ if (!records)
78
+ return { ok: false, reason: "malformed_transcript" };
79
+ const session = records.filter((record) => stringField(record, "sessionId") === input.sessionId);
80
+ const users = session.filter((record) => record.role === "user" && visibleUserPrompt(record));
81
+ const candidates = users.filter((record) => {
82
+ const prompt = visibleUserPrompt(record);
83
+ return Boolean(prompt
84
+ && (record[RECORD_OFFSET] ?? Number.MAX_SAFE_INTEGER) >= identity.boundary
85
+ && codeBuddyPromptDigest(prompt) === identity.promptDigest);
86
+ });
87
+ if (candidates.length === 0)
88
+ return { ok: false, reason: "user_prompt_missing" };
89
+ if (candidates.length > 1)
90
+ return { ok: false, reason: "turn_ambiguous" };
91
+ const user = candidates[0];
92
+ const userId = stringField(user, "id");
93
+ const userPrompt = visibleUserPrompt(user);
94
+ if (!userId || !userPrompt)
95
+ return { ok: false, reason: "turn_not_found" };
96
+ return {
97
+ ok: true,
98
+ records,
99
+ userId,
100
+ userPrompt,
101
+ sessionTurnIndex: users.indexOf(user) + 1,
102
+ };
103
+ }
104
+ function parseJsonLines(text) {
105
+ const records = [];
106
+ let cursor = 0;
107
+ let byteOffset = 0;
108
+ while (cursor < text.length) {
109
+ const newline = text.indexOf("\n", cursor);
110
+ const lineEnd = newline >= 0 ? newline : text.length;
111
+ const rawLine = text.slice(cursor, lineEnd);
112
+ const line = rawLine.endsWith("\r") ? rawLine.slice(0, -1) : rawLine;
113
+ if (line.trim()) {
114
+ try {
115
+ const parsed = JSON.parse(line);
116
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
117
+ Object.defineProperty(parsed, RECORD_OFFSET, { configurable: false, enumerable: false, value: byteOffset });
118
+ records.push(parsed);
119
+ }
120
+ }
121
+ catch {
122
+ return undefined;
123
+ }
124
+ }
125
+ const newlineBytes = newline >= 0 ? (rawLine.endsWith("\r") ? 2 : 1) : 0;
126
+ byteOffset += Buffer.byteLength(rawLine, "utf8") + newlineBytes;
127
+ cursor = newline >= 0 ? newline + 1 : text.length;
128
+ }
129
+ const byId = new Map();
130
+ const withoutId = [];
131
+ for (const record of records) {
132
+ const id = stringField(record, "id");
133
+ if (id)
134
+ byId.set(id, record);
135
+ else
136
+ withoutId.push(record);
137
+ }
138
+ return [...withoutId, ...byId.values()];
139
+ }
140
+ function visibleUserPrompt(record) {
141
+ const content = messageContentText(record.content, "input_text");
142
+ if (!content)
143
+ return undefined;
144
+ const match = content.match(/<user_query>([\s\S]*?)<\/user_query>/);
145
+ return (match?.[1] ?? content).trim() || undefined;
146
+ }
147
+ function assistantText(record) {
148
+ return messageContentText(record.content, "output_text")?.trim() || undefined;
149
+ }
150
+ function messageContentText(value, expectedType) {
151
+ if (!Array.isArray(value))
152
+ return undefined;
153
+ const parts = value.flatMap((item) => {
154
+ if (!item || typeof item !== "object")
155
+ return [];
156
+ const block = item;
157
+ return block.type === expectedType && typeof block.text === "string" ? [block.text] : [];
158
+ });
159
+ return parts.join("\n") || undefined;
160
+ }
161
+ function contentText(value) {
162
+ if (typeof value === "string")
163
+ return value;
164
+ if (!Array.isArray(value))
165
+ return undefined;
166
+ const parts = value.flatMap((item) => {
167
+ if (!item || typeof item !== "object")
168
+ return [];
169
+ const text = item.text;
170
+ return typeof text === "string" ? [text] : [];
171
+ });
172
+ return parts.join("\n") || undefined;
173
+ }
174
+ function belongsTo(record, userId, records) {
175
+ let current = record;
176
+ const seen = new Set();
177
+ for (let depth = 0; current && depth < 100; depth += 1) {
178
+ const id = stringField(current, "id");
179
+ if (id && seen.has(id))
180
+ return false;
181
+ if (id)
182
+ seen.add(id);
183
+ const parent = stringField(current, "parentId");
184
+ if (!parent)
185
+ return false;
186
+ if (parent === userId)
187
+ return true;
188
+ current = records.find((candidate) => stringField(candidate, "id") === parent);
189
+ }
190
+ return false;
191
+ }
192
+ function activitiesBetween(records, userId, assistant) {
193
+ const result = [];
194
+ for (const record of records) {
195
+ const type = stringField(record, "type");
196
+ if (type !== "function_call" && type !== "function_call_result")
197
+ continue;
198
+ if (!belongsTo(record, userId, records) || !belongsTo(assistant, userId, records))
199
+ continue;
200
+ const name = stringField(record, "name") ?? stringField(record, "function") ?? "tool";
201
+ result.push({ kind: "tool", name, ...(contentText(record.arguments) ? { input: contentText(record.arguments) } : {}), ...(contentText(record.output) ? { output: contentText(record.output) } : {}) });
202
+ }
203
+ return result;
204
+ }
205
+ function stringField(record, key) {
206
+ const value = record[key];
207
+ return typeof value === "string" && value.trim() ? value.trim() : undefined;
208
+ }
@@ -0,0 +1,44 @@
1
+ export const codeBuddyAdapterLifecycle = {
2
+ async status({ argv, serviceOptions }) {
3
+ try {
4
+ return (await load()).readCodeBuddyAdapterStatus(options(argv, serviceOptions));
5
+ }
6
+ catch (error) {
7
+ return failure("status", error);
8
+ }
9
+ },
10
+ async prepareEnable({ argv, serviceOptions }) {
11
+ try {
12
+ return (await load()).enableCodeBuddyAdapter(options(argv, serviceOptions));
13
+ }
14
+ catch (error) {
15
+ return failure("enable", error);
16
+ }
17
+ },
18
+ async disable({ argv, serviceOptions }) {
19
+ try {
20
+ return (await load()).disableCodeBuddyAdapter(options(argv, serviceOptions));
21
+ }
22
+ catch (error) {
23
+ return failure("disable", error);
24
+ }
25
+ },
26
+ async remove({ argv, serviceOptions }) {
27
+ try {
28
+ return (await load()).removeCodeBuddyPluginInstallation(options(argv, serviceOptions));
29
+ }
30
+ catch (error) {
31
+ return {
32
+ ok: false,
33
+ action: "codebuddy-plugin-remove",
34
+ reason: "plugin_remove_failed",
35
+ message: error instanceof Error ? error.message : String(error),
36
+ };
37
+ }
38
+ },
39
+ };
40
+ async function load() { return await import(new URL("../../../../memorax-code-codebuddy-adapter/src/config.mjs", import.meta.url).href); }
41
+ function options(argv, serviceOptions) { const index = argv.indexOf("--codebuddy-home"); return { ...(index >= 0 && argv[index + 1] ? { codeBuddyHome: argv[index + 1] } : {}), memoraxCodeHome: serviceOptions.home }; }
42
+ function failure(action, error) {
43
+ return { ok: false, action, error: error instanceof Error ? error.message : String(error) };
44
+ }