@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
|
@@ -4,6 +4,7 @@ import { join } from "node:path";
|
|
|
4
4
|
import { loadMemoraxCodeConfig } from "../config/memorax-code.js";
|
|
5
5
|
import { isTraceClient } from "./context.js";
|
|
6
6
|
export const TRACE_CLIENTS = ["codex", "claude", "dsh", "opencode"];
|
|
7
|
+
export const TRACE_RUNTIME_CLIENTS = [...TRACE_CLIENTS, "codebuddy"];
|
|
7
8
|
export const CODEX_TRACE_DEFAULT_CONFIG = {
|
|
8
9
|
enabled: true,
|
|
9
10
|
captureContent: true,
|
|
@@ -32,17 +33,22 @@ export const OPENCODE_TRACE_DEFAULT_CONFIG = {
|
|
|
32
33
|
maxEventChars: 20_000,
|
|
33
34
|
maxFileBytes: 52_428_800,
|
|
34
35
|
};
|
|
36
|
+
export const CODEBUDDY_TRACE_DEFAULT_CONFIG = {
|
|
37
|
+
...OPENCODE_TRACE_DEFAULT_CONFIG,
|
|
38
|
+
};
|
|
35
39
|
const TRACE_DEFAULT_CONFIGS = {
|
|
36
40
|
codex: CODEX_TRACE_DEFAULT_CONFIG,
|
|
37
41
|
claude: CLAUDE_TRACE_DEFAULT_CONFIG,
|
|
38
42
|
dsh: DSH_TRACE_DEFAULT_CONFIG,
|
|
39
43
|
opencode: OPENCODE_TRACE_DEFAULT_CONFIG,
|
|
44
|
+
codebuddy: CODEBUDDY_TRACE_DEFAULT_CONFIG,
|
|
40
45
|
};
|
|
41
46
|
const TRACE_ENV_PREFIXES = {
|
|
42
47
|
codex: "MEMORAX_CODE_CODEX_TRACE",
|
|
43
48
|
claude: "MEMORAX_CODE_CLAUDE_TRACE",
|
|
44
49
|
dsh: "MEMORAX_CODE_DSH_TRACE",
|
|
45
50
|
opencode: "MEMORAX_CODE_OPENCODE_TRACE",
|
|
51
|
+
codebuddy: "MEMORAX_CODE_CODEBUDDY_TRACE",
|
|
46
52
|
};
|
|
47
53
|
export const TRACE_CLEANUP_DEBOUNCE_MS = 60 * 60 * 1000;
|
|
48
54
|
export const TRACE_CURRENT_TURN_TTL_MS = 30 * 60 * 1000;
|
|
@@ -62,6 +68,9 @@ export function dshTraceConfigFromEnv(env = process.env, fileConfig) {
|
|
|
62
68
|
export function openCodeTraceConfigFromEnv(env = process.env, fileConfig) {
|
|
63
69
|
return clientTraceConfigFromEnv("opencode", env, fileConfig);
|
|
64
70
|
}
|
|
71
|
+
export function codeBuddyTraceConfigFromEnv(env = process.env, fileConfig) {
|
|
72
|
+
return clientTraceConfigFromEnv("codebuddy", env, fileConfig);
|
|
73
|
+
}
|
|
65
74
|
export function clientTraceConfigFromEnv(client, env = process.env, fileConfig) {
|
|
66
75
|
assertTraceClient(client);
|
|
67
76
|
const config = fileConfig ?? loadMemoraxCodeConfig(memoraxCodeHomeForTrace(env), { warn: () => undefined });
|
|
@@ -91,6 +100,9 @@ export function dshTracePaths(memoraxCodeHome = memoraxCodeHomeForTrace(process.
|
|
|
91
100
|
export function openCodeTracePaths(memoraxCodeHome = memoraxCodeHomeForTrace(process.env)) {
|
|
92
101
|
return clientTracePaths("opencode", memoraxCodeHome);
|
|
93
102
|
}
|
|
103
|
+
export function codeBuddyTracePaths(memoraxCodeHome = memoraxCodeHomeForTrace(process.env)) {
|
|
104
|
+
return clientTracePaths("codebuddy", memoraxCodeHome);
|
|
105
|
+
}
|
|
94
106
|
export function clientTracePaths(client, memoraxCodeHome = memoraxCodeHomeForTrace(process.env)) {
|
|
95
107
|
assertTraceClient(client);
|
|
96
108
|
const root = join(memoraxCodeHome, "debug", "traces", client);
|
|
@@ -62,6 +62,26 @@ export function traceContextFromOpenCodeHookBody(body, capturedAt = new Date().t
|
|
|
62
62
|
capturedAt,
|
|
63
63
|
});
|
|
64
64
|
}
|
|
65
|
+
export function traceContextFromCodeBuddyHookBody(body, capturedAt = new Date().toISOString()) {
|
|
66
|
+
if (!isRecord(body))
|
|
67
|
+
return undefined;
|
|
68
|
+
const sessionId = stringField(body, "session_id") ?? stringField(body, "sessionId");
|
|
69
|
+
if (!sessionId)
|
|
70
|
+
return undefined;
|
|
71
|
+
const cwd = stringField(body, "cwd");
|
|
72
|
+
return pruneTraceContext({
|
|
73
|
+
schemaVersion: "1",
|
|
74
|
+
client: "codebuddy",
|
|
75
|
+
sessionId,
|
|
76
|
+
turnId: stringField(body, "turn_id") ?? stringField(body, "turnId"),
|
|
77
|
+
transcriptPath: stringField(body, "transcript_path") ?? stringField(body, "transcriptPath"),
|
|
78
|
+
cwd,
|
|
79
|
+
memoryProject: resolveMemoryProject(cwd),
|
|
80
|
+
workspaceKind: stringField(body, "workspace_kind") ?? stringField(body, "workspaceKind"),
|
|
81
|
+
contextOrigin: "codebuddy-hook-body",
|
|
82
|
+
capturedAt,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
65
85
|
export function traceContextFromDshTurnStart(body, capturedAt = new Date().toISOString()) {
|
|
66
86
|
return traceContextFromDshBody(body, "dsh-cordis-turn-start", capturedAt);
|
|
67
87
|
}
|
|
@@ -129,7 +149,7 @@ function pruneRecord(value) {
|
|
|
129
149
|
return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== undefined));
|
|
130
150
|
}
|
|
131
151
|
export function isTraceClient(value) {
|
|
132
|
-
return value === "codex" || value === "claude" || value === "dsh" || value === "opencode";
|
|
152
|
+
return value === "codex" || value === "claude" || value === "dsh" || value === "opencode" || value === "codebuddy";
|
|
133
153
|
}
|
|
134
154
|
function traceContextFromDshBody(body, contextOrigin, capturedAt) {
|
|
135
155
|
if (!isRecord(body))
|
|
@@ -60,6 +60,9 @@ export async function recordCodexTraceEvent(input) {
|
|
|
60
60
|
export async function recordClaudeTraceEvent(input) {
|
|
61
61
|
return recordTraceEventForClient("claude", input);
|
|
62
62
|
}
|
|
63
|
+
export async function recordCodeBuddyTraceEvent(input) {
|
|
64
|
+
return recordTraceEventForClient("codebuddy", input);
|
|
65
|
+
}
|
|
63
66
|
export function traceTurnEventId(traceContext, type) {
|
|
64
67
|
if (!traceContext?.sessionId || !traceContext.turnId)
|
|
65
68
|
return undefined;
|
|
@@ -168,6 +171,12 @@ export async function markCurrentCodexTurnOutcome(traceContext, outcome, options
|
|
|
168
171
|
export async function markCurrentClaudeTurnOutcome(traceContext, outcome, options = {}) {
|
|
169
172
|
return markCurrentTraceTurnOutcome(traceContext, outcome, { ...options, client: "claude" });
|
|
170
173
|
}
|
|
174
|
+
export async function writeCurrentCodeBuddyTurn(traceContext, options = {}) {
|
|
175
|
+
return writeCurrentTraceTurn(traceContext, { ...options, client: "codebuddy" });
|
|
176
|
+
}
|
|
177
|
+
export async function markCurrentCodeBuddyTurnOutcome(traceContext, outcome, options = {}) {
|
|
178
|
+
return markCurrentTraceTurnOutcome(traceContext, outcome, { ...options, client: "codebuddy" });
|
|
179
|
+
}
|
|
171
180
|
async function readCurrentTraceTurnRecord(options) {
|
|
172
181
|
const client = options.client;
|
|
173
182
|
if (!isTraceClient(client))
|
|
@@ -303,7 +312,9 @@ function buildTraceEvent(input, config, now, eventId) {
|
|
|
303
312
|
ok: input.ok,
|
|
304
313
|
outcome: input.outcome,
|
|
305
314
|
related_turns: input.relatedTurns?.map(relatedTurnJson),
|
|
306
|
-
activities: input.activities
|
|
315
|
+
activities: input.activities === undefined
|
|
316
|
+
? undefined
|
|
317
|
+
: sanitizeTraceValue(input.activities, config, captureContent),
|
|
307
318
|
usage: input.usage,
|
|
308
319
|
session_turn_index: input.sessionTurnIndex,
|
|
309
320
|
request: input.request === undefined ? undefined : sanitizeTraceValue(input.request, config, captureContent),
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { readClientHookPluginRoot } from "../../memorax-code-adapter-common/src/config-utils.mjs";
|
|
5
6
|
import {
|
|
6
7
|
runEnsureBackendHook,
|
|
7
8
|
stringValue,
|
|
@@ -31,6 +32,7 @@ process.exit(0);
|
|
|
31
32
|
function pluginRoot() {
|
|
32
33
|
return stringValue(process.env.CLAUDE_PLUGIN_ROOT)
|
|
33
34
|
?? stringValue(process.env.PLUGIN_ROOT)
|
|
35
|
+
?? readClientHookPluginRoot()
|
|
34
36
|
?? dirname(dirname(fileURLToPath(import.meta.url)));
|
|
35
37
|
}
|
|
36
38
|
|
|
@@ -32,6 +32,7 @@ const STABLE_SHELL_REQUIRED_FILES = Object.freeze([
|
|
|
32
32
|
"memorax-code-adapter-common/src/hooks/capture-cwd-hook.mjs",
|
|
33
33
|
"memorax-code-adapter-common/src/hooks/client-hook-launcher.mjs",
|
|
34
34
|
"memorax-code-adapter-common/src/config-utils.mjs",
|
|
35
|
+
"memorax-code-adapter-common/src/setup-completion.mjs",
|
|
35
36
|
"memorax-code-adapter-common/src/hooks/ensure-backend-runner.mjs",
|
|
36
37
|
"memorax-code-adapter-common/src/hooks/hook-runtime-generation.mjs",
|
|
37
38
|
"memorax-code-adapter-common/src/hooks/memory-skill-reminder-hook.mjs",
|
|
@@ -226,10 +227,12 @@ export function ensureClaudePluginInstalled(options = {}) {
|
|
|
226
227
|
}
|
|
227
228
|
|
|
228
229
|
function writeInstalledPluginMetadata(installPath, claudeCommand) {
|
|
230
|
+
const npmExecPath = stringOption(process.env.MEMORAX_CODE_NPM_EXEC_PATH);
|
|
229
231
|
atomicWriteJson(join(installPath, ".memorax-code-package.json"), {
|
|
230
232
|
version: 1,
|
|
231
233
|
memoraxCodeCommand: process.argv[1],
|
|
232
234
|
claudeCommand,
|
|
235
|
+
...(npmExecPath ? { npmExecPath } : {}),
|
|
233
236
|
writtenAt: new Date().toISOString(),
|
|
234
237
|
});
|
|
235
238
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export type AutomaticUpdateRecord = Readonly<{
|
|
2
|
+
version: 1;
|
|
3
|
+
installedVersion: string;
|
|
4
|
+
nextCheckAt: string;
|
|
5
|
+
}>;
|
|
6
|
+
|
|
7
|
+
export type AutomaticUpdateState =
|
|
8
|
+
| Readonly<{ status: "absent" }>
|
|
9
|
+
| Readonly<{ status: "valid"; record: AutomaticUpdateRecord }>
|
|
10
|
+
| Readonly<{ status: "invalid"; reason: string }>
|
|
11
|
+
| Readonly<{ status: "unsupported"; version: number }>;
|
|
12
|
+
|
|
13
|
+
export const AUTOMATIC_UPDATE_RECORD_VERSION: 1;
|
|
14
|
+
export const AUTOMATIC_UPDATE_CHECK_INTERVAL_MS: number;
|
|
15
|
+
export const AUTOMATIC_UPDATE_RETRY_INTERVAL_MS: number;
|
|
16
|
+
|
|
17
|
+
export function automaticUpdateStatePath(memoraxCodeHome: string): string;
|
|
18
|
+
export function readAutomaticUpdateState(memoraxCodeHome: string): AutomaticUpdateState;
|
|
19
|
+
export function writeAutomaticUpdateState(options: {
|
|
20
|
+
memoraxCodeHome: string;
|
|
21
|
+
nowMs: number;
|
|
22
|
+
installedVersion: string;
|
|
23
|
+
retry: boolean;
|
|
24
|
+
}): AutomaticUpdateRecord;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { join, resolve } from "node:path";
|
|
2
|
+
import {
|
|
3
|
+
readJsonRuntimeRecord,
|
|
4
|
+
writePrivateJsonRecord,
|
|
5
|
+
} from "./runtime-record.mjs";
|
|
6
|
+
|
|
7
|
+
export const AUTOMATIC_UPDATE_RECORD_VERSION = 1;
|
|
8
|
+
export const AUTOMATIC_UPDATE_CHECK_INTERVAL_MS = 8 * 60 * 60 * 1_000;
|
|
9
|
+
export const AUTOMATIC_UPDATE_RETRY_INTERVAL_MS = 15 * 60 * 1_000;
|
|
10
|
+
|
|
11
|
+
const RECORD_KEYS = new Set(["version", "installedVersion", "nextCheckAt"]);
|
|
12
|
+
|
|
13
|
+
export function automaticUpdateStatePath(memoraxCodeHome) {
|
|
14
|
+
return join(resolve(memoraxCodeHome), "runtime", "install", "automatic-update.json");
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function readAutomaticUpdateState(memoraxCodeHome) {
|
|
18
|
+
const state = readJsonRuntimeRecord(automaticUpdateStatePath(memoraxCodeHome));
|
|
19
|
+
if (state.status !== "present") return state;
|
|
20
|
+
const value = state.value;
|
|
21
|
+
if (value.version !== AUTOMATIC_UPDATE_RECORD_VERSION) {
|
|
22
|
+
return Number.isSafeInteger(value.version) && value.version > 0
|
|
23
|
+
? { status: "unsupported", version: value.version }
|
|
24
|
+
: { status: "invalid", reason: "invalid_version" };
|
|
25
|
+
}
|
|
26
|
+
if (Object.keys(value).length !== RECORD_KEYS.size
|
|
27
|
+
|| Object.keys(value).some((key) => !RECORD_KEYS.has(key))) {
|
|
28
|
+
return { status: "invalid", reason: "unknown_or_missing_fields" };
|
|
29
|
+
}
|
|
30
|
+
const installedVersion = nonEmptyString(value.installedVersion);
|
|
31
|
+
const nextCheckAt = timestamp(value.nextCheckAt);
|
|
32
|
+
if (!installedVersion || !nextCheckAt) {
|
|
33
|
+
return { status: "invalid", reason: "invalid_fields" };
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
status: "valid",
|
|
37
|
+
record: {
|
|
38
|
+
version: AUTOMATIC_UPDATE_RECORD_VERSION,
|
|
39
|
+
installedVersion,
|
|
40
|
+
nextCheckAt,
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function writeAutomaticUpdateState({ memoraxCodeHome, nowMs, installedVersion, retry }) {
|
|
46
|
+
const timestampMs = Number(nowMs);
|
|
47
|
+
const version = nonEmptyString(installedVersion);
|
|
48
|
+
if (!Number.isFinite(timestampMs) || !version) {
|
|
49
|
+
throw new TypeError("automatic update state requires a timestamp and installed version");
|
|
50
|
+
}
|
|
51
|
+
const record = {
|
|
52
|
+
version: AUTOMATIC_UPDATE_RECORD_VERSION,
|
|
53
|
+
installedVersion: version,
|
|
54
|
+
nextCheckAt: new Date(
|
|
55
|
+
timestampMs + (retry ? AUTOMATIC_UPDATE_RETRY_INTERVAL_MS : AUTOMATIC_UPDATE_CHECK_INTERVAL_MS),
|
|
56
|
+
).toISOString(),
|
|
57
|
+
};
|
|
58
|
+
writePrivateJsonRecord(automaticUpdateStatePath(memoraxCodeHome), record, {
|
|
59
|
+
durableBoundary: memoraxCodeHome,
|
|
60
|
+
});
|
|
61
|
+
return record;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function nonEmptyString(value) {
|
|
65
|
+
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function timestamp(value) {
|
|
69
|
+
const candidate = nonEmptyString(value);
|
|
70
|
+
return candidate && Number.isFinite(Date.parse(candidate))
|
|
71
|
+
? new Date(Date.parse(candidate)).toISOString()
|
|
72
|
+
: undefined;
|
|
73
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { accessSync, constants, existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { join, win32 } from "node:path";
|
|
4
|
+
|
|
5
|
+
const APP_NAMES = ["WorkBuddy.app", "CodeBuddy.app"];
|
|
6
|
+
const BUNDLED_SEGMENTS = ["Contents", "Resources", "app.asar.unpacked", "cli", "bin", "codebuddy"];
|
|
7
|
+
const WINDOWS_SEGMENTS = [
|
|
8
|
+
["resources", "app.asar.unpacked", "cli", "bin", "codebuddy"],
|
|
9
|
+
["resources", "app.asar.unpacked", "cli", "bin", "codebuddy.exe"],
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Resolve the CodeBuddy executable from the environment, installed adapter
|
|
14
|
+
* metadata, or the WorkBuddy/CodeBuddy application bundle. Hooks are often
|
|
15
|
+
* launched by a GUI process and therefore cannot rely on the user's shell
|
|
16
|
+
* PATH or on npm's environment setup.
|
|
17
|
+
*/
|
|
18
|
+
export function resolveHookCodeBuddyCommand({
|
|
19
|
+
env = process.env,
|
|
20
|
+
pluginRoot,
|
|
21
|
+
homeDir = homedir(),
|
|
22
|
+
platform = process.platform,
|
|
23
|
+
pathExists = commandPathExists,
|
|
24
|
+
} = {}) {
|
|
25
|
+
const configured = stringValue(env.MEMORAX_CODE_CODEBUDDY_COMMAND)
|
|
26
|
+
?? stringValue(env.CODEBUDDY_CLI_PATH)
|
|
27
|
+
?? stringValue(env.WORKBUDDY_CODEBUDDY_PATH);
|
|
28
|
+
if (configured) return configured;
|
|
29
|
+
|
|
30
|
+
const metadataCommand = readMetadataCommand(stringValue(pluginRoot) ?? stringValue(env.CODEBUDDY_PLUGIN_ROOT));
|
|
31
|
+
if (metadataCommand && pathExists(metadataCommand, platform)) return metadataCommand;
|
|
32
|
+
|
|
33
|
+
if (platform === "darwin") {
|
|
34
|
+
for (const root of [join(homeDir, "Applications"), "/Applications"]) {
|
|
35
|
+
for (const appName of APP_NAMES) {
|
|
36
|
+
const command = join(root, appName, ...BUNDLED_SEGMENTS);
|
|
37
|
+
if (pathExists(command, platform)) return command;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if (platform === "win32") {
|
|
42
|
+
for (const root of [
|
|
43
|
+
env.LOCALAPPDATA && win32.join(env.LOCALAPPDATA, "Programs", "WorkBuddy"),
|
|
44
|
+
env.LOCALAPPDATA && win32.join(env.LOCALAPPDATA, "Programs", "CodeBuddy"),
|
|
45
|
+
env.LOCALAPPDATA && win32.join(env.LOCALAPPDATA, "WorkBuddy"),
|
|
46
|
+
env.LOCALAPPDATA && win32.join(env.LOCALAPPDATA, "CodeBuddy"),
|
|
47
|
+
env.ProgramFiles && win32.join(env.ProgramFiles, "WorkBuddy"),
|
|
48
|
+
env.ProgramFiles && win32.join(env.ProgramFiles, "CodeBuddy"),
|
|
49
|
+
].filter(Boolean)) {
|
|
50
|
+
for (const segments of WINDOWS_SEGMENTS) {
|
|
51
|
+
const command = win32.join(root, ...segments);
|
|
52
|
+
if (pathExists(command, platform)) return command;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return "codebuddy";
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function readMetadataCommand(pluginRoot) {
|
|
60
|
+
if (!pluginRoot) return undefined;
|
|
61
|
+
try {
|
|
62
|
+
const metadata = JSON.parse(readFileSync(join(pluginRoot, ".memorax-code-package.json"), "utf8"));
|
|
63
|
+
return stringValue(metadata?.codeBuddyCommand);
|
|
64
|
+
} catch {
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function commandPathExists(command, platform) {
|
|
70
|
+
if (!command.includes("/") && !command.includes("\\")) return true;
|
|
71
|
+
if (!existsSync(command)) return false;
|
|
72
|
+
try {
|
|
73
|
+
accessSync(command, platform === "win32" ? constants.F_OK : constants.X_OK);
|
|
74
|
+
return true;
|
|
75
|
+
} catch {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function stringValue(value) {
|
|
81
|
+
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
82
|
+
}
|
|
@@ -13,6 +13,8 @@ export function readJsonFile(path: string): any;
|
|
|
13
13
|
export function readJsonValue(path: string): any;
|
|
14
14
|
export function readStdinJson(): Promise<any>;
|
|
15
15
|
export function injectClientHookInput(input: Record<string, unknown>): void;
|
|
16
|
+
export function injectClientHookPluginRoot(pluginRoot: string): void;
|
|
17
|
+
export function readClientHookPluginRoot(): string | undefined;
|
|
16
18
|
export function sha256(value: string | NodeJS.ArrayBufferView): string;
|
|
17
19
|
export function stringOption(value: unknown): string | undefined;
|
|
18
20
|
export function atomicWriteJson(path: string, value: unknown): void;
|
|
@@ -24,6 +24,7 @@ const JSON_FILE_LOCK_RETRY_MS = 10;
|
|
|
24
24
|
const PROCESS_START_TOLERANCE_MS = 5000;
|
|
25
25
|
const LOCK_SLEEP_BUFFER = new Int32Array(new SharedArrayBuffer(4));
|
|
26
26
|
const HOOK_INPUT_SYMBOL = Symbol.for("memorax-code.client-hook.input.v1");
|
|
27
|
+
const HOOK_PLUGIN_ROOT_SYMBOL = Symbol.for("memorax-code.client-hook.plugin-root.v1");
|
|
27
28
|
|
|
28
29
|
export function readAdapterState(path) {
|
|
29
30
|
const state = readJsonFile(path);
|
|
@@ -64,6 +65,16 @@ export function injectClientHookInput(input) {
|
|
|
64
65
|
globalThis[HOOK_INPUT_SYMBOL] = input;
|
|
65
66
|
}
|
|
66
67
|
|
|
68
|
+
export function injectClientHookPluginRoot(pluginRoot) {
|
|
69
|
+
const root = stringOption(pluginRoot);
|
|
70
|
+
if (!root) throw new TypeError("client Hook plugin root must be a non-empty string");
|
|
71
|
+
globalThis[HOOK_PLUGIN_ROOT_SYMBOL] = root;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function readClientHookPluginRoot() {
|
|
75
|
+
return stringOption(globalThis[HOOK_PLUGIN_ROOT_SYMBOL]);
|
|
76
|
+
}
|
|
77
|
+
|
|
67
78
|
export function sha256(value) {
|
|
68
79
|
return createHash("sha256").update(value).digest("hex");
|
|
69
80
|
}
|
|
@@ -5,6 +5,7 @@ import { isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
|
5
5
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
6
6
|
import {
|
|
7
7
|
injectClientHookInput,
|
|
8
|
+
injectClientHookPluginRoot,
|
|
8
9
|
readStdinJson,
|
|
9
10
|
stringOption,
|
|
10
11
|
withJsonFileLock,
|
|
@@ -47,6 +48,8 @@ export async function runClientHookLauncher({
|
|
|
47
48
|
});
|
|
48
49
|
if (!selection) return;
|
|
49
50
|
injectClientHookInput(input);
|
|
51
|
+
const originalPluginRoot = stringOption(pluginRoot);
|
|
52
|
+
if (originalPluginRoot) injectClientHookPluginRoot(originalPluginRoot);
|
|
50
53
|
await import(pathToFileURL(selection.modulePath).href);
|
|
51
54
|
} catch (error) {
|
|
52
55
|
if (process.env[debugEnv] === "1") {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
3
|
-
import { basename, delimiter, join } from "node:path";
|
|
2
|
+
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
3
|
+
import { basename, delimiter, isAbsolute, join } from "node:path";
|
|
4
4
|
import {
|
|
5
5
|
localBackendRecoveryArguments,
|
|
6
6
|
resolveBackendConnection,
|
|
@@ -16,8 +16,17 @@ export async function runEnsureBackendHook(options) {
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
export async function ensureBackendAvailable(options, input = {}) {
|
|
19
|
-
if (isRepoMemoryJobWorker()
|
|
19
|
+
if (isRepoMemoryJobWorker()) return;
|
|
20
20
|
const homes = options.resolveHomes(input);
|
|
21
|
+
const metadata = packageMetadata(options.pluginRoot);
|
|
22
|
+
const command = memoraxCodeCommandInfo(
|
|
23
|
+
options.memoraxCodeCommand,
|
|
24
|
+
metadata,
|
|
25
|
+
options.platform,
|
|
26
|
+
);
|
|
27
|
+
if (ensureDisabled(options.ensureBackendValue)) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
21
30
|
let connection;
|
|
22
31
|
try {
|
|
23
32
|
connection = options.backendConnection
|
|
@@ -34,19 +43,19 @@ export async function ensureBackendAvailable(options, input = {}) {
|
|
|
34
43
|
|
|
35
44
|
const recoveryArguments = localBackendRecoveryArguments(connection);
|
|
36
45
|
if (recoveryArguments === undefined) return;
|
|
37
|
-
const command = memoraxCodeCommandInfo(options.memoraxCodeCommand, options.pluginRoot);
|
|
38
46
|
if (!command.value || command.removed === true || !memoraxCodeCommandAvailable(command.value)) return;
|
|
39
47
|
const result = await runMemoraxCode(
|
|
40
48
|
command.value,
|
|
41
49
|
options.buildStartArgs(homes, recoveryArguments),
|
|
42
50
|
parsePositiveInt(options.startTimeoutValue, DEFAULT_ENSURE_BACKEND_START_TIMEOUT_MS),
|
|
43
51
|
options.nodePath,
|
|
44
|
-
options.recoveryEnv,
|
|
52
|
+
recoveryEnvironment(options.recoveryEnv, metadata),
|
|
45
53
|
);
|
|
46
54
|
if (result.code !== 0) {
|
|
47
55
|
options.debug?.(
|
|
48
56
|
`MemoraX Code backend start failed with code ${result.code}${result.stderr ? `: ${result.stderr}` : ""}`,
|
|
49
57
|
);
|
|
58
|
+
return;
|
|
50
59
|
}
|
|
51
60
|
}
|
|
52
61
|
|
|
@@ -79,22 +88,48 @@ async function backendHealthy(connection, timeoutMs) {
|
|
|
79
88
|
}
|
|
80
89
|
}
|
|
81
90
|
|
|
82
|
-
function memoraxCodeCommandInfo(explicitCommand,
|
|
91
|
+
function memoraxCodeCommandInfo(explicitCommand, metadata, platform = process.platform) {
|
|
83
92
|
const explicit = stringValue(explicitCommand);
|
|
84
93
|
if (explicit) return { value: explicit, removed: pathLooksRemoved(explicit) };
|
|
85
|
-
const
|
|
86
|
-
if (
|
|
94
|
+
const command = stringValue(metadata?.memoraxCodeCommand);
|
|
95
|
+
if (command) return { value: command, removed: pathLooksRemoved(command) };
|
|
96
|
+
if (platform === "win32") return { value: undefined, removed: false };
|
|
87
97
|
return { value: "memorax-code", removed: false };
|
|
88
98
|
}
|
|
89
99
|
|
|
90
|
-
function
|
|
100
|
+
function packageMetadata(pluginRoot) {
|
|
91
101
|
const root = stringValue(pluginRoot);
|
|
92
102
|
if (!root) return undefined;
|
|
93
103
|
const path = join(root, ".memorax-code-package.json");
|
|
94
104
|
if (!existsSync(path)) return undefined;
|
|
95
105
|
try {
|
|
96
106
|
const metadata = JSON.parse(readFileSync(path, "utf8"));
|
|
97
|
-
return
|
|
107
|
+
return metadata && typeof metadata === "object" && !Array.isArray(metadata)
|
|
108
|
+
? metadata
|
|
109
|
+
: undefined;
|
|
110
|
+
} catch {
|
|
111
|
+
return undefined;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function recoveryEnvironment(recoveryEnv, metadata) {
|
|
116
|
+
if (stringValue(recoveryEnv?.MEMORAX_CODE_NPM_EXEC_PATH)
|
|
117
|
+
|| stringValue(process.env.MEMORAX_CODE_NPM_EXEC_PATH)) {
|
|
118
|
+
return recoveryEnv;
|
|
119
|
+
}
|
|
120
|
+
const npmExecPath = metadataNpmExecPath(metadata);
|
|
121
|
+
return npmExecPath
|
|
122
|
+
? { ...(recoveryEnv ?? {}), MEMORAX_CODE_NPM_EXEC_PATH: npmExecPath }
|
|
123
|
+
: recoveryEnv;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function metadataNpmExecPath(metadata) {
|
|
127
|
+
const npmExecPath = stringValue(metadata?.npmExecPath);
|
|
128
|
+
if (!npmExecPath || !isAbsolute(npmExecPath) || !/\.(?:cjs|js|mjs)$/i.test(npmExecPath)) {
|
|
129
|
+
return undefined;
|
|
130
|
+
}
|
|
131
|
+
try {
|
|
132
|
+
return statSync(npmExecPath).isFile() ? npmExecPath : undefined;
|
|
98
133
|
} catch {
|
|
99
134
|
return undefined;
|
|
100
135
|
}
|