@mono-agent/agent-app 0.20.10 → 0.20.14
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 +33 -18
- package/dist/app-config.d.ts +2 -2
- package/dist/app-controller-channels.d.ts +4 -0
- package/dist/app-controller-channels.d.ts.map +1 -1
- package/dist/app-controller-channels.js +20 -2
- package/dist/app-controller-channels.js.map +1 -1
- package/dist/app-controller-continuation.d.ts.map +1 -1
- package/dist/app-controller-continuation.js +2 -3
- package/dist/app-controller-continuation.js.map +1 -1
- package/dist/app-controller-lifecycle.d.ts +4 -0
- package/dist/app-controller-lifecycle.d.ts.map +1 -1
- package/dist/app-controller-lifecycle.js +7 -0
- package/dist/app-controller-lifecycle.js.map +1 -1
- package/dist/app-controller-monitors.d.ts +26 -0
- package/dist/app-controller-monitors.d.ts.map +1 -0
- package/dist/app-controller-monitors.js +127 -0
- package/dist/app-controller-monitors.js.map +1 -0
- package/dist/app-controller-responder.d.ts +7 -17
- package/dist/app-controller-responder.d.ts.map +1 -1
- package/dist/app-controller-responder.js +33 -69
- package/dist/app-controller-responder.js.map +1 -1
- package/dist/app-controller-utils.d.ts +1 -5
- package/dist/app-controller-utils.d.ts.map +1 -1
- package/dist/app-controller-utils.js +4 -21
- package/dist/app-controller-utils.js.map +1 -1
- package/dist/app-controller.d.ts +25 -11
- package/dist/app-controller.d.ts.map +1 -1
- package/dist/app-controller.js +36 -1
- package/dist/app-controller.js.map +1 -1
- package/dist/channel-drivers/slack.d.ts.map +1 -1
- package/dist/channel-drivers/slack.js +24 -0
- package/dist/channel-drivers/slack.js.map +1 -1
- package/dist/channel-drivers/telegram.d.ts.map +1 -1
- package/dist/channel-drivers/telegram.js +26 -0
- package/dist/channel-drivers/telegram.js.map +1 -1
- package/dist/channel-drivers/tui.d.ts +68 -8
- package/dist/channel-drivers/tui.d.ts.map +1 -1
- package/dist/channel-drivers/tui.js +388 -89
- package/dist/channel-drivers/tui.js.map +1 -1
- package/dist/channel-runtime-controls.d.ts +2 -1
- package/dist/channel-runtime-controls.d.ts.map +1 -1
- package/dist/channel-runtime-controls.js +79 -22
- package/dist/channel-runtime-controls.js.map +1 -1
- package/dist/cli-args.d.ts +4 -6
- package/dist/cli-args.d.ts.map +1 -1
- package/dist/cli-args.js +16 -36
- package/dist/cli-args.js.map +1 -1
- package/dist/cli-commands.d.ts.map +1 -1
- package/dist/cli-commands.js +11 -0
- package/dist/cli-commands.js.map +1 -1
- package/dist/cli-help.d.ts +26 -0
- package/dist/cli-help.d.ts.map +1 -1
- package/dist/cli-help.js +25 -34
- package/dist/cli-help.js.map +1 -1
- package/dist/cli-init-command.d.ts +1 -2
- package/dist/cli-init-command.d.ts.map +1 -1
- package/dist/cli-init-command.js +5 -49
- package/dist/cli-init-command.js.map +1 -1
- package/dist/config-reference.d.ts.map +1 -1
- package/dist/config-reference.js +161 -52
- package/dist/config-reference.js.map +1 -1
- package/dist/configuration-proposal-tool.d.ts.map +1 -1
- package/dist/configuration-proposal-tool.js +3 -11
- package/dist/configuration-proposal-tool.js.map +1 -1
- package/dist/configured-agent.d.ts +18 -3
- package/dist/configured-agent.d.ts.map +1 -1
- package/dist/configured-agent.js +67 -86
- package/dist/configured-agent.js.map +1 -1
- package/dist/conversation-title.d.ts.map +1 -1
- package/dist/conversation-title.js +9 -2
- package/dist/conversation-title.js.map +1 -1
- package/dist/doctor.d.ts +7 -13
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +191 -725
- package/dist/doctor.js.map +1 -1
- package/dist/first-run-readiness.d.ts.map +1 -1
- package/dist/first-run-readiness.js +0 -6
- package/dist/first-run-readiness.js.map +1 -1
- package/dist/local-configuration.d.ts.map +1 -1
- package/dist/local-configuration.js +9 -37
- package/dist/local-configuration.js.map +1 -1
- package/dist/memory-remember.d.ts +49 -0
- package/dist/memory-remember.d.ts.map +1 -0
- package/dist/memory-remember.js +190 -0
- package/dist/memory-remember.js.map +1 -0
- package/dist/memory-retrieval.d.ts +24 -0
- package/dist/memory-retrieval.d.ts.map +1 -1
- package/dist/memory-retrieval.js +23 -0
- package/dist/memory-retrieval.js.map +1 -1
- package/dist/model-effort-capabilities.d.ts +8 -9
- package/dist/model-effort-capabilities.d.ts.map +1 -1
- package/dist/model-effort-capabilities.js +17 -35
- package/dist/model-effort-capabilities.js.map +1 -1
- package/dist/modules/base.d.ts +3 -4
- package/dist/modules/base.d.ts.map +1 -1
- package/dist/modules/base.js +3 -5
- package/dist/modules/base.js.map +1 -1
- package/dist/modules/catalog.d.ts.map +1 -1
- package/dist/modules/catalog.js +11 -9
- package/dist/modules/catalog.js.map +1 -1
- package/dist/modules/known-tools.d.ts +2 -2
- package/dist/modules/known-tools.d.ts.map +1 -1
- package/dist/modules/known-tools.js +6 -0
- package/dist/modules/known-tools.js.map +1 -1
- package/dist/monitor-channel-routing.d.ts +20 -0
- package/dist/monitor-channel-routing.d.ts.map +1 -0
- package/dist/monitor-channel-routing.js +73 -0
- package/dist/monitor-channel-routing.js.map +1 -0
- package/dist/monitors-command.d.ts +23 -0
- package/dist/monitors-command.d.ts.map +1 -0
- package/dist/monitors-command.js +207 -0
- package/dist/monitors-command.js.map +1 -0
- package/dist/monitors-config.d.ts +74 -0
- package/dist/monitors-config.d.ts.map +1 -0
- package/dist/monitors-config.js +146 -0
- package/dist/monitors-config.js.map +1 -0
- package/dist/monitors-context.d.ts +58 -0
- package/dist/monitors-context.d.ts.map +1 -0
- package/dist/monitors-context.js +182 -0
- package/dist/monitors-context.js.map +1 -0
- package/dist/monitors-runtime.d.ts +36 -0
- package/dist/monitors-runtime.d.ts.map +1 -0
- package/dist/monitors-runtime.js +103 -0
- package/dist/monitors-runtime.js.map +1 -0
- package/dist/monitors-service.d.ts +71 -0
- package/dist/monitors-service.d.ts.map +1 -0
- package/dist/monitors-service.js +1665 -0
- package/dist/monitors-service.js.map +1 -0
- package/dist/monitors-store.d.ts +83 -0
- package/dist/monitors-store.d.ts.map +1 -0
- package/dist/monitors-store.js +283 -0
- package/dist/monitors-store.js.map +1 -0
- package/dist/process-jobs-context.d.ts +8 -0
- package/dist/process-jobs-context.d.ts.map +1 -1
- package/dist/process-jobs-context.js +11 -0
- package/dist/process-jobs-context.js.map +1 -1
- package/dist/process-jobs-protection.js +1 -1
- package/dist/process-jobs-protection.js.map +1 -1
- package/dist/process-jobs-runtime.d.ts.map +1 -1
- package/dist/process-jobs-runtime.js +1 -12
- package/dist/process-jobs-runtime.js.map +1 -1
- package/dist/process-jobs-service.d.ts.map +1 -1
- package/dist/process-jobs-service.js +3 -149
- package/dist/process-jobs-service.js.map +1 -1
- package/dist/process-output-redaction.d.ts +33 -0
- package/dist/process-output-redaction.d.ts.map +1 -0
- package/dist/process-output-redaction.js +149 -0
- package/dist/process-output-redaction.js.map +1 -0
- package/dist/provider-model-catalog.d.ts +78 -0
- package/dist/provider-model-catalog.d.ts.map +1 -0
- package/dist/provider-model-catalog.js +439 -0
- package/dist/provider-model-catalog.js.map +1 -0
- package/dist/provider-setup.d.ts +4 -13
- package/dist/provider-setup.d.ts.map +1 -1
- package/dist/provider-setup.js +5 -115
- package/dist/provider-setup.js.map +1 -1
- package/dist/readiness-probe-worker.d.ts +1 -2
- package/dist/readiness-probe-worker.d.ts.map +1 -1
- package/dist/readiness-probe-worker.js +3 -8
- package/dist/readiness-probe-worker.js.map +1 -1
- package/dist/readiness-probe.d.ts +1 -1
- package/dist/readiness-probe.d.ts.map +1 -1
- package/dist/readiness-probe.js +3 -33
- package/dist/readiness-probe.js.map +1 -1
- package/dist/request-model-override.d.ts +4 -36
- package/dist/request-model-override.d.ts.map +1 -1
- package/dist/request-model-override.js +58 -146
- package/dist/request-model-override.js.map +1 -1
- package/dist/runtime-option-extensions.d.ts +2 -4
- package/dist/runtime-option-extensions.d.ts.map +1 -1
- package/dist/runtime-option-extensions.js +4 -8
- package/dist/runtime-option-extensions.js.map +1 -1
- package/dist/runtime-routes.d.ts +5 -5
- package/dist/runtime-routes.d.ts.map +1 -1
- package/dist/runtime-routes.js +29 -18
- package/dist/runtime-routes.js.map +1 -1
- package/dist/sandbox-settings-cleanup.d.ts +15 -0
- package/dist/sandbox-settings-cleanup.d.ts.map +1 -0
- package/dist/sandbox-settings-cleanup.js +88 -0
- package/dist/sandbox-settings-cleanup.js.map +1 -0
- package/dist/trigger-overrides.d.ts +6 -0
- package/dist/trigger-overrides.d.ts.map +1 -1
- package/dist/trigger-overrides.js +36 -4
- package/dist/trigger-overrides.js.map +1 -1
- package/dist/untrusted-text.d.ts +36 -0
- package/dist/untrusted-text.d.ts.map +1 -0
- package/dist/untrusted-text.js +150 -0
- package/dist/untrusted-text.js.map +1 -0
- package/dist/wizard/answers.d.ts +3 -9
- package/dist/wizard/answers.d.ts.map +1 -1
- package/dist/wizard/answers.js +11 -36
- package/dist/wizard/answers.js.map +1 -1
- package/dist/wizard/from-flags.d.ts +1 -2
- package/dist/wizard/from-flags.d.ts.map +1 -1
- package/dist/wizard/from-flags.js +8 -3
- package/dist/wizard/from-flags.js.map +1 -1
- package/dist/wizard/model-discovery.d.ts +7 -8
- package/dist/wizard/model-discovery.d.ts.map +1 -1
- package/dist/wizard/model-discovery.js +58 -431
- package/dist/wizard/model-discovery.js.map +1 -1
- package/dist/wizard/presets.js +2 -2
- package/dist/wizard/presets.js.map +1 -1
- package/dist/wizard/prompts.d.ts +13 -12
- package/dist/wizard/prompts.d.ts.map +1 -1
- package/dist/wizard/prompts.js +24 -45
- package/dist/wizard/prompts.js.map +1 -1
- package/dist/wizard/run.d.ts.map +1 -1
- package/dist/wizard/run.js +29 -152
- package/dist/wizard/run.js.map +1 -1
- package/package.json +17 -17
- package/schema/mono-agent.config.schema.json +197 -47
- package/skills/mono-agent-composer/SKILL.md +2 -2
- package/skills/mono-agent-composer/references/config-blueprint.md +45 -40
- package/skills/mono-agent-composer/references/discovery-questions.md +8 -13
- package/skills/mono-agent-composer/references/feature-coverage.md +11 -9
- package/skills/mono-agent-composer/references/package-map.md +22 -16
- package/skills/mono-agent-composer/references/playbooks.md +22 -26
- package/skills/mono-agent-composer/references/validation.md +1 -4
- package/dist/codex-model-catalog.d.ts +0 -15
- package/dist/codex-model-catalog.d.ts.map +0 -1
- package/dist/codex-model-catalog.js +0 -220
- package/dist/codex-model-catalog.js.map +0 -1
package/dist/doctor.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { execFile as execFileCallback } from "node:child_process";
|
|
2
2
|
import { constants } from "node:fs";
|
|
3
3
|
import { access, lstat, mkdir, readdir, readFile, realpath, stat } from "node:fs/promises";
|
|
4
|
-
import { homedir } from "node:os";
|
|
5
4
|
import { isIP } from "node:net";
|
|
6
5
|
import { basename, join, relative, resolve, sep } from "node:path";
|
|
7
6
|
import { promisify } from "node:util";
|
|
@@ -12,10 +11,11 @@ import { promisify } from "node:util";
|
|
|
12
11
|
import { getBuiltinModels as getPiBuiltinModels, getBuiltinProviders as getPiBuiltinProviders, } from "@earendil-works/pi-ai/providers/all";
|
|
13
12
|
import { validateCronExpression } from "@mono-agent/cron-adapter";
|
|
14
13
|
import { classifyContinuationMcpServerTransport, isStdioMcpServerSpec, loadToolPolicyFromJsonFile, } from "@mono-agent/agent-harness";
|
|
15
|
-
import {
|
|
16
|
-
import { buildMonoAgentConfigView, findJsonSecretConfigWarnings, findRemovedConfigWarnings, readMonoAgentConfigJson, redactMonoAgentConfig, resolveSupermemoryContainer, } from "@mono-agent/config";
|
|
17
|
-
import { describeSandboxEffectiveState, resolveSandboxEffectiveState, sandboxEffectiveStateWarning, } from "@mono-agent/runtime-adapter";
|
|
14
|
+
import { describeMonoRuntimeSupport, inspectCodexSubscriptionSearch, isValidMcpServerName, modelReferenceKey, networkPolicyAllowsUrl, parseMonoRuntimeModelReference, } from "@mono-agent/runtime-adapter";
|
|
15
|
+
import { buildMonoAgentConfigView, EFFORT_LEVELS, findJsonSecretConfigWarnings, findRemovedConfigWarnings, readMonoAgentConfigJson, redactMonoAgentConfig, resolveSupermemoryContainer, } from "@mono-agent/config";
|
|
16
|
+
import { describeSandboxEffectiveState, MODEL_REFERENCE_ECHO_MAX_BYTES, MODEL_REFERENCE_REASON_MAX_BYTES, resolveSandboxEffectiveState, RuntimeAdapterError, sandboxEffectiveStateWarning, sanitizeModelReferenceText, } from "@mono-agent/runtime-adapter";
|
|
18
17
|
import { isAppCoreConfigError, loadAppCoreConfig, } from "./app-config.js";
|
|
18
|
+
import { isRememberToolAllowed, isRememberToolPolicyName } from "./memory-remember.js";
|
|
19
19
|
import { adapterSendToolNames, isAdapterSendToolAllowed, resolveAdapterSendToolsSettings } from "./adapter-send-tools.js";
|
|
20
20
|
import { canonicalToolName, isAllowAllTools, isKnownToolName, isMcpToolName, suggestToolName } from "./modules/known-tools.js";
|
|
21
21
|
import { collectChannelConfigViews } from "./channel-config-view.js";
|
|
@@ -39,17 +39,22 @@ import { DEFAULT_MEMORY_EMBEDDING_ENDPOINTS, probeMemoryEmbeddingSelection, } fr
|
|
|
39
39
|
import { piAuthRecoveryCommand } from "./provider-setup.js";
|
|
40
40
|
import { inspectPiAuthStore } from "./pi-auth-store-inspection.js";
|
|
41
41
|
import { checkManagedProjectSkills, managedProjectSkillsExist } from "./project-skills.js";
|
|
42
|
-
import { configuredRuntimeFallbackModels, configuredRuntimeModels } from "./runtime-routes.js";
|
|
43
42
|
import { runtimeProvenanceDetail } from "./runtime-provenance.js";
|
|
43
|
+
import { resolveAdvertisedModelEffort } from "./model-effort-capabilities.js";
|
|
44
44
|
import { loadSupermemoryPlugin } from "./supermemory-plugin.js";
|
|
45
45
|
import { DEFAULT_LAUNCHD_LOG_POLICY, inspectLaunchdLogs, LAUNCHD_LOG_MONITOR_INTERVAL_SECONDS, launchdLogPathsForConfig, } from "./launchd-logs.js";
|
|
46
46
|
import { readLaunchdLogMonitorStatus } from "./launchd-log-monitor-status.js";
|
|
47
47
|
import { exporterSection, runsSection } from "./doctor-observability.js";
|
|
48
48
|
import { sessionToolHistorySection } from "./doctor-session-history.js";
|
|
49
|
-
import { runtimeRouteContainsUnsupportedHistoryTool } from "./app-controller-utils.js";
|
|
50
49
|
const execFile = promisify(execFileCallback);
|
|
51
50
|
const CONTINUATION_V2_ROLLBACK_GUARD = "UPGRADED-TO-RECORDS-V3";
|
|
52
51
|
const CONTINUATION_V2_ROLLBACK_GUARD_CONTENT = "This state directory uses continuation records v3. Older runtimes must not open records-v2.\n";
|
|
52
|
+
const DOCTOR_STATUS_TIMEOUT_MS = 5_000;
|
|
53
|
+
const DOCTOR_STATUS_MAX_BUFFER_BYTES = 64 * 1024;
|
|
54
|
+
const defaultDoctorStatusExecFile = async (file, args, options) => {
|
|
55
|
+
const { stdout } = await execFile(file, [...args], options);
|
|
56
|
+
return { stdout };
|
|
57
|
+
};
|
|
53
58
|
/**
|
|
54
59
|
* Loads every config section the app would use at start and reports it
|
|
55
60
|
* per-section, so an engineer can see exactly what would run, wait, or fail —
|
|
@@ -92,13 +97,13 @@ export async function validateMonoAgentFolder(options) {
|
|
|
92
97
|
sections.push({ id: "secret-placement", label: "Config warnings", status: "waiting", details: configWarnings });
|
|
93
98
|
}
|
|
94
99
|
sections.push(runtimeSection(coreConfig));
|
|
95
|
-
sections.push(await credentialsSection(coreConfig, options.env, options.
|
|
100
|
+
sections.push(await credentialsSection(coreConfig, options.env, options.verifiedCredentialModelRefs, staticTriggerCredentialRefs));
|
|
96
101
|
sections.push(await contextSection(coreConfig, options.cwd));
|
|
97
102
|
sections.push(await memorySection(coreConfig, options.cwd, options.env, liveness, allowFilesystemWrites, options.preferAppPluginInstall === true));
|
|
98
103
|
sections.push(await toolsSection(coreConfig, options));
|
|
99
104
|
sections.push(await sessionToolHistorySection({
|
|
100
105
|
historyRoot: join(coreConfig.artifacts.dir, "..", "history"),
|
|
101
|
-
requestScopedToolSupported:
|
|
106
|
+
requestScopedToolSupported: true,
|
|
102
107
|
}));
|
|
103
108
|
sections.push(await webToolsSection(coreConfig, options, liveness));
|
|
104
109
|
sections.push(await continuationSection(coreConfig, options));
|
|
@@ -266,40 +271,7 @@ async function collectStaticTriggerCredentialRefs(drivers, input) {
|
|
|
266
271
|
return refs;
|
|
267
272
|
}
|
|
268
273
|
async function applyRequestModelOverrideCompatibilityChecks(sections, config, drivers, input) {
|
|
269
|
-
const baseIsDirectCodex = config.runtime.model.sdk === "codex";
|
|
270
|
-
const routeSafety = config.runtime.routeSafety ?? "uniform";
|
|
271
|
-
const directBoundaryConflicts = [];
|
|
272
|
-
const sandboxBypasses = [];
|
|
273
|
-
const toolPolicyBypasses = [];
|
|
274
|
-
const effortBypasses = [];
|
|
275
|
-
const turnCapBypasses = [];
|
|
276
|
-
const mcpBypasses = [];
|
|
277
|
-
const skillBypasses = [];
|
|
278
274
|
const piModelResolutionFailures = [];
|
|
279
|
-
const monoSandboxActive = config.sandbox !== undefined && config.sandbox.mode !== "off";
|
|
280
|
-
const restrictiveToolPolicy = !hasAllowAllOnlyToolPolicy(config.tools);
|
|
281
|
-
let configuredMcpServerNames = [];
|
|
282
|
-
if (config.tools.mcpConfigPath !== undefined) {
|
|
283
|
-
try {
|
|
284
|
-
configuredMcpServerNames = Object.keys((await loadToolPolicyFromJsonFile(config.tools.mcpConfigPath)).mcpServers ?? {});
|
|
285
|
-
}
|
|
286
|
-
catch {
|
|
287
|
-
// The tools section owns missing/malformed MCP-file diagnostics.
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
let adapterToolNames = [];
|
|
291
|
-
try {
|
|
292
|
-
const settings = await resolveAdapterSendToolsSettings(input, {
|
|
293
|
-
allowedTools: config.tools.allowedTools,
|
|
294
|
-
disallowedTools: config.tools.disallowedTools,
|
|
295
|
-
});
|
|
296
|
-
if (settings !== undefined)
|
|
297
|
-
adapterToolNames = adapterSendToolNames(settings);
|
|
298
|
-
}
|
|
299
|
-
catch {
|
|
300
|
-
// Channel/tools sections own adapter config diagnostics.
|
|
301
|
-
}
|
|
302
|
-
const effectiveMcpSources = effectiveMcpRuntimeSources(config, configuredMcpServerNames, adapterToolNames);
|
|
303
275
|
for (const driver of drivers) {
|
|
304
276
|
if (driver.id !== "webhook" && driver.id !== "cron")
|
|
305
277
|
continue;
|
|
@@ -313,50 +285,19 @@ async function applyRequestModelOverrideCompatibilityChecks(sections, config, dr
|
|
|
313
285
|
}
|
|
314
286
|
for (const { entryPath, entry } of staticTriggerConfigEntries(driver.id, loaded)) {
|
|
315
287
|
const hasModelOverride = typeof entry.model === "string";
|
|
316
|
-
|
|
317
|
-
if (!hasModelOverride && !hasEffortOverride)
|
|
288
|
+
if (!hasModelOverride)
|
|
318
289
|
continue;
|
|
319
290
|
try {
|
|
320
|
-
const model =
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
if (routeSafety === "uniform" && hasModelOverride && (model.sdk === "codex") !== baseIsDirectCodex) {
|
|
333
|
-
directBoundaryConflicts.push(location);
|
|
334
|
-
}
|
|
335
|
-
if (routeSafety === "uniform" && hasModelOverride && monoSandboxActive && (model.sdk === "claude" || model.sdk === "opencode" || model.sdk === "codex")) {
|
|
336
|
-
sandboxBypasses.push(location);
|
|
337
|
-
}
|
|
338
|
-
if (hasModelOverride && restrictiveToolPolicy && model.sdk === "opencode") {
|
|
339
|
-
toolPolicyBypasses.push(location);
|
|
340
|
-
}
|
|
341
|
-
const effectiveEffort = hasEffortOverride ? entry.effort : config.runtime.effort;
|
|
342
|
-
const legacyFallbacks = (config.runtime.fallbacks?.length ?? 0) > 0
|
|
343
|
-
? []
|
|
344
|
-
: config.runtime.fallbackModels ?? [];
|
|
345
|
-
const directOpenCodeModels = [model, ...legacyFallbacks]
|
|
346
|
-
.filter((candidate) => candidate.sdk === "opencode");
|
|
347
|
-
if (directOpenCodeModels.length > 0 && effectiveEffort !== undefined) {
|
|
348
|
-
effortBypasses.push(`${location} (effective effort=${effectiveEffort}) (direct OpenCode route=${directOpenCodeModels.map(referenceOf).join(", ")})`);
|
|
349
|
-
}
|
|
350
|
-
if (directOpenCodeModels.length > 0 && Number(config.runtime.maxTurns) > 0) {
|
|
351
|
-
turnCapBypasses.push(`${location} (runtime.maxTurns=${config.runtime.maxTurns}; direct OpenCode route=${directOpenCodeModels.map(referenceOf).join(", ")})`);
|
|
352
|
-
}
|
|
353
|
-
if (directOpenCodeModels.length > 0 && effectiveMcpSources.length > 0) {
|
|
354
|
-
mcpBypasses.push(`${location} (direct OpenCode route=${directOpenCodeModels.map(referenceOf).join(", ")}; MCP sources=${effectiveMcpSources.join("; ")})`);
|
|
355
|
-
}
|
|
356
|
-
if (directOpenCodeModels.length > 0
|
|
357
|
-
&& config.context.skillDisclosure === "index"
|
|
358
|
-
&& config.context.skillsRoot !== undefined) {
|
|
359
|
-
skillBypasses.push(location);
|
|
291
|
+
const model = parseMonoRuntimeModelReference(entry.model);
|
|
292
|
+
const resolutionIssue = piModelResolutionIssue(config, model);
|
|
293
|
+
if (resolutionIssue !== undefined) {
|
|
294
|
+
// The parser accepted this value, but acceptance is no longer a length
|
|
295
|
+
// guarantee: the byte ceiling was removed because no constant could
|
|
296
|
+
// hold for every provider, so a legitimately long id parses. This is a
|
|
297
|
+
// diagnostic, not the protocol string, so it is bounded here rather
|
|
298
|
+
// than at the grammar.
|
|
299
|
+
const echoed = sanitizeModelReferenceText(entry.model, MODEL_REFERENCE_ECHO_MAX_BYTES);
|
|
300
|
+
piModelResolutionFailures.push(`${entryPath}.model=${echoed}: ${resolutionIssue}.`);
|
|
360
301
|
}
|
|
361
302
|
}
|
|
362
303
|
catch {
|
|
@@ -364,14 +305,7 @@ async function applyRequestModelOverrideCompatibilityChecks(sections, config, dr
|
|
|
364
305
|
}
|
|
365
306
|
}
|
|
366
307
|
}
|
|
367
|
-
if (
|
|
368
|
-
&& sandboxBypasses.length === 0
|
|
369
|
-
&& toolPolicyBypasses.length === 0
|
|
370
|
-
&& effortBypasses.length === 0
|
|
371
|
-
&& turnCapBypasses.length === 0
|
|
372
|
-
&& mcpBypasses.length === 0
|
|
373
|
-
&& skillBypasses.length === 0
|
|
374
|
-
&& piModelResolutionFailures.length === 0)
|
|
308
|
+
if (piModelResolutionFailures.length === 0)
|
|
375
309
|
return;
|
|
376
310
|
const index = sections.findIndex((section) => section.id === "runtime");
|
|
377
311
|
if (index < 0)
|
|
@@ -382,63 +316,14 @@ async function applyRequestModelOverrideCompatibilityChecks(sections, config, dr
|
|
|
382
316
|
status: "error",
|
|
383
317
|
details: [
|
|
384
318
|
...runtime.details,
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
: [
|
|
388
|
-
"Uniform route safety cannot cross the direct-Codex runtime boundary because tool and sandbox contracts would change mid-agent. Choose per-route-native to opt into explicit route-local contracts.",
|
|
389
|
-
...directBoundaryConflicts,
|
|
390
|
-
]),
|
|
391
|
-
...(sandboxBypasses.length === 0
|
|
392
|
-
? []
|
|
393
|
-
: [
|
|
394
|
-
"Claude or direct OpenCode model overrides cannot run under uniform route safety while mono-agent SRT is active; direct Codex is also provider-owned. Choose per-route-native only after reviewing the explicit route-local contracts.",
|
|
395
|
-
...sandboxBypasses,
|
|
396
|
-
]),
|
|
397
|
-
...(toolPolicyBypasses.length === 0
|
|
398
|
-
? []
|
|
399
|
-
: [
|
|
400
|
-
"Per-trigger direct OpenCode model overrides require an effective allow-all policy because OpenCode's provider-owned tool loop does not consume mono-agent allowedTools/disallowedTools.",
|
|
401
|
-
...toolPolicyBypasses,
|
|
402
|
-
]),
|
|
403
|
-
...(effortBypasses.length === 0
|
|
404
|
-
? []
|
|
405
|
-
: [
|
|
406
|
-
"Per-trigger direct OpenCode routes cannot receive runtime effort because the OpenCode SDK does not expose effort control.",
|
|
407
|
-
...effortBypasses,
|
|
408
|
-
]),
|
|
409
|
-
...(turnCapBypasses.length === 0
|
|
410
|
-
? []
|
|
411
|
-
: [
|
|
412
|
-
"Per-trigger direct OpenCode routes cannot enforce runtime.maxTurns; omit it, set it to 0, or use a runtime with a hard turn cap.",
|
|
413
|
-
...turnCapBypasses,
|
|
414
|
-
]),
|
|
415
|
-
...(mcpBypasses.length === 0
|
|
416
|
-
? []
|
|
417
|
-
: [
|
|
418
|
-
"Per-trigger direct OpenCode routes cannot receive configured or auto-provisioned MCP runtime options.",
|
|
419
|
-
...mcpBypasses,
|
|
420
|
-
]),
|
|
421
|
-
...(skillBypasses.length === 0
|
|
422
|
-
? []
|
|
423
|
-
: [
|
|
424
|
-
"Per-trigger direct OpenCode routes cannot use index skill disclosure because the bridge disables external/runtime skills; use full disclosure or a Pi runtime.",
|
|
425
|
-
...skillBypasses,
|
|
426
|
-
]),
|
|
427
|
-
...(piModelResolutionFailures.length === 0
|
|
428
|
-
? []
|
|
429
|
-
: [
|
|
430
|
-
"Per-trigger Pi model overrides must resolve through providers.local or Pi's exact built-in catalog before execution.",
|
|
431
|
-
...piModelResolutionFailures,
|
|
432
|
-
]),
|
|
319
|
+
"Per-trigger Pi model overrides must resolve through providers.local or Pi's exact built-in catalog before execution.",
|
|
320
|
+
...piModelResolutionFailures,
|
|
433
321
|
],
|
|
434
322
|
};
|
|
435
323
|
}
|
|
436
324
|
function isUnknownRecord(value) {
|
|
437
325
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
438
326
|
}
|
|
439
|
-
function hasAllowAllOnlyToolPolicy(tools) {
|
|
440
|
-
return isAllowAllTools(tools.allowedTools) && tools.disallowedTools.length === 0;
|
|
441
|
-
}
|
|
442
327
|
/** Adapter send tools each channel owns; an allowed entry needs BOTH the tool AND the enabled channel. */
|
|
443
328
|
const CHANNEL_OWNED_SEND_TOOLS = {
|
|
444
329
|
slack: ["SlackSendMessage"],
|
|
@@ -511,45 +396,28 @@ function runtimeSection(config) {
|
|
|
511
396
|
const details = [];
|
|
512
397
|
let status = "ok";
|
|
513
398
|
const routes = configuredRuntimeRouteChecks(config);
|
|
514
|
-
const routeSafety = config.runtime.routeSafety ?? "uniform";
|
|
515
|
-
details.push(`Route safety: ${routeSafety}.`);
|
|
516
|
-
if (routes.length > 1) {
|
|
517
|
-
details.push(routeSafety === "per-route-native"
|
|
518
|
-
? "Mixed runtime families are allowed; every attempt uses its explicit route-native safety contract."
|
|
519
|
-
: "Fallback routes use the uniform compatibility contract; validation fails closed when any route cannot represent a required capability.");
|
|
520
|
-
}
|
|
521
|
-
const directOpenCodeModels = routes.filter((route) => route.model.sdk === "opencode");
|
|
522
|
-
if (Number(config.runtime.maxTurns) > 0 && directOpenCodeModels.length > 0) {
|
|
523
|
-
status = "error";
|
|
524
|
-
details.push(`Direct OpenCode model${directOpenCodeModels.length === 1 ? "" : "s"} ${directOpenCodeModels.map((route) => referenceOf(route.model)).join(", ")} cannot enforce runtime.maxTurns=${config.runtime.maxTurns}; omit it, set it to 0, or use a runtime with a hard turn cap.`);
|
|
525
|
-
}
|
|
526
|
-
if (directOpenCodeModels.length > 0
|
|
527
|
-
&& config.context.skillDisclosure === "index"
|
|
528
|
-
&& config.context.skillsRoot !== undefined) {
|
|
529
|
-
status = "error";
|
|
530
|
-
details.push(`Direct OpenCode model${directOpenCodeModels.length === 1 ? "" : "s"} ${directOpenCodeModels.map((route) => referenceOf(route.model)).join(", ")} cannot use context.skillDisclosure=index because runtime skills are disabled; use full disclosure or a Pi runtime.`);
|
|
531
|
-
}
|
|
532
399
|
for (const route of routes) {
|
|
533
400
|
try {
|
|
534
|
-
const support = describeMonoRuntimeSupport(route.model
|
|
401
|
+
const support = describeMonoRuntimeSupport(route.model);
|
|
535
402
|
const resolutionIssue = piModelResolutionIssue(config, route.model);
|
|
536
|
-
const
|
|
537
|
-
if (
|
|
538
|
-
details.push(`${route.label} ${
|
|
403
|
+
const effortWarning = runtimeRouteEffortWarning(config, route);
|
|
404
|
+
if (resolutionIssue === undefined) {
|
|
405
|
+
details.push(`${route.label} ${displayReferenceOf(route.model)} runs on ${support.backend.label} ` +
|
|
539
406
|
`(effort: ${route.effort ?? "provider default"}).`);
|
|
540
407
|
}
|
|
541
408
|
else {
|
|
542
409
|
status = "error";
|
|
543
|
-
details.push(`${route.label} ${
|
|
410
|
+
details.push(`${route.label} ${displayReferenceOf(route.model)}: ${resolutionIssue}.`);
|
|
544
411
|
}
|
|
545
|
-
if (
|
|
546
|
-
status
|
|
547
|
-
|
|
412
|
+
if (effortWarning !== undefined) {
|
|
413
|
+
if (status !== "error")
|
|
414
|
+
status = "waiting";
|
|
415
|
+
details.push(effortWarning);
|
|
548
416
|
}
|
|
549
417
|
}
|
|
550
418
|
catch (error) {
|
|
551
419
|
status = "error";
|
|
552
|
-
details.push(`${route.label} ${
|
|
420
|
+
details.push(`${route.label} ${displayReferenceOf(route.model)}: ${displayReason(error)}.`);
|
|
553
421
|
}
|
|
554
422
|
}
|
|
555
423
|
return { id: "runtime", label: "Runtime", status, details };
|
|
@@ -557,48 +425,52 @@ function runtimeSection(config) {
|
|
|
557
425
|
function configuredRuntimeRouteChecks(config) {
|
|
558
426
|
const primary = {
|
|
559
427
|
label: "Primary model",
|
|
428
|
+
configPath: "runtime.effort",
|
|
560
429
|
model: config.runtime.model,
|
|
561
430
|
...(config.runtime.effort === undefined ? {} : { effort: config.runtime.effort }),
|
|
562
|
-
executionMode: config.runtime.executionMode,
|
|
563
431
|
};
|
|
564
|
-
if ((config.runtime.fallbacks?.length ?? 0) > 0) {
|
|
565
|
-
return [
|
|
566
|
-
primary,
|
|
567
|
-
...(config.runtime.fallbacks ?? []).map((fallback) => ({
|
|
568
|
-
label: "Fallback model",
|
|
569
|
-
model: fallback.model,
|
|
570
|
-
...(fallback.effort === undefined ? {} : { effort: fallback.effort }),
|
|
571
|
-
})),
|
|
572
|
-
];
|
|
573
|
-
}
|
|
574
432
|
return [
|
|
575
433
|
primary,
|
|
576
|
-
...(config.runtime.
|
|
434
|
+
...(config.runtime.fallbacks ?? []).map((fallback, index) => ({
|
|
577
435
|
label: "Fallback model",
|
|
578
|
-
|
|
579
|
-
|
|
436
|
+
configPath: `runtime.fallbacks[${index}].effort`,
|
|
437
|
+
model: fallback.model,
|
|
438
|
+
...(fallback.effort === undefined ? {} : { effort: fallback.effort }),
|
|
580
439
|
})),
|
|
581
440
|
];
|
|
582
441
|
}
|
|
583
|
-
function
|
|
442
|
+
function runtimeRouteEffortWarning(config, route) {
|
|
584
443
|
if (route.effort === undefined)
|
|
585
444
|
return undefined;
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
445
|
+
// Always go through the shared resolver. Deriving the ladder from
|
|
446
|
+
// `thinkingLevelMap`'s KEYS treated an override table as the complete
|
|
447
|
+
// supported set: claude-fable-5 maps only {off, xhigh, max}, which yielded
|
|
448
|
+
// [none, xhigh, max] and made a perfectly valid `effort: medium` warn and
|
|
449
|
+
// recommend xhigh. The resolver reports [minimal, low, medium, high, xhigh,
|
|
450
|
+
// max] for that model, and it is the same one the catalog and pickers use --
|
|
451
|
+
// doctor disagreeing with what the selector offers is its own bug.
|
|
452
|
+
const resolved = resolveAdvertisedModelEffort(route.model, {
|
|
453
|
+
...(config.providers?.local === undefined ? {} : { localProviders: config.providers.local }),
|
|
454
|
+
});
|
|
455
|
+
// A NON-REASONING route has no advertised ladder at all, so the level-list
|
|
456
|
+
// check below returns clean and `effort: high` was reported ready. Keep this
|
|
457
|
+
// ahead of that check: a local model can declare `reasoning_mode: "none"`
|
|
458
|
+
// WITH `reasoning_levels`, and "this route does not reason" is the accurate
|
|
459
|
+
// diagnosis there, not "pick another level".
|
|
460
|
+
if (resolved.reasoning === false && route.effort !== "none") {
|
|
461
|
+
return `[WARN] ${route.configPath}=${route.effort} is unsupported because known model metadata marks ${displayReferenceOf(route.model)} as non-reasoning; use none, or omit it for the provider default. The runtime remains permissive and will forward the configured value.`;
|
|
462
|
+
}
|
|
463
|
+
const advertised = resolved.effortLevels
|
|
464
|
+
?.filter((level) => EFFORT_LEVELS.includes(level));
|
|
465
|
+
if (advertised === undefined || advertised.length === 0 || advertised.includes(route.effort))
|
|
466
|
+
return undefined;
|
|
467
|
+
const configuredIndex = EFFORT_LEVELS.indexOf(route.effort);
|
|
468
|
+
const nearest = advertised.reduce((best, candidate) => {
|
|
469
|
+
const candidateIndex = EFFORT_LEVELS.indexOf(candidate);
|
|
470
|
+
const bestIndex = EFFORT_LEVELS.indexOf(best);
|
|
471
|
+
return Math.abs(candidateIndex - configuredIndex) < Math.abs(bestIndex - configuredIndex) ? candidate : best;
|
|
472
|
+
});
|
|
473
|
+
return `[WARN] ${route.configPath}=${route.effort} is outside ${displayReferenceOf(route.model)}'s advertised effort levels (${advertised.join(", ")}); nearest advertised level: ${nearest}. The runtime remains permissive and will forward the configured value.`;
|
|
602
474
|
}
|
|
603
475
|
/**
|
|
604
476
|
* Mirrors the Pi runtime's actual model-resolution boundary. Mono-agent only
|
|
@@ -607,17 +479,14 @@ function runtimeRouteEffortIssue(config, route) {
|
|
|
607
479
|
* provider, `resolvePiRuntimeModel` performs an exact built-in catalog lookup.
|
|
608
480
|
*/
|
|
609
481
|
function piModelResolutionIssue(config, model) {
|
|
610
|
-
if (model.sdk !== "pi" || model.provider === undefined) {
|
|
611
|
-
return undefined;
|
|
612
|
-
}
|
|
613
482
|
const localProvider = config.providers?.local?.find((provider) => provider.id === model.provider);
|
|
614
483
|
if (localProvider !== undefined) {
|
|
615
484
|
if (localProvider.enabled === false) {
|
|
616
|
-
return `provider \`${model.provider}\` is disabled in providers.local`;
|
|
485
|
+
return `provider \`${displayText(model.provider)}\` is disabled in providers.local`;
|
|
617
486
|
}
|
|
618
487
|
const localModel = localProvider.models?.find((candidate) => candidate.name === model.model || candidate.alias === model.model);
|
|
619
488
|
if (localModel?.enabled === false) {
|
|
620
|
-
return `model \`${model.model}\` is disabled in providers.local for provider \`${model.provider}\``;
|
|
489
|
+
return `model \`${displayText(model.model)}\` is disabled in providers.local for provider \`${displayText(model.provider)}\``;
|
|
621
490
|
}
|
|
622
491
|
return undefined;
|
|
623
492
|
}
|
|
@@ -625,7 +494,14 @@ function piModelResolutionIssue(config, model) {
|
|
|
625
494
|
&& getPiBuiltinModels(model.provider).some((candidate) => candidate.id === model.model)) {
|
|
626
495
|
return undefined;
|
|
627
496
|
}
|
|
628
|
-
|
|
497
|
+
// This is doctor's own validate-time diagnostic, not the runtime failure
|
|
498
|
+
// string. The one that must stay byte-identical for `NON_RETRYABLE_PROVIDER_RE`
|
|
499
|
+
// is built in agent-runtime's `ai/providers/pi-models.js`; nothing matches
|
|
500
|
+
// against this text. So the reference is bounded here, which matters now that
|
|
501
|
+
// the parser imposes no length ceiling and a legitimately long id reaches this
|
|
502
|
+
// line whole.
|
|
503
|
+
const reference = displayText(`${model.provider}:${model.model}`);
|
|
504
|
+
return (`pi model not found: ${reference}; no matching providers.local entry exists and Pi's built-in catalog has no exact model. ` +
|
|
629
505
|
"The sibling Pi CLI models.json is not a mono-agent runtime source; add providers.local for a self-hosted provider or choose a built-in Pi model");
|
|
630
506
|
}
|
|
631
507
|
function isPiBuiltinProvider(provider) {
|
|
@@ -641,229 +517,21 @@ async function readPiAuthProviders(path) {
|
|
|
641
517
|
return inspection;
|
|
642
518
|
return { status: "ok", providers: inspection.auth };
|
|
643
519
|
}
|
|
644
|
-
/**
|
|
645
|
-
* What each SDK-authenticated backend truthfully reads for credentials. Only the
|
|
646
|
-
* env vars are statically checkable; the login paths are recorded so the warning
|
|
647
|
-
* can stay honest (a logged-in user is fine and we must not claim otherwise).
|
|
648
|
-
*
|
|
649
|
-
* - claude (`claude:*`, sdk + cli): the Claude Code process authenticates from
|
|
650
|
-
* `ANTHROPIC_API_KEY`, `ANTHROPIC_AUTH_TOKEN`, or `CLAUDE_CODE_OAUTH_TOKEN` in
|
|
651
|
-
* the env, OR from a `claude /login` subscription session stored outside the
|
|
652
|
-
* environment (macOS Keychain / `~/.claude`), OR from a Bedrock/Vertex
|
|
653
|
-
* configuration (`CLAUDE_CODE_USE_BEDROCK`/`CLAUDE_CODE_USE_VERTEX` + cloud
|
|
654
|
-
* credentials) — none of which the env-key check can see, hence the hedge in
|
|
655
|
-
* the warning. Its own error string is verbatim: "Claude Code authentication
|
|
656
|
-
* failed. Run `claude /login` or configure ANTHROPIC_API_KEY,
|
|
657
|
-
* ANTHROPIC_AUTH_TOKEN, or CLAUDE_CODE_OAUTH_TOKEN."
|
|
658
|
-
* - codex (`codex:*`, cli): the Codex app-server authenticates from
|
|
659
|
-
* `OPENAI_API_KEY` in the env, OR from a `codex login` ChatGPT session stored
|
|
660
|
-
* in `~/.codex/auth.json` — also outside the environment.
|
|
661
|
-
*/
|
|
662
|
-
const SDK_AUTH_SCHEMES = {
|
|
663
|
-
claude: {
|
|
664
|
-
envKeys: ["ANTHROPIC_API_KEY", "ANTHROPIC_AUTH_TOKEN", "CLAUDE_CODE_OAUTH_TOKEN"],
|
|
665
|
-
loginCommand: "claude /login",
|
|
666
|
-
loginDetail: "a `claude /login` subscription session or a Bedrock/Vertex configuration (CLAUDE_CODE_USE_BEDROCK/VERTEX)",
|
|
667
|
-
failureHint: 'the first turn fails with an opaque "Claude Code process exited with code 1" that names nothing',
|
|
668
|
-
statusCommand: "claude auth status --json",
|
|
669
|
-
statusArgs: ["auth", "status", "--json"],
|
|
670
|
-
},
|
|
671
|
-
codex: {
|
|
672
|
-
envKeys: ["OPENAI_API_KEY"],
|
|
673
|
-
loginCommand: "codex login",
|
|
674
|
-
loginDetail: "a `codex login` ChatGPT session (`~/.codex/auth.json`, outside the environment)",
|
|
675
|
-
failureHint: "the first turn fails to authenticate",
|
|
676
|
-
statusCommand: "codex login status",
|
|
677
|
-
statusArgs: ["login", "status"],
|
|
678
|
-
},
|
|
679
|
-
};
|
|
680
|
-
const SDK_AUTH_STATUS_TIMEOUT_MS = 5_000;
|
|
681
|
-
const SDK_AUTH_STATUS_MAX_BUFFER_BYTES = 64 * 1024;
|
|
682
|
-
function isSdkAuthName(value) {
|
|
683
|
-
return value === "claude" || value === "codex";
|
|
684
|
-
}
|
|
685
|
-
const defaultSdkAuthStatusExecFile = async (file, args, options) => {
|
|
686
|
-
const { stdout } = await execFile(file, [...args], options);
|
|
687
|
-
return { stdout };
|
|
688
|
-
};
|
|
689
|
-
/**
|
|
690
|
-
* Performs the SDK's local, read-only login-status command. A zero Codex exit
|
|
691
|
-
* confirms its external login; Claude additionally requires strict JSON with
|
|
692
|
-
* `loggedIn: true`. Missing binaries, timeouts, non-zero exits, and malformed
|
|
693
|
-
* output all fail closed without leaking command output into validation.
|
|
694
|
-
*/
|
|
695
|
-
async function checkSdkExternalLoginStatus(sdk, env, cwd, run) {
|
|
696
|
-
const scheme = SDK_AUTH_SCHEMES[sdk];
|
|
697
|
-
try {
|
|
698
|
-
const { stdout } = await run(sdk, scheme.statusArgs, {
|
|
699
|
-
cwd,
|
|
700
|
-
env,
|
|
701
|
-
timeout: SDK_AUTH_STATUS_TIMEOUT_MS,
|
|
702
|
-
maxBuffer: SDK_AUTH_STATUS_MAX_BUFFER_BYTES,
|
|
703
|
-
encoding: "utf8",
|
|
704
|
-
});
|
|
705
|
-
if (sdk === "codex") {
|
|
706
|
-
return true;
|
|
707
|
-
}
|
|
708
|
-
const parsed = JSON.parse(stdout);
|
|
709
|
-
return parsed !== null
|
|
710
|
-
&& typeof parsed === "object"
|
|
711
|
-
&& !Array.isArray(parsed)
|
|
712
|
-
&& parsed.loggedIn === true;
|
|
713
|
-
}
|
|
714
|
-
catch {
|
|
715
|
-
return false;
|
|
716
|
-
}
|
|
717
|
-
}
|
|
718
|
-
/** Read provider IDs directly from auth.json without launching mutation-capable OpenCode middleware. */
|
|
719
|
-
async function inspectOpenCodeCredentialProviders(env) {
|
|
720
|
-
if (env.OPENCODE_AUTH_CONTENT !== undefined) {
|
|
721
|
-
return { status: "inline_auth_unsupported" };
|
|
722
|
-
}
|
|
723
|
-
const dataHome = typeof env.XDG_DATA_HOME === "string" && env.XDG_DATA_HOME.length > 0
|
|
724
|
-
? env.XDG_DATA_HOME
|
|
725
|
-
: join(typeof env.HOME === "string" && env.HOME.length > 0 ? env.HOME : homedir(), ".local", "share");
|
|
726
|
-
const opencodeData = join(dataHome, "opencode");
|
|
727
|
-
const marker = await regularCurrentUserFile(join(opencodeData, "opencode.db"));
|
|
728
|
-
if (!marker)
|
|
729
|
-
return { status: "migration_required" };
|
|
730
|
-
const authPath = join(opencodeData, "auth.json");
|
|
731
|
-
if (!(await regularCurrentUserFile(authPath)))
|
|
732
|
-
return { status: "auth_missing" };
|
|
733
|
-
try {
|
|
734
|
-
const parsed = JSON.parse(await readFile(authPath, "utf8"));
|
|
735
|
-
if (!isUnknownRecord(parsed))
|
|
736
|
-
return { status: "auth_invalid" };
|
|
737
|
-
const providers = new Set();
|
|
738
|
-
for (const [provider, credential] of Object.entries(parsed)) {
|
|
739
|
-
if (provider.length === 0 || provider.trim() !== provider || !isOpenCodeCredentialEntry(credential)) {
|
|
740
|
-
return { status: "auth_invalid" };
|
|
741
|
-
}
|
|
742
|
-
providers.add(provider);
|
|
743
|
-
}
|
|
744
|
-
return { status: "ok", providers };
|
|
745
|
-
}
|
|
746
|
-
catch {
|
|
747
|
-
return { status: "auth_invalid" };
|
|
748
|
-
}
|
|
749
|
-
}
|
|
750
|
-
async function regularCurrentUserFile(path) {
|
|
751
|
-
try {
|
|
752
|
-
const info = await lstat(path);
|
|
753
|
-
return info.isFile() && (typeof process.getuid !== "function" || info.uid === process.getuid());
|
|
754
|
-
}
|
|
755
|
-
catch {
|
|
756
|
-
return false;
|
|
757
|
-
}
|
|
758
|
-
}
|
|
759
|
-
function isOpenCodeCredentialEntry(value) {
|
|
760
|
-
if (!isUnknownRecord(value))
|
|
761
|
-
return false;
|
|
762
|
-
if (value.type === "oauth") {
|
|
763
|
-
return typeof value.refresh === "string"
|
|
764
|
-
&& typeof value.access === "string"
|
|
765
|
-
&& (value.refresh.trim().length > 0 || value.access.trim().length > 0)
|
|
766
|
-
&& typeof value.expires === "number"
|
|
767
|
-
&& Number.isSafeInteger(value.expires)
|
|
768
|
-
&& value.expires >= 0
|
|
769
|
-
&& (value.accountId === undefined || typeof value.accountId === "string")
|
|
770
|
-
&& (value.enterpriseUrl === undefined || typeof value.enterpriseUrl === "string");
|
|
771
|
-
}
|
|
772
|
-
if (value.type === "api") {
|
|
773
|
-
return typeof value.key === "string"
|
|
774
|
-
&& value.key.trim().length > 0
|
|
775
|
-
&& (value.metadata === undefined
|
|
776
|
-
|| (isUnknownRecord(value.metadata) && Object.values(value.metadata).every((entry) => typeof entry === "string")));
|
|
777
|
-
}
|
|
778
|
-
if (value.type === "wellknown") {
|
|
779
|
-
return typeof value.key === "string" && value.key.trim().length > 0
|
|
780
|
-
&& typeof value.token === "string" && value.token.trim().length > 0;
|
|
781
|
-
}
|
|
782
|
-
return false;
|
|
783
|
-
}
|
|
784
|
-
async function checkOpenCodeVersion(env, cwd, run) {
|
|
785
|
-
const versionEnv = {};
|
|
786
|
-
for (const key of [
|
|
787
|
-
"PATH",
|
|
788
|
-
"LANG",
|
|
789
|
-
"LC_ALL",
|
|
790
|
-
"LC_CTYPE",
|
|
791
|
-
"SHELL",
|
|
792
|
-
"TMPDIR",
|
|
793
|
-
"TMP",
|
|
794
|
-
"TEMP",
|
|
795
|
-
"SystemRoot",
|
|
796
|
-
"WINDIR",
|
|
797
|
-
"ComSpec",
|
|
798
|
-
"PATHEXT",
|
|
799
|
-
]) {
|
|
800
|
-
const value = env[key];
|
|
801
|
-
if (typeof value === "string" && value.length > 0)
|
|
802
|
-
versionEnv[key] = value;
|
|
803
|
-
}
|
|
804
|
-
try {
|
|
805
|
-
const { stdout } = await run("opencode", ["--version"], {
|
|
806
|
-
cwd,
|
|
807
|
-
env: versionEnv,
|
|
808
|
-
timeout: SDK_AUTH_STATUS_TIMEOUT_MS,
|
|
809
|
-
maxBuffer: SDK_AUTH_STATUS_MAX_BUFFER_BYTES,
|
|
810
|
-
encoding: "utf8",
|
|
811
|
-
});
|
|
812
|
-
const match = /^v?(\d+)\.(\d+)\.(\d+)(?:\+[0-9A-Za-z.-]+)?$/u.exec(stdout.trim());
|
|
813
|
-
if (match === null)
|
|
814
|
-
return false;
|
|
815
|
-
const major = Number(match[1]);
|
|
816
|
-
const minor = Number(match[2]);
|
|
817
|
-
const patch = Number(match[3]);
|
|
818
|
-
return Number.isSafeInteger(major)
|
|
819
|
-
&& Number.isSafeInteger(minor)
|
|
820
|
-
&& Number.isSafeInteger(patch)
|
|
821
|
-
&& (major > 1 || (major === 1 && minor >= 15));
|
|
822
|
-
}
|
|
823
|
-
catch {
|
|
824
|
-
return false;
|
|
825
|
-
}
|
|
826
|
-
}
|
|
827
|
-
/** First env key whose value is present and non-blank, or undefined when none are set. */
|
|
828
|
-
function firstPresentEnvKey(env, keys) {
|
|
829
|
-
for (const key of keys) {
|
|
830
|
-
const value = env[key];
|
|
831
|
-
if (typeof value === "string" && value.trim().length > 0) {
|
|
832
|
-
return key;
|
|
833
|
-
}
|
|
834
|
-
}
|
|
835
|
-
return undefined;
|
|
836
|
-
}
|
|
837
520
|
/**
|
|
838
521
|
* Checks that every referenced model (primary, fallbacks, and the agent-host memory LLM)
|
|
839
522
|
* has discoverable credentials, so a keyless or expired-OAuth provider is caught at
|
|
840
523
|
* `validate` time instead of degrading crons/memory silently at runtime (the failure mode
|
|
841
|
-
* that broke memory capture for ~10 days
|
|
842
|
-
* and the E1 fresh-instance case where `claude:*` with no `ANTHROPIC_API_KEY` "validates"
|
|
843
|
-
* clean but the first turn dies with an opaque "process exited with code 1").
|
|
844
|
-
*
|
|
845
|
-
* The check is read-only. Static validation never launches a process; live validation may
|
|
846
|
-
* run bounded, local SDK login-status commands, which inspect durable external login state
|
|
847
|
-
* without making a model turn or mutating the auth store.
|
|
524
|
+
* that broke memory capture for ~10 days when an auth-store OAuth token quietly expired.
|
|
848
525
|
*
|
|
849
|
-
* -
|
|
526
|
+
* The read-only check inspects the auth store (`piAuthPath`) and the config's own
|
|
850
527
|
* `providers.local` custom providers. A custom/local provider follows its declared
|
|
851
528
|
* `apiKey` / `apiKeyEnv` contract instead of Pi OAuth; an OAuth provider absent from
|
|
852
529
|
* the store, or whose access token has expired, is flagged `waiting` with a re-auth hint.
|
|
853
|
-
* - SDK-authenticated providers (`claude:*` / `codex:*`): inspect the RESOLVED ENV (process
|
|
854
|
-
* env + loaded `.env`) for the backend's accepted keys. During live validation only, a
|
|
855
|
-
* missing env credential falls back to `claude auth status --json` / `codex login status`.
|
|
856
|
-
* The commands use the same resolved environment (including PATH and HOME), are bounded,
|
|
857
|
-
* cached once per SDK, and never make a model turn. Static validation remains process-free.
|
|
858
|
-
* - Direct OpenCode (`opencode:<provider>:<model>`): inspect exact provider IDs in the
|
|
859
|
-
* standard auth.json and require the native DB migration marker. Static validation launches
|
|
860
|
-
* no process. Live validation runs only a bounded `opencode --version` preflight; it never
|
|
861
|
-
* runs the mutation-capable auth middleware or makes a model turn.
|
|
862
530
|
*
|
|
863
531
|
* `waiting` (never `error`) keeps the verdict non-fatal, mirroring the Ollama/Phoenix
|
|
864
532
|
* probes — the goal is visibility, not blocking start.
|
|
865
533
|
*/
|
|
866
|
-
async function credentialsSection(config, env,
|
|
534
|
+
async function credentialsSection(config, env, verifiedCredentialModelRefs = [], staticTriggerRefs = []) {
|
|
867
535
|
const refs = [
|
|
868
536
|
{ label: "Primary", ref: config.runtime.model },
|
|
869
537
|
...configuredRuntimeRouteChecks(config).slice(1).map((route) => ({
|
|
@@ -872,7 +540,7 @@ async function credentialsSection(config, env, cwd, liveness, verifiedCredential
|
|
|
872
540
|
})),
|
|
873
541
|
...staticTriggerRefs,
|
|
874
542
|
];
|
|
875
|
-
if (config.memory?.llm
|
|
543
|
+
if (config.memory?.llm?.provider === "agent-host") {
|
|
876
544
|
try {
|
|
877
545
|
refs.push({ label: "Memory LLM", ref: parseMonoRuntimeModelReference(config.memory.llm.model) });
|
|
878
546
|
}
|
|
@@ -880,9 +548,7 @@ async function credentialsSection(config, env, cwd, liveness, verifiedCredential
|
|
|
880
548
|
// A malformed memory model reference is surfaced by the memory/runtime shape checks.
|
|
881
549
|
}
|
|
882
550
|
}
|
|
883
|
-
const authenticatedRefs = refs
|
|
884
|
-
|| (r.ref.sdk === "opencode" && typeof r.ref.provider === "string")
|
|
885
|
-
|| isSdkAuthName(r.ref.sdk));
|
|
551
|
+
const authenticatedRefs = refs;
|
|
886
552
|
if (authenticatedRefs.length === 0) {
|
|
887
553
|
return {
|
|
888
554
|
id: "credentials",
|
|
@@ -895,102 +561,19 @@ async function credentialsSection(config, env, cwd, liveness, verifiedCredential
|
|
|
895
561
|
let status = "ok";
|
|
896
562
|
const verified = new Set(verifiedCredentialModelRefs);
|
|
897
563
|
for (const { label, ref } of authenticatedRefs) {
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
564
|
+
// The set holds whole canonical references, so the LOOKUP key stays whole;
|
|
565
|
+
// only the printed form is bounded.
|
|
566
|
+
if (verified.has(referenceOf(ref))) {
|
|
567
|
+
details.push(`${label} ${displayReferenceOf(ref)}: credentials verified by a successful live model check.`);
|
|
901
568
|
}
|
|
902
569
|
}
|
|
903
570
|
const unverifiedRefs = authenticatedRefs.filter(({ ref }) => !verified.has(referenceOf(ref)));
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
const sdkRefs = unverifiedRefs.filter((r) => isSdkAuthName(r.ref.sdk));
|
|
907
|
-
if (piRefs.length > 0) {
|
|
908
|
-
const piStatus = await appendPiCredentialDetails(config, piRefs, details, env);
|
|
571
|
+
if (unverifiedRefs.length > 0) {
|
|
572
|
+
const piStatus = await appendPiCredentialDetails(config, unverifiedRefs, details, env);
|
|
909
573
|
if (piStatus === "waiting") {
|
|
910
574
|
status = "waiting";
|
|
911
575
|
}
|
|
912
576
|
}
|
|
913
|
-
const openCodeInspection = openCodeRefs.length > 0
|
|
914
|
-
? inspectOpenCodeCredentialProviders(env)
|
|
915
|
-
: undefined;
|
|
916
|
-
const openCodeVersion = liveness && openCodeRefs.length > 0
|
|
917
|
-
? checkOpenCodeVersion(env, cwd, sdkAuthStatusExecFile)
|
|
918
|
-
: undefined;
|
|
919
|
-
const sdkAuthStatuses = new Map();
|
|
920
|
-
const externalLoginStatus = (sdk) => {
|
|
921
|
-
const cached = sdkAuthStatuses.get(sdk);
|
|
922
|
-
if (cached !== undefined) {
|
|
923
|
-
return cached;
|
|
924
|
-
}
|
|
925
|
-
const pending = checkSdkExternalLoginStatus(sdk, env, cwd, sdkAuthStatusExecFile);
|
|
926
|
-
sdkAuthStatuses.set(sdk, pending);
|
|
927
|
-
return pending;
|
|
928
|
-
};
|
|
929
|
-
// Start each unique local status check before awaiting details so two SDKs
|
|
930
|
-
// cost one bounded timeout window rather than running serially.
|
|
931
|
-
if (liveness) {
|
|
932
|
-
for (const { ref } of sdkRefs) {
|
|
933
|
-
const scheme = SDK_AUTH_SCHEMES[ref.sdk];
|
|
934
|
-
if (scheme !== undefined && firstPresentEnvKey(env, scheme.envKeys) === undefined) {
|
|
935
|
-
void externalLoginStatus(ref.sdk);
|
|
936
|
-
}
|
|
937
|
-
}
|
|
938
|
-
}
|
|
939
|
-
for (const { label, ref } of sdkRefs) {
|
|
940
|
-
const refStr = referenceOf(ref);
|
|
941
|
-
const scheme = SDK_AUTH_SCHEMES[ref.sdk];
|
|
942
|
-
if (scheme === undefined) {
|
|
943
|
-
continue;
|
|
944
|
-
}
|
|
945
|
-
const present = firstPresentEnvKey(env, scheme.envKeys);
|
|
946
|
-
if (present !== undefined) {
|
|
947
|
-
details.push(`${label} ${refStr}: SDK credential present in the resolved env (${present}); credential detected, live model verification is still pending.`);
|
|
948
|
-
continue;
|
|
949
|
-
}
|
|
950
|
-
if (liveness && await externalLoginStatus(ref.sdk)) {
|
|
951
|
-
details.push(`${label} ${refStr}: external sign-in detected by read-only \`${scheme.statusCommand}\`; ` +
|
|
952
|
-
"credentials are not verified until a live model turn succeeds.");
|
|
953
|
-
continue;
|
|
954
|
-
}
|
|
955
|
-
status = "waiting";
|
|
956
|
-
details.push(`[WARN] ${label} ${refStr}: no SDK credential in the resolved env (checked ${scheme.envKeys.join(", ")}). ` +
|
|
957
|
-
(liveness
|
|
958
|
-
? `External login was not verified by \`${scheme.statusCommand}\`; `
|
|
959
|
-
: `If you authenticated via ${scheme.loginDetail} this is fine and can't be verified during static validation; `) +
|
|
960
|
-
`otherwise ${scheme.failureHint} — set ${scheme.envKeys[0]} or run \`${scheme.loginCommand}\`.`);
|
|
961
|
-
}
|
|
962
|
-
if (openCodeRefs.length > 0) {
|
|
963
|
-
const inspection = openCodeInspection === undefined ? undefined : await openCodeInspection;
|
|
964
|
-
const supportedVersion = openCodeVersion === undefined ? false : await openCodeVersion;
|
|
965
|
-
for (const { label, ref } of openCodeRefs) {
|
|
966
|
-
const refStr = referenceOf(ref);
|
|
967
|
-
const provider = ref.provider;
|
|
968
|
-
const credentialPresent = inspection?.status === "ok" && inspection.providers.has(provider);
|
|
969
|
-
if (credentialPresent && liveness && supportedVersion) {
|
|
970
|
-
details.push(`${label} ${refStr}: provider \`${provider}\` credential present in the standard OpenCode auth store; stable OpenCode CLI >=1.15.0 detected without a model turn. Credential detected; live model verification is still pending.`);
|
|
971
|
-
}
|
|
972
|
-
else {
|
|
973
|
-
status = "waiting";
|
|
974
|
-
const warning = inspection?.status === "migration_required"
|
|
975
|
-
? "the native OpenCode database migration marker is missing or invalid; run `opencode db migrate --pure` once"
|
|
976
|
-
: inspection?.status === "inline_auth_unsupported"
|
|
977
|
-
? "OPENCODE_AUTH_CONTENT is unsupported for direct runs; persist credentials with `opencode auth login` and unset it"
|
|
978
|
-
: inspection?.status === "auth_invalid"
|
|
979
|
-
? "the standard OpenCode auth.json is malformed or contains an unsupported credential entry"
|
|
980
|
-
: credentialPresent && !liveness
|
|
981
|
-
? "credentials and migration marker are present, but the required stable OpenCode CLI >=1.15.0 is unverified during static validation"
|
|
982
|
-
: credentialPresent && !supportedVersion
|
|
983
|
-
? "credentials are present, but stable OpenCode CLI >=1.15.0 could not be verified"
|
|
984
|
-
: inspection?.status === "ok"
|
|
985
|
-
? `no exact credential entry exists for provider \`${provider}\`; run \`opencode auth login\` for that provider`
|
|
986
|
-
: "the standard OpenCode auth.json is missing; run `opencode auth login`";
|
|
987
|
-
const safetyNote = liveness
|
|
988
|
-
? "No model turn or mutation-capable OpenCode command was run."
|
|
989
|
-
: "No OpenCode process was launched.";
|
|
990
|
-
details.push(`[WARN] ${label} ${refStr}: ${warning}. ${safetyNote}`);
|
|
991
|
-
}
|
|
992
|
-
}
|
|
993
|
-
}
|
|
994
577
|
return { id: "credentials", label: "Provider credentials", status, details };
|
|
995
578
|
}
|
|
996
579
|
/**
|
|
@@ -1014,34 +597,38 @@ async function appendPiCredentialDetails(config, piRefs, details, env) {
|
|
|
1014
597
|
}
|
|
1015
598
|
for (const { label, ref } of piRefs) {
|
|
1016
599
|
const provider = ref.provider;
|
|
1017
|
-
|
|
1018
|
-
|
|
600
|
+
// Display-only from here down: `provider` is still the lookup key into the
|
|
601
|
+
// auth store and providers.local, `providerLabel` and `refStr` are what the
|
|
602
|
+
// detail lines quote back at the operator.
|
|
603
|
+
const providerLabel = displayText(provider);
|
|
604
|
+
const refStr = displayReferenceOf(ref);
|
|
605
|
+
const loginCommand = piAuthRecoveryCommand(providerLabel, authPath);
|
|
1019
606
|
const localProvider = localProviders.get(provider);
|
|
1020
607
|
if (localProvider !== undefined) {
|
|
1021
608
|
if (localProvider.enabled === false) {
|
|
1022
609
|
status = "waiting";
|
|
1023
|
-
details.push(`[WARN] ${label} ${refStr}: provider \`${
|
|
610
|
+
details.push(`[WARN] ${label} ${refStr}: provider \`${providerLabel}\` is configured in providers.local but disabled (\`enabled: false\`); the runtime will throw \`provider disabled: ${providerLabel}\` on the first turn. Set \`enabled: true\` on that providers.local entry.`);
|
|
1024
611
|
}
|
|
1025
612
|
else if (localProvider.apiKey !== undefined) {
|
|
1026
|
-
details.push(`${label} ${refStr}: provider \`${
|
|
613
|
+
details.push(`${label} ${refStr}: provider \`${providerLabel}\` configured via config providers.local (API key configured); credential detected, live model verification is still pending.`);
|
|
1027
614
|
}
|
|
1028
615
|
else if (localProvider.apiKeyEnv !== undefined) {
|
|
1029
616
|
if (hasNonEmptyCredentialValue(env[localProvider.apiKeyEnv])) {
|
|
1030
|
-
details.push(`${label} ${refStr}: provider \`${
|
|
617
|
+
details.push(`${label} ${refStr}: provider \`${providerLabel}\` configured via config providers.local with ${localProvider.apiKeyEnv} present in the resolved environment; credential detected, live model verification is still pending.`);
|
|
1031
618
|
}
|
|
1032
619
|
else {
|
|
1033
620
|
status = "waiting";
|
|
1034
|
-
details.push(`[WARN] ${label} ${refStr}: provider \`${
|
|
621
|
+
details.push(`[WARN] ${label} ${refStr}: provider \`${providerLabel}\` declares apiKeyEnv \`${localProvider.apiKeyEnv}\`, but the resolved environment has no non-empty value and no inline apiKey fallback. Set ${localProvider.apiKeyEnv} before starting.`);
|
|
1035
622
|
}
|
|
1036
623
|
}
|
|
1037
624
|
else {
|
|
1038
|
-
details.push(`${label} ${refStr}: provider \`${
|
|
625
|
+
details.push(`${label} ${refStr}: provider \`${providerLabel}\` configured via config providers.local (keyless local provider; no API key declared).`);
|
|
1039
626
|
}
|
|
1040
627
|
continue;
|
|
1041
628
|
}
|
|
1042
629
|
const apiKeyEnv = PI_API_KEY_ENV_BY_PROVIDER[provider];
|
|
1043
630
|
if (apiKeyEnv !== undefined && hasNonEmptyCredentialValue(env[apiKeyEnv])) {
|
|
1044
|
-
details.push(`${label} ${refStr}: Pi API-key credential for \`${
|
|
631
|
+
details.push(`${label} ${refStr}: Pi API-key credential for \`${providerLabel}\` present in the resolved environment (${apiKeyEnv}); credential detected, live model verification is still pending.`);
|
|
1045
632
|
continue;
|
|
1046
633
|
}
|
|
1047
634
|
if (authInspection?.status === "unsafe") {
|
|
@@ -1056,37 +643,37 @@ async function appendPiCredentialDetails(config, piRefs, details, env) {
|
|
|
1056
643
|
if (entry === undefined) {
|
|
1057
644
|
status = "waiting";
|
|
1058
645
|
details.push(apiKeyEnv === undefined
|
|
1059
|
-
? `[WARN] ${label} ${refStr}: no Pi credentials found for provider \`${
|
|
1060
|
-
: `[WARN] ${label} ${refStr}: no Pi API key credentials found for provider \`${
|
|
646
|
+
? `[WARN] ${label} ${refStr}: no Pi credentials found for provider \`${providerLabel}\` in the auth store. Authenticate it with \`${loginCommand}\`, or set providers.piAuthPath.`
|
|
647
|
+
: `[WARN] ${label} ${refStr}: no Pi API key credentials found for provider \`${providerLabel}\` in the auth store or resolved environment. Run \`${loginCommand}\`, or set ${apiKeyEnv}.`);
|
|
1061
648
|
continue;
|
|
1062
649
|
}
|
|
1063
650
|
const isOAuth = entry.type === "oauth";
|
|
1064
651
|
const isApiKey = entry.type === "api_key";
|
|
1065
652
|
if (isApiKey && !hasNonEmptyCredentialValue(entry.key)) {
|
|
1066
653
|
status = "waiting";
|
|
1067
|
-
details.push(`[WARN] ${label} ${refStr}: stored API-key credential for \`${
|
|
654
|
+
details.push(`[WARN] ${label} ${refStr}: stored API-key credential for \`${providerLabel}\` has no usable key. Run \`${loginCommand}\`; no secret value was displayed.`);
|
|
1068
655
|
continue;
|
|
1069
656
|
}
|
|
1070
657
|
if (isOAuth && !hasNonEmptyCredentialValue(entry.access) && !hasNonEmptyCredentialValue(entry.refresh)) {
|
|
1071
658
|
status = "waiting";
|
|
1072
|
-
details.push(`[WARN] ${label} ${refStr}: stored OAuth credential for \`${
|
|
659
|
+
details.push(`[WARN] ${label} ${refStr}: stored OAuth credential for \`${providerLabel}\` has no usable access or refresh token. Re-authenticate with \`${loginCommand}\`; no secret value was displayed.`);
|
|
1073
660
|
continue;
|
|
1074
661
|
}
|
|
1075
662
|
if (!isOAuth && !isApiKey) {
|
|
1076
663
|
status = "waiting";
|
|
1077
|
-
details.push(`[WARN] ${label} ${refStr}: stored credential for \`${
|
|
664
|
+
details.push(`[WARN] ${label} ${refStr}: stored credential for \`${providerLabel}\` has an unsupported or missing type. Re-authenticate with \`${loginCommand}\`; no secret value was displayed.`);
|
|
1078
665
|
continue;
|
|
1079
666
|
}
|
|
1080
667
|
const expired = typeof entry.expires === "number" && entry.expires < now;
|
|
1081
668
|
const whenNote = typeof entry.expires === "number" ? ` ${new Date(entry.expires).toISOString()}` : "";
|
|
1082
669
|
if (isOAuth && expired) {
|
|
1083
670
|
status = "waiting";
|
|
1084
|
-
details.push(`[WARN] ${label} ${refStr}: OAuth token for \`${
|
|
671
|
+
details.push(`[WARN] ${label} ${refStr}: OAuth token for \`${providerLabel}\` expired${whenNote} — the runtime may auto-refresh, but this credential is not ready until a request succeeds; if runs fail with "No API key for provider: ${providerLabel}" re-authenticate with \`${loginCommand}\`.`);
|
|
1085
672
|
continue;
|
|
1086
673
|
}
|
|
1087
674
|
details.push(isOAuth
|
|
1088
|
-
? `${label} ${refStr}: OAuth credentials for \`${
|
|
1089
|
-
: `${label} ${refStr}: API key credentials for \`${
|
|
675
|
+
? `${label} ${refStr}: OAuth credentials for \`${providerLabel}\` present (token valid${whenNote}); credential detected, live model verification is still pending.`
|
|
676
|
+
: `${label} ${refStr}: API key credentials for \`${providerLabel}\` present; credential detected, live model verification is still pending.`);
|
|
1090
677
|
}
|
|
1091
678
|
return status;
|
|
1092
679
|
}
|
|
@@ -1248,12 +835,12 @@ async function memorySection(config, cwd, env, liveness, allowFilesystemWrites,
|
|
|
1248
835
|
const resolutionIssue = piModelResolutionIssue(config, model);
|
|
1249
836
|
if (resolutionIssue !== undefined) {
|
|
1250
837
|
status = "error";
|
|
1251
|
-
details.push(`Agent-host memory LLM ${
|
|
838
|
+
details.push(`Agent-host memory LLM ${displayReferenceOf(model)}: ${resolutionIssue}.`);
|
|
1252
839
|
}
|
|
1253
840
|
}
|
|
1254
841
|
catch (error) {
|
|
1255
842
|
status = "error";
|
|
1256
|
-
details.push(`Agent-host memory LLM ${config.memory.llm.model}: ${
|
|
843
|
+
details.push(`Agent-host memory LLM ${displayText(config.memory.llm.model)}: ${displayReason(error)}.`);
|
|
1257
844
|
}
|
|
1258
845
|
}
|
|
1259
846
|
}
|
|
@@ -1573,12 +1160,12 @@ async function localEmbeddingLivenessWarnings(embeddings, apiKey) {
|
|
|
1573
1160
|
}
|
|
1574
1161
|
if (provider === "ollama" && /HTTP 404/u.test(reason)) {
|
|
1575
1162
|
return [
|
|
1576
|
-
`[WARN] Ollama embedding model ${embeddings.model} could not be proved at ${endpoint} (${reason}); ` +
|
|
1577
|
-
`run \`ollama pull ${embeddings.model}\` and verify its embedding capability.`,
|
|
1163
|
+
`[WARN] Ollama embedding model ${displayText(embeddings.model)} could not be proved at ${endpoint} (${reason}); ` +
|
|
1164
|
+
`run \`ollama pull ${displayText(embeddings.model)}\` and verify its embedding capability.`,
|
|
1578
1165
|
];
|
|
1579
1166
|
}
|
|
1580
1167
|
return [
|
|
1581
|
-
`[WARN] ${label} embedding readiness failed for ${embeddings.model} at ${endpoint} (${reason}). ` +
|
|
1168
|
+
`[WARN] ${label} embedding readiness failed for ${displayText(embeddings.model)} at ${endpoint} (${reason}). ` +
|
|
1582
1169
|
"Verify the selected model, authentication, and configured dimension.",
|
|
1583
1170
|
];
|
|
1584
1171
|
}
|
|
@@ -1620,8 +1207,8 @@ function memoryLlmLabel(llm) {
|
|
|
1620
1207
|
return "none";
|
|
1621
1208
|
}
|
|
1622
1209
|
return llm.provider === "ollama"
|
|
1623
|
-
? `ollama:${llm.model}`
|
|
1624
|
-
: `agent-host:${llm.model
|
|
1210
|
+
? `ollama:${displayText(llm.model)}`
|
|
1211
|
+
: `agent-host:${displayText(llm.model)}`;
|
|
1625
1212
|
}
|
|
1626
1213
|
async function toolsSection(config, input) {
|
|
1627
1214
|
const details = [];
|
|
@@ -1650,6 +1237,32 @@ async function toolsSection(config, input) {
|
|
|
1650
1237
|
details.push(`Allowed tools: ${allowedTools.join(", ")}.`);
|
|
1651
1238
|
let mcpNoteAdded = false;
|
|
1652
1239
|
for (const name of allowedTools) {
|
|
1240
|
+
if (isRememberToolPolicyName(name)) {
|
|
1241
|
+
// Reconcile with the runtime's own resolution, which honours the
|
|
1242
|
+
// `mcp__mono-agent-memory-write__*` spellings and deny-wins. This check
|
|
1243
|
+
// must precede the generic MCP branch below.
|
|
1244
|
+
if (!isRememberToolAllowed(config.tools)) {
|
|
1245
|
+
status = "waiting";
|
|
1246
|
+
details.push(`${name} is listed but tool policy still resolves to denied (deny wins, including the mcp__mono-agent-memory-write__* spellings) - the tool will not be offered.`);
|
|
1247
|
+
continue;
|
|
1248
|
+
}
|
|
1249
|
+
// Unlike MemoryRecall, Remember IS allowlist-gated, so listing it is
|
|
1250
|
+
// correct and required under a restrictive policy. It still needs a
|
|
1251
|
+
// configured memory block with the write surface left enabled.
|
|
1252
|
+
if (config.memory === undefined) {
|
|
1253
|
+
status = "waiting";
|
|
1254
|
+
details.push(`${name} is in allowedTools but no memory block is configured - durable writes will not work. Configure memory (or remove this entry).`);
|
|
1255
|
+
}
|
|
1256
|
+
else if (config.memory.rememberTool?.enabled === false) {
|
|
1257
|
+
status = "waiting";
|
|
1258
|
+
details.push(`${name} is in allowedTools but memory.rememberTool.enabled is off - durable writes will not work. Enable memory.rememberTool (or remove this entry).`);
|
|
1259
|
+
}
|
|
1260
|
+
else if (config.memory.backend === "supermemory") {
|
|
1261
|
+
status = "waiting";
|
|
1262
|
+
details.push(`${name} is in allowedTools but the supermemory backend exposes no durable write surface - the tool will not be offered.`);
|
|
1263
|
+
}
|
|
1264
|
+
continue;
|
|
1265
|
+
}
|
|
1653
1266
|
if (isMcpToolName(name)) {
|
|
1654
1267
|
// MCP tool names are owned by their servers; we cannot verify them offline.
|
|
1655
1268
|
if (!mcpNoteAdded) {
|
|
@@ -1685,45 +1298,6 @@ async function toolsSection(config, input) {
|
|
|
1685
1298
|
// Under allow-all the disallow list is already folded into the "except" clause above.
|
|
1686
1299
|
details.push(`Disallowed tools: ${config.tools.disallowedTools.join(", ")}.`);
|
|
1687
1300
|
}
|
|
1688
|
-
const runtimeModels = configuredRuntimeModels(config.runtime);
|
|
1689
|
-
const directCodexModels = runtimeModels
|
|
1690
|
-
.filter((model) => model.sdk === "codex")
|
|
1691
|
-
.map(referenceOf);
|
|
1692
|
-
const directOpenCodeModels = runtimeModels
|
|
1693
|
-
.filter((model) => model.sdk === "opencode")
|
|
1694
|
-
.map(referenceOf);
|
|
1695
|
-
const claudeCliModels = [
|
|
1696
|
-
{ model: config.runtime.model, executionMode: config.runtime.executionMode },
|
|
1697
|
-
...configuredRuntimeFallbackModels(config.runtime).map((model) => ({
|
|
1698
|
-
model,
|
|
1699
|
-
executionMode: defaultExecutionModeForModel(model),
|
|
1700
|
-
})),
|
|
1701
|
-
]
|
|
1702
|
-
.filter(({ model, executionMode }) => model.sdk === "claude" && executionMode === "cli")
|
|
1703
|
-
.map(({ model }) => referenceOf(model));
|
|
1704
|
-
const allowAllOnly = hasAllowAllOnlyToolPolicy(config.tools);
|
|
1705
|
-
const dedicatedNoToolsProbe = input.codexNoToolsProbe === true
|
|
1706
|
-
&& allowedTools.length === 0
|
|
1707
|
-
&& config.tools.disallowedTools.length === 0;
|
|
1708
|
-
if (directCodexModels.length > 0
|
|
1709
|
-
&& !dedicatedNoToolsProbe
|
|
1710
|
-
&& !allowAllOnly) {
|
|
1711
|
-
status = "error";
|
|
1712
|
-
details.push(`Direct Codex model${directCodexModels.length === 1 ? "" : "s"} ${directCodexModels.join(", ")} cannot enforce ` +
|
|
1713
|
-
"tools.allowedTools/tools.disallowedTools. Use allow-all-only (allowedTools omitted or containing \"*\", with no disallowedTools), " +
|
|
1714
|
-
"or select a runtime that supports restrictive tool policies.");
|
|
1715
|
-
}
|
|
1716
|
-
if (directOpenCodeModels.length > 0 && !allowAllOnly) {
|
|
1717
|
-
status = "error";
|
|
1718
|
-
details.push(`Direct OpenCode model${directOpenCodeModels.length === 1 ? "" : "s"} ${directOpenCodeModels.join(", ")} cannot enforce ` +
|
|
1719
|
-
"tools.allowedTools/tools.disallowedTools. Use allow-all-only (allowedTools omitted or containing \"*\", with no disallowedTools), " +
|
|
1720
|
-
"or use a Pi runtime (including pi:opencode-go:*).");
|
|
1721
|
-
}
|
|
1722
|
-
if (claudeCliModels.length > 0 && allowedTools.length === 0) {
|
|
1723
|
-
status = "error";
|
|
1724
|
-
details.push(`Claude CLI model${claudeCliModels.length === 1 ? "" : "s"} ${claudeCliModels.join(", ")} cannot enforce an empty ` +
|
|
1725
|
-
"tools.allowedTools list because omitting --tools enables Claude Code's default tool set. Use Claude SDK for a chat-only agent, or configure a non-empty enforceable tool list.");
|
|
1726
|
-
}
|
|
1727
1301
|
let configuredMcpServerNames = [];
|
|
1728
1302
|
let configuredMcpServers = {};
|
|
1729
1303
|
if (config.tools.mcpConfigPath !== undefined) {
|
|
@@ -1781,7 +1355,6 @@ async function toolsSection(config, input) {
|
|
|
1781
1355
|
const adapterSendTools = await resolveAdapterSendToolsSettings(input, {
|
|
1782
1356
|
allowedTools: config.tools.allowedTools,
|
|
1783
1357
|
disallowedTools: config.tools.disallowedTools,
|
|
1784
|
-
suppressInteractionTools: directOpenCodeModels.length > 0,
|
|
1785
1358
|
});
|
|
1786
1359
|
if (adapterSendTools === undefined) {
|
|
1787
1360
|
details.push("No adapter-derived send tools enabled.");
|
|
@@ -1789,19 +1362,13 @@ async function toolsSection(config, input) {
|
|
|
1789
1362
|
else {
|
|
1790
1363
|
details.push(`Adapter send tools: ${adapterSendToolNames(adapterSendTools).join(", ")}.`);
|
|
1791
1364
|
}
|
|
1792
|
-
const blockedAdapterEndpoints = await adapterSendToolNetworkPolicyWarnings(config, input, adapterSendTools
|
|
1365
|
+
const blockedAdapterEndpoints = await adapterSendToolNetworkPolicyWarnings(config, input, adapterSendTools);
|
|
1793
1366
|
if (blockedAdapterEndpoints.length > 0) {
|
|
1794
1367
|
if (status !== "error") {
|
|
1795
1368
|
status = "waiting";
|
|
1796
1369
|
}
|
|
1797
1370
|
details.push(...blockedAdapterEndpoints);
|
|
1798
1371
|
}
|
|
1799
|
-
const effectiveMcpSources = effectiveMcpRuntimeSources(config, configuredMcpServerNames, adapterSendTools === undefined ? [] : adapterSendToolNames(adapterSendTools));
|
|
1800
|
-
if (directOpenCodeModels.length > 0 && effectiveMcpSources.length > 0) {
|
|
1801
|
-
status = "error";
|
|
1802
|
-
details.push(`Direct OpenCode model${directOpenCodeModels.length === 1 ? "" : "s"} ${directOpenCodeModels.join(", ")} cannot safely consume ` +
|
|
1803
|
-
`MCP runtime options from ${effectiveMcpSources.join("; ")}. Disable those MCP sources or use a Pi runtime (including pi:opencode-go:*).`);
|
|
1804
|
-
}
|
|
1805
1372
|
// Subagents: the Agent tool is registered only when BOTH the capability is
|
|
1806
1373
|
// enabled and the tool is allowed, so surface either half being missing.
|
|
1807
1374
|
const subagents = config.subagents;
|
|
@@ -1824,14 +1391,6 @@ async function toolsSection(config, input) {
|
|
|
1824
1391
|
}
|
|
1825
1392
|
else {
|
|
1826
1393
|
details.push("Subagents inherit the parent's skill index and the ReadSkill tool.");
|
|
1827
|
-
// The parent's own chain is capability-checked elsewhere; a profile that
|
|
1828
|
-
// pins its own model is not, so a direct-OpenCode profile would silently
|
|
1829
|
-
// run without the index it was told it had.
|
|
1830
|
-
for (const definition of subagents.definitions ?? []) {
|
|
1831
|
-
if (definition.model?.sdk === "opencode") {
|
|
1832
|
-
details.push(`Subagent "${definition.name}" is pinned to a direct OpenCode model, which cannot use ReadSkill; it runs without the skill index.`);
|
|
1833
|
-
}
|
|
1834
|
-
}
|
|
1835
1394
|
}
|
|
1836
1395
|
for (const tool of subagents.inline?.allowedTools ?? []) {
|
|
1837
1396
|
if (!isKnownToolName(tool) && !isMcpToolName(tool)) {
|
|
@@ -2001,14 +1560,14 @@ async function probeSearxngEndpoint(endpoint) {
|
|
|
2001
1560
|
}
|
|
2002
1561
|
}
|
|
2003
1562
|
async function readAgentBrowserVersion(command, input) {
|
|
2004
|
-
const run = input.
|
|
1563
|
+
const run = input.statusExecFile ?? defaultDoctorStatusExecFile;
|
|
2005
1564
|
try {
|
|
2006
1565
|
const { stdout } = await run(command, ["--version"], {
|
|
2007
1566
|
cwd: input.cwd,
|
|
2008
1567
|
env: Object.fromEntries(["PATH", "LANG", "LC_ALL", "LC_CTYPE", "SHELL", "TMPDIR", "TMP", "TEMP"]
|
|
2009
1568
|
.flatMap((key) => typeof input.env[key] === "string" ? [[key, input.env[key]]] : [])),
|
|
2010
|
-
timeout:
|
|
2011
|
-
maxBuffer:
|
|
1569
|
+
timeout: DOCTOR_STATUS_TIMEOUT_MS,
|
|
1570
|
+
maxBuffer: DOCTOR_STATUS_MAX_BUFFER_BYTES,
|
|
2012
1571
|
encoding: "utf8",
|
|
2013
1572
|
});
|
|
2014
1573
|
const match = /(?:^|\s)v?(\d+)\.(\d+)\.(\d+)(?:\s|$)/u.exec(stdout.trim());
|
|
@@ -2166,7 +1725,7 @@ async function processJobsSection(config, input) {
|
|
|
2166
1725
|
`Owner-only local state: ${settings.stateDir}.`,
|
|
2167
1726
|
`Concurrency: ${String(settings.maxConcurrent)} global, ${String(settings.maxActivePerConversation)} per conversation, ${String(settings.maxQueued)} queued.`,
|
|
2168
1727
|
`Caps: runtime=${String(settings.maxRuntimeMs)}ms, queue-age=${String(settings.maxQueueAgeMs)}ms, output=${String(settings.maxOutputBytes)} bytes, chain-depth=${String(settings.maxChainDepth)}.`,
|
|
2169
|
-
`Runtime availability: Pi-native Exec/Bash only; configured primary is ${config.runtime.model.
|
|
1728
|
+
`Runtime availability: Pi-native Exec/Bash only; configured primary provider is ${displayText(config.runtime.model.provider)}.`,
|
|
2170
1729
|
];
|
|
2171
1730
|
const inspection = await inspectProcessJobState(input.cwd, settings.stateDir);
|
|
2172
1731
|
return {
|
|
@@ -3205,7 +2764,7 @@ function formatContinuationHost(host) {
|
|
|
3205
2764
|
* needs the remote adapter API (or the app-owned interaction bridge) admitted
|
|
3206
2765
|
* by sandbox.network.
|
|
3207
2766
|
*/
|
|
3208
|
-
async function adapterSendToolNetworkPolicyWarnings(config, input, settings
|
|
2767
|
+
async function adapterSendToolNetworkPolicyWarnings(config, input, settings) {
|
|
3209
2768
|
if (config.sandbox === undefined || config.sandbox.mode !== "native") {
|
|
3210
2769
|
return [];
|
|
3211
2770
|
}
|
|
@@ -3225,7 +2784,7 @@ async function adapterSendToolNetworkPolicyWarnings(config, input, settings, sup
|
|
|
3225
2784
|
url: settings.telegram.apiRoot ?? "https://api.telegram.org",
|
|
3226
2785
|
});
|
|
3227
2786
|
}
|
|
3228
|
-
const askUserAllowed =
|
|
2787
|
+
const askUserAllowed = isAdapterSendToolAllowed("AskUser", {
|
|
3229
2788
|
allowedTools: config.tools.allowedTools,
|
|
3230
2789
|
disallowedTools: config.tools.disallowedTools,
|
|
3231
2790
|
});
|
|
@@ -3273,100 +2832,7 @@ function endpointHost(url) {
|
|
|
3273
2832
|
return url;
|
|
3274
2833
|
}
|
|
3275
2834
|
}
|
|
3276
|
-
function effectiveMcpRuntimeSources(config, configuredMcpServerNames, adapterToolNames) {
|
|
3277
|
-
const sources = [];
|
|
3278
|
-
if (configuredMcpServerNames.length > 0) {
|
|
3279
|
-
sources.push(`tools.mcpConfigPath (${configuredMcpServerNames.join(", ")})`);
|
|
3280
|
-
}
|
|
3281
|
-
if (config.memory?.recallTool?.enabled === true) {
|
|
3282
|
-
sources.push("memory.recallTool");
|
|
3283
|
-
}
|
|
3284
|
-
if (config.memory?.backend === "supermemory"
|
|
3285
|
-
&& config.memory.supermemory?.exposeMcpServer === true
|
|
3286
|
-
&& config.memory.supermemory.apiKey !== undefined) {
|
|
3287
|
-
sources.push("memory.supermemory.exposeMcpServer");
|
|
3288
|
-
}
|
|
3289
|
-
if (adapterToolNames.length > 0) {
|
|
3290
|
-
sources.push(`adapter send tools (${adapterToolNames.join(", ")})`);
|
|
3291
|
-
}
|
|
3292
|
-
return sources;
|
|
3293
|
-
}
|
|
3294
2835
|
async function sandboxSection(config, engine) {
|
|
3295
|
-
const runtimeModels = configuredRuntimeModels(config.runtime);
|
|
3296
|
-
const directCodexRefs = runtimeModels
|
|
3297
|
-
.filter((model) => model.sdk === "codex")
|
|
3298
|
-
.map((model) => model.reference ?? `codex:${model.model}`);
|
|
3299
|
-
const claudeRefs = runtimeModels
|
|
3300
|
-
.filter((model) => model.sdk === "claude")
|
|
3301
|
-
.map((model) => model.reference ?? `claude:${model.model}`);
|
|
3302
|
-
const directOpenCodeRefs = runtimeModels
|
|
3303
|
-
.filter((model) => model.sdk === "opencode")
|
|
3304
|
-
.map((model) => model.reference ?? `opencode:${model.provider ?? "unknown"}:${model.model}`);
|
|
3305
|
-
const routeSafety = config.runtime.routeSafety ?? "uniform";
|
|
3306
|
-
if (routeSafety === "per-route-native") {
|
|
3307
|
-
const monoSandboxActive = config.sandbox !== undefined && config.sandbox.mode !== "off";
|
|
3308
|
-
const piRoutes = runtimeModels.filter((model) => model.sdk === "pi");
|
|
3309
|
-
const details = runtimeModels.map((model, index) => routeNativeSafetyDetail(model, index, config));
|
|
3310
|
-
let status = !monoSandboxActive && piRoutes.length > 0 ? "disabled" : "ok";
|
|
3311
|
-
if (monoSandboxActive && piRoutes.length > 0) {
|
|
3312
|
-
const state = await resolveSandboxEffectiveState({
|
|
3313
|
-
policy: config.sandbox,
|
|
3314
|
-
...(engine === undefined ? {} : { engine }),
|
|
3315
|
-
});
|
|
3316
|
-
const warning = sandboxEffectiveStateWarning(state);
|
|
3317
|
-
details.push(`Pi route SRT policy: mode ${config.sandbox.mode}, network ${config.sandbox.network.mode}, fallback ${config.sandbox.fallback}.`, describeSandboxEffectiveState(state), ...(warning === undefined ? [] : [warning]));
|
|
3318
|
-
if (warning !== undefined || state.effective === "blocked")
|
|
3319
|
-
status = "waiting";
|
|
3320
|
-
}
|
|
3321
|
-
else if (piRoutes.length > 0) {
|
|
3322
|
-
details.push("Pi route SRT policy: disabled; Bash and stdio MCP subprocesses run unsandboxed.");
|
|
3323
|
-
}
|
|
3324
|
-
else if (monoSandboxActive) {
|
|
3325
|
-
details.push("The configured mono-agent SRT policy has no Pi route to enforce it; provider-owned route contracts below apply instead.");
|
|
3326
|
-
status = "waiting";
|
|
3327
|
-
}
|
|
3328
|
-
if (monoSandboxActive && runtimeModels.some((model) => model.sdk !== "pi")) {
|
|
3329
|
-
details.push("[WARN] Per-route-native explicitly does not project mono-agent readableRoots, writableRoots, denyWrite, or network rules onto non-Pi routes; review each route contract before start.");
|
|
3330
|
-
status = "waiting";
|
|
3331
|
-
}
|
|
3332
|
-
if (runtimeModels.some((model) => model.sdk === "codex") && config.runtime.permissionMode === "bypassPermissions") {
|
|
3333
|
-
status = "waiting";
|
|
3334
|
-
}
|
|
3335
|
-
return { id: "sandbox", label: "Sandbox", status, details };
|
|
3336
|
-
}
|
|
3337
|
-
const incompatibleDetails = [];
|
|
3338
|
-
if (config.sandbox !== undefined && config.sandbox.mode !== "off" && directCodexRefs.length > 0) {
|
|
3339
|
-
const codexPosture = directCodexSandboxPosture(config.runtime.permissionMode);
|
|
3340
|
-
incompatibleDetails.push(`Native mono-agent sandbox policy cannot govern direct Codex runtime${directCodexRefs.length === 1 ? "" : "s"}: ${directCodexRefs.join(", ")}.`, `${codexPosture.detail} Remove the mono-agent sandbox block or use Pi when exact srt roots, denyWrite, or network policy are required.`);
|
|
3341
|
-
}
|
|
3342
|
-
if (config.sandbox !== undefined && config.sandbox.mode !== "off" && claudeRefs.length > 0) {
|
|
3343
|
-
incompatibleDetails.push(`Native mono-agent sandbox policy cannot govern Claude runtime${claudeRefs.length === 1 ? "" : "s"}: ${claudeRefs.join(", ")}.`, "Claude's provider-owned tool loop does not consume mono-agent sandboxPolicy. Set sandbox.mode to off, remove the sandbox block, or use a Pi runtime when mono-agent srt enforcement is required.");
|
|
3344
|
-
}
|
|
3345
|
-
if (config.sandbox !== undefined && config.sandbox.mode !== "off" && directOpenCodeRefs.length > 0) {
|
|
3346
|
-
incompatibleDetails.push(`Native mono-agent sandbox policy cannot govern direct OpenCode runtime${directOpenCodeRefs.length === 1 ? "" : "s"}: ${directOpenCodeRefs.join(", ")}.`, "Direct OpenCode's provider-owned tool loop does not consume mono-agent sandboxPolicy. Set sandbox.mode to off, remove the sandbox block, or use a pi:opencode-go:* runtime when mono-agent srt enforcement is required.");
|
|
3347
|
-
}
|
|
3348
|
-
if (incompatibleDetails.length > 0) {
|
|
3349
|
-
return {
|
|
3350
|
-
id: "sandbox",
|
|
3351
|
-
label: "Sandbox",
|
|
3352
|
-
status: "error",
|
|
3353
|
-
details: incompatibleDetails,
|
|
3354
|
-
};
|
|
3355
|
-
}
|
|
3356
|
-
if (directCodexRefs.length > 0 && (config.sandbox === undefined || config.sandbox.mode === "off")) {
|
|
3357
|
-
const posture = directCodexSandboxPosture(config.runtime.permissionMode);
|
|
3358
|
-
return {
|
|
3359
|
-
id: "sandbox",
|
|
3360
|
-
label: "Sandbox",
|
|
3361
|
-
status: posture.status,
|
|
3362
|
-
details: [
|
|
3363
|
-
posture.detail,
|
|
3364
|
-
config.sandbox === undefined
|
|
3365
|
-
? "No mono-agent native srt policy is configured."
|
|
3366
|
-
: "The mono-agent native srt policy is explicitly off; the Codex-native posture still applies.",
|
|
3367
|
-
],
|
|
3368
|
-
};
|
|
3369
|
-
}
|
|
3370
2836
|
if (config.sandbox === undefined) {
|
|
3371
2837
|
return { id: "sandbox", label: "Sandbox", status: "disabled", details: ["No sandbox policy configured."] };
|
|
3372
2838
|
}
|
|
@@ -3394,44 +2860,6 @@ async function sandboxSection(config, engine) {
|
|
|
3394
2860
|
details,
|
|
3395
2861
|
};
|
|
3396
2862
|
}
|
|
3397
|
-
function routeNativeSafetyDetail(model, index, config) {
|
|
3398
|
-
const label = index === 0 ? "Primary" : `Fallback ${index}`;
|
|
3399
|
-
const ref = referenceOf(model);
|
|
3400
|
-
if (model.sdk === "pi") {
|
|
3401
|
-
if (config.sandbox === undefined || config.sandbox.mode === "off") {
|
|
3402
|
-
return `${label} ${ref}: Pi-owned tools use mono-agent tool policy; SRT is disabled, so Bash and stdio MCP subprocesses run unsandboxed.`;
|
|
3403
|
-
}
|
|
3404
|
-
if (config.sandbox.fallback === "unsafe-host-process") {
|
|
3405
|
-
return `${label} ${ref}: Pi-owned tools use the configured mono-agent SRT policy; its explicit unsafe-host-process fallback can run subprocesses unsandboxed when SRT is unavailable.`;
|
|
3406
|
-
}
|
|
3407
|
-
return `${label} ${ref}: Pi-owned tools use the configured mono-agent SRT policy and fail closed when it is unavailable.`;
|
|
3408
|
-
}
|
|
3409
|
-
if (model.sdk === "claude") {
|
|
3410
|
-
return `${label} ${ref}: Claude provider-owned permissions apply; mono-agent SRT filesystem/network rules are not projected.`;
|
|
3411
|
-
}
|
|
3412
|
-
if (model.sdk === "codex") {
|
|
3413
|
-
return `${label} ${ref}: ${directCodexSandboxPosture(config.runtime.permissionMode).detail}`;
|
|
3414
|
-
}
|
|
3415
|
-
return `${label} ${ref}: OpenCode provider-owned execution with an effective allow-all tool policy applies; mono-agent SRT rules are not projected.`;
|
|
3416
|
-
}
|
|
3417
|
-
function directCodexSandboxPosture(permissionMode) {
|
|
3418
|
-
if (permissionMode === "bypassPermissions") {
|
|
3419
|
-
return {
|
|
3420
|
-
status: "waiting",
|
|
3421
|
-
detail: "[WARN] Direct Codex bypassPermissions uses native danger-full-access with no filesystem or network sandbox; unattended approval prompts are still disabled.",
|
|
3422
|
-
};
|
|
3423
|
-
}
|
|
3424
|
-
if (permissionMode === "plan") {
|
|
3425
|
-
return {
|
|
3426
|
-
status: "ok",
|
|
3427
|
-
detail: "Direct Codex plan mode uses its native read-only sandbox with network disabled; unattended escalation requests are denied.",
|
|
3428
|
-
};
|
|
3429
|
-
}
|
|
3430
|
-
return {
|
|
3431
|
-
status: "ok",
|
|
3432
|
-
detail: "Direct Codex default/acceptEdits mode uses its native workspace-write sandbox with network disabled; unattended escalation requests are denied.",
|
|
3433
|
-
};
|
|
3434
|
-
}
|
|
3435
2863
|
async function channelSection(driver, input) {
|
|
3436
2864
|
const id = `channel:${driver.id}`;
|
|
3437
2865
|
try {
|
|
@@ -3464,6 +2892,44 @@ async function channelSection(driver, input) {
|
|
|
3464
2892
|
function referenceOf(model) {
|
|
3465
2893
|
return modelReferenceKey(model);
|
|
3466
2894
|
}
|
|
2895
|
+
/**
|
|
2896
|
+
* Bounded, single-line rendering of operator-supplied text for a diagnostic.
|
|
2897
|
+
*
|
|
2898
|
+
* `referenceOf` is the canonical KEY -- it is compared against
|
|
2899
|
+
* `verifiedCredentialModelRefs`, which carries whole references -- so it must
|
|
2900
|
+
* stay unbounded. What gets PRINTED must not be. The parser deliberately has no
|
|
2901
|
+
* length ceiling any more (a reference cannot be truncated into validity, so a
|
|
2902
|
+
* ceiling there could only refuse a model that runs), which means acceptance
|
|
2903
|
+
* stopped being a length guarantee: a 500,000-byte reference parses today and
|
|
2904
|
+
* every one of these lines is echoed by `mono-agent validate`, the daemon log
|
|
2905
|
+
* and launchd's captured stdout. Escaping comes with the clamp, so a reference
|
|
2906
|
+
* carrying a newline also cannot forge a detail line that reads as doctor's own.
|
|
2907
|
+
*/
|
|
2908
|
+
function displayText(value) {
|
|
2909
|
+
return sanitizeModelReferenceText(value, MODEL_REFERENCE_ECHO_MAX_BYTES);
|
|
2910
|
+
}
|
|
2911
|
+
/** Bounded display form of a model reference. Never use it as a map or set key. */
|
|
2912
|
+
function displayReferenceOf(model) {
|
|
2913
|
+
return displayText(referenceOf(model));
|
|
2914
|
+
}
|
|
2915
|
+
/**
|
|
2916
|
+
* Bounded display form of a thrown reason. A reason gets the larger budget because it is one
|
|
2917
|
+
* fixed repair sentence plus at most one echo of the value.
|
|
2918
|
+
*
|
|
2919
|
+
* Unwrap one layer first, as `modelReferenceReason` in @mono-agent/config and `reasonOf` in
|
|
2920
|
+
* trigger-overrides already do: the adapter's `message` wraps a 32-byte generic prefix around
|
|
2921
|
+
* a reason ALREADY at the full reason budget, so clamping the wrapped form spends the budget
|
|
2922
|
+
* on the prefix and pays for it out of the tail -- which is where the kernel parser puts the
|
|
2923
|
+
* concrete repair whenever it quotes the operator's value first.
|
|
2924
|
+
*/
|
|
2925
|
+
function displayReason(error) {
|
|
2926
|
+
const reason = error instanceof RuntimeAdapterError && typeof error.details.reason === "string"
|
|
2927
|
+
? error.details.reason
|
|
2928
|
+
: error instanceof Error
|
|
2929
|
+
? error.message
|
|
2930
|
+
: String(error);
|
|
2931
|
+
return sanitizeModelReferenceText(reason, MODEL_REFERENCE_REASON_MAX_BYTES);
|
|
2932
|
+
}
|
|
3467
2933
|
async function pathExists(path) {
|
|
3468
2934
|
try {
|
|
3469
2935
|
await stat(path);
|