@memorax/memorax-code 0.1.8 → 0.1.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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/clients/codex/rollout-turn.js +52 -2
- 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,233 @@
|
|
|
1
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { join, resolve } from "node:path";
|
|
4
|
+
import { withJsonFileLockAsync } from "./memorax-code-adapter-common/src/config-utils.mjs";
|
|
5
|
+
import {
|
|
6
|
+
AUTOMATIC_UPDATE_CHECK_INTERVAL_MS,
|
|
7
|
+
AUTOMATIC_UPDATE_RECORD_VERSION,
|
|
8
|
+
AUTOMATIC_UPDATE_RETRY_INTERVAL_MS,
|
|
9
|
+
automaticUpdateStatePath,
|
|
10
|
+
readAutomaticUpdateState,
|
|
11
|
+
writeAutomaticUpdateState,
|
|
12
|
+
} from "./memorax-code-adapter-common/src/automatic-update-state.mjs";
|
|
13
|
+
import {
|
|
14
|
+
readSetupCompletionRecord,
|
|
15
|
+
withSetupCompletionLock,
|
|
16
|
+
} from "./memorax-code-adapter-common/src/setup-completion.mjs";
|
|
17
|
+
import {
|
|
18
|
+
npmCommandCwd,
|
|
19
|
+
resolveNpmInvocation,
|
|
20
|
+
runNpmCommand,
|
|
21
|
+
waitForChildProcess,
|
|
22
|
+
} from "./npm-invocation.mjs";
|
|
23
|
+
|
|
24
|
+
export {
|
|
25
|
+
AUTOMATIC_UPDATE_CHECK_INTERVAL_MS,
|
|
26
|
+
AUTOMATIC_UPDATE_RECORD_VERSION,
|
|
27
|
+
AUTOMATIC_UPDATE_RETRY_INTERVAL_MS,
|
|
28
|
+
automaticUpdateStatePath,
|
|
29
|
+
readAutomaticUpdateState,
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export async function runAutomaticUpdate(options) {
|
|
33
|
+
const memoraxCodeHome = resolve(requiredString(options?.memoraxCodeHome, "memoraxCodeHome"));
|
|
34
|
+
const packageRoot = resolve(requiredString(options?.packageRoot, "packageRoot"));
|
|
35
|
+
const packageName = requiredString(options?.packageName, "packageName");
|
|
36
|
+
const packageVersion = requiredString(options?.packageVersion, "packageVersion");
|
|
37
|
+
const env = options?.env ?? process.env;
|
|
38
|
+
if (automaticUpdateDisabled(env.MEMORAX_CODE_AUTO_UPDATE)) {
|
|
39
|
+
return { ok: true, disposition: "disabled" };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const completion = readSetupCompletionRecord(memoraxCodeHome);
|
|
43
|
+
if (completion.status === "absent") {
|
|
44
|
+
return { ok: true, disposition: "setup-incomplete" };
|
|
45
|
+
}
|
|
46
|
+
if (completion.status !== "valid") {
|
|
47
|
+
return { ok: false, disposition: "failed", reason: "setup_completion_invalid" };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const channel = packageVersion.includes("-") ? "preview" : "latest";
|
|
51
|
+
return await runAutomaticUpdateCore({
|
|
52
|
+
memoraxCodeHome,
|
|
53
|
+
installedVersion: packageVersion,
|
|
54
|
+
completedByVersion: completion.record.completedByVersion,
|
|
55
|
+
channel,
|
|
56
|
+
resolveTargetVersion: async (targetChannel) => resolveTargetVersion({
|
|
57
|
+
channel: targetChannel,
|
|
58
|
+
env,
|
|
59
|
+
packageName,
|
|
60
|
+
}),
|
|
61
|
+
installVersion: async (targetVersion) => (
|
|
62
|
+
await runNpmCommand(["install", "-g", `${packageName}@${targetVersion}`], {
|
|
63
|
+
env: {
|
|
64
|
+
...env,
|
|
65
|
+
MEMORAX_CODE_AUTOMATIC_UPDATE_PROCESS: "1",
|
|
66
|
+
MEMORAX_CODE_HOME: memoraxCodeHome,
|
|
67
|
+
},
|
|
68
|
+
stdio: "ignore",
|
|
69
|
+
windowsHide: true,
|
|
70
|
+
})
|
|
71
|
+
).exitCode === 0,
|
|
72
|
+
reconcile: async (targetVersion) => await runAutomaticSetup({
|
|
73
|
+
env,
|
|
74
|
+
memoraxCodeHome,
|
|
75
|
+
packageRoot,
|
|
76
|
+
targetVersion,
|
|
77
|
+
}),
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export async function runAutomaticUpdateCore(options) {
|
|
82
|
+
const memoraxCodeHome = resolve(requiredString(options?.memoraxCodeHome, "memoraxCodeHome"));
|
|
83
|
+
const installedVersion = requiredString(options?.installedVersion, "installedVersion");
|
|
84
|
+
const completedByVersion = requiredString(options?.completedByVersion, "completedByVersion");
|
|
85
|
+
const channel = options?.channel === "preview" ? "preview" : "latest";
|
|
86
|
+
const resolveTargetVersion = requiredFunction(options?.resolveTargetVersion, "resolveTargetVersion");
|
|
87
|
+
const installVersion = requiredFunction(options?.installVersion, "installVersion");
|
|
88
|
+
const reconcile = requiredFunction(options?.reconcile, "reconcile");
|
|
89
|
+
const path = automaticUpdateStatePath(memoraxCodeHome);
|
|
90
|
+
|
|
91
|
+
return await withJsonFileLockAsync(path, async () => {
|
|
92
|
+
const nowMs = currentTime(options);
|
|
93
|
+
const finish = (result, effectiveVersion, retry) => {
|
|
94
|
+
const state = writeAutomaticUpdateState({
|
|
95
|
+
memoraxCodeHome,
|
|
96
|
+
nowMs,
|
|
97
|
+
installedVersion: effectiveVersion,
|
|
98
|
+
retry,
|
|
99
|
+
});
|
|
100
|
+
return { ...result, state };
|
|
101
|
+
};
|
|
102
|
+
const state = readAutomaticUpdateState(memoraxCodeHome);
|
|
103
|
+
if (state.status === "valid"
|
|
104
|
+
&& state.record.installedVersion === installedVersion
|
|
105
|
+
&& nowMs < Date.parse(state.record.nextCheckAt)) {
|
|
106
|
+
return { ok: true, disposition: "throttled", state: state.record };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
let targetVersion;
|
|
110
|
+
try {
|
|
111
|
+
targetVersion = requiredString(
|
|
112
|
+
await resolveTargetVersion(channel),
|
|
113
|
+
"targetVersion",
|
|
114
|
+
);
|
|
115
|
+
} catch {
|
|
116
|
+
return finish({ ok: false, disposition: "failed", reason: "check_failed" }, installedVersion, true);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
let effectiveVersion = installedVersion;
|
|
120
|
+
let updated = false;
|
|
121
|
+
if (targetVersion !== installedVersion) {
|
|
122
|
+
let installed = false;
|
|
123
|
+
try {
|
|
124
|
+
installed = await installVersion(targetVersion) === true;
|
|
125
|
+
} catch {
|
|
126
|
+
installed = false;
|
|
127
|
+
}
|
|
128
|
+
if (!installed) {
|
|
129
|
+
return finish({ ok: false, disposition: "failed", reason: "update_failed" }, installedVersion, true);
|
|
130
|
+
}
|
|
131
|
+
effectiveVersion = targetVersion;
|
|
132
|
+
updated = true;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (updated || completedByVersion !== effectiveVersion) {
|
|
136
|
+
let reconciled = false;
|
|
137
|
+
try {
|
|
138
|
+
reconciled = await reconcile(effectiveVersion) === true;
|
|
139
|
+
} catch {
|
|
140
|
+
reconciled = false;
|
|
141
|
+
}
|
|
142
|
+
if (!reconciled) {
|
|
143
|
+
return finish({ ok: false, disposition: "failed", reason: "reconcile_failed" }, effectiveVersion, true);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const outcome = updated
|
|
148
|
+
? "updated"
|
|
149
|
+
: completedByVersion === effectiveVersion
|
|
150
|
+
? "up-to-date"
|
|
151
|
+
: "reconciled";
|
|
152
|
+
return finish({ ok: true, disposition: outcome }, effectiveVersion, false);
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function resolveTargetVersion({ channel, env, packageName }) {
|
|
157
|
+
const args = ["view", `${packageName}@${channel}`, "version", "--json"];
|
|
158
|
+
const invocation = resolveNpmInvocation(args, { env });
|
|
159
|
+
const cwd = npmCommandCwd(env);
|
|
160
|
+
const result = spawnSync(invocation.command, invocation.args, {
|
|
161
|
+
cwd,
|
|
162
|
+
encoding: "utf8",
|
|
163
|
+
env: { ...env, PWD: cwd, MEMORAX_CODE_AUTOMATIC_UPDATE_PROCESS: "1" },
|
|
164
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
165
|
+
timeout: 30_000,
|
|
166
|
+
maxBuffer: 64 * 1024,
|
|
167
|
+
windowsHide: true,
|
|
168
|
+
});
|
|
169
|
+
if (result.error || result.signal || result.status !== 0) {
|
|
170
|
+
throw new Error("npm registry version check failed");
|
|
171
|
+
}
|
|
172
|
+
let version;
|
|
173
|
+
try {
|
|
174
|
+
version = JSON.parse(String(result.stdout ?? ""));
|
|
175
|
+
} catch {
|
|
176
|
+
throw new Error("npm registry returned an invalid version response");
|
|
177
|
+
}
|
|
178
|
+
if (typeof version !== "string"
|
|
179
|
+
|| !/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/.test(version)) {
|
|
180
|
+
throw new Error("npm registry returned an invalid package version");
|
|
181
|
+
}
|
|
182
|
+
return version;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
async function runAutomaticSetup({ env, memoraxCodeHome, packageRoot, targetVersion }) {
|
|
186
|
+
return await withSetupCompletionLock(memoraxCodeHome, async (completion) => {
|
|
187
|
+
if (completion.status !== "valid") return false;
|
|
188
|
+
const setupPath = join(packageRoot, "bin", "memorax-code-setup.mjs");
|
|
189
|
+
if (!existsSync(setupPath)) return false;
|
|
190
|
+
const setupEnv = {
|
|
191
|
+
...env,
|
|
192
|
+
MEMORAX_CODE_AUTOMATIC_UPDATE_PROCESS: "1",
|
|
193
|
+
MEMORAX_CODE_HOME: memoraxCodeHome,
|
|
194
|
+
MEMORAX_CODE_SETUP_AUTOMATIC_UPDATE: "1",
|
|
195
|
+
MEMORAX_CODE_SETUP_UPDATE: "1",
|
|
196
|
+
};
|
|
197
|
+
delete setupEnv.MEMORAX_CODE_SETUP_MODE;
|
|
198
|
+
const child = spawn(process.execPath, [setupPath], {
|
|
199
|
+
env: setupEnv,
|
|
200
|
+
stdio: "ignore",
|
|
201
|
+
windowsHide: true,
|
|
202
|
+
});
|
|
203
|
+
if ((await waitForChildProcess(child)).exitCode !== 0) return false;
|
|
204
|
+
const reconciled = readSetupCompletionRecord(memoraxCodeHome);
|
|
205
|
+
return reconciled.status === "valid"
|
|
206
|
+
&& reconciled.record.completedByVersion === targetVersion;
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function currentTime(options) {
|
|
211
|
+
const value = typeof options?.now === "function" ? Number(options.now()) : Date.now();
|
|
212
|
+
if (!Number.isFinite(value)) throw new TypeError("automatic update now must return a timestamp");
|
|
213
|
+
return value;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function requiredFunction(value, name) {
|
|
217
|
+
if (typeof value !== "function") throw new TypeError(`automatic update requires ${name}`);
|
|
218
|
+
return value;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function requiredString(value, name) {
|
|
222
|
+
const result = nonEmptyString(value);
|
|
223
|
+
if (!result) throw new TypeError(`automatic update requires ${name}`);
|
|
224
|
+
return result;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function nonEmptyString(value) {
|
|
228
|
+
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function automaticUpdateDisabled(value) {
|
|
232
|
+
return ["0", "false", "no", "off", "disabled"].includes(String(value ?? "").trim().toLowerCase());
|
|
233
|
+
}
|
|
@@ -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
|
}
|