@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.
- package/README.md +10 -0
- package/bin/memorax-code-codebuddy.mjs +4 -0
- package/bin/memorax-code-setup.mjs +182 -65
- package/bin/memorax-code.mjs +74 -45
- package/docs/configuration.md +85 -22
- package/docs/troubleshooting.md +72 -5
- package/lib/automatic-update.mjs +233 -0
- package/lib/memorax-code-adapter-common/src/automatic-update-state.d.mts +24 -0
- package/lib/memorax-code-adapter-common/src/automatic-update-state.mjs +73 -0
- package/lib/memorax-code-adapter-common/src/clients/codebuddy-command.mjs +82 -0
- package/lib/memorax-code-adapter-common/src/config-utils.d.mts +2 -0
- package/lib/memorax-code-adapter-common/src/config-utils.mjs +11 -0
- package/lib/memorax-code-adapter-common/src/hooks/client-hook-launcher.mjs +3 -0
- package/lib/memorax-code-adapter-common/src/hooks/ensure-backend-runner.mjs +45 -10
- package/lib/memorax-code-adapter-common/src/repo-memory/repo-memory-job-worker.mjs +88 -3
- package/lib/memorax-code-adapter-common/src/windows-cli-invocation.mjs +11 -1
- package/lib/memorax-code-backend/dist/app/backend-server.js +2 -2
- package/lib/memorax-code-backend/dist/app/memory-observability.js +2 -2
- package/lib/memorax-code-backend/dist/clients/codebuddy/jsonl-history.js +208 -0
- package/lib/memorax-code-backend/dist/clients/codebuddy/lifecycle.js +44 -0
- package/lib/memorax-code-backend/dist/clients/codebuddy/memory-hook-runtime.js +250 -0
- package/lib/memorax-code-backend/dist/clients/codebuddy/turn-id.js +16 -0
- package/lib/memorax-code-backend/dist/clients/codex/plugin-install.js +129 -7
- package/lib/memorax-code-backend/dist/config/memorax-code.js +14 -1
- package/lib/memorax-code-backend/dist/entrypoints/backend-cli.js +43 -6
- package/lib/memorax-code-backend/dist/lifecycle/active-clients.js +3 -0
- package/lib/memorax-code-backend/dist/lifecycle/automatic-update-scheduler.js +133 -0
- package/lib/memorax-code-backend/dist/lifecycle/client-plugin-removal.js +14 -2
- package/lib/memorax-code-backend/dist/lifecycle/client-selection.js +6 -4
- package/lib/memorax-code-backend/dist/lifecycle/orchestrator.js +48 -9
- package/lib/memorax-code-backend/dist/memory/cli.js +4 -2
- package/lib/memorax-code-backend/dist/memory/hook-command.js +31 -1
- package/lib/memorax-code-backend/dist/memory/reminder-trace-recorder.js +5 -1
- package/lib/memorax-code-backend/dist/memory/service.js +12 -0
- package/lib/memorax-code-backend/dist/shared/windows-cli-invocation.js +10 -1
- package/lib/memorax-code-backend/dist/trace/config.js +12 -0
- package/lib/memorax-code-backend/dist/trace/context.js +21 -1
- package/lib/memorax-code-backend/dist/trace/store.js +12 -1
- package/lib/memorax-code-backend/package.json +1 -1
- package/lib/memorax-code-claude-adapter/.claude-plugin/plugin.json +1 -1
- package/lib/memorax-code-claude-adapter/hooks/runtime-shell.json +1 -1
- package/lib/memorax-code-claude-adapter/package.json +1 -1
- package/lib/memorax-code-claude-adapter/runtime-hooks/ensure-backend.mjs +2 -0
- package/lib/memorax-code-claude-adapter/src/plugin-install.mjs +3 -0
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/.claude-plugin/plugin.json +1 -1
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/hooks/runtime-shell.json +1 -1
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/automatic-update-state.d.mts +24 -0
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/automatic-update-state.mjs +73 -0
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/clients/codebuddy-command.mjs +82 -0
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/config-utils.d.mts +2 -0
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/config-utils.mjs +11 -0
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/hooks/client-hook-launcher.mjs +3 -0
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/hooks/ensure-backend-runner.mjs +45 -10
- 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
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/windows-cli-invocation.mjs +11 -1
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/package.json +1 -1
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/runtime-hooks/ensure-backend.mjs +2 -0
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/src/plugin-install.mjs +3 -0
- package/lib/memorax-code-codebuddy-adapter/.codebuddy-plugin/plugin.json +7 -0
- package/lib/memorax-code-codebuddy-adapter/hooks/common-runtime.mjs +13 -0
- package/lib/memorax-code-codebuddy-adapter/hooks/hooks.json +38 -0
- package/lib/memorax-code-codebuddy-adapter/hooks/repo-memory-job.mjs +40 -0
- package/lib/memorax-code-codebuddy-adapter/hooks/runtime-hook.mjs +289 -0
- package/lib/memorax-code-codebuddy-adapter/package.json +9 -0
- package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/SKILL.md +85 -0
- package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/agents/claude.yaml +10 -0
- package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/agents/openai.yaml +7 -0
- package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/defaults.json +12 -0
- package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/references/memorax-add.md +88 -0
- package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/references/memorax-search.md +93 -0
- package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/references/personal-read.md +46 -0
- package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/references/personal-write.md +120 -0
- package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/references/repo-build.md +319 -0
- package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/references/repo-read.md +103 -0
- package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/references/repo-templates.md +390 -0
- package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/references/repo-update.md +127 -0
- package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/scripts/collect_all.py +579 -0
- package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/scripts/detect_updates.py +919 -0
- package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/scripts/git_commit_facets.py +222 -0
- package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/scripts/github_resource_facets.py +512 -0
- package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/scripts/gitlab_resource_facets.py +517 -0
- package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/scripts/prepare_repo_memory.py +411 -0
- package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/scripts/user_profile_memory.py +528 -0
- package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/scripts/validate_memory.py +248 -0
- package/lib/memorax-code-codebuddy-adapter/src/cli.mjs +58 -0
- package/lib/memorax-code-codebuddy-adapter/src/config.mjs +340 -0
- package/lib/memorax-code-codebuddy-adapter/src/hook-manifest.mjs +47 -0
- package/lib/memorax-code-codebuddy-adapter/src/runtime-observation.mjs +65 -0
- package/lib/memorax-code-codex-adapter/.codex-plugin/plugin.json +1 -1
- package/lib/memorax-code-codex-adapter/hooks/runtime-shell.json +1 -1
- package/lib/memorax-code-codex-adapter/package.json +1 -1
- package/lib/memorax-code-codex-adapter/runtime-hooks/ensure-backend.mjs +4 -1
- package/lib/memorax-code-dsh-adapter/package.json +2 -1
- package/lib/memorax-code-dsh-adapter/src/profile-lifecycle.mjs +1 -0
- package/lib/memorax-code-opencode-adapter/package.json +1 -1
- package/lib/npm-invocation.mjs +51 -8
- package/lib/resolve-codebuddy-command.mjs +123 -0
- package/lib/run-entrypoint.mjs +25 -1
- package/lib/windows-cli-invocation.mjs +11 -1
- package/lib/windows-user-path.mjs +218 -0
- 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
|
|
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 (
|
|
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 {
|
|
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
|
|
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 {
|
|
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(
|
|
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
|
+
}
|