@agentmemory/agentmemory 0.9.31 → 0.9.32
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/.env.example +18 -15
- package/AGENTS.md +4 -4
- package/README.md +92 -270
- package/dist/.env.example +18 -15
- package/dist/cli.d.mts.map +1 -1
- package/dist/cli.mjs +530 -75
- package/dist/cli.mjs.map +1 -1
- package/dist/{compression-recovery-CnhLQC6N.mjs → compression-recovery-CZh63Jmz.mjs} +174 -89
- package/dist/compression-recovery-CZh63Jmz.mjs.map +1 -0
- package/dist/{connect-BSLIAMpw.mjs → connect-BX3njQ3Y.mjs} +28 -14
- package/dist/connect-BX3njQ3Y.mjs.map +1 -0
- package/dist/hooks/_capture.d.mts +3 -2
- package/dist/hooks/_capture.d.mts.map +1 -1
- package/dist/hooks/_capture.mjs +87 -8
- package/dist/hooks/_capture.mjs.map +1 -1
- package/dist/hooks/post-commit.mjs +58 -5
- package/dist/hooks/post-commit.mjs.map +1 -1
- package/dist/hooks/post-tool-failure.mjs +7 -2
- package/dist/hooks/post-tool-failure.mjs.map +1 -1
- package/dist/hooks/post-tool-use.mjs +7 -2
- package/dist/hooks/post-tool-use.mjs.map +1 -1
- package/dist/hooks/pre-compact.mjs +67 -12
- package/dist/hooks/pre-compact.mjs.map +1 -1
- package/dist/hooks/pre-tool-use.mjs +58 -5
- package/dist/hooks/pre-tool-use.mjs.map +1 -1
- package/dist/hooks/session-end.mjs +14 -3
- package/dist/hooks/session-end.mjs.map +1 -1
- package/dist/hooks/session-start.mjs +58 -5
- package/dist/hooks/session-start.mjs.map +1 -1
- package/dist/hooks/stop.mjs +2 -1
- package/dist/hooks/stop.mjs.map +1 -1
- package/dist/hooks/subagent-stop.mjs +15 -5
- package/dist/hooks/subagent-stop.mjs.map +1 -1
- package/dist/hooks/task-completed.mjs +8 -1
- package/dist/hooks/task-completed.mjs.map +1 -1
- package/dist/index.mjs +724 -200
- package/dist/index.mjs.map +1 -1
- package/dist/integrations/dsh.mjs +31 -4
- package/dist/integrations/dsh.mjs.map +1 -1
- package/dist/{secret-store-D9GwO0xp.mjs → secret-store-iYXRcrO8.mjs} +197 -55
- package/dist/secret-store-iYXRcrO8.mjs.map +1 -0
- package/dist/{src-DaMtDzt_.mjs → src-C23nMpda.mjs} +359 -62
- package/dist/src-C23nMpda.mjs.map +1 -0
- package/dist/{standalone-BlqshyZ5.mjs → standalone-BYo5USDb.mjs} +169 -10
- package/dist/standalone-BYo5USDb.mjs.map +1 -0
- package/dist/standalone.d.mts.map +1 -1
- package/dist/standalone.mjs +240 -10
- package/dist/standalone.mjs.map +1 -1
- package/dist/viewer/index.html +49 -28
- package/integrations/hermes/README.md +11 -1
- package/integrations/hermes/__init__.py +1 -0
- package/integrations/hermes/plugin.yaml +1 -1
- package/integrations/omp/dist/index.mjs +31 -4
- package/integrations/omp/package.json +1 -1
- package/integrations/omp/skills/agentmemory-agents/REFERENCE.md +1 -1
- package/integrations/omp/skills/agentmemory-architecture/SKILL.md +5 -1
- package/integrations/omp/skills/agentmemory-config/REFERENCE.md +2 -1
- package/integrations/omp/skills/agentmemory-config/SKILL.md +29 -9
- package/integrations/omp/skills/agentmemory-hooks/SKILL.md +17 -6
- package/integrations/omp/skills/agentmemory-mcp-tools/REFERENCE.md +1 -1
- package/integrations/omp/skills/agentmemory-mcp-tools/SKILL.md +10 -0
- package/integrations/omp/skills/agentmemory-rest-api/REFERENCE.md +2 -1
- package/integrations/omp/skills/agentmemory-rest-api/SKILL.md +1 -1
- package/integrations/openclaw/README.md +10 -0
- package/integrations/openclaw/openclaw.plugin.json +1 -1
- package/integrations/openclaw/package.json +1 -1
- package/integrations/openclaw/plugin.yaml +1 -1
- package/integrations/openclaw/skills/agentmemory-agents/REFERENCE.md +1 -1
- package/integrations/openclaw/skills/agentmemory-architecture/SKILL.md +5 -1
- package/integrations/openclaw/skills/agentmemory-config/REFERENCE.md +2 -1
- package/integrations/openclaw/skills/agentmemory-config/SKILL.md +29 -9
- package/integrations/openclaw/skills/agentmemory-hooks/SKILL.md +17 -6
- package/integrations/openclaw/skills/agentmemory-mcp-tools/REFERENCE.md +1 -1
- package/integrations/openclaw/skills/agentmemory-mcp-tools/SKILL.md +10 -0
- package/integrations/openclaw/skills/agentmemory-rest-api/REFERENCE.md +2 -1
- package/integrations/openclaw/skills/agentmemory-rest-api/SKILL.md +1 -1
- package/integrations/pi/README.md +1 -0
- package/integrations/pi/dist/index.mjs +92 -11
- package/integrations/pi/index.ts +56 -6
- package/integrations/pi/package.json +1 -1
- package/integrations/pi/skills/agentmemory-agents/REFERENCE.md +1 -1
- package/integrations/pi/skills/agentmemory-architecture/SKILL.md +5 -1
- package/integrations/pi/skills/agentmemory-config/REFERENCE.md +2 -1
- package/integrations/pi/skills/agentmemory-config/SKILL.md +29 -9
- package/integrations/pi/skills/agentmemory-hooks/SKILL.md +17 -6
- package/integrations/pi/skills/agentmemory-mcp-tools/REFERENCE.md +1 -1
- package/integrations/pi/skills/agentmemory-mcp-tools/SKILL.md +10 -0
- package/integrations/pi/skills/agentmemory-rest-api/REFERENCE.md +2 -1
- package/integrations/pi/skills/agentmemory-rest-api/SKILL.md +1 -1
- package/package.json +4 -3
- package/plugin/.claude-plugin/plugin.json +16 -3
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/.devin-plugin/plugin.json +3 -4
- package/plugin/.mcp.json +5 -1
- package/plugin/README.md +71 -0
- package/plugin/hooks/hooks.json +24 -12
- package/plugin/opencode/plugin.json +1 -1
- package/plugin/plugin.json +1 -1
- package/plugin/scripts/_capture.d.mts +3 -2
- package/plugin/scripts/_capture.d.mts.map +1 -1
- package/plugin/scripts/_capture.mjs +87 -8
- package/plugin/scripts/_capture.mjs.map +1 -1
- package/plugin/scripts/plugin-bridge.mjs +25 -6
- package/plugin/scripts/post-tool-failure.mjs +7 -2
- package/plugin/scripts/post-tool-failure.mjs.map +1 -1
- package/plugin/scripts/post-tool-use.mjs +7 -2
- package/plugin/scripts/post-tool-use.mjs.map +1 -1
- package/plugin/scripts/pre-compact.mjs +67 -12
- package/plugin/scripts/pre-compact.mjs.map +1 -1
- package/plugin/scripts/pre-tool-use.mjs +58 -5
- package/plugin/scripts/pre-tool-use.mjs.map +1 -1
- package/plugin/scripts/session-end.mjs +14 -3
- package/plugin/scripts/session-end.mjs.map +1 -1
- package/plugin/scripts/session-start.mjs +58 -5
- package/plugin/scripts/session-start.mjs.map +1 -1
- package/plugin/scripts/stop.mjs +2 -1
- package/plugin/scripts/stop.mjs.map +1 -1
- package/plugin/scripts/subagent-stop.mjs +15 -5
- package/plugin/scripts/subagent-stop.mjs.map +1 -1
- package/plugin/scripts/task-completed.mjs +8 -1
- package/plugin/scripts/task-completed.mjs.map +1 -1
- package/plugin/skills/agentmemory-agents/REFERENCE.md +1 -1
- package/plugin/skills/agentmemory-architecture/SKILL.md +5 -1
- package/plugin/skills/agentmemory-config/REFERENCE.md +2 -1
- package/plugin/skills/agentmemory-config/SKILL.md +29 -9
- package/plugin/skills/agentmemory-hooks/SKILL.md +17 -6
- package/plugin/skills/agentmemory-mcp-tools/REFERENCE.md +1 -1
- package/plugin/skills/agentmemory-mcp-tools/SKILL.md +10 -0
- package/plugin/skills/agentmemory-rest-api/REFERENCE.md +2 -1
- package/plugin/skills/agentmemory-rest-api/SKILL.md +1 -1
- package/dist/compression-recovery-CnhLQC6N.mjs.map +0 -1
- package/dist/connect-BSLIAMpw.mjs.map +0 -1
- package/dist/secret-store-D9GwO0xp.mjs.map +0 -1
- package/dist/src-DaMtDzt_.mjs.map +0 -1
- package/dist/standalone-BlqshyZ5.mjs.map +0 -1
- package/plugin/scripts/post-commit.d.mts +0 -1
- package/plugin/scripts/post-commit.mjs +0 -204
- package/plugin/scripts/post-commit.mjs.map +0 -1
package/dist/cli.mjs
CHANGED
|
@@ -1,21 +1,27 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { a as runAdapter, i as resolveAdapter, r as knownAgents, t as ADAPTERS } from "./connect-
|
|
2
|
+
import { a as runAdapter, i as resolveAdapter, r as knownAgents, t as ADAPTERS } from "./connect-BX3njQ3Y.mjs";
|
|
3
3
|
import { s as generateId } from "./keyed-mutex-DCUh7PG-.mjs";
|
|
4
|
-
import { A as
|
|
5
|
-
import { $t as resolveDataDir, Gt as retainSent, Ht as drainSpool, Jt as spoolSummary, Ut as parseSentMark, an as runtimeMetadataPath, nn as registerStartupCleanup, nt as CURL_AUTH_HEADER, rn as runStartupCleanup, t as parseCompressionScope, tn as SHUTDOWN_HARD_EXIT_MS } from "./compression-recovery-
|
|
4
|
+
import { A as setBootVerbose, B as detectLlmProviderKind, C as getAllTools, E as VERSION, F as runtimeConfigPath, I as LLM_PROVIDER_KEYS, M as engineFlushWaitMs, N as engineStateConfigPaths, P as redactCredentialUrls, S as ESSENTIAL_TOOLS, T as III_PINNED_VERSION, V as embeddingOptInHint, Z as getRedisUrl, et as getStateBackend, i as resolveClientSecret, it as hydrateProcessEnvFromFile, mt as llmOptInHint, t as bearerHeaders, tt as getStateSaveIntervalMs, xt as parseEnvContent } from "./secret-store-iYXRcrO8.mjs";
|
|
5
|
+
import { $t as resolveDataDir, Gt as retainSent, Ht as drainSpool, Jt as spoolSummary, Ut as parseSentMark, an as runtimeMetadataPath, nn as registerStartupCleanup, nt as CURL_AUTH_HEADER, rn as runStartupCleanup, t as parseCompressionScope, tn as SHUTDOWN_HARD_EXIT_MS } from "./compression-recovery-CZh63Jmz.mjs";
|
|
6
6
|
import { execFileSync, spawn, spawnSync } from "node:child_process";
|
|
7
7
|
import { appendFileSync, chmodSync, closeSync, constants, existsSync, fstatSync, fsyncSync, mkdirSync, openSync, readFileSync, readSync, readdirSync, readlinkSync, realpathSync, renameSync, rmSync, statSync, unlinkSync, writeFileSync, writeSync } from "node:fs";
|
|
8
|
-
import { delimiter, dirname, isAbsolute, join, resolve } from "node:path";
|
|
8
|
+
import { basename, delimiter, dirname, isAbsolute, join, resolve, win32 } from "node:path";
|
|
9
9
|
import { fileURLToPath } from "node:url";
|
|
10
10
|
import { homedir, platform } from "node:os";
|
|
11
11
|
import * as p from "@clack/prompts";
|
|
12
12
|
import pc from "picocolors";
|
|
13
|
-
import { createHash } from "node:crypto";
|
|
13
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
14
14
|
import { copyFile, mkdir } from "node:fs/promises";
|
|
15
15
|
import { parse, stringify } from "yaml";
|
|
16
16
|
import { createConnection } from "node:net";
|
|
17
17
|
import { registerWorker } from "iii-sdk";
|
|
18
18
|
//#region src/cli/doctor-diagnostics.ts
|
|
19
|
+
function canAutoFix(d, status) {
|
|
20
|
+
return !d.manualOnly && !status?.manualFix;
|
|
21
|
+
}
|
|
22
|
+
function doctorPromptsAllowed(env, stdinIsTTY, stdoutIsTTY) {
|
|
23
|
+
return Boolean(stdinIsTTY) && Boolean(stdoutIsTTY) && !env["CI"];
|
|
24
|
+
}
|
|
19
25
|
/** Common placeholder values shipped in .env.example. */
|
|
20
26
|
const PLACEHOLDER_VALUES = new Set([
|
|
21
27
|
"",
|
|
@@ -70,22 +76,28 @@ function buildDiagnostics(effects) {
|
|
|
70
76
|
},
|
|
71
77
|
{
|
|
72
78
|
id: "no-llm-provider-key",
|
|
73
|
-
message: "No LLM provider
|
|
74
|
-
fixPreview: "Open ~/.agentmemory/.env in $EDITOR and
|
|
75
|
-
moreInfo: "
|
|
79
|
+
message: "No LLM provider chosen for agentmemory in ~/.agentmemory/.env or the environment.",
|
|
80
|
+
fixPreview: "Open ~/.agentmemory/.env in $EDITOR, set AGENTMEMORY_LLM_PROVIDER and that provider's key, then re-check.",
|
|
81
|
+
moreInfo: "agentmemory sends session content to an LLM provider only after you choose one: set AGENTMEMORY_LLM_PROVIDER to anthropic, openai, gemini, openrouter or minimax, and set that provider's key (ANTHROPIC_API_KEY, OPENAI_API_KEY, GEMINI_API_KEY or GOOGLE_API_KEY, OPENROUTER_API_KEY, MINIMAX_API_KEY). A key alone never turns LLM use on. Placeholder values count as missing. AGENTMEMORY_ALLOW_AGENT_SDK=true needs no key. A value exported in the environment wins over the file, but only for a daemon started from that environment.",
|
|
76
82
|
check: async () => {
|
|
77
83
|
const file = effects.readEnvFile();
|
|
78
84
|
const env = { ...file };
|
|
79
85
|
for (const [k, v] of Object.entries(effects.processEnv())) if (v !== void 0) env[k] = v;
|
|
80
86
|
for (const k of placeholderProviderKeys(env)) delete env[k];
|
|
81
|
-
if (detectLlmProviderKind(env) !== "llm")
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
87
|
+
if (detectLlmProviderKind(env) !== "llm") {
|
|
88
|
+
const hint = llmOptInHint(env);
|
|
89
|
+
if (hint) return {
|
|
90
|
+
ok: false,
|
|
91
|
+
detail: hint
|
|
92
|
+
};
|
|
93
|
+
return {
|
|
94
|
+
ok: false,
|
|
95
|
+
detail: effects.envFileExists() ? "no provider chosen" : "no provider chosen (run env-missing fix first)"
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
const selected = (env.AGENTMEMORY_LLM_PROVIDER ?? "").trim().toLowerCase();
|
|
99
|
+
const selectedKeys = selected in LLM_PROVIDER_KEYS ? LLM_PROVIDER_KEYS[selected] : [];
|
|
100
|
+
const real = realProviderKeys(env).filter((k) => selectedKeys.includes(k));
|
|
89
101
|
const fromEnv = real.filter((k) => file[k] !== env[k]);
|
|
90
102
|
const named = real.map((k) => fromEnv.includes(k) ? `${k} (environment)` : k);
|
|
91
103
|
if (named.length === 0) return {
|
|
@@ -100,28 +112,61 @@ function buildDiagnostics(effects) {
|
|
|
100
112
|
},
|
|
101
113
|
fix: (ctx) => effects.openEditor(ctx.envPath)
|
|
102
114
|
},
|
|
115
|
+
{
|
|
116
|
+
id: "embedding-provider-not-chosen",
|
|
117
|
+
message: "Cloud embeddings are not set up: EMBEDDING_PROVIDER does not choose a present key, or names a provider whose key is missing.",
|
|
118
|
+
fixPreview: "Open ~/.agentmemory/.env in $EDITOR and set EMBEDDING_PROVIDER (local, openai, voyage, cohere, gemini or openrouter).",
|
|
119
|
+
moreInfo: "Cloud embeddings send memory text to that provider, so agentmemory uses them only when EMBEDDING_PROVIDER names the provider. A key alone never turns them on. EMBEDDING_PROVIDER=local runs on-device. Without a provider, search is keyword and graph only.",
|
|
120
|
+
check: async () => {
|
|
121
|
+
const env = { ...effects.readEnvFile() };
|
|
122
|
+
for (const [k, v] of Object.entries(effects.processEnv())) if (v !== void 0) env[k] = v;
|
|
123
|
+
const hint = embeddingOptInHint(env);
|
|
124
|
+
return hint ? {
|
|
125
|
+
ok: false,
|
|
126
|
+
detail: hint
|
|
127
|
+
} : { ok: true };
|
|
128
|
+
},
|
|
129
|
+
fix: (ctx) => effects.openEditor(ctx.envPath)
|
|
130
|
+
},
|
|
103
131
|
{
|
|
104
132
|
id: "engine-version-mismatch",
|
|
105
|
-
message: "iii binary
|
|
133
|
+
message: "No iii binary matches the version agentmemory pins to.",
|
|
106
134
|
fixPreview: "Reinstall the pinned iii-engine and restart the managed Worker Compose project.",
|
|
107
|
-
moreInfo: "agentmemory manages its pinned iii engine and workers through Worker Compose; a foreign process on PATH is not the instance binary. Running a mismatched binary surfaces as EPIPE reconnect loops and empty search results.",
|
|
135
|
+
moreInfo: "agentmemory manages its pinned iii engine and workers through Worker Compose; a foreign process on PATH is not the instance binary. Running a mismatched binary surfaces as EPIPE reconnect loops and empty search results. Before an instance is configured, a matching private install at ~/.agentmemory/bin/iii can satisfy the pin.",
|
|
108
136
|
check: async (ctx) => {
|
|
137
|
+
if (await effects.externalEngineRunning()) return {
|
|
138
|
+
ok: false,
|
|
139
|
+
detail: "external engine version is unknown",
|
|
140
|
+
manualFix: "Verify the external engine version, or stop it before starting a managed instance."
|
|
141
|
+
};
|
|
142
|
+
const privateVersion = effects.iiiBinaryVersion(effects.localBinIiiPath());
|
|
109
143
|
const bin = effects.findIiiBinary();
|
|
144
|
+
const pathVersion = bin ? effects.iiiBinaryVersion(bin) : null;
|
|
145
|
+
if (privateVersion === ctx.pinnedVersion) {
|
|
146
|
+
const note = pathVersion && pathVersion !== ctx.pinnedVersion ? `; PATH iii is ${pathVersion}; the pinned private install is available for startup` : "";
|
|
147
|
+
return {
|
|
148
|
+
ok: true,
|
|
149
|
+
detail: `private install ${privateVersion} (pinned ${ctx.pinnedVersion})${note}`
|
|
150
|
+
};
|
|
151
|
+
}
|
|
110
152
|
if (!bin) return {
|
|
111
153
|
ok: false,
|
|
112
|
-
detail: "iii not on PATH"
|
|
154
|
+
detail: "iii not on PATH and no private install"
|
|
113
155
|
};
|
|
114
|
-
|
|
115
|
-
if (!v) return {
|
|
156
|
+
if (!pathVersion) return {
|
|
116
157
|
ok: false,
|
|
117
158
|
detail: "iii on PATH but --version failed"
|
|
118
159
|
};
|
|
119
160
|
return {
|
|
120
|
-
ok:
|
|
121
|
-
detail: `${
|
|
161
|
+
ok: pathVersion === ctx.pinnedVersion,
|
|
162
|
+
detail: `${pathVersion} (pinned ${ctx.pinnedVersion})`
|
|
122
163
|
};
|
|
123
164
|
},
|
|
124
165
|
fix: async () => {
|
|
166
|
+
if (await effects.externalEngineRunning()) return {
|
|
167
|
+
ok: false,
|
|
168
|
+
message: "Verify or stop the external engine manually."
|
|
169
|
+
};
|
|
125
170
|
const r = await effects.runIiiInstaller();
|
|
126
171
|
if (!r.ok) return r;
|
|
127
172
|
await effects.runStop();
|
|
@@ -188,20 +233,25 @@ function buildDiagnostics(effects) {
|
|
|
188
233
|
},
|
|
189
234
|
{
|
|
190
235
|
id: "iii-on-path-not-local-bin",
|
|
191
|
-
message: "iii is on PATH but
|
|
236
|
+
message: "iii is on PATH but agentmemory's private install is absent or mismatched.",
|
|
192
237
|
fixPreview: "Install the pinned version to ~/.agentmemory/bin — won't touch your PATH.",
|
|
193
238
|
moreInfo: "agentmemory installs its pinned engine to ~/.agentmemory/bin/iii so a user-managed iii on PATH (homebrew, cargo, manual install) stays untouched. When agentmemory needs the pin and PATH doesn't have it, it falls back to the private install. If neither exists, run the installer.",
|
|
194
239
|
manualOnly: true,
|
|
195
|
-
check: async () => {
|
|
240
|
+
check: async (ctx) => {
|
|
196
241
|
const bin = effects.findIiiBinary();
|
|
197
242
|
if (!bin) return {
|
|
198
243
|
ok: true,
|
|
199
244
|
detail: "iii not on PATH (handled elsewhere)"
|
|
200
245
|
};
|
|
201
246
|
const localBin = effects.localBinIiiPath();
|
|
247
|
+
if (bin === localBin) return { ok: true };
|
|
248
|
+
if (effects.iiiBinaryVersion(localBin) === ctx.pinnedVersion) return {
|
|
249
|
+
ok: true,
|
|
250
|
+
detail: `private install pinned; PATH iii at ${bin} stays untouched`
|
|
251
|
+
};
|
|
202
252
|
return {
|
|
203
|
-
ok:
|
|
204
|
-
detail:
|
|
253
|
+
ok: false,
|
|
254
|
+
detail: `iii at: ${bin}`
|
|
205
255
|
};
|
|
206
256
|
},
|
|
207
257
|
fix: async () => effects.runIiiInstaller().then((r) => ({
|
|
@@ -222,7 +272,7 @@ function dryRunPlan(ctx, results) {
|
|
|
222
272
|
if (status.ok) continue;
|
|
223
273
|
n++;
|
|
224
274
|
lines.push(`${n}. [${diagnostic.id}] ${diagnostic.message}`);
|
|
225
|
-
lines.push(` would fix: ${diagnostic.fixPreview}`);
|
|
275
|
+
lines.push(` ${canAutoFix(diagnostic, status) ? "would fix" : "manual fix"}: ${status.manualFix ?? diagnostic.fixPreview}`);
|
|
226
276
|
if (status.detail) lines.push(` detail: ${status.detail}`);
|
|
227
277
|
}
|
|
228
278
|
if (lines.length === 0) lines.push(`All checks passing for ${ctx.baseUrl} — no fixes to run.`);
|
|
@@ -511,6 +561,251 @@ function installIiiArchive(opts) {
|
|
|
511
561
|
};
|
|
512
562
|
return { ok: true };
|
|
513
563
|
}
|
|
564
|
+
function iiiZipArchivePath(binDir) {
|
|
565
|
+
return join(binDir, `.iii-download-${process.pid}-${randomBytes(4).toString("hex")}.zip`);
|
|
566
|
+
}
|
|
567
|
+
function removeQuietly(path) {
|
|
568
|
+
try {
|
|
569
|
+
rmSync(path, {
|
|
570
|
+
force: true,
|
|
571
|
+
recursive: true
|
|
572
|
+
});
|
|
573
|
+
} catch {}
|
|
574
|
+
}
|
|
575
|
+
function removeStaleInstallFiles(binDir, now = Date.now()) {
|
|
576
|
+
const removed = [];
|
|
577
|
+
let entries;
|
|
578
|
+
try {
|
|
579
|
+
entries = readdirSync(binDir);
|
|
580
|
+
} catch {
|
|
581
|
+
return removed;
|
|
582
|
+
}
|
|
583
|
+
for (const name of entries) {
|
|
584
|
+
if (!name.startsWith(".iii-download-") && !name.startsWith(".iii-staging-")) continue;
|
|
585
|
+
const path = join(binDir, name);
|
|
586
|
+
try {
|
|
587
|
+
if (now - statSync(path).mtimeMs < 9e5) continue;
|
|
588
|
+
} catch {
|
|
589
|
+
continue;
|
|
590
|
+
}
|
|
591
|
+
removeQuietly(path);
|
|
592
|
+
if (!existsSync(path)) removed.push(name);
|
|
593
|
+
}
|
|
594
|
+
return removed;
|
|
595
|
+
}
|
|
596
|
+
const PROXY_ENV_KEYS = [
|
|
597
|
+
"HTTPS_PROXY",
|
|
598
|
+
"https_proxy",
|
|
599
|
+
"HTTP_PROXY",
|
|
600
|
+
"http_proxy",
|
|
601
|
+
"ALL_PROXY",
|
|
602
|
+
"all_proxy"
|
|
603
|
+
];
|
|
604
|
+
function proxyConfigured(env) {
|
|
605
|
+
return PROXY_ENV_KEYS.some((key) => Boolean(env[key]?.trim()));
|
|
606
|
+
}
|
|
607
|
+
function windowsCurlDownloadCommand(url, dest, env = process.env) {
|
|
608
|
+
return {
|
|
609
|
+
command: join(env["SystemRoot"] || env["SYSTEMROOT"] || "C:\\Windows", "System32", "curl.exe"),
|
|
610
|
+
args: [
|
|
611
|
+
"-fsSL",
|
|
612
|
+
"--connect-timeout",
|
|
613
|
+
String(15),
|
|
614
|
+
"--max-time",
|
|
615
|
+
String(300),
|
|
616
|
+
"-o",
|
|
617
|
+
dest,
|
|
618
|
+
url
|
|
619
|
+
]
|
|
620
|
+
};
|
|
621
|
+
}
|
|
622
|
+
function powershellLiteral(value) {
|
|
623
|
+
return `'${value.replace(/'/g, "''")}'`;
|
|
624
|
+
}
|
|
625
|
+
function windowsZipExtractCommands(archive, binDir, env = process.env) {
|
|
626
|
+
const system32 = join(env["SystemRoot"] || env["SYSTEMROOT"] || "C:\\Windows", "System32");
|
|
627
|
+
return [{
|
|
628
|
+
command: join(system32, "tar.exe"),
|
|
629
|
+
args: [
|
|
630
|
+
"-xf",
|
|
631
|
+
archive,
|
|
632
|
+
"-C",
|
|
633
|
+
binDir
|
|
634
|
+
]
|
|
635
|
+
}, {
|
|
636
|
+
command: join(system32, "WindowsPowerShell", "v1.0", "powershell.exe"),
|
|
637
|
+
args: [
|
|
638
|
+
"-NoProfile",
|
|
639
|
+
"-NonInteractive",
|
|
640
|
+
"-Command",
|
|
641
|
+
`Expand-Archive -LiteralPath ${powershellLiteral(archive)} -DestinationPath ${powershellLiteral(binDir)} -Force`
|
|
642
|
+
]
|
|
643
|
+
}];
|
|
644
|
+
}
|
|
645
|
+
async function downloadToFile(url, dest, env = process.env, run = runInstallCommand) {
|
|
646
|
+
if (proxyConfigured(env)) {
|
|
647
|
+
const step = windowsCurlDownloadCommand(url, dest, env);
|
|
648
|
+
const result = await run(step.command, step.args);
|
|
649
|
+
if (result.status !== 0) throw new Error(describeInstallFailure(result));
|
|
650
|
+
return;
|
|
651
|
+
}
|
|
652
|
+
const res = await fetch(url, {
|
|
653
|
+
redirect: "follow",
|
|
654
|
+
signal: AbortSignal.timeout(300 * 1e3)
|
|
655
|
+
});
|
|
656
|
+
if (!res.ok) throw new Error(`download failed with HTTP ${res.status} ${res.statusText}`.trim());
|
|
657
|
+
writeFileSync(dest, Buffer.from(await res.arrayBuffer()));
|
|
658
|
+
}
|
|
659
|
+
function runInstallCommand(command, args) {
|
|
660
|
+
return new Promise((done) => {
|
|
661
|
+
let stdout = "";
|
|
662
|
+
let stderr = "";
|
|
663
|
+
let timedOut = false;
|
|
664
|
+
const child = spawn(command, args, {
|
|
665
|
+
stdio: [
|
|
666
|
+
"ignore",
|
|
667
|
+
"pipe",
|
|
668
|
+
"pipe"
|
|
669
|
+
],
|
|
670
|
+
windowsHide: true,
|
|
671
|
+
shell: false
|
|
672
|
+
});
|
|
673
|
+
const killOnExit = () => {
|
|
674
|
+
try {
|
|
675
|
+
child.kill();
|
|
676
|
+
} catch {}
|
|
677
|
+
};
|
|
678
|
+
process.once("exit", killOnExit);
|
|
679
|
+
const timer = setTimeout(() => {
|
|
680
|
+
timedOut = true;
|
|
681
|
+
child.kill();
|
|
682
|
+
}, III_INSTALL_SPAWN_TIMEOUT_MS);
|
|
683
|
+
child.stdout?.on("data", (chunk) => {
|
|
684
|
+
stdout += chunk;
|
|
685
|
+
});
|
|
686
|
+
child.stderr?.on("data", (chunk) => {
|
|
687
|
+
stderr += chunk;
|
|
688
|
+
});
|
|
689
|
+
let spawnError;
|
|
690
|
+
child.on("error", (error) => {
|
|
691
|
+
spawnError = error;
|
|
692
|
+
if (child.pid !== void 0) return;
|
|
693
|
+
clearTimeout(timer);
|
|
694
|
+
process.off("exit", killOnExit);
|
|
695
|
+
done({
|
|
696
|
+
status: null,
|
|
697
|
+
stdout,
|
|
698
|
+
stderr,
|
|
699
|
+
error
|
|
700
|
+
});
|
|
701
|
+
});
|
|
702
|
+
child.on("close", (status, signal) => {
|
|
703
|
+
clearTimeout(timer);
|
|
704
|
+
process.off("exit", killOnExit);
|
|
705
|
+
if (spawnError && status === null && !timedOut) {
|
|
706
|
+
done({
|
|
707
|
+
status: null,
|
|
708
|
+
stdout,
|
|
709
|
+
stderr,
|
|
710
|
+
signal,
|
|
711
|
+
error: spawnError
|
|
712
|
+
});
|
|
713
|
+
return;
|
|
714
|
+
}
|
|
715
|
+
if (timedOut) {
|
|
716
|
+
done({
|
|
717
|
+
status: null,
|
|
718
|
+
stdout,
|
|
719
|
+
stderr,
|
|
720
|
+
signal,
|
|
721
|
+
error: Object.assign(/* @__PURE__ */ new Error("timed out"), { code: "ETIMEDOUT" })
|
|
722
|
+
});
|
|
723
|
+
return;
|
|
724
|
+
}
|
|
725
|
+
done({
|
|
726
|
+
status,
|
|
727
|
+
stdout,
|
|
728
|
+
stderr,
|
|
729
|
+
signal
|
|
730
|
+
});
|
|
731
|
+
});
|
|
732
|
+
});
|
|
733
|
+
}
|
|
734
|
+
async function installIiiZip(opts) {
|
|
735
|
+
const expected = expectedIiiSha256(opts.version, opts.asset, opts.checksums);
|
|
736
|
+
if (!expected) return {
|
|
737
|
+
ok: false,
|
|
738
|
+
reason: "no-checksum",
|
|
739
|
+
detail: `no pinned SHA-256 for ${opts.asset} in iii v${opts.version}, so the download cannot be verified`
|
|
740
|
+
};
|
|
741
|
+
const run = opts.run ?? runInstallCommand;
|
|
742
|
+
const download = opts.download ?? ((url, dest) => downloadToFile(url, dest, opts.env ?? process.env, run));
|
|
743
|
+
const archive = iiiZipArchivePath(opts.binDir);
|
|
744
|
+
try {
|
|
745
|
+
mkdirSync(opts.binDir, { recursive: true });
|
|
746
|
+
removeStaleInstallFiles(opts.binDir);
|
|
747
|
+
await download(opts.releaseUrl, archive);
|
|
748
|
+
} catch (err) {
|
|
749
|
+
removeQuietly(archive);
|
|
750
|
+
return {
|
|
751
|
+
ok: false,
|
|
752
|
+
reason: "download",
|
|
753
|
+
detail: err instanceof Error ? err.message : String(err)
|
|
754
|
+
};
|
|
755
|
+
}
|
|
756
|
+
let actual;
|
|
757
|
+
try {
|
|
758
|
+
actual = sha256File(archive);
|
|
759
|
+
} catch (err) {
|
|
760
|
+
removeQuietly(archive);
|
|
761
|
+
return {
|
|
762
|
+
ok: false,
|
|
763
|
+
reason: "download",
|
|
764
|
+
detail: err instanceof Error ? err.message : String(err)
|
|
765
|
+
};
|
|
766
|
+
}
|
|
767
|
+
if (actual !== expected) {
|
|
768
|
+
removeQuietly(archive);
|
|
769
|
+
return {
|
|
770
|
+
ok: false,
|
|
771
|
+
reason: "checksum",
|
|
772
|
+
detail: `SHA-256 mismatch for ${opts.asset}: expected ${expected}, got ${actual}. The download was deleted and nothing was installed`
|
|
773
|
+
};
|
|
774
|
+
}
|
|
775
|
+
const staging = join(opts.binDir, `.iii-staging-${process.pid}-${randomBytes(4).toString("hex")}`);
|
|
776
|
+
const staged = join(staging, basename(opts.binPath));
|
|
777
|
+
const failures = [];
|
|
778
|
+
try {
|
|
779
|
+
for (const step of windowsZipExtractCommands(archive, staging, opts.env)) {
|
|
780
|
+
removeQuietly(staging);
|
|
781
|
+
mkdirSync(staging, { recursive: true });
|
|
782
|
+
const result = await run(step.command, step.args);
|
|
783
|
+
if (result.status === 0 && existsSync(staged)) try {
|
|
784
|
+
renameSync(staged, opts.binPath);
|
|
785
|
+
return { ok: true };
|
|
786
|
+
} catch (err) {
|
|
787
|
+
failures.push(`could not move ${staged} to ${opts.binPath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
788
|
+
break;
|
|
789
|
+
}
|
|
790
|
+
failures.push(`${step.command}: ${result.status === 0 ? `finished but ${basename(opts.binPath)} is missing from the archive` : describeInstallFailure(result)}`);
|
|
791
|
+
}
|
|
792
|
+
return {
|
|
793
|
+
ok: false,
|
|
794
|
+
reason: "extract",
|
|
795
|
+
detail: failures.join("; ")
|
|
796
|
+
};
|
|
797
|
+
} catch (err) {
|
|
798
|
+
failures.push(err instanceof Error ? err.message : String(err));
|
|
799
|
+
return {
|
|
800
|
+
ok: false,
|
|
801
|
+
reason: "extract",
|
|
802
|
+
detail: failures.join("; ")
|
|
803
|
+
};
|
|
804
|
+
} finally {
|
|
805
|
+
removeQuietly(staging);
|
|
806
|
+
removeQuietly(archive);
|
|
807
|
+
}
|
|
808
|
+
}
|
|
514
809
|
//#endregion
|
|
515
810
|
//#region src/cli/startup-stderr.ts
|
|
516
811
|
function createStartupStderrCapture(maxBytes = 16 * 1024) {
|
|
@@ -725,6 +1020,12 @@ function sameResolvedExecutable(actual, expected, windows = false) {
|
|
|
725
1020
|
return false;
|
|
726
1021
|
}
|
|
727
1022
|
}
|
|
1023
|
+
function classifyComposeProcess(inspected, expected, windows = false) {
|
|
1024
|
+
if (!inspected || !inspected.command) return "unknown";
|
|
1025
|
+
if (!(inspected.command.includes("compose --namespace") && inspected.command.includes(expected.namespace) && inspected.command.includes(expected.file))) return "foreign";
|
|
1026
|
+
if (inspected.executable && sameResolvedExecutable(inspected.executable, expected.binPath, windows)) return "match";
|
|
1027
|
+
return "unknown";
|
|
1028
|
+
}
|
|
728
1029
|
function readUnixProcessExecutable(pid, os) {
|
|
729
1030
|
if (!Number.isSafeInteger(pid) || pid <= 0) return "";
|
|
730
1031
|
if (os === "darwin") return readMacProcessExecutable(pid);
|
|
@@ -1002,6 +1303,7 @@ async function runOnboarding() {
|
|
|
1002
1303
|
if (hint) p.log.info(hint);
|
|
1003
1304
|
}
|
|
1004
1305
|
const envPath = await seedEnvFile(provider);
|
|
1306
|
+
const providerWritten = setLlmProviderInEnv(envPath, provider);
|
|
1005
1307
|
await maybePromptContextInjection(envPath);
|
|
1006
1308
|
writePrefs({
|
|
1007
1309
|
lastAgent: agents[0] ?? null,
|
|
@@ -1015,8 +1317,9 @@ async function runOnboarding() {
|
|
|
1015
1317
|
else lines.push(`! Could not write ~/.agentmemory/.env — run \`agentmemory init\` after this completes.`);
|
|
1016
1318
|
if (provider) {
|
|
1017
1319
|
const envKey = PROVIDERS.find((x) => x.value === provider)?.envKey;
|
|
1018
|
-
if (envKey) lines.push(`
|
|
1019
|
-
} else lines.push(" No provider chosen
|
|
1320
|
+
if (envKey) lines.push(providerWritten ? ` Set AGENTMEMORY_LLM_PROVIDER=${provider}. Add ${envKey}= to that file to enable ${provider}.` : ` Add AGENTMEMORY_LLM_PROVIDER=${provider} and ${envKey}= to that file to enable ${provider}.`);
|
|
1321
|
+
} else if (providerWritten) lines.push(" No LLM provider chosen. LLM summaries and compression stay off.");
|
|
1322
|
+
else lines.push(` ! Could not remove AGENTMEMORY_LLM_PROVIDER from ${envPath}. Delete that line to keep LLM features off.`);
|
|
1020
1323
|
p.note(lines.join("\n"), "ready");
|
|
1021
1324
|
if (agents.length > 0) await wireSelectedAgents(agents);
|
|
1022
1325
|
return {
|
|
@@ -1024,6 +1327,19 @@ async function runOnboarding() {
|
|
|
1024
1327
|
provider
|
|
1025
1328
|
};
|
|
1026
1329
|
}
|
|
1330
|
+
function setLlmProviderInEnv(envPath, provider) {
|
|
1331
|
+
if (provider === null) delete process.env["AGENTMEMORY_LLM_PROVIDER"];
|
|
1332
|
+
if (!envPath || !existsSync(envPath)) return provider === null;
|
|
1333
|
+
try {
|
|
1334
|
+
const current = readFileSync(envPath, "utf-8");
|
|
1335
|
+
const kept = current.split("\n").filter((entry) => !/^\s*AGENTMEMORY_LLM_PROVIDER\s*=/.test(entry)).join("\n");
|
|
1336
|
+
const next = provider === null ? kept : `${kept}${kept.length > 0 && !kept.endsWith("\n") ? "\n" : ""}AGENTMEMORY_LLM_PROVIDER=${provider}\n`;
|
|
1337
|
+
if (next !== current) writeFileSync(envPath, next, { mode: 384 });
|
|
1338
|
+
return true;
|
|
1339
|
+
} catch {
|
|
1340
|
+
return false;
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1027
1343
|
function enableInjectContextInEnv(envPath) {
|
|
1028
1344
|
if (!envPath || !existsSync(envPath)) return false;
|
|
1029
1345
|
try {
|
|
@@ -1361,6 +1677,10 @@ const COMPOSE_WORKER_VERSIONS = {
|
|
|
1361
1677
|
state: "0.22.20",
|
|
1362
1678
|
http: "0.22.7"
|
|
1363
1679
|
};
|
|
1680
|
+
const COMPOSE_CONTAINER_COUNT = Object.keys(COMPOSE_WORKER_VERSIONS).length + 1;
|
|
1681
|
+
function composeDownTimeoutMs() {
|
|
1682
|
+
return (20 * COMPOSE_CONTAINER_COUNT + 30) * 1e3;
|
|
1683
|
+
}
|
|
1364
1684
|
function composeNamespace(restPort) {
|
|
1365
1685
|
return `agentmemory-${restPort}`;
|
|
1366
1686
|
}
|
|
@@ -1376,8 +1696,8 @@ function composeProjectPaths(dataDir) {
|
|
|
1376
1696
|
}
|
|
1377
1697
|
const AGENTMEMORY_WORKER_ENV_ALLOWLIST = new Set([
|
|
1378
1698
|
"EMBEDDING_PROVIDER",
|
|
1699
|
+
"AGENTMEMORY_LLM_PROVIDER",
|
|
1379
1700
|
"OPENAI_API_KEY",
|
|
1380
|
-
"OPENAI_API_KEY_FOR_LLM",
|
|
1381
1701
|
"OPENAI_BASE_URL",
|
|
1382
1702
|
"OPENAI_MODEL",
|
|
1383
1703
|
"OPENAI_EMBEDDING_API_KEY",
|
|
@@ -1515,17 +1835,33 @@ function restartPolicyLines(indent, enabled) {
|
|
|
1515
1835
|
`${indent} window: 60s`
|
|
1516
1836
|
];
|
|
1517
1837
|
}
|
|
1518
|
-
function shellQuote$1(value
|
|
1838
|
+
function shellQuote$1(value) {
|
|
1519
1839
|
if (/[\u0000-\u001f\u007f]/.test(value)) throw new Error("A shell command argument contains an unsupported control character.");
|
|
1520
|
-
if (windows) {
|
|
1521
|
-
if (value.includes("\"")) throw new Error("Windows shell arguments cannot contain a double quote.");
|
|
1522
|
-
return `"${value.replace(/%/g, "%%")}"`;
|
|
1523
|
-
}
|
|
1524
1840
|
return `'${value.replace(/'/g, `'"'"'`)}'`;
|
|
1525
1841
|
}
|
|
1526
|
-
function shellCommand(program, args
|
|
1527
|
-
|
|
1528
|
-
|
|
1842
|
+
function shellCommand(program, args) {
|
|
1843
|
+
return [program, ...args].map((part) => shellQuote$1(part)).join(" ");
|
|
1844
|
+
}
|
|
1845
|
+
const WINDOWS_RUN_SAFE = /^[A-Za-z0-9._:\\/-]+$/;
|
|
1846
|
+
const WINDOWS_EXE_SAFE = /^[A-Za-z0-9._-]+$/;
|
|
1847
|
+
function windowsWorkerScript(script) {
|
|
1848
|
+
if (!WINDOWS_RUN_SAFE.test(script)) throw new Error(`On Windows, AGENTMEMORY_WORKER_BIN_DIR must be a path without spaces or cmd metacharacters; got ${JSON.stringify(script)}.`);
|
|
1849
|
+
return script;
|
|
1850
|
+
}
|
|
1851
|
+
function windowsNodeRun(nodeBin) {
|
|
1852
|
+
const base = win32.basename(nodeBin);
|
|
1853
|
+
if (!WINDOWS_EXE_SAFE.test(base)) throw new Error(`The Node executable name must not contain spaces or cmd metacharacters on Windows; got ${JSON.stringify(nodeBin)}.`);
|
|
1854
|
+
return `${base} dist/index.mjs`;
|
|
1855
|
+
}
|
|
1856
|
+
function windowsWorkerPath(nodeBin, inherited) {
|
|
1857
|
+
const entries = [win32.dirname(nodeBin), ...(inherited ?? "").split(";")].map((entry) => entry.trim()).filter((entry) => entry.length > 0);
|
|
1858
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1859
|
+
return entries.filter((entry) => {
|
|
1860
|
+
const key = entry.toLowerCase();
|
|
1861
|
+
if (seen.has(key)) return false;
|
|
1862
|
+
seen.add(key);
|
|
1863
|
+
return true;
|
|
1864
|
+
}).join(";");
|
|
1529
1865
|
}
|
|
1530
1866
|
function yamlString(value) {
|
|
1531
1867
|
if (/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/.test(value)) throw new Error("A Compose path or configuration value contains an unsupported control character.");
|
|
@@ -1597,17 +1933,18 @@ function renderWorkerCompose(options) {
|
|
|
1597
1933
|
const { restPort, viewerPort, enginePort } = options.ports;
|
|
1598
1934
|
const paths = composeProjectPaths(options.dataDir);
|
|
1599
1935
|
const namespace = composeNamespace(restPort);
|
|
1600
|
-
const
|
|
1936
|
+
const windows = options.windows ?? process.platform === "win32";
|
|
1937
|
+
const workerBinDir = options.workerBinDir ? options.workerBinDirIsAbsolute ? windows ? win32.resolve(options.workerBinDir) : resolve(options.workerBinDir) : localWorkerDirectory(options.workerBinDir, options.cwd ?? process.cwd(), options.projectRoot) : null;
|
|
1601
1938
|
const startupSeconds = Math.ceil((options.startupTimeoutMs ?? 3e5) / 1e3);
|
|
1602
1939
|
const restartEnabled = !options.disableRestartPolicy;
|
|
1603
1940
|
if (!Number.isSafeInteger(startupSeconds) || startupSeconds < 1 || startupSeconds * 1e3 > 3600999) throw new Error("Compose worker startup timeout must be between 1 and 3600000 milliseconds.");
|
|
1604
1941
|
const workerSource = (name) => workerBinDir ? `path://${localWorkerProjectDir(paths, name)}` : `package://api.workers.iii.dev/${name}`;
|
|
1605
|
-
const workerScript = (name) => workerBinDir ? join(workerBinDir, name) : "";
|
|
1942
|
+
const workerScript = (name) => workerBinDir ? windows ? win32.join(workerBinDir, name) : join(workerBinDir, name) : "";
|
|
1606
1943
|
const filteredEnvFile = options.envFile ? join(paths.directory, ".agentmemory-filtered.env") : void 0;
|
|
1607
1944
|
const lines = [
|
|
1608
1945
|
`namespace: ${namespace}`,
|
|
1609
1946
|
"startup_timeout: 60s",
|
|
1610
|
-
|
|
1947
|
+
`stop_timeout: 20s`,
|
|
1611
1948
|
"required_default: true",
|
|
1612
1949
|
"engine:",
|
|
1613
1950
|
` url: ws://127.0.0.1:${enginePort}`,
|
|
@@ -1623,7 +1960,7 @@ function renderWorkerCompose(options) {
|
|
|
1623
1960
|
"containers:"
|
|
1624
1961
|
];
|
|
1625
1962
|
const stateScript = workerScript("state");
|
|
1626
|
-
lines.push(" state:", ` worker: ${yamlString(workerSource("state"))}`, ...!workerBinDir ? [` version: ${yamlString(COMPOSE_WORKER_VERSIONS.state)}`] : [], " config_name: state", ...restartPolicyLines(" ", restartEnabled), ...workerBinDir ? [` scripts:`, ` run: ${yamlString(shellCommand(stateScript, []
|
|
1963
|
+
lines.push(" state:", ` worker: ${yamlString(workerSource("state"))}`, ...!workerBinDir ? [` version: ${yamlString(COMPOSE_WORKER_VERSIONS.state)}`] : [], " config_name: state", ...restartPolicyLines(" ", restartEnabled), ...workerBinDir ? [` scripts:`, ` run: ${yamlString(windows ? windowsWorkerScript(stateScript) : shellCommand(stateScript, []))}`] : [], " config_override:", " adapter:", " name: kv", " config:");
|
|
1627
1964
|
if (options.stateBackend?.kind === "redis") {
|
|
1628
1965
|
const redisUrl = options.redisUrl ?? options.stateBackend.redisUrl;
|
|
1629
1966
|
if (!redisUrl) throw new Error("AGENTMEMORY_STATE_BACKEND=redis requires AGENTMEMORY_REDIS_URL to be set (e.g. redis://localhost:6379).");
|
|
@@ -1631,11 +1968,11 @@ function renderWorkerCompose(options) {
|
|
|
1631
1968
|
lines.splice(lines.length - 2, 2, " name: redis", " config:", ` redis_url: ${yamlString(redisUrl)}`);
|
|
1632
1969
|
} else lines.push(" store_method: file_based", ` file_path: ${yamlString(join(options.dataDir, "state_store.db"))}`, ` save_interval_ms: ${stateSaveInterval(options.workerEnvironment?.AGENTMEMORY_STATE_SAVE_INTERVAL_MS)}`);
|
|
1633
1970
|
const httpScript = workerScript("http");
|
|
1634
|
-
lines.push(" http:", ` worker: ${yamlString(workerSource("http"))}`, ...!workerBinDir ? [` version: ${yamlString(COMPOSE_WORKER_VERSIONS.http)}`] : [], " config_name: http", ...restartPolicyLines(" ", restartEnabled), ...workerBinDir ? [` scripts:`, ` run: ${yamlString(shellCommand(httpScript, []
|
|
1635
|
-
lines.push(" agentmemory:", ` worker: ${yamlString(`path://${options.projectRoot}`)}`, " start_after: [state, http]", ` startup_timeout: ${startupSeconds}s`, " config_name: agentmemory", ...restartPolicyLines(" ", restartEnabled), " scripts:", ` run: ${yamlString(shellCommand(options.nodeBin, ["dist/index.mjs"]
|
|
1971
|
+
lines.push(" http:", ` worker: ${yamlString(workerSource("http"))}`, ...!workerBinDir ? [` version: ${yamlString(COMPOSE_WORKER_VERSIONS.http)}`] : [], " config_name: http", ...restartPolicyLines(" ", restartEnabled), ...workerBinDir ? [` scripts:`, ` run: ${yamlString(windows ? windowsWorkerScript(httpScript) : shellCommand(httpScript, []))}`] : [], " config_override:", " host: 127.0.0.1", ` port: ${restPort}`, " default_timeout: 180000", " cors:", " allowed_origins:", ` - ${yamlString(`http://localhost:${restPort}`)}`, ` - ${yamlString(`http://localhost:${viewerPort}`)}`, ` - ${yamlString(`http://127.0.0.1:${restPort}`)}`, ` - ${yamlString(`http://127.0.0.1:${viewerPort}`)}`, " allowed_methods: [GET, POST, PUT, DELETE, OPTIONS]");
|
|
1972
|
+
lines.push(" agentmemory:", ` worker: ${yamlString(`path://${options.projectRoot}`)}`, " start_after: [state, http]", ` startup_timeout: ${startupSeconds}s`, " config_name: agentmemory", ...restartPolicyLines(" ", restartEnabled), " scripts:", ` run: ${yamlString(windows ? windowsNodeRun(options.nodeBin) : shellCommand(options.nodeBin, ["dist/index.mjs"]))}`);
|
|
1636
1973
|
if (filteredEnvFile) lines.push(` env_file: [${yamlString(filteredEnvFile)}]`);
|
|
1637
1974
|
const explicitWorkerEnvironment = Object.entries(options.workerEnvironment ?? {}).filter(([key, value]) => value !== void 0 && AGENTMEMORY_WORKER_ENV_ALLOWLIST.has(key) && !COMPOSE_RESERVED_ENV.has(key)).sort(([left], [right]) => left.localeCompare(right));
|
|
1638
|
-
lines.push(" environment:", ...explicitWorkerEnvironment.map(([key, value]) => ` ${key}: ${yamlString(value)}`), ` HOME: ${yamlString(options.homeDir)}`, ` PATH: ${yamlString(`${dirname(options.nodeBin)}:/usr/local/bin:/usr/bin:/bin`)}`, ` AGENTMEMORY_DATA_DIR: ${yamlString(options.dataDir)}`, ` AGENTMEMORY_RUNTIME_DIR: ${yamlString(options.dataDir)}`, ...filteredEnvFile ? [` AGENTMEMORY_ENV_FILE: ${yamlString(filteredEnvFile)}`] : [], ...options.stateBackend?.kind === "redis" && options.redisUrl ? [` AGENTMEMORY_REDIS_URL: ${yamlString(options.redisUrl)}`, " AGENTMEMORY_STATE_BACKEND: 'redis'"] : [], ` III_ENGINE_PORT: ${yamlString(String(enginePort))}`, ` III_REST_PORT: ${yamlString(String(restPort))}`, ` III_VIEWER_PORT: ${yamlString(String(viewerPort))}`, " III_TELEMETRY_ENABLED: 'false'", ...options.linux ?? process.platform === "linux" ? [" MALLOC_ARENA_MAX: '2'"] : [], "");
|
|
1975
|
+
lines.push(" environment:", ...explicitWorkerEnvironment.map(([key, value]) => ` ${key}: ${yamlString(value)}`), ...windows ? [` NoDefaultCurrentDirectoryInExePath: ${yamlString("1")}`] : [], ` HOME: ${yamlString(options.homeDir)}`, ` PATH: ${yamlString(windows ? windowsWorkerPath(options.nodeBin, options.workerEnvironment?.PATH ?? process.env.PATH) : `${dirname(options.nodeBin)}:/usr/local/bin:/usr/bin:/bin`)}`, ` AGENTMEMORY_DATA_DIR: ${yamlString(options.dataDir)}`, ` AGENTMEMORY_RUNTIME_DIR: ${yamlString(options.dataDir)}`, ...filteredEnvFile ? [` AGENTMEMORY_ENV_FILE: ${yamlString(filteredEnvFile)}`] : [], ...options.stateBackend?.kind === "redis" && options.redisUrl ? [` AGENTMEMORY_REDIS_URL: ${yamlString(options.redisUrl)}`, " AGENTMEMORY_STATE_BACKEND: 'redis'"] : [], ` III_ENGINE_PORT: ${yamlString(String(enginePort))}`, ` III_REST_PORT: ${yamlString(String(restPort))}`, ` III_VIEWER_PORT: ${yamlString(String(viewerPort))}`, " III_TELEMETRY_ENABLED: 'false'", ...options.linux ?? process.platform === "linux" ? [" MALLOC_ARENA_MAX: '2'"] : [], "");
|
|
1639
1976
|
return lines.join("\n");
|
|
1640
1977
|
}
|
|
1641
1978
|
function writeWorkerCompose(options) {
|
|
@@ -1731,7 +2068,7 @@ function composeStateRegistryFailure(dataDir, file) {
|
|
|
1731
2068
|
for (const stateFile of files) {
|
|
1732
2069
|
const state = JSON.parse(readFileSync(stateFile, "utf8"));
|
|
1733
2070
|
const message = Object.entries(state.containers ?? {}).filter(([, value]) => value.status === "failed").map(([name, value]) => `${name}: ${value.last_error ?? "failed"}`).join("\n");
|
|
1734
|
-
if (state.compose_path
|
|
2071
|
+
if (!sameComposeFile(state.compose_path, file)) continue;
|
|
1735
2072
|
if (message && composeRegistryFailureMessage(message)) return message;
|
|
1736
2073
|
}
|
|
1737
2074
|
} catch {}
|
|
@@ -1743,8 +2080,30 @@ function composeWorkerFailureSummary(project) {
|
|
|
1743
2080
|
if (!failed.length) return null;
|
|
1744
2081
|
return failed.map((container) => `${container.container ?? "worker"}: ${container.last_error ?? "failed after restart attempts were exhausted"}`).join("\n");
|
|
1745
2082
|
}
|
|
1746
|
-
function
|
|
1747
|
-
return
|
|
2083
|
+
function defaultCanonicalizer(windows) {
|
|
2084
|
+
return (value) => {
|
|
2085
|
+
try {
|
|
2086
|
+
return windows ? realpathSync.native(value) : realpathSync(value);
|
|
2087
|
+
} catch {
|
|
2088
|
+
return value;
|
|
2089
|
+
}
|
|
2090
|
+
};
|
|
2091
|
+
}
|
|
2092
|
+
function normalizeWindowsPath(value) {
|
|
2093
|
+
let out = value;
|
|
2094
|
+
if (out.startsWith("\\\\?\\UNC\\")) out = `\\\\${out.slice(8)}`;
|
|
2095
|
+
else if (out.startsWith("\\\\?\\")) out = out.slice(4);
|
|
2096
|
+
return out.replace(/\//g, "\\").toLowerCase();
|
|
2097
|
+
}
|
|
2098
|
+
function sameComposeFile(left, right, windows = process.platform === "win32", canonicalize = defaultCanonicalizer(windows)) {
|
|
2099
|
+
if (!left) return false;
|
|
2100
|
+
if (!windows) return left === right || canonicalize(left) === canonicalize(right);
|
|
2101
|
+
if (normalizeWindowsPath(left) === normalizeWindowsPath(right)) return true;
|
|
2102
|
+
return normalizeWindowsPath(canonicalize(left)) === normalizeWindowsPath(canonicalize(right));
|
|
2103
|
+
}
|
|
2104
|
+
function ownedComposeProject(status, file, windows = process.platform === "win32", canonicalize = defaultCanonicalizer(windows)) {
|
|
2105
|
+
if (sameComposeFile(status.file, file, windows, canonicalize)) return status;
|
|
2106
|
+
return status.projects?.find((item) => sameComposeFile(item.file, file, windows, canonicalize)) ?? null;
|
|
1748
2107
|
}
|
|
1749
2108
|
function cleanDaemonEnv(env) {
|
|
1750
2109
|
const out = {};
|
|
@@ -1794,8 +2153,29 @@ function composeEnvironment(env, stateDir) {
|
|
|
1794
2153
|
...env.AGENTMEMORY_DISABLE_COMPOSE_RESTART === "1" ? { AGENTMEMORY_DISABLE_COMPOSE_RESTART: "1" } : {}
|
|
1795
2154
|
};
|
|
1796
2155
|
}
|
|
1797
|
-
|
|
1798
|
-
|
|
2156
|
+
let quietDepth = 0;
|
|
2157
|
+
let savedDebug = null;
|
|
2158
|
+
async function withQuietSdkDebug(run) {
|
|
2159
|
+
if (quietDepth === 0) {
|
|
2160
|
+
savedDebug = console.debug;
|
|
2161
|
+
console.debug = () => {};
|
|
2162
|
+
}
|
|
2163
|
+
quietDepth++;
|
|
2164
|
+
try {
|
|
2165
|
+
return await run();
|
|
2166
|
+
} finally {
|
|
2167
|
+
quietDepth--;
|
|
2168
|
+
if (quietDepth === 0 && savedDebug) {
|
|
2169
|
+
console.debug = savedDebug;
|
|
2170
|
+
savedDebug = null;
|
|
2171
|
+
}
|
|
2172
|
+
}
|
|
2173
|
+
}
|
|
2174
|
+
async function composeCall(options, register = registerWorker) {
|
|
2175
|
+
return withQuietSdkDebug(() => composeCallWithClient(options, register));
|
|
2176
|
+
}
|
|
2177
|
+
async function composeCallWithClient(options, register) {
|
|
2178
|
+
const client = register(options.engineUrl, {
|
|
1799
2179
|
workerName: "agentmemory-cli",
|
|
1800
2180
|
namespace: options.namespace,
|
|
1801
2181
|
invocationTimeoutMs: options.timeoutMs ?? 3e4,
|
|
@@ -2253,8 +2633,20 @@ function fallbackIiiPaths() {
|
|
|
2253
2633
|
paths.push("/usr/local/bin/iii");
|
|
2254
2634
|
return paths;
|
|
2255
2635
|
}
|
|
2636
|
+
const III_VERSION_PROBE_TIMEOUTS_MS = [3e3, 3e4];
|
|
2637
|
+
function isProbeTimeout(err) {
|
|
2638
|
+
const e = err;
|
|
2639
|
+
return e?.code === "ETIMEDOUT" || e?.signal === "SIGTERM";
|
|
2640
|
+
}
|
|
2641
|
+
let iiiProbeScan = null;
|
|
2256
2642
|
function iiiBinVersion(binPath) {
|
|
2257
|
-
|
|
2643
|
+
if (iiiProbeScan?.has(binPath)) return iiiProbeScan.get(binPath);
|
|
2644
|
+
const version = probeIiiVersion(binPath);
|
|
2645
|
+
iiiProbeScan?.set(binPath, version);
|
|
2646
|
+
return version;
|
|
2647
|
+
}
|
|
2648
|
+
function probeIiiVersion(binPath) {
|
|
2649
|
+
for (const timeout of III_VERSION_PROBE_TIMEOUTS_MS) try {
|
|
2258
2650
|
const match = execFileSync(binPath, ["--version"], {
|
|
2259
2651
|
encoding: "utf-8",
|
|
2260
2652
|
stdio: [
|
|
@@ -2262,12 +2654,14 @@ function iiiBinVersion(binPath) {
|
|
|
2262
2654
|
"pipe",
|
|
2263
2655
|
"ignore"
|
|
2264
2656
|
],
|
|
2265
|
-
timeout
|
|
2657
|
+
timeout
|
|
2266
2658
|
}).match(/(\d+\.\d+\.\d+(?:[-+][\w.]+)?)/);
|
|
2267
2659
|
return match ? match[1] : null;
|
|
2268
|
-
} catch {
|
|
2269
|
-
return null;
|
|
2660
|
+
} catch (err) {
|
|
2661
|
+
if (!isProbeTimeout(err)) return null;
|
|
2662
|
+
vlog(`iii --version at ${binPath} timed out after ${timeout}ms`);
|
|
2270
2663
|
}
|
|
2664
|
+
return null;
|
|
2271
2665
|
}
|
|
2272
2666
|
function resolveCompatibleIii(iiiBinPath) {
|
|
2273
2667
|
if (!iiiBinPath) return null;
|
|
@@ -2434,7 +2828,7 @@ async function startWorkerForEngineState(background = false) {
|
|
|
2434
2828
|
child.unref();
|
|
2435
2829
|
} else {
|
|
2436
2830
|
importedStartupWorker = true;
|
|
2437
|
-
await import("./src-
|
|
2831
|
+
await import("./src-C23nMpda.mjs");
|
|
2438
2832
|
}
|
|
2439
2833
|
},
|
|
2440
2834
|
probe: workerReadinessProbe,
|
|
@@ -2514,8 +2908,8 @@ async function maybeOfferGlobalInstall() {
|
|
|
2514
2908
|
if (runCommand(npmBin, [
|
|
2515
2909
|
"install",
|
|
2516
2910
|
"-g",
|
|
2517
|
-
`@agentmemory/agentmemory@0.9.
|
|
2518
|
-
], { label: `Installing @agentmemory/agentmemory@0.9.
|
|
2911
|
+
`@agentmemory/agentmemory@0.9.32`
|
|
2912
|
+
], { label: `Installing @agentmemory/agentmemory@0.9.32 globally` })) {
|
|
2519
2913
|
p.log.success("Installed globally. `agentmemory stop` etc. will now work in new shells.");
|
|
2520
2914
|
writePrefs({ skipGlobalInstall: true });
|
|
2521
2915
|
} else p.log.warn("Global install failed. Try manually: npm install -g @agentmemory/agentmemory");
|
|
@@ -2532,7 +2926,28 @@ async function runIiiInstaller() {
|
|
|
2532
2926
|
};
|
|
2533
2927
|
}
|
|
2534
2928
|
if (IS_WINDOWS || isZipAsset) {
|
|
2535
|
-
|
|
2929
|
+
const binDir = agentmemoryBinDir();
|
|
2930
|
+
const binPath = privateIiiPath();
|
|
2931
|
+
const label = `Installing iii-engine v${IIPINNED_VERSION} (pinned)`;
|
|
2932
|
+
const spinner = p.spinner();
|
|
2933
|
+
spinner.start(label);
|
|
2934
|
+
const outcome = await installIiiZip({
|
|
2935
|
+
releaseUrl,
|
|
2936
|
+
version: IIPINNED_VERSION,
|
|
2937
|
+
asset: asset ?? "",
|
|
2938
|
+
binDir,
|
|
2939
|
+
binPath
|
|
2940
|
+
});
|
|
2941
|
+
if (outcome.ok) {
|
|
2942
|
+
spinner.stop(`${label} ${pc.green("✓")}`);
|
|
2943
|
+
iiiProbeScan?.delete(binPath);
|
|
2944
|
+
return {
|
|
2945
|
+
ok: true,
|
|
2946
|
+
binPath
|
|
2947
|
+
};
|
|
2948
|
+
}
|
|
2949
|
+
spinner.stop(`${label} ${pc.red("✗")}`);
|
|
2950
|
+
p.log.warn(`iii-engine auto-install failed: ${outcome.detail.slice(0, 300)}\nInstall it manually, then re-run agentmemory:\n 1. Download ${releaseUrl}\n Its SHA-256 must be ${expectedIiiSha256(IIPINNED_VERSION, asset ?? "") ?? `the value in ${releaseUrl.replace(/\.zip$/, ".sha256")}`}\n 2. Extract iii.exe into ${binDir}`);
|
|
2536
2951
|
return {
|
|
2537
2952
|
ok: false,
|
|
2538
2953
|
binPath: null
|
|
@@ -2578,6 +2993,7 @@ async function runIiiInstaller() {
|
|
|
2578
2993
|
};
|
|
2579
2994
|
}
|
|
2580
2995
|
spinner.stop(`${label} ${pc.green("✓")}`);
|
|
2996
|
+
iiiProbeScan?.delete(binPath);
|
|
2581
2997
|
return {
|
|
2582
2998
|
ok: true,
|
|
2583
2999
|
binPath
|
|
@@ -2679,6 +3095,12 @@ function printComposeStartupLogs() {
|
|
|
2679
3095
|
if (logs.text) p.note(`Log file: ${logs.path}\n\n${logs.text}`, "Recent Worker Compose output");
|
|
2680
3096
|
}
|
|
2681
3097
|
function composePidMatches(pid, state) {
|
|
3098
|
+
return composePidIdentity(pid, state) === "match";
|
|
3099
|
+
}
|
|
3100
|
+
function composePidIdentity(pid, state) {
|
|
3101
|
+
return classifyComposeProcess(inspectComposeProcess(pid), state, IS_WINDOWS);
|
|
3102
|
+
}
|
|
3103
|
+
function inspectComposeProcess(pid) {
|
|
2682
3104
|
try {
|
|
2683
3105
|
let command = "";
|
|
2684
3106
|
let executable = "";
|
|
@@ -2730,11 +3152,12 @@ function composePidMatches(pid, state) {
|
|
|
2730
3152
|
}).trim();
|
|
2731
3153
|
executable = readUnixProcessExecutable(pid, "linux");
|
|
2732
3154
|
}
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
3155
|
+
return {
|
|
3156
|
+
command,
|
|
3157
|
+
executable
|
|
3158
|
+
};
|
|
2736
3159
|
} catch {
|
|
2737
|
-
return
|
|
3160
|
+
return null;
|
|
2738
3161
|
}
|
|
2739
3162
|
}
|
|
2740
3163
|
function composeChildProcesses(state) {
|
|
@@ -2895,6 +3318,14 @@ function pickCompatibleIii(candidates) {
|
|
|
2895
3318
|
return null;
|
|
2896
3319
|
}
|
|
2897
3320
|
async function startEngine() {
|
|
3321
|
+
iiiProbeScan = /* @__PURE__ */ new Map();
|
|
3322
|
+
try {
|
|
3323
|
+
return await scanAndStartEngine();
|
|
3324
|
+
} finally {
|
|
3325
|
+
iiiProbeScan = null;
|
|
3326
|
+
}
|
|
3327
|
+
}
|
|
3328
|
+
async function scanAndStartEngine() {
|
|
2898
3329
|
startupFailure = null;
|
|
2899
3330
|
engineReadinessDeadline = null;
|
|
2900
3331
|
let stateBackendKind;
|
|
@@ -2931,7 +3362,7 @@ async function startEngine() {
|
|
|
2931
3362
|
const pathIii = whichBinary("iii");
|
|
2932
3363
|
vlog(`iii binary: ${pathIii ?? "(not on PATH)"}`);
|
|
2933
3364
|
const fallbacks = fallbackIiiPaths().filter((p) => existsSync(p));
|
|
2934
|
-
|
|
3365
|
+
vlog(`fallback iii candidates: ${fallbacks.length > 0 ? fallbacks.join(", ") : "(none)"}`);
|
|
2935
3366
|
let iiiBin = pickCompatibleIii([pathIii, ...fallbacks]);
|
|
2936
3367
|
if (iiiBin) {
|
|
2937
3368
|
if (iiiBin !== pathIii) {
|
|
@@ -2948,6 +3379,9 @@ async function startEngine() {
|
|
|
2948
3379
|
choice = "install";
|
|
2949
3380
|
const detected = iiiBinVersion(pathIii);
|
|
2950
3381
|
p.log.info(`iii on PATH is v${detected ?? "unknown"} but agentmemory pins v${IIPINNED_VERSION}. Installing pinned engine to ~/.agentmemory/bin (leaves your existing iii untouched).`);
|
|
3382
|
+
} else if (IS_WINDOWS) {
|
|
3383
|
+
choice = "install";
|
|
3384
|
+
p.log.warn("iii-engine binary not found locally.");
|
|
2951
3385
|
} else if (!interactive) {
|
|
2952
3386
|
choice = "install";
|
|
2953
3387
|
p.log.info("Non-interactive environment detected — auto-installing iii-engine.");
|
|
@@ -3020,7 +3454,7 @@ function installInstructions() {
|
|
|
3020
3454
|
` 1. Open https://github.com/iii-hq/iii/releases/tag/iii%2Fv${IIPINNED_VERSION}`,
|
|
3021
3455
|
` 2. Download iii-x86_64-pc-windows-msvc.zip (or iii-aarch64-pc-windows-msvc.zip on ARM)`,
|
|
3022
3456
|
verify,
|
|
3023
|
-
" 3. Extract iii.exe to %USERPROFILE%\\.
|
|
3457
|
+
" 3. Extract iii.exe to %USERPROFILE%\\.agentmemory\\bin\\iii.exe (or add to PATH)",
|
|
3024
3458
|
" 4. Re-run: npx @agentmemory/agentmemory",
|
|
3025
3459
|
"",
|
|
3026
3460
|
"Or skip the engine entirely (standalone MCP): npx @agentmemory/agentmemory mcp",
|
|
@@ -3062,7 +3496,7 @@ async function reconcilePersistedComposeEngine() {
|
|
|
3062
3496
|
const state = readEngineState();
|
|
3063
3497
|
if (state?.kind !== "compose") return false;
|
|
3064
3498
|
if (!await isEngineRunning()) {
|
|
3065
|
-
if (state.composePid && pidAlive(state.composePid)) {
|
|
3499
|
+
if (state.composePid && pidAlive(state.composePid) && composePidIdentity(state.composePid, state) !== "foreign") {
|
|
3066
3500
|
p.log.error(`Worker Compose daemon ${state.composePid} is alive but REST ${state.restPort} is not ready. Inspect ${state.logFile}; use agentmemory stop to reconcile.`);
|
|
3067
3501
|
process.exit(1);
|
|
3068
3502
|
}
|
|
@@ -3330,13 +3764,15 @@ async function runStatus() {
|
|
|
3330
3764
|
lines.push(` Injected: ~${estInjectedTokens.toLocaleString()} tokens`);
|
|
3331
3765
|
}
|
|
3332
3766
|
if (flagsRes) {
|
|
3333
|
-
const provider = flagsRes.provider === "llm" ? pc.green("✓ llm") : pc.yellow("✗ noop (
|
|
3767
|
+
const provider = flagsRes.provider === "llm" ? pc.green("✓ llm") : pc.yellow("✗ noop (LLM features off)");
|
|
3334
3768
|
const embedding = embeddingStatusLabel(flagsRes.embeddingProvider);
|
|
3335
3769
|
const embed = embedding ? pc.green(`✓ ${embedding}`) : pc.dim("bm25-only");
|
|
3336
3770
|
const flagRows = (flagsRes.flags || []).map((f) => ` ${f.enabled ? pc.green("✓") : pc.dim("✗")} ${pc.bold(f.key.padEnd(32))} ${f.label}`);
|
|
3337
3771
|
lines.push("");
|
|
3338
3772
|
lines.push(`Provider: ${provider}`);
|
|
3339
3773
|
lines.push(`Embeddings: ${embed}`);
|
|
3774
|
+
if (typeof flagsRes.providerHint === "string") lines.push(pc.yellow(` ${flagsRes.providerHint}`));
|
|
3775
|
+
if (typeof flagsRes.embeddingHint === "string") lines.push(pc.yellow(` ${flagsRes.embeddingHint}`));
|
|
3340
3776
|
lines.push(`Flags:`);
|
|
3341
3777
|
flagRows.forEach((r) => lines.push(r));
|
|
3342
3778
|
}
|
|
@@ -3452,6 +3888,11 @@ function buildDoctorEffects() {
|
|
|
3452
3888
|
return state?.kind === "compose" ? state.binPath : privateIiiPath();
|
|
3453
3889
|
},
|
|
3454
3890
|
iiiBinaryVersion: (binPath) => iiiBinVersion(binPath),
|
|
3891
|
+
externalEngineRunning: async () => {
|
|
3892
|
+
const state = readEngineState();
|
|
3893
|
+
const remoteTarget = shouldUseAgentmemoryUrl() && process.env["AGENTMEMORY_URL"];
|
|
3894
|
+
return !(state?.kind === "compose" && state.restPort === getRestPort() && !remoteTarget) && await isEngineRunning();
|
|
3895
|
+
},
|
|
3455
3896
|
viewerReachable: async (timeoutMs = 2e3) => {
|
|
3456
3897
|
try {
|
|
3457
3898
|
await discoverViewerPort();
|
|
@@ -3595,11 +4036,11 @@ async function passiveServerChecks() {
|
|
|
3595
4036
|
}, {
|
|
3596
4037
|
name: "LLM provider",
|
|
3597
4038
|
ok: hasLlm,
|
|
3598
|
-
hint: hasLlm ? void 0 : "set
|
|
4039
|
+
hint: hasLlm ? void 0 : typeof flags?.providerHint === "string" ? flags.providerHint : "set AGENTMEMORY_LLM_PROVIDER and that provider's key (for example ANTHROPIC_API_KEY) in ~/.agentmemory/.env"
|
|
3599
4040
|
}, {
|
|
3600
4041
|
name: "Embedding provider",
|
|
3601
4042
|
ok: hasEmbed,
|
|
3602
|
-
hint: hasEmbed ? void 0 : "Running BM25-only. Set EMBEDDING_PROVIDER=local for on-device semantic search, or
|
|
4043
|
+
hint: hasEmbed ? void 0 : typeof flags?.embeddingHint === "string" ? flags.embeddingHint : "Running BM25-only. Set EMBEDDING_PROVIDER=local for on-device semantic search, or EMBEDDING_PROVIDER=openai, voyage, cohere, gemini or openrouter with that provider's key"
|
|
3603
4044
|
});
|
|
3604
4045
|
for (const f of flags?.flags || []) checks.push({
|
|
3605
4046
|
name: f.label,
|
|
@@ -3703,6 +4144,8 @@ async function runDoctor() {
|
|
|
3703
4144
|
let fixed = 0;
|
|
3704
4145
|
let skipped = 0;
|
|
3705
4146
|
let quit = false;
|
|
4147
|
+
const promptsAllowed = doctorPromptsAllowed(process.env, process.stdin.isTTY, process.stdout.isTTY);
|
|
4148
|
+
let autoFixHintShown = false;
|
|
3706
4149
|
for (const d of diagnostics) {
|
|
3707
4150
|
if (quit) {
|
|
3708
4151
|
skipped++;
|
|
@@ -3715,13 +4158,25 @@ async function runDoctor() {
|
|
|
3715
4158
|
}
|
|
3716
4159
|
failed++;
|
|
3717
4160
|
p.log.warn(`${d.id} ✗ ${status.detail ?? ""}`.trim());
|
|
4161
|
+
if (!canAutoFix(d, status)) {
|
|
4162
|
+
p.log.info(`manual fix: ${status.manualFix ?? d.fixPreview}`);
|
|
4163
|
+
skipped++;
|
|
4164
|
+
continue;
|
|
4165
|
+
}
|
|
3718
4166
|
p.log.info(`why: ${d.fixPreview}`);
|
|
3719
|
-
if (d.manualOnly) p.log.info(`(manual fix only — see "${d.id}" docs)`);
|
|
3720
4167
|
if (applyAll) {
|
|
3721
4168
|
if ((await applyFixWithReport(d, ctx, false)).ok) fixed++;
|
|
3722
4169
|
if (!(await d.check(ctx)).ok) p.log.warn(`${d.id} still failing after fix.`);
|
|
3723
4170
|
continue;
|
|
3724
4171
|
}
|
|
4172
|
+
if (!promptsAllowed) {
|
|
4173
|
+
skipped++;
|
|
4174
|
+
if (!autoFixHintShown) {
|
|
4175
|
+
autoFixHintShown = true;
|
|
4176
|
+
p.log.info("Not prompting in a non-interactive shell. Run `agentmemory doctor --all` to apply the fixes.");
|
|
4177
|
+
}
|
|
4178
|
+
continue;
|
|
4179
|
+
}
|
|
3725
4180
|
while (true) {
|
|
3726
4181
|
const action = await askFixAction(d);
|
|
3727
4182
|
if (action === "fix") {
|
|
@@ -3938,7 +4393,7 @@ async function runInit() {
|
|
|
3938
4393
|
"All keys are commented out by default. Uncomment the ones you want.",
|
|
3939
4394
|
"",
|
|
3940
4395
|
"Common next steps:",
|
|
3941
|
-
" 1.
|
|
4396
|
+
" 1. Optional: to send session content to an LLM for summaries, set AGENTMEMORY_LLM_PROVIDER and that provider's key (for example ANTHROPIC_API_KEY)",
|
|
3942
4397
|
" 2. Run `npx @agentmemory/agentmemory doctor` to verify the daemon sees them",
|
|
3943
4398
|
" 3. Run `npx @agentmemory/agentmemory` to start the worker"
|
|
3944
4399
|
].join("\n"), "Next steps");
|
|
@@ -4016,7 +4471,7 @@ async function runDemoBody(base) {
|
|
|
4016
4471
|
...semanticHits > 0 ? [c.accent(`Notice: searching "database performance optimization"`), c.accent(`found the N+1 query fix — keyword matching can't do that.`)] : [
|
|
4017
4472
|
c.dim(`Note: "database performance optimization" found nothing —`),
|
|
4018
4473
|
c.dim(`set EMBEDDING_PROVIDER=local for on-device semantic recall,`),
|
|
4019
|
-
c.dim(`or
|
|
4474
|
+
c.dim(`or set EMBEDDING_PROVIDER and its API key in ~/.agentmemory/.env.`)
|
|
4020
4475
|
],
|
|
4021
4476
|
"",
|
|
4022
4477
|
`Viewer: ${c.url(getViewerUrl())}`,
|
|
@@ -4313,7 +4768,7 @@ async function runStop() {
|
|
|
4313
4768
|
namespace: state.namespace,
|
|
4314
4769
|
functionId: "compose::down",
|
|
4315
4770
|
file: state.file,
|
|
4316
|
-
timeoutMs:
|
|
4771
|
+
timeoutMs: composeDownTimeoutMs()
|
|
4317
4772
|
});
|
|
4318
4773
|
} catch (err) {
|
|
4319
4774
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -4416,10 +4871,10 @@ async function runStop() {
|
|
|
4416
4871
|
p.outro("Stopped. Memories persisted to disk; restart anytime with: npx @agentmemory/agentmemory");
|
|
4417
4872
|
}
|
|
4418
4873
|
async function runMcp() {
|
|
4419
|
-
await import("./standalone-
|
|
4874
|
+
await import("./standalone-BYo5USDb.mjs");
|
|
4420
4875
|
}
|
|
4421
4876
|
async function runConnectCmd() {
|
|
4422
|
-
const { runConnect } = await import("./connect-
|
|
4877
|
+
const { runConnect } = await import("./connect-BX3njQ3Y.mjs").then((n) => n.n);
|
|
4423
4878
|
await runConnect(args.slice(1));
|
|
4424
4879
|
}
|
|
4425
4880
|
async function runImportJsonl() {
|