@memorax/memorax-code 0.1.9 → 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.
- 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 +82 -22
- package/docs/troubleshooting.md +69 -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 +279 -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 +277 -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 +112 -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
|
@@ -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 (
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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",
|
|
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
|
-
|
|
623
|
+
finish({ ok: false, stdout, stderr: stderr || error.message });
|
|
502
624
|
});
|
|
503
625
|
child.on("close", (code) => {
|
|
504
|
-
|
|
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 {
|
|
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", () =>
|
|
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 {
|