@mono-agent/agent-app 0.8.0 → 0.9.0
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 +71 -22
- package/dist/account-home.d.ts +6 -0
- package/dist/account-home.d.ts.map +1 -0
- package/dist/account-home.js +9 -0
- package/dist/account-home.js.map +1 -0
- package/dist/app-config.d.ts +3 -1
- package/dist/app-config.d.ts.map +1 -1
- package/dist/app-config.js +8 -5
- package/dist/app-config.js.map +1 -1
- package/dist/app.d.ts +25 -0
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +237 -16
- package/dist/app.js.map +1 -1
- package/dist/background-environment.d.ts +16 -0
- package/dist/background-environment.d.ts.map +1 -0
- package/dist/background-environment.js +68 -0
- package/dist/background-environment.js.map +1 -0
- package/dist/background-runtime.d.ts +62 -0
- package/dist/background-runtime.d.ts.map +1 -0
- package/dist/background-runtime.js +1115 -0
- package/dist/background-runtime.js.map +1 -0
- package/dist/background-snapshot-key.d.ts +13 -0
- package/dist/background-snapshot-key.d.ts.map +1 -0
- package/dist/background-snapshot-key.js +194 -0
- package/dist/background-snapshot-key.js.map +1 -0
- package/dist/background-snapshot.d.ts +96 -0
- package/dist/background-snapshot.d.ts.map +1 -0
- package/dist/background-snapshot.js +450 -0
- package/dist/background-snapshot.js.map +1 -0
- package/dist/background-worker-lease.d.ts +39 -0
- package/dist/background-worker-lease.d.ts.map +1 -0
- package/dist/background-worker-lease.js +356 -0
- package/dist/background-worker-lease.js.map +1 -0
- package/dist/background.d.ts +74 -2
- package/dist/background.d.ts.map +1 -1
- package/dist/background.js +815 -73
- package/dist/background.js.map +1 -1
- package/dist/channel-plugins.d.ts +2 -0
- package/dist/channel-plugins.d.ts.map +1 -1
- package/dist/channel-plugins.js +8 -0
- package/dist/channel-plugins.js.map +1 -1
- package/dist/channels.d.ts +10 -0
- package/dist/channels.d.ts.map +1 -1
- package/dist/channels.js +71 -3
- package/dist/channels.js.map +1 -1
- package/dist/cli.d.ts +12 -2
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +341 -63
- package/dist/cli.js.map +1 -1
- package/dist/config-reference.d.ts.map +1 -1
- package/dist/config-reference.js +253 -0
- package/dist/config-reference.js.map +1 -1
- package/dist/configuration-proposal-tool.d.ts +7 -0
- package/dist/configuration-proposal-tool.d.ts.map +1 -1
- package/dist/configuration-proposal-tool.js +55 -6
- package/dist/configuration-proposal-tool.js.map +1 -1
- package/dist/configured-agent.d.ts +4 -0
- package/dist/configured-agent.d.ts.map +1 -1
- package/dist/configured-agent.js +15 -0
- package/dist/configured-agent.js.map +1 -1
- package/dist/continuation-command.d.ts +15 -0
- package/dist/continuation-command.d.ts.map +1 -0
- package/dist/continuation-command.js +170 -0
- package/dist/continuation-command.js.map +1 -0
- package/dist/continuation-config.d.ts +25 -0
- package/dist/continuation-config.d.ts.map +1 -0
- package/dist/continuation-config.js +236 -0
- package/dist/continuation-config.js.map +1 -0
- package/dist/continuation-service.d.ts +99 -0
- package/dist/continuation-service.d.ts.map +1 -0
- package/dist/continuation-service.js +1313 -0
- package/dist/continuation-service.js.map +1 -0
- package/dist/continuation-store.d.ts +104 -0
- package/dist/continuation-store.d.ts.map +1 -0
- package/dist/continuation-store.js +688 -0
- package/dist/continuation-store.js.map +1 -0
- package/dist/continuations.d.ts +167 -0
- package/dist/continuations.d.ts.map +1 -0
- package/dist/continuations.js +71 -0
- package/dist/continuations.js.map +1 -0
- package/dist/doctor.d.ts +2 -0
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +426 -27
- package/dist/doctor.js.map +1 -1
- package/dist/first-run-managed-memory.d.ts.map +1 -1
- package/dist/first-run-managed-memory.js +66 -3
- package/dist/first-run-managed-memory.js.map +1 -1
- package/dist/first-run-readiness.d.ts.map +1 -1
- package/dist/first-run-readiness.js +2 -31
- package/dist/first-run-readiness.js.map +1 -1
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/init.d.ts +7 -0
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +7 -2
- package/dist/init.js.map +1 -1
- package/dist/launchd.d.ts +16 -6
- package/dist/launchd.d.ts.map +1 -1
- package/dist/launchd.js +30 -15
- package/dist/launchd.js.map +1 -1
- package/dist/local-configuration.d.ts +61 -18
- package/dist/local-configuration.d.ts.map +1 -1
- package/dist/local-configuration.js +530 -145
- package/dist/local-configuration.js.map +1 -1
- package/dist/managed-runtime-packages.d.ts +9 -0
- package/dist/managed-runtime-packages.d.ts.map +1 -0
- package/dist/managed-runtime-packages.js +56 -0
- package/dist/managed-runtime-packages.js.map +1 -0
- package/dist/memory-embedding-service.d.ts +33 -0
- package/dist/memory-embedding-service.d.ts.map +1 -0
- package/dist/memory-embedding-service.js +207 -0
- package/dist/memory-embedding-service.js.map +1 -0
- package/dist/memory-recall.d.ts +5 -4
- package/dist/memory-recall.d.ts.map +1 -1
- package/dist/memory-recall.js +21 -8
- package/dist/memory-recall.js.map +1 -1
- package/dist/modules/catalog.d.ts.map +1 -1
- package/dist/modules/catalog.js +91 -9
- package/dist/modules/catalog.js.map +1 -1
- package/dist/notify-runtime.d.ts +26 -0
- package/dist/notify-runtime.d.ts.map +1 -0
- package/dist/notify-runtime.js +27 -0
- package/dist/notify-runtime.js.map +1 -0
- package/dist/notify-tool.d.ts +51 -0
- package/dist/notify-tool.d.ts.map +1 -0
- package/dist/notify-tool.js +182 -0
- package/dist/notify-tool.js.map +1 -0
- package/dist/proactive-notify.d.ts +2 -0
- package/dist/proactive-notify.d.ts.map +1 -1
- package/dist/proactive-notify.js +1 -0
- package/dist/proactive-notify.js.map +1 -1
- package/dist/process-incarnation.d.ts +23 -0
- package/dist/process-incarnation.d.ts.map +1 -0
- package/dist/process-incarnation.js +208 -0
- package/dist/process-incarnation.js.map +1 -0
- package/dist/project-skills.d.ts +1 -1
- package/dist/project-skills.js +6 -6
- package/dist/recipes/base.d.ts +15 -0
- package/dist/recipes/base.d.ts.map +1 -0
- package/dist/recipes/base.js +51 -0
- package/dist/recipes/base.js.map +1 -0
- package/dist/recipes/catalog.d.ts +4 -0
- package/dist/recipes/catalog.d.ts.map +1 -0
- package/dist/recipes/catalog.js +525 -0
- package/dist/recipes/catalog.js.map +1 -0
- package/dist/recipes/index.d.ts +11 -0
- package/dist/recipes/index.d.ts.map +1 -0
- package/dist/recipes/index.js +14 -0
- package/dist/recipes/index.js.map +1 -0
- package/dist/recipes/types.d.ts +70 -0
- package/dist/recipes/types.d.ts.map +1 -0
- package/dist/recipes/types.js +15 -0
- package/dist/recipes/types.js.map +1 -0
- package/dist/sandbox-manager.d.ts +17 -0
- package/dist/sandbox-manager.d.ts.map +1 -1
- package/dist/sandbox-manager.js +673 -127
- package/dist/sandbox-manager.js.map +1 -1
- package/dist/setup.d.ts +29 -0
- package/dist/setup.d.ts.map +1 -0
- package/dist/setup.js +97 -0
- package/dist/setup.js.map +1 -0
- package/dist/supermemory-plugin.d.ts +5 -3
- package/dist/supermemory-plugin.d.ts.map +1 -1
- package/dist/supermemory-plugin.js +11 -5
- package/dist/supermemory-plugin.js.map +1 -1
- package/dist/tui-command.d.ts +43 -2
- package/dist/tui-command.d.ts.map +1 -1
- package/dist/tui-command.js +261 -27
- package/dist/tui-command.js.map +1 -1
- package/dist/wizard/answers.d.ts +4 -4
- package/dist/wizard/answers.d.ts.map +1 -1
- package/dist/wizard/answers.js +4 -21
- package/dist/wizard/answers.js.map +1 -1
- package/dist/wizard/run.d.ts.map +1 -1
- package/dist/wizard/run.js +162 -5
- package/dist/wizard/run.js.map +1 -1
- package/package.json +15 -15
- package/schema/mono-agent.config.schema.json +206 -3
- package/skills/mono-agent-composer/SKILL.md +4 -4
- package/skills/mono-agent-composer/references/config-blueprint.md +6 -6
- package/skills/mono-agent-composer/references/discovery-questions.md +25 -10
- package/skills/mono-agent-composer/references/feature-coverage.md +7 -7
- package/skills/mono-agent-composer/references/package-map.md +1 -1
- package/skills/mono-agent-composer/references/validation.md +2 -2
package/dist/doctor.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { execFile as execFileCallback } from "node:child_process";
|
|
2
2
|
import { constants } from "node:fs";
|
|
3
|
-
import { access, lstat, mkdir, readFile, stat } from "node:fs/promises";
|
|
3
|
+
import { access, lstat, mkdir, readdir, readFile, stat } from "node:fs/promises";
|
|
4
4
|
import { homedir } from "node:os";
|
|
5
5
|
import { isIP } from "node:net";
|
|
6
6
|
import { join } from "node:path";
|
|
@@ -8,8 +8,8 @@ import { promisify } from "node:util";
|
|
|
8
8
|
import { getBuiltinModels as getPiBuiltinModels, getBuiltinProviders as getPiBuiltinProviders, } from "@earendil-works/pi-ai/providers/all";
|
|
9
9
|
import { listRecordedRuns } from "@mono-agent/observability";
|
|
10
10
|
import { serializeTraceSpans } from "@mono-agent/observability/otel";
|
|
11
|
-
import { loadToolPolicyFromJsonFile } from "@mono-agent/agent-harness";
|
|
12
|
-
import { defaultExecutionModeForModel, describeMonoRuntimeSupport, modelReferenceKey, networkPolicyAllowsUrl, parseMonoRuntimeModelReference, resolveModelEffortLevels, } from "@mono-agent/runtime-adapter";
|
|
11
|
+
import { classifyContinuationMcpServerTransport, isStdioMcpServerSpec, loadToolPolicyFromJsonFile, } from "@mono-agent/agent-harness";
|
|
12
|
+
import { defaultExecutionModeForModel, describeMonoRuntimeSupport, isValidMcpServerName, modelReferenceKey, networkPolicyAllowsUrl, parseMonoRuntimeModelReference, resolveModelEffortLevels, } from "@mono-agent/runtime-adapter";
|
|
13
13
|
import { buildMonoAgentConfigView, findJsonSecretConfigWarnings, findRemovedConfigWarnings, readMonoAgentConfigJson, redactMonoAgentConfig, resolveSupermemoryContainer, } from "@mono-agent/config";
|
|
14
14
|
import { describeSandboxEffectiveState, resolveSandboxEffectiveState, sandboxEffectiveStateWarning, } from "@mono-agent/runtime-adapter";
|
|
15
15
|
import { describeSensitiveDataExportWarning, isAppCoreConfigError, loadAppCoreConfig, phoenixAppBaseUrl, resolveAppArtifactDir, resolveAppObservabilityExporters, } from "./app-config.js";
|
|
@@ -18,8 +18,12 @@ import { canonicalToolName, isAllowAllTools, isKnownToolName, isMcpToolName, sug
|
|
|
18
18
|
import { collectChannelConfigViews } from "./channel-config-view.js";
|
|
19
19
|
import { resolveChannelDrivers } from "./channels.js";
|
|
20
20
|
import { findUnknownAppConfigWarnings } from "./config-reference.js";
|
|
21
|
+
import { loadContinuationSettings } from "./continuation-config.js";
|
|
22
|
+
import { CONTINUATION_RECORD_STORE_SCHEMA_VERSION, CONTINUATION_STORE_SCHEMA_VERSION, } from "./continuation-store.js";
|
|
23
|
+
import { CONTINUATION_STATES } from "./continuations.js";
|
|
21
24
|
import { formatInteractionBridgeUrl, loadInteractionSettings } from "./interaction-bridge.js";
|
|
22
25
|
import { FIRST_RUN_MEMORY_INITIALIZING_MARKER } from "./first-run-managed-memory.js";
|
|
26
|
+
import { DEFAULT_MEMORY_EMBEDDING_ENDPOINTS, probeMemoryEmbeddingSelection, } from "./memory-embedding-service.js";
|
|
23
27
|
import { buildRunsHealthDisplay, RUNS_HEALTH_MAX_RUNS } from "./runs-health.js";
|
|
24
28
|
import { piAuthRecoveryCommand } from "./provider-setup.js";
|
|
25
29
|
import { inspectPiAuthStore } from "./pi-auth-store-inspection.js";
|
|
@@ -71,8 +75,9 @@ export async function validateMonoAgentFolder(options) {
|
|
|
71
75
|
sections.push(runtimeSection(coreConfig));
|
|
72
76
|
sections.push(await credentialsSection(coreConfig, options.env, options.cwd, liveness, options.verifiedCredentialModelRefs, options.sdkAuthStatusExecFile, staticTriggerCredentialRefs));
|
|
73
77
|
sections.push(await contextSection(coreConfig, options.cwd));
|
|
74
|
-
sections.push(await memorySection(coreConfig, options.cwd, liveness, allowFilesystemWrites));
|
|
78
|
+
sections.push(await memorySection(coreConfig, options.cwd, options.env, liveness, allowFilesystemWrites, options.preferAppPluginInstall === true));
|
|
75
79
|
sections.push(await toolsSection(coreConfig, options));
|
|
80
|
+
sections.push(await continuationSection(coreConfig, options));
|
|
76
81
|
sections.push(await sandboxSection(coreConfig, options.sandboxEngine));
|
|
77
82
|
}
|
|
78
83
|
sections.push(await exporterSection(options, liveness));
|
|
@@ -1038,7 +1043,7 @@ async function contextSection(config, cwd) {
|
|
|
1038
1043
|
return { id: "context", label: "Context & skills", status, details };
|
|
1039
1044
|
}
|
|
1040
1045
|
const DEFAULT_CONSOLIDATION_CRON = "0 */2 * * *";
|
|
1041
|
-
async function memorySection(config, cwd, liveness, allowFilesystemWrites) {
|
|
1046
|
+
async function memorySection(config, cwd, env, liveness, allowFilesystemWrites, preferAppPluginInstall) {
|
|
1042
1047
|
if (config.memory === undefined) {
|
|
1043
1048
|
return { id: "memory", label: "Memory", status: "disabled", details: ["No memory configured."] };
|
|
1044
1049
|
}
|
|
@@ -1056,7 +1061,7 @@ async function memorySection(config, cwd, liveness, allowFilesystemWrites) {
|
|
|
1056
1061
|
};
|
|
1057
1062
|
}
|
|
1058
1063
|
try {
|
|
1059
|
-
const plugin = await loadSupermemoryPlugin({ cwd });
|
|
1064
|
+
const plugin = await loadSupermemoryPlugin({ cwd, preferAppInstall: preferAppPluginInstall });
|
|
1060
1065
|
const validation = plugin.validateSupermemoryConfig({
|
|
1061
1066
|
baseUrl: sm.baseUrl,
|
|
1062
1067
|
container: resolveSupermemoryContainer(config),
|
|
@@ -1146,7 +1151,7 @@ async function memorySection(config, cwd, liveness, allowFilesystemWrites) {
|
|
|
1146
1151
|
};
|
|
1147
1152
|
}
|
|
1148
1153
|
if (config.memory.mode === "journal" || config.memory.mode === "bujo") {
|
|
1149
|
-
const warns = await memoryLivenessWarnings(config.memory, liveness, allowFilesystemWrites);
|
|
1154
|
+
const warns = await memoryLivenessWarnings(config.memory, env, liveness, allowFilesystemWrites);
|
|
1150
1155
|
if (warns.length > 0) {
|
|
1151
1156
|
return {
|
|
1152
1157
|
id: "memory",
|
|
@@ -1304,20 +1309,34 @@ async function fetchOllamaModels(endpoint) {
|
|
|
1304
1309
|
clearTimeout(timer);
|
|
1305
1310
|
}
|
|
1306
1311
|
}
|
|
1307
|
-
async function memoryLivenessWarnings(memory, liveness, allowFilesystemWrites) {
|
|
1312
|
+
async function memoryLivenessWarnings(memory, env, liveness, allowFilesystemWrites) {
|
|
1308
1313
|
const warns = [];
|
|
1309
1314
|
const mode = memory.mode;
|
|
1310
|
-
const embeddingsUsesOllama = (memory.embeddings?.provider ?? "ollama") === "ollama";
|
|
1311
1315
|
const llmUsesOllama = memory.llm?.provider === "ollama";
|
|
1312
1316
|
const ollamaModelsByEndpoint = new Map();
|
|
1313
1317
|
// 1. Memory root writable (every embedded tier) — local I/O, always checked.
|
|
1314
1318
|
const rootWarns = await memoryRootWritableWarnings(mode, memory.path, allowFilesystemWrites);
|
|
1315
1319
|
warns.push(...rootWarns);
|
|
1320
|
+
const embeddings = memory.embeddings;
|
|
1321
|
+
let embeddingApiKey = embeddings?.apiKey;
|
|
1322
|
+
if (embeddings?.apiKeyEnv !== undefined) {
|
|
1323
|
+
embeddingApiKey = env[embeddings.apiKeyEnv]?.trim();
|
|
1324
|
+
if (embeddingApiKey === undefined || embeddingApiKey.length === 0) {
|
|
1325
|
+
warns.push(`[WARN] Embeddings apiKeyEnv ${embeddings.apiKeyEnv} is declared, but the resolved environment has no ` +
|
|
1326
|
+
`non-empty value. Set ${embeddings.apiKeyEnv} before starting managed memory; no keyless probe was attempted.`);
|
|
1327
|
+
embeddingApiKey = undefined;
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1316
1330
|
// Network-dependent probes below only ever produce `waiting`, so the start
|
|
1317
1331
|
// preflight skips them (liveness=false) without changing the pass/fail verdict.
|
|
1318
1332
|
if (!liveness) {
|
|
1319
1333
|
return warns;
|
|
1320
1334
|
}
|
|
1335
|
+
if (embeddings !== undefined
|
|
1336
|
+
&& (embeddings.provider === "ollama" || embeddings.provider === "lmstudio")
|
|
1337
|
+
&& (embeddings.apiKeyEnv === undefined || embeddingApiKey !== undefined)) {
|
|
1338
|
+
warns.push(...await localEmbeddingLivenessWarnings(embeddings, embeddingApiKey));
|
|
1339
|
+
}
|
|
1321
1340
|
async function modelsForOllamaEndpoint(endpoint) {
|
|
1322
1341
|
const normalizedEndpoint = endpoint.replace(/\/$/u, "");
|
|
1323
1342
|
if (ollamaModelsByEndpoint.has(normalizedEndpoint)) {
|
|
@@ -1334,18 +1353,9 @@ async function memoryLivenessWarnings(memory, liveness, allowFilesystemWrites) {
|
|
|
1334
1353
|
return undefined;
|
|
1335
1354
|
}
|
|
1336
1355
|
}
|
|
1337
|
-
// 2. Ollama liveness
|
|
1338
|
-
// agent-host chat LLMs have no
|
|
1339
|
-
|
|
1340
|
-
const endpoint = memory.embeddings?.endpoint ?? "http://localhost:11434";
|
|
1341
|
-
const ollamaModels = await modelsForOllamaEndpoint(endpoint);
|
|
1342
|
-
if (ollamaModels !== undefined) {
|
|
1343
|
-
const embeddingsModel = memory.embeddings?.model ?? "nomic-embed-text:v1.5";
|
|
1344
|
-
if (!ollamaModels.includes(embeddingsModel)) {
|
|
1345
|
-
warns.push(`[WARN] Embeddings model ${embeddingsModel} not pulled — run \`ollama pull ${embeddingsModel}\`.`);
|
|
1346
|
-
}
|
|
1347
|
-
}
|
|
1348
|
-
}
|
|
1356
|
+
// 2. Chat-LLM Ollama liveness remains independent from the selected
|
|
1357
|
+
// embeddings service. OpenAI embeddings and agent-host chat LLMs have no
|
|
1358
|
+
// local typed model catalog to validate here.
|
|
1349
1359
|
if (llmUsesOllama && memory.llm !== undefined) {
|
|
1350
1360
|
const endpoint = memory.llm.endpoint ?? "http://localhost:11434";
|
|
1351
1361
|
const ollamaModels = await modelsForOllamaEndpoint(endpoint);
|
|
@@ -1358,6 +1368,52 @@ async function memoryLivenessWarnings(memory, liveness, allowFilesystemWrites) {
|
|
|
1358
1368
|
}
|
|
1359
1369
|
return warns;
|
|
1360
1370
|
}
|
|
1371
|
+
async function localEmbeddingLivenessWarnings(embeddings, apiKey) {
|
|
1372
|
+
if (embeddings.provider !== "ollama" && embeddings.provider !== "lmstudio")
|
|
1373
|
+
return [];
|
|
1374
|
+
const provider = embeddings.provider;
|
|
1375
|
+
const label = provider === "ollama" ? "Ollama" : "LM Studio";
|
|
1376
|
+
const endpoint = (embeddings.endpoint ?? DEFAULT_MEMORY_EMBEDDING_ENDPOINTS[provider]).replace(/\/+$/u, "");
|
|
1377
|
+
// Runtime readiness is authoritative on the exact configured model. Typed
|
|
1378
|
+
// catalogs help the wizard offer choices, but an unavailable/incomplete
|
|
1379
|
+
// catalog must not make its explicit manual fallback permanently unready.
|
|
1380
|
+
try {
|
|
1381
|
+
await probeMemoryEmbeddingSelection({
|
|
1382
|
+
provider,
|
|
1383
|
+
endpoint,
|
|
1384
|
+
model: embeddings.model,
|
|
1385
|
+
expectedDimension: embeddings.dim ?? 768,
|
|
1386
|
+
timeoutMs: BUJO_PROBE_TIMEOUT_MS,
|
|
1387
|
+
...(apiKey === undefined ? {} : { apiKey }),
|
|
1388
|
+
});
|
|
1389
|
+
return [];
|
|
1390
|
+
}
|
|
1391
|
+
catch (error) {
|
|
1392
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
1393
|
+
if (/HTTP 401/u.test(reason)) {
|
|
1394
|
+
return [
|
|
1395
|
+
`[WARN] ${label} authentication failed at ${endpoint} (HTTP 401). ` +
|
|
1396
|
+
"Set the configured embeddings apiKeyEnv to a valid bearer token and retry.",
|
|
1397
|
+
];
|
|
1398
|
+
}
|
|
1399
|
+
if (/could not connect|timed out/iu.test(reason)) {
|
|
1400
|
+
return [
|
|
1401
|
+
`[WARN] ${label} not reachable at ${endpoint}; embeddings will fail at runtime (${reason}). ` +
|
|
1402
|
+
`Start ${label} or fix the endpoint.`,
|
|
1403
|
+
];
|
|
1404
|
+
}
|
|
1405
|
+
if (provider === "ollama" && /HTTP 404/u.test(reason)) {
|
|
1406
|
+
return [
|
|
1407
|
+
`[WARN] Ollama embedding model ${embeddings.model} could not be proved at ${endpoint} (${reason}); ` +
|
|
1408
|
+
`run \`ollama pull ${embeddings.model}\` and verify its embedding capability.`,
|
|
1409
|
+
];
|
|
1410
|
+
}
|
|
1411
|
+
return [
|
|
1412
|
+
`[WARN] ${label} embedding readiness failed for ${embeddings.model} at ${endpoint} (${reason}). ` +
|
|
1413
|
+
"Verify the selected model, authentication, and configured dimension.",
|
|
1414
|
+
];
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1361
1417
|
async function memoryRootWritableWarnings(mode, memoryPath, allowFilesystemWrites) {
|
|
1362
1418
|
if (allowFilesystemWrites) {
|
|
1363
1419
|
try {
|
|
@@ -1519,17 +1575,39 @@ async function toolsSection(config, input) {
|
|
|
1519
1575
|
}
|
|
1520
1576
|
}
|
|
1521
1577
|
for (const serverName of config.tools.mcpRequestContextServers ?? []) {
|
|
1578
|
+
if (!isValidMcpServerName(serverName)) {
|
|
1579
|
+
status = "error";
|
|
1580
|
+
details.push(`tools.mcpRequestContextServers entry "${serverName}" is not a runtime-valid MCP server name (letters, digits, underscores, and hyphens only).`);
|
|
1581
|
+
continue;
|
|
1582
|
+
}
|
|
1522
1583
|
const spec = configuredMcpServers[serverName];
|
|
1523
1584
|
if (spec === undefined) {
|
|
1524
1585
|
status = "error";
|
|
1525
1586
|
details.push(`tools.mcpRequestContextServers names unknown MCP server "${serverName}"; declare it in tools.mcpConfigPath.`);
|
|
1526
1587
|
continue;
|
|
1527
1588
|
}
|
|
1528
|
-
if (!
|
|
1589
|
+
if (!isStdioMcpServerSpec(spec)) {
|
|
1529
1590
|
status = "error";
|
|
1530
1591
|
details.push(`tools.mcpRequestContextServers entry "${serverName}" must reference a stdio MCP server (command/type:stdio), not HTTP/SSE.`);
|
|
1531
1592
|
}
|
|
1532
1593
|
}
|
|
1594
|
+
for (const serverName of config.tools.continuationServers ?? []) {
|
|
1595
|
+
if (!isValidMcpServerName(serverName)) {
|
|
1596
|
+
status = "error";
|
|
1597
|
+
details.push(`tools.continuationServers entry "${serverName}" is not a runtime-valid MCP server name (letters, digits, underscores, and hyphens only).`);
|
|
1598
|
+
continue;
|
|
1599
|
+
}
|
|
1600
|
+
const spec = configuredMcpServers[serverName];
|
|
1601
|
+
if (spec === undefined) {
|
|
1602
|
+
status = "error";
|
|
1603
|
+
details.push(`tools.continuationServers names unknown MCP server "${serverName}"; declare it in tools.mcpConfigPath.`);
|
|
1604
|
+
continue;
|
|
1605
|
+
}
|
|
1606
|
+
if (classifyContinuationMcpServerTransport(spec) === "unsupported") {
|
|
1607
|
+
status = "error";
|
|
1608
|
+
details.push(`tools.continuationServers entry "${serverName}" must reference a stdio or loopback HTTP MCP server; remote HTTP and SSE are not supported.`);
|
|
1609
|
+
}
|
|
1610
|
+
}
|
|
1533
1611
|
const adapterSendTools = await resolveAdapterSendToolsSettings(input, {
|
|
1534
1612
|
allowedTools: config.tools.allowedTools,
|
|
1535
1613
|
disallowedTools: config.tools.disallowedTools,
|
|
@@ -1556,12 +1634,333 @@ async function toolsSection(config, input) {
|
|
|
1556
1634
|
}
|
|
1557
1635
|
return { id: "tools", label: "Tools & MCP", status, details };
|
|
1558
1636
|
}
|
|
1559
|
-
function
|
|
1560
|
-
|
|
1637
|
+
async function continuationSection(config, input) {
|
|
1638
|
+
let settings;
|
|
1639
|
+
try {
|
|
1640
|
+
settings = await loadContinuationSettings({
|
|
1641
|
+
cwd: input.cwd,
|
|
1642
|
+
configPath: input.configPath,
|
|
1643
|
+
env: input.env,
|
|
1644
|
+
});
|
|
1645
|
+
}
|
|
1646
|
+
catch (error) {
|
|
1647
|
+
return {
|
|
1648
|
+
id: "continuations",
|
|
1649
|
+
label: "Durable continuations",
|
|
1650
|
+
status: "error",
|
|
1651
|
+
details: [`Continuation configuration is invalid: ${continuationReason(error)}`],
|
|
1652
|
+
};
|
|
1653
|
+
}
|
|
1654
|
+
const continuationServers = config.tools.continuationServers ?? [];
|
|
1655
|
+
if (!settings.configured && continuationServers.length === 0) {
|
|
1656
|
+
return {
|
|
1657
|
+
id: "continuations",
|
|
1658
|
+
label: "Durable continuations",
|
|
1659
|
+
status: "disabled",
|
|
1660
|
+
details: ["No continuation-capable MCP servers or detached continuation routes are configured."],
|
|
1661
|
+
};
|
|
1662
|
+
}
|
|
1663
|
+
if (!settings.enabled) {
|
|
1664
|
+
return {
|
|
1665
|
+
id: "continuations",
|
|
1666
|
+
label: "Durable continuations",
|
|
1667
|
+
status: "error",
|
|
1668
|
+
details: ["Continuation service is disabled while continuation functionality is configured."],
|
|
1669
|
+
};
|
|
1670
|
+
}
|
|
1671
|
+
const details = [
|
|
1672
|
+
`Loopback service: http://${formatContinuationHost(settings.host)}:${String(settings.port)}.`,
|
|
1673
|
+
`Owner-only state: ${settings.stateDir}.`,
|
|
1674
|
+
continuationServers.length === 0
|
|
1675
|
+
? "Run-scoped continuation MCP servers: none."
|
|
1676
|
+
: `Run-scoped continuation MCP servers: ${continuationServers.join(", ")}.`,
|
|
1677
|
+
];
|
|
1678
|
+
const routeNames = Object.entries(settings.namedRoutes).map(([name, route]) => `${name} (${route.mode})`);
|
|
1679
|
+
details.push(routeNames.length === 0 ? "Named detached routes: none." : `Named detached routes: ${routeNames.join(", ")}.`);
|
|
1680
|
+
const detachedNames = Object.keys(settings.detachedServices);
|
|
1681
|
+
details.push(detachedNames.length === 0 ? "Detached services: none." : `Detached services: ${detachedNames.join(", ")}.`);
|
|
1682
|
+
const state = await inspectContinuationState(settings.stateDir);
|
|
1683
|
+
details.push(...state.details);
|
|
1684
|
+
return {
|
|
1685
|
+
id: "continuations",
|
|
1686
|
+
label: "Durable continuations",
|
|
1687
|
+
status: state.status,
|
|
1688
|
+
details,
|
|
1689
|
+
};
|
|
1690
|
+
}
|
|
1691
|
+
async function inspectContinuationState(stateDir) {
|
|
1692
|
+
const details = [];
|
|
1693
|
+
let directory;
|
|
1694
|
+
try {
|
|
1695
|
+
directory = await lstat(stateDir);
|
|
1696
|
+
}
|
|
1697
|
+
catch (error) {
|
|
1698
|
+
if (continuationFsCode(error) === "ENOENT") {
|
|
1699
|
+
return {
|
|
1700
|
+
status: "ok",
|
|
1701
|
+
details: ["State has not been initialized; the app will create it with owner-only permissions on first start."],
|
|
1702
|
+
};
|
|
1703
|
+
}
|
|
1704
|
+
return { status: "error", details: [`Continuation state cannot be inspected: ${continuationReason(error)}`] };
|
|
1705
|
+
}
|
|
1706
|
+
if (!directory.isDirectory() || directory.isSymbolicLink()) {
|
|
1707
|
+
return { status: "error", details: ["Continuation state path must be a real directory, not a file or symlink."] };
|
|
1708
|
+
}
|
|
1709
|
+
const directorySecurityError = continuationOwnershipError(directory, "state directory", 0o700);
|
|
1710
|
+
if (directorySecurityError !== undefined) {
|
|
1711
|
+
return { status: "error", details: [directorySecurityError] };
|
|
1712
|
+
}
|
|
1713
|
+
const secretError = await inspectContinuationSecret(join(stateDir, "continuation-secret"));
|
|
1714
|
+
if (secretError !== undefined) {
|
|
1715
|
+
return { status: "error", details: [secretError] };
|
|
1716
|
+
}
|
|
1717
|
+
const manifestPath = join(stateDir, "continuation-store-v2.json");
|
|
1718
|
+
let manifestInfo;
|
|
1719
|
+
try {
|
|
1720
|
+
manifestInfo = await lstat(manifestPath);
|
|
1721
|
+
}
|
|
1722
|
+
catch (error) {
|
|
1723
|
+
if (continuationFsCode(error) !== "ENOENT") {
|
|
1724
|
+
return { status: "error", details: [`Continuation store manifest cannot be inspected: ${continuationReason(error)}`] };
|
|
1725
|
+
}
|
|
1726
|
+
}
|
|
1727
|
+
if (manifestInfo !== undefined) {
|
|
1728
|
+
if (!manifestInfo.isFile() || manifestInfo.isSymbolicLink()) {
|
|
1729
|
+
return { status: "error", details: ["Continuation store manifest must be a regular file, not a symlink."] };
|
|
1730
|
+
}
|
|
1731
|
+
const manifestSecurityError = continuationOwnershipError(manifestInfo, "store manifest", 0o600);
|
|
1732
|
+
if (manifestSecurityError !== undefined)
|
|
1733
|
+
return { status: "error", details: [manifestSecurityError] };
|
|
1734
|
+
let manifest;
|
|
1735
|
+
try {
|
|
1736
|
+
manifest = JSON.parse(await readFile(manifestPath, "utf8"));
|
|
1737
|
+
}
|
|
1738
|
+
catch (error) {
|
|
1739
|
+
return { status: "error", details: [`Continuation store manifest contains invalid JSON: ${continuationReason(error)}`] };
|
|
1740
|
+
}
|
|
1741
|
+
if (!isContinuationStoreManifest(manifest)) {
|
|
1742
|
+
return { status: "error", details: ["Continuation store manifest has an unsupported or malformed schema."] };
|
|
1743
|
+
}
|
|
1744
|
+
const recordsDirectoryError = await inspectContinuationRecordsDirectory(join(stateDir, "records-v2"));
|
|
1745
|
+
if (recordsDirectoryError !== undefined)
|
|
1746
|
+
return { status: "error", details: [recordsDirectoryError] };
|
|
1747
|
+
const transaction = await inspectContinuationTransaction(join(stateDir, "continuation-transaction-v2.json"));
|
|
1748
|
+
if (!transaction.valid)
|
|
1749
|
+
return { status: "error", details: [transaction.detail] };
|
|
1750
|
+
const owner = await inspectContinuationOwnerDatabase(join(stateDir, "continuations-owner.sqlite"));
|
|
1751
|
+
if (!owner.valid)
|
|
1752
|
+
return { status: "error", details: [owner.detail] };
|
|
1753
|
+
details.push(`Store v2: ${String(manifest.stats.records)} retained; ${String(manifest.stats.active)} active; ${String(manifest.stats.unresolvedDelivery)} delivery unknown; ${String(manifest.stats.deadLettered)} dead-lettered; ${String(manifest.stats.historyDegraded)} history-degraded deliveries; ${String(manifest.stats.terminalTombstones)} terminal tombstones; ${String(manifest.stats.capturedText)} captured answers.`, `Retention: at most ${String(manifest.stats.limits.terminalMaxRecords)} terminal tombstones and ${String(manifest.stats.limits.capturedTextMaxRecords)} captured answers.`, transaction.detail, owner.detail);
|
|
1754
|
+
return {
|
|
1755
|
+
status: transaction.pending
|
|
1756
|
+
|| manifest.stats.unresolvedDelivery > 0
|
|
1757
|
+
|| manifest.stats.deadLettered > 0
|
|
1758
|
+
|| manifest.stats.historyDegraded > 0
|
|
1759
|
+
? "waiting"
|
|
1760
|
+
: "ok",
|
|
1761
|
+
details,
|
|
1762
|
+
};
|
|
1763
|
+
}
|
|
1764
|
+
const storePath = join(stateDir, "continuations-v1.json");
|
|
1765
|
+
let storeInfo;
|
|
1766
|
+
try {
|
|
1767
|
+
storeInfo = await lstat(storePath);
|
|
1768
|
+
}
|
|
1769
|
+
catch (error) {
|
|
1770
|
+
if (continuationFsCode(error) === "ENOENT") {
|
|
1771
|
+
details.push("No continuation ledger has been written yet.");
|
|
1772
|
+
const owner = await inspectContinuationOwnerDatabase(join(stateDir, "continuations-owner.sqlite"));
|
|
1773
|
+
details.push(owner.detail);
|
|
1774
|
+
return { status: owner.valid ? "ok" : "error", details };
|
|
1775
|
+
}
|
|
1776
|
+
return { status: "error", details: [`Continuation ledger cannot be inspected: ${continuationReason(error)}`] };
|
|
1777
|
+
}
|
|
1778
|
+
if (!storeInfo.isFile() || storeInfo.isSymbolicLink()) {
|
|
1779
|
+
return { status: "error", details: ["Continuation ledger must be a regular file, not a symlink."] };
|
|
1780
|
+
}
|
|
1781
|
+
const storeSecurityError = continuationOwnershipError(storeInfo, "ledger", 0o600);
|
|
1782
|
+
if (storeSecurityError !== undefined) {
|
|
1783
|
+
return { status: "error", details: [storeSecurityError] };
|
|
1784
|
+
}
|
|
1785
|
+
let parsed;
|
|
1786
|
+
try {
|
|
1787
|
+
parsed = JSON.parse(await readFile(storePath, "utf8"));
|
|
1788
|
+
}
|
|
1789
|
+
catch (error) {
|
|
1790
|
+
return { status: "error", details: [`Continuation ledger contains invalid JSON: ${continuationReason(error)}`] };
|
|
1791
|
+
}
|
|
1792
|
+
if (!isContinuationLedger(parsed)) {
|
|
1793
|
+
return { status: "error", details: ["Continuation ledger has an unsupported or malformed schema."] };
|
|
1794
|
+
}
|
|
1795
|
+
const counts = Object.fromEntries(CONTINUATION_STATES.map((state) => [state, 0]));
|
|
1796
|
+
for (const record of Object.values(parsed.records)) {
|
|
1797
|
+
if (!isDoctorObject(record) || typeof record.state !== "string" || !CONTINUATION_STATES.includes(record.state)) {
|
|
1798
|
+
return { status: "error", details: ["Continuation ledger contains a record with an invalid lifecycle state."] };
|
|
1799
|
+
}
|
|
1800
|
+
counts[record.state] += 1;
|
|
1801
|
+
}
|
|
1802
|
+
const total = Object.values(counts).reduce((sum, count) => sum + count, 0);
|
|
1803
|
+
const pending = counts.claimed + counts.result_received + counts.synthesizing + counts.ready_to_deliver + counts.delivery_retry;
|
|
1804
|
+
details.push(`Legacy ledger awaiting v2 migration: ${String(total)} total; ${String(pending)} pending; ${String(counts.delivery_unknown)} delivery unknown; ${String(counts.dead_lettered)} dead-lettered.`);
|
|
1805
|
+
const owner = await inspectContinuationOwnerDatabase(join(stateDir, "continuations-owner.sqlite"));
|
|
1806
|
+
details.push(owner.detail);
|
|
1807
|
+
return {
|
|
1808
|
+
status: !owner.valid
|
|
1809
|
+
? "error"
|
|
1810
|
+
: counts.delivery_unknown > 0 || counts.dead_lettered > 0
|
|
1811
|
+
? "waiting"
|
|
1812
|
+
: "ok",
|
|
1813
|
+
details,
|
|
1814
|
+
};
|
|
1815
|
+
}
|
|
1816
|
+
async function inspectContinuationSecret(path) {
|
|
1817
|
+
let info;
|
|
1818
|
+
try {
|
|
1819
|
+
info = await lstat(path);
|
|
1820
|
+
}
|
|
1821
|
+
catch (error) {
|
|
1822
|
+
return continuationFsCode(error) === "ENOENT"
|
|
1823
|
+
? undefined
|
|
1824
|
+
: `Continuation secret cannot be inspected: ${continuationReason(error)}`;
|
|
1825
|
+
}
|
|
1826
|
+
if (!info.isFile() || info.isSymbolicLink())
|
|
1827
|
+
return "Continuation secret must be a regular file, not a symlink.";
|
|
1828
|
+
const securityError = continuationOwnershipError(info, "secret", 0o600);
|
|
1829
|
+
if (securityError !== undefined)
|
|
1830
|
+
return securityError;
|
|
1831
|
+
try {
|
|
1832
|
+
const secret = Buffer.from((await readFile(path, "utf8")).trim(), "base64url");
|
|
1833
|
+
if (secret.length !== 32)
|
|
1834
|
+
return "Continuation secret contents are invalid.";
|
|
1835
|
+
}
|
|
1836
|
+
catch (error) {
|
|
1837
|
+
return `Continuation secret cannot be read: ${continuationReason(error)}`;
|
|
1838
|
+
}
|
|
1839
|
+
return undefined;
|
|
1840
|
+
}
|
|
1841
|
+
async function inspectContinuationOwnerDatabase(path) {
|
|
1842
|
+
let info;
|
|
1843
|
+
try {
|
|
1844
|
+
info = await lstat(path);
|
|
1845
|
+
}
|
|
1846
|
+
catch (error) {
|
|
1847
|
+
return continuationFsCode(error) === "ENOENT"
|
|
1848
|
+
? { valid: true, detail: "The OS-backed continuation ownership database has not been initialized yet." }
|
|
1849
|
+
: { valid: false, detail: `Continuation ownership database cannot be inspected: ${continuationReason(error)}` };
|
|
1850
|
+
}
|
|
1851
|
+
if (!info.isFile() || info.isSymbolicLink()) {
|
|
1852
|
+
return { valid: false, detail: "Continuation ownership database is not a regular file." };
|
|
1853
|
+
}
|
|
1854
|
+
const securityError = continuationOwnershipError(info, "ownership database", 0o600);
|
|
1855
|
+
if (securityError !== undefined)
|
|
1856
|
+
return { valid: false, detail: securityError };
|
|
1857
|
+
return { valid: true, detail: "OS-backed exclusive ownership is released automatically on clean stop or process death." };
|
|
1858
|
+
}
|
|
1859
|
+
async function inspectContinuationRecordsDirectory(path) {
|
|
1860
|
+
let info;
|
|
1861
|
+
try {
|
|
1862
|
+
info = await lstat(path);
|
|
1863
|
+
}
|
|
1864
|
+
catch (error) {
|
|
1865
|
+
return continuationFsCode(error) === "ENOENT"
|
|
1866
|
+
? "Continuation record directory is missing."
|
|
1867
|
+
: `Continuation record directory cannot be inspected: ${continuationReason(error)}`;
|
|
1868
|
+
}
|
|
1869
|
+
if (!info.isDirectory() || info.isSymbolicLink()) {
|
|
1870
|
+
return "Continuation record directory must be a real directory, not a file or symlink.";
|
|
1871
|
+
}
|
|
1872
|
+
const directorySecurityError = continuationOwnershipError(info, "record directory", 0o700);
|
|
1873
|
+
if (directorySecurityError !== undefined)
|
|
1874
|
+
return directorySecurityError;
|
|
1875
|
+
try {
|
|
1876
|
+
for (const entry of await readdir(path, { withFileTypes: true })) {
|
|
1877
|
+
if (!entry.name.endsWith(".json") && !(entry.name.startsWith(".") && entry.name.endsWith(".tmp"))) {
|
|
1878
|
+
return `Continuation record directory contains an unexpected entry: ${entry.name}.`;
|
|
1879
|
+
}
|
|
1880
|
+
const recordInfo = await lstat(join(path, entry.name));
|
|
1881
|
+
if (!recordInfo.isFile() || recordInfo.isSymbolicLink()) {
|
|
1882
|
+
return `Continuation record entry is not a regular file: ${entry.name}.`;
|
|
1883
|
+
}
|
|
1884
|
+
const securityError = continuationOwnershipError(recordInfo, "record", 0o600);
|
|
1885
|
+
if (securityError !== undefined)
|
|
1886
|
+
return securityError;
|
|
1887
|
+
}
|
|
1888
|
+
}
|
|
1889
|
+
catch (error) {
|
|
1890
|
+
return `Continuation record directory cannot be inspected: ${continuationReason(error)}`;
|
|
1891
|
+
}
|
|
1892
|
+
return undefined;
|
|
1893
|
+
}
|
|
1894
|
+
async function inspectContinuationTransaction(path) {
|
|
1895
|
+
let info;
|
|
1896
|
+
try {
|
|
1897
|
+
info = await lstat(path);
|
|
1898
|
+
}
|
|
1899
|
+
catch (error) {
|
|
1900
|
+
return continuationFsCode(error) === "ENOENT"
|
|
1901
|
+
? { valid: true, pending: false, detail: "No interrupted durable transaction is awaiting recovery." }
|
|
1902
|
+
: { valid: false, pending: false, detail: `Continuation transaction cannot be inspected: ${continuationReason(error)}` };
|
|
1903
|
+
}
|
|
1904
|
+
if (!info.isFile() || info.isSymbolicLink()) {
|
|
1905
|
+
return { valid: false, pending: true, detail: "Continuation transaction must be a regular file, not a symlink." };
|
|
1906
|
+
}
|
|
1907
|
+
const securityError = continuationOwnershipError(info, "transaction", 0o600);
|
|
1908
|
+
if (securityError !== undefined)
|
|
1909
|
+
return { valid: false, pending: true, detail: securityError };
|
|
1910
|
+
return {
|
|
1911
|
+
valid: true,
|
|
1912
|
+
pending: true,
|
|
1913
|
+
detail: "An interrupted durable transaction is present and will be completed idempotently by the state owner.",
|
|
1914
|
+
};
|
|
1915
|
+
}
|
|
1916
|
+
function continuationOwnershipError(info, label, expectedMode) {
|
|
1917
|
+
if (typeof process.getuid === "function" && Number(info.uid) !== process.getuid()) {
|
|
1918
|
+
return `Continuation ${label} is not owned by the current user.`;
|
|
1919
|
+
}
|
|
1920
|
+
if (process.platform !== "win32" && (Number(info.mode) & 0o777) !== expectedMode) {
|
|
1921
|
+
return `Continuation ${label} permissions must be ${expectedMode.toString(8)}.`;
|
|
1922
|
+
}
|
|
1923
|
+
return undefined;
|
|
1924
|
+
}
|
|
1925
|
+
function isContinuationLedger(value) {
|
|
1926
|
+
return isDoctorObject(value)
|
|
1927
|
+
&& value.schemaVersion === CONTINUATION_STORE_SCHEMA_VERSION
|
|
1928
|
+
&& isDoctorObject(value.records);
|
|
1929
|
+
}
|
|
1930
|
+
function isContinuationStoreManifest(value) {
|
|
1931
|
+
if (!isDoctorObject(value)
|
|
1932
|
+
|| value.schemaVersion !== CONTINUATION_RECORD_STORE_SCHEMA_VERSION
|
|
1933
|
+
|| typeof value.generation !== "string"
|
|
1934
|
+
|| !isDoctorObject(value.stats)
|
|
1935
|
+
|| value.stats.format !== "per-record-v2"
|
|
1936
|
+
|| !isDoctorObject(value.stats.limits))
|
|
1561
1937
|
return false;
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1938
|
+
return [
|
|
1939
|
+
value.stats.records,
|
|
1940
|
+
value.stats.active,
|
|
1941
|
+
value.stats.unresolvedDelivery,
|
|
1942
|
+
value.stats.deadLettered,
|
|
1943
|
+
value.stats.historyDegraded,
|
|
1944
|
+
value.stats.terminalTombstones,
|
|
1945
|
+
value.stats.compacted,
|
|
1946
|
+
value.stats.capturedText,
|
|
1947
|
+
value.stats.limits.terminalMaxRecords,
|
|
1948
|
+
value.stats.limits.terminalMaxAgeMs,
|
|
1949
|
+
value.stats.limits.capturedTextMaxRecords,
|
|
1950
|
+
value.stats.limits.capturedTextMaxAgeMs,
|
|
1951
|
+
].every((entry) => Number.isSafeInteger(entry) && Number(entry) >= 0);
|
|
1952
|
+
}
|
|
1953
|
+
function isDoctorObject(value) {
|
|
1954
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1955
|
+
}
|
|
1956
|
+
function continuationFsCode(error) {
|
|
1957
|
+
return isDoctorObject(error) && typeof error.code === "string" ? error.code : undefined;
|
|
1958
|
+
}
|
|
1959
|
+
function continuationReason(error) {
|
|
1960
|
+
return error instanceof Error ? error.message : String(error);
|
|
1961
|
+
}
|
|
1962
|
+
function formatContinuationHost(host) {
|
|
1963
|
+
return host.includes(":") && !host.startsWith("[") ? `[${host}]` : host;
|
|
1565
1964
|
}
|
|
1566
1965
|
/**
|
|
1567
1966
|
* Adapter-send tools run in a stdio MCP child governed by the native SRT
|