@mono-agent/agent-app 0.20.11 → 0.21.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 +260 -98
- package/dist/adapter-send-tools.d.ts +3 -2
- package/dist/adapter-send-tools.d.ts.map +1 -1
- package/dist/adapter-send-tools.js +15 -5
- package/dist/adapter-send-tools.js.map +1 -1
- package/dist/app-controller-channels.d.ts +6 -0
- package/dist/app-controller-channels.d.ts.map +1 -1
- package/dist/app-controller-channels.js +29 -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-maintenance.d.ts +3 -1
- package/dist/app-controller-maintenance.d.ts.map +1 -1
- package/dist/app-controller-maintenance.js +3 -0
- package/dist/app-controller-maintenance.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 +9 -17
- package/dist/app-controller-responder.d.ts.map +1 -1
- package/dist/app-controller-responder.js +54 -86
- package/dist/app-controller-responder.js.map +1 -1
- package/dist/app-controller-traceability.d.ts.map +1 -1
- package/dist/app-controller-traceability.js +3 -0
- package/dist/app-controller-traceability.js.map +1 -1
- package/dist/app-controller-types.d.ts +1 -0
- package/dist/app-controller-types.d.ts.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 +26 -10
- package/dist/app-controller.d.ts.map +1 -1
- package/dist/app-controller.js +39 -1
- package/dist/app-controller.js.map +1 -1
- package/dist/artifact-retention.js +11 -0
- package/dist/artifact-retention.js.map +1 -1
- package/dist/background.d.ts +0 -6
- package/dist/background.d.ts.map +1 -1
- package/dist/background.js +5 -58
- package/dist/background.js.map +1 -1
- package/dist/channel-drivers/slack.d.ts.map +1 -1
- package/dist/channel-drivers/slack.js +18 -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 +407 -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 +5 -8
- package/dist/cli-args.d.ts.map +1 -1
- package/dist/cli-args.js +33 -52
- package/dist/cli-args.js.map +1 -1
- package/dist/cli-background-command.d.ts.map +1 -1
- package/dist/cli-background-command.js +4 -0
- package/dist/cli-background-command.js.map +1 -1
- package/dist/cli-commands.d.ts.map +1 -1
- package/dist/cli-commands.js +24 -2
- 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 +58 -53
- 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 +31 -71
- package/dist/cli-init-command.js.map +1 -1
- package/dist/cli-install-skill-command.d.ts.map +1 -1
- package/dist/cli-install-skill-command.js +12 -3
- package/dist/cli-install-skill-command.js.map +1 -1
- package/dist/config-reference.d.ts +1 -0
- package/dist/config-reference.d.ts.map +1 -1
- package/dist/config-reference.js +217 -60
- package/dist/config-reference.js.map +1 -1
- package/dist/configured-agent.d.ts +27 -4
- package/dist/configured-agent.d.ts.map +1 -1
- package/dist/configured-agent.js +159 -112
- 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/cron-control-store.d.ts.map +1 -1
- package/dist/cron-control-store.js +4 -2
- package/dist/cron-control-store.js.map +1 -1
- package/dist/doctor.d.ts +7 -13
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +406 -770
- 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/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/interaction-bridge.d.ts +3 -3
- package/dist/interaction-bridge.d.ts.map +1 -1
- package/dist/interaction-bridge.js +27 -10
- package/dist/interaction-bridge.js.map +1 -1
- package/dist/launchd.d.ts +2 -0
- package/dist/launchd.d.ts.map +1 -1
- package/dist/launchd.js +3 -0
- package/dist/launchd.js.map +1 -1
- package/dist/local-tui-session.d.ts +14 -0
- package/dist/local-tui-session.d.ts.map +1 -0
- package/dist/local-tui-session.js +100 -0
- package/dist/local-tui-session.js.map +1 -0
- package/dist/mcp-apps.d.ts.map +1 -1
- package/dist/mcp-apps.js +3 -0
- package/dist/mcp-apps.js.map +1 -1
- package/dist/memory-journal.d.ts +53 -0
- package/dist/memory-journal.d.ts.map +1 -0
- package/dist/memory-journal.js +586 -0
- package/dist/memory-journal.js.map +1 -0
- package/dist/memory-recall.d.ts.map +1 -1
- package/dist/memory-recall.js +16 -2
- package/dist/memory-recall.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 +33 -0
- package/dist/memory-retrieval.d.ts.map +1 -1
- package/dist/memory-retrieval.js +43 -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 +4 -6
- 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 +7 -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 +217 -0
- package/dist/monitors-command.js.map +1 -0
- package/dist/monitors-config.d.ts +77 -0
- package/dist/monitors-config.d.ts.map +1 -0
- package/dist/monitors-config.js +150 -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 +194 -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 +1796 -0
- package/dist/monitors-service.js.map +1 -0
- package/dist/monitors-store.d.ts +93 -0
- package/dist/monitors-store.d.ts.map +1 -0
- package/dist/monitors-store.js +313 -0
- package/dist/monitors-store.js.map +1 -0
- package/dist/notify-destinations.d.ts +3 -2
- package/dist/notify-destinations.d.ts.map +1 -1
- package/dist/notify-destinations.js +6 -7
- package/dist/notify-destinations.js.map +1 -1
- package/dist/posted-reply-history.d.ts.map +1 -1
- package/dist/posted-reply-history.js +40 -3
- package/dist/posted-reply-history.js.map +1 -1
- package/dist/proactive-notify.d.ts +20 -1
- package/dist/proactive-notify.d.ts.map +1 -1
- package/dist/proactive-notify.js +56 -3
- package/dist/proactive-notify.js.map +1 -1
- package/dist/process-job-channel-routing.js +2 -1
- package/dist/process-job-channel-routing.js.map +1 -1
- package/dist/process-job-output-tail.d.ts +53 -0
- package/dist/process-job-output-tail.d.ts.map +1 -0
- package/dist/process-job-output-tail.js +257 -0
- package/dist/process-job-output-tail.js.map +1 -0
- package/dist/process-jobs-config.d.ts +1 -1
- package/dist/process-jobs-config.js +1 -1
- package/dist/process-jobs-config.js.map +1 -1
- package/dist/process-jobs-context.d.ts +11 -1
- package/dist/process-jobs-context.d.ts.map +1 -1
- package/dist/process-jobs-context.js +55 -11
- 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 +15 -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 +118 -172
- package/dist/process-jobs-service.js.map +1 -1
- package/dist/process-jobs-store.d.ts +3 -1
- package/dist/process-jobs-store.d.ts.map +1 -1
- package/dist/process-jobs-store.js +4 -1
- package/dist/process-jobs-store.js.map +1 -1
- package/dist/process-output-redaction.d.ts +58 -0
- package/dist/process-output-redaction.d.ts.map +1 -0
- package/dist/process-output-redaction.js +311 -0
- package/dist/process-output-redaction.js.map +1 -0
- package/dist/project-skills.d.ts +11 -5
- package/dist/project-skills.d.ts.map +1 -1
- package/dist/project-skills.js +88 -39
- package/dist/project-skills.js.map +1 -1
- package/dist/provider-auth-checks.d.ts +27 -0
- package/dist/provider-auth-checks.d.ts.map +1 -0
- package/dist/provider-auth-checks.js +349 -0
- package/dist/provider-auth-checks.js.map +1 -0
- package/dist/provider-auth-observations.d.ts +22 -0
- package/dist/provider-auth-observations.d.ts.map +1 -0
- package/dist/provider-auth-observations.js +139 -0
- package/dist/provider-auth-observations.js.map +1 -0
- package/dist/provider-auth-operator.d.ts +19 -0
- package/dist/provider-auth-operator.d.ts.map +1 -0
- package/dist/provider-auth-operator.js +560 -0
- package/dist/provider-auth-operator.js.map +1 -0
- package/dist/provider-auth-status.d.ts +20 -0
- package/dist/provider-auth-status.d.ts.map +1 -0
- package/dist/provider-auth-status.js +235 -0
- package/dist/provider-auth-status.js.map +1 -0
- package/dist/provider-model-catalog.d.ts +95 -0
- package/dist/provider-model-catalog.d.ts.map +1 -0
- package/dist/provider-model-catalog.js +550 -0
- package/dist/provider-model-catalog.js.map +1 -0
- package/dist/provider-setup.d.ts +26 -13
- package/dist/provider-setup.d.ts.map +1 -1
- package/dist/provider-setup.js +106 -130
- 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/reply-artifacts.d.ts +3 -1
- package/dist/reply-artifacts.d.ts.map +1 -1
- package/dist/reply-artifacts.js +45 -10
- package/dist/reply-artifacts.js.map +1 -1
- package/dist/request-model-override.d.ts +14 -37
- package/dist/request-model-override.d.ts.map +1 -1
- package/dist/request-model-override.js +105 -143
- package/dist/request-model-override.js.map +1 -1
- package/dist/request-scoped-mcp.d.ts +3 -0
- package/dist/request-scoped-mcp.d.ts.map +1 -1
- package/dist/request-scoped-mcp.js +1 -1
- package/dist/request-scoped-mcp.js.map +1 -1
- package/dist/run-history.d.ts.map +1 -1
- package/dist/run-history.js +56 -13
- package/dist/run-history.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/seen-conversations.d.ts +4 -3
- package/dist/seen-conversations.d.ts.map +1 -1
- package/dist/seen-conversations.js +6 -5
- package/dist/seen-conversations.js.map +1 -1
- package/dist/session-history.d.ts +14 -0
- package/dist/session-history.d.ts.map +1 -1
- package/dist/session-history.js +120 -5
- package/dist/session-history.js.map +1 -1
- package/dist/systemd-command.d.ts +8 -0
- package/dist/systemd-command.d.ts.map +1 -0
- package/dist/systemd-command.js +206 -0
- package/dist/systemd-command.js.map +1 -0
- package/dist/systemd.d.ts +51 -0
- package/dist/systemd.d.ts.map +1 -0
- package/dist/systemd.js +299 -0
- package/dist/systemd.js.map +1 -0
- package/dist/tool-output-artifacts.d.ts +4 -0
- package/dist/tool-output-artifacts.d.ts.map +1 -0
- package/dist/tool-output-artifacts.js +17 -0
- package/dist/tool-output-artifacts.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/tui-command.d.ts +0 -45
- package/dist/tui-command.d.ts.map +1 -1
- package/dist/tui-command.js +27 -264
- package/dist/tui-command.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/web-command.d.ts +3 -0
- package/dist/web-command.d.ts.map +1 -1
- package/dist/web-command.js +112 -7
- package/dist/web-command.js.map +1 -1
- package/dist/web-request-coordinator.d.ts +19 -0
- package/dist/web-request-coordinator.d.ts.map +1 -0
- package/dist/web-request-coordinator.js +242 -0
- package/dist/web-request-coordinator.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 +25 -45
- package/dist/wizard/prompts.js.map +1 -1
- package/dist/wizard/run.d.ts.map +1 -1
- package/dist/wizard/run.js +30 -153
- package/dist/wizard/run.js.map +1 -1
- package/package.json +18 -18
- package/schema/mono-agent.config.schema.json +301 -53
- package/skills/mono-agent-composer/SKILL.md +5 -5
- package/skills/mono-agent-composer/references/config-blueprint.md +61 -47
- package/skills/mono-agent-composer/references/discovery-questions.md +18 -18
- package/skills/mono-agent-composer/references/feature-coverage.md +39 -21
- package/skills/mono-agent-composer/references/package-map.md +26 -17
- package/skills/mono-agent-composer/references/playbooks.md +39 -28
- package/skills/mono-agent-composer/references/validation.md +4 -3
- 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/configuration-proposal-main.d.ts +0 -3
- package/dist/configuration-proposal-main.d.ts.map +0 -1
- package/dist/configuration-proposal-main.js +0 -13
- package/dist/configuration-proposal-main.js.map +0 -1
- package/dist/configuration-proposal-tool.d.ts +0 -33
- package/dist/configuration-proposal-tool.d.ts.map +0 -1
- package/dist/configuration-proposal-tool.js +0 -188
- package/dist/configuration-proposal-tool.js.map +0 -1
- package/dist/local-configuration.d.ts +0 -99
- package/dist/local-configuration.d.ts.map +0 -1
- package/dist/local-configuration.js +0 -1330
- package/dist/local-configuration.js.map +0 -1
package/dist/doctor.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import { inspectWebControl } from "./web-request-coordinator.js";
|
|
1
2
|
import { execFile as execFileCallback } from "node:child_process";
|
|
2
3
|
import { constants } from "node:fs";
|
|
3
4
|
import { access, lstat, mkdir, readdir, readFile, realpath, stat } from "node:fs/promises";
|
|
4
|
-
import { homedir } from "node:os";
|
|
5
5
|
import { isIP } from "node:net";
|
|
6
6
|
import { basename, join, relative, resolve, sep } from "node:path";
|
|
7
7
|
import { promisify } from "node:util";
|
|
@@ -12,10 +12,12 @@ import { promisify } from "node:util";
|
|
|
12
12
|
import { getBuiltinModels as getPiBuiltinModels, getBuiltinProviders as getPiBuiltinProviders, } from "@earendil-works/pi-ai/providers/all";
|
|
13
13
|
import { validateCronExpression } from "@mono-agent/cron-adapter";
|
|
14
14
|
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";
|
|
15
|
+
import { describeMonoRuntimeSupport, inspectCodexSubscriptionSearch, isValidMcpServerName, modelReferenceKey, networkPolicyAllowsUrl, parseMonoRuntimeModelReference, } from "@mono-agent/runtime-adapter";
|
|
16
|
+
import { buildMonoAgentConfigView, EFFORT_LEVELS, findJsonSecretConfigWarnings, findRemovedConfigWarnings, readMonoAgentConfigJson, redactMonoAgentConfig, resolveSupermemoryContainer, } from "@mono-agent/config";
|
|
17
|
+
import { describeSandboxEffectiveState, MODEL_REFERENCE_ECHO_MAX_BYTES, MODEL_REFERENCE_REASON_MAX_BYTES, resolveSandboxEffectiveState, RuntimeAdapterError, sandboxEffectiveStateWarning, sanitizeModelReferenceText, } from "@mono-agent/runtime-adapter";
|
|
18
18
|
import { isAppCoreConfigError, loadAppCoreConfig, } from "./app-config.js";
|
|
19
|
+
import { isRememberToolAllowed, isRememberToolPolicyName } from "./memory-remember.js";
|
|
20
|
+
import { isMemoryJournalToolAllowed, isMemoryJournalToolPolicyName } from "./memory-journal.js";
|
|
19
21
|
import { adapterSendToolNames, isAdapterSendToolAllowed, resolveAdapterSendToolsSettings } from "./adapter-send-tools.js";
|
|
20
22
|
import { canonicalToolName, isAllowAllTools, isKnownToolName, isMcpToolName, suggestToolName } from "./modules/known-tools.js";
|
|
21
23
|
import { collectChannelConfigViews } from "./channel-config-view.js";
|
|
@@ -37,19 +39,26 @@ import { formatInteractionBridgeUrl, loadInteractionSettings } from "./interacti
|
|
|
37
39
|
import { FIRST_RUN_MEMORY_INITIALIZING_MARKER } from "./first-run-managed-memory.js";
|
|
38
40
|
import { DEFAULT_MEMORY_EMBEDDING_ENDPOINTS, probeMemoryEmbeddingSelection, } from "./memory-embedding-service.js";
|
|
39
41
|
import { piAuthRecoveryCommand } from "./provider-setup.js";
|
|
42
|
+
import { collectUsedProviderReferences } from "./provider-auth-status.js";
|
|
40
43
|
import { inspectPiAuthStore } from "./pi-auth-store-inspection.js";
|
|
41
|
-
import { checkManagedProjectSkills, managedProjectSkillsExist } from "./project-skills.js";
|
|
42
|
-
import { configuredRuntimeFallbackModels, configuredRuntimeModels } from "./runtime-routes.js";
|
|
44
|
+
import { activeProjectSkillSelections, checkManagedProjectSkills, isRetiredProjectSkillName, managedProjectSkillsExist, } from "./project-skills.js";
|
|
43
45
|
import { runtimeProvenanceDetail } from "./runtime-provenance.js";
|
|
46
|
+
import { resolveAdvertisedModelEffort } from "./model-effort-capabilities.js";
|
|
44
47
|
import { loadSupermemoryPlugin } from "./supermemory-plugin.js";
|
|
45
48
|
import { DEFAULT_LAUNCHD_LOG_POLICY, inspectLaunchdLogs, LAUNCHD_LOG_MONITOR_INTERVAL_SECONDS, launchdLogPathsForConfig, } from "./launchd-logs.js";
|
|
46
49
|
import { readLaunchdLogMonitorStatus } from "./launchd-log-monitor-status.js";
|
|
47
50
|
import { exporterSection, runsSection } from "./doctor-observability.js";
|
|
48
51
|
import { sessionToolHistorySection } from "./doctor-session-history.js";
|
|
49
|
-
import { runtimeRouteContainsUnsupportedHistoryTool } from "./app-controller-utils.js";
|
|
50
52
|
const execFile = promisify(execFileCallback);
|
|
51
53
|
const CONTINUATION_V2_ROLLBACK_GUARD = "UPGRADED-TO-RECORDS-V3";
|
|
52
54
|
const CONTINUATION_V2_ROLLBACK_GUARD_CONTENT = "This state directory uses continuation records v3. Older runtimes must not open records-v2.\n";
|
|
55
|
+
const DOCTOR_STATUS_TIMEOUT_MS = 5_000;
|
|
56
|
+
const DOCTOR_STATUS_MAX_BUFFER_BYTES = 64 * 1024;
|
|
57
|
+
const OLLAMA_WEB_SEARCH_PROBE_MAX_BYTES = 2 * 1024 * 1024;
|
|
58
|
+
const defaultDoctorStatusExecFile = async (file, args, options) => {
|
|
59
|
+
const { stdout } = await execFile(file, [...args], options);
|
|
60
|
+
return { stdout };
|
|
61
|
+
};
|
|
53
62
|
/**
|
|
54
63
|
* Loads every config section the app would use at start and reports it
|
|
55
64
|
* per-section, so an engineer can see exactly what would run, wait, or fail —
|
|
@@ -73,7 +82,7 @@ export async function validateMonoAgentFolder(options) {
|
|
|
73
82
|
}
|
|
74
83
|
sections.push(await runtimeProvenanceSection(options.verifiedRuntimeProvenanceDetail));
|
|
75
84
|
if (coreConfig !== undefined) {
|
|
76
|
-
const staticTriggerCredentialRefs = await collectStaticTriggerCredentialRefs(drivers, options);
|
|
85
|
+
const staticTriggerCredentialRefs = await collectStaticTriggerCredentialRefs(coreConfig, drivers, options);
|
|
77
86
|
const jsonResult = await readMonoAgentConfigJson(options.configPath);
|
|
78
87
|
// Channel secrets (bot tokens, API keys) live outside the core view, so the
|
|
79
88
|
// placement check spans both: core sections + every channel's config view.
|
|
@@ -92,13 +101,13 @@ export async function validateMonoAgentFolder(options) {
|
|
|
92
101
|
sections.push({ id: "secret-placement", label: "Config warnings", status: "waiting", details: configWarnings });
|
|
93
102
|
}
|
|
94
103
|
sections.push(runtimeSection(coreConfig));
|
|
95
|
-
sections.push(await credentialsSection(coreConfig, options.env, options.
|
|
104
|
+
sections.push(await credentialsSection(coreConfig, options.env, options.verifiedCredentialModelRefs, staticTriggerCredentialRefs));
|
|
96
105
|
sections.push(await contextSection(coreConfig, options.cwd));
|
|
97
106
|
sections.push(await memorySection(coreConfig, options.cwd, options.env, liveness, allowFilesystemWrites, options.preferAppPluginInstall === true));
|
|
98
107
|
sections.push(await toolsSection(coreConfig, options));
|
|
99
108
|
sections.push(await sessionToolHistorySection({
|
|
100
109
|
historyRoot: join(coreConfig.artifacts.dir, "..", "history"),
|
|
101
|
-
requestScopedToolSupported:
|
|
110
|
+
requestScopedToolSupported: true,
|
|
102
111
|
}));
|
|
103
112
|
sections.push(await webToolsSection(coreConfig, options, liveness));
|
|
104
113
|
sections.push(await continuationSection(coreConfig, options));
|
|
@@ -233,73 +242,13 @@ function staticTriggerConfigEntries(driverId, loaded) {
|
|
|
233
242
|
* Collect static trigger model overrides for credential readiness. Dynamic
|
|
234
243
|
* webhook request-body overrides are intentionally unavailable at validate time.
|
|
235
244
|
*/
|
|
236
|
-
async function collectStaticTriggerCredentialRefs(drivers, input) {
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
if (driver.id !== "webhook" && driver.id !== "cron")
|
|
241
|
-
continue;
|
|
242
|
-
let loaded;
|
|
243
|
-
try {
|
|
244
|
-
loaded = await driver.loadConfig(input);
|
|
245
|
-
}
|
|
246
|
-
catch {
|
|
247
|
-
// The channel section owns malformed/unreadable config diagnostics.
|
|
248
|
-
continue;
|
|
249
|
-
}
|
|
250
|
-
for (const { entryPath, entry } of staticTriggerConfigEntries(driver.id, loaded)) {
|
|
251
|
-
if (typeof entry.model !== "string")
|
|
252
|
-
continue;
|
|
253
|
-
try {
|
|
254
|
-
const ref = parseMonoRuntimeModelReference(entry.model);
|
|
255
|
-
const key = `${entryPath}\u0000${modelReferenceKey(ref)}`;
|
|
256
|
-
if (seen.has(key))
|
|
257
|
-
continue;
|
|
258
|
-
seen.add(key);
|
|
259
|
-
refs.push({ label: entryPath, ref });
|
|
260
|
-
}
|
|
261
|
-
catch {
|
|
262
|
-
// The channel section owns invalid model-reference syntax.
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
return refs;
|
|
245
|
+
async function collectStaticTriggerCredentialRefs(config, drivers, input) {
|
|
246
|
+
return (await collectUsedProviderReferences(config, drivers, input))
|
|
247
|
+
.filter(({ usage }) => usage.kind === "cron" || usage.kind === "webhook")
|
|
248
|
+
.map(({ usage, ref }) => ({ label: usage.label, ref }));
|
|
267
249
|
}
|
|
268
250
|
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
251
|
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
252
|
for (const driver of drivers) {
|
|
304
253
|
if (driver.id !== "webhook" && driver.id !== "cron")
|
|
305
254
|
continue;
|
|
@@ -313,50 +262,19 @@ async function applyRequestModelOverrideCompatibilityChecks(sections, config, dr
|
|
|
313
262
|
}
|
|
314
263
|
for (const { entryPath, entry } of staticTriggerConfigEntries(driver.id, loaded)) {
|
|
315
264
|
const hasModelOverride = typeof entry.model === "string";
|
|
316
|
-
|
|
317
|
-
if (!hasModelOverride && !hasEffortOverride)
|
|
265
|
+
if (!hasModelOverride)
|
|
318
266
|
continue;
|
|
319
267
|
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);
|
|
268
|
+
const model = parseMonoRuntimeModelReference(entry.model);
|
|
269
|
+
const resolutionIssue = piModelResolutionIssue(config, model);
|
|
270
|
+
if (resolutionIssue !== undefined) {
|
|
271
|
+
// The parser accepted this value, but acceptance is no longer a length
|
|
272
|
+
// guarantee: the byte ceiling was removed because no constant could
|
|
273
|
+
// hold for every provider, so a legitimately long id parses. This is a
|
|
274
|
+
// diagnostic, not the protocol string, so it is bounded here rather
|
|
275
|
+
// than at the grammar.
|
|
276
|
+
const echoed = sanitizeModelReferenceText(entry.model, MODEL_REFERENCE_ECHO_MAX_BYTES);
|
|
277
|
+
piModelResolutionFailures.push(`${entryPath}.model=${echoed}: ${resolutionIssue}.`);
|
|
360
278
|
}
|
|
361
279
|
}
|
|
362
280
|
catch {
|
|
@@ -364,14 +282,7 @@ async function applyRequestModelOverrideCompatibilityChecks(sections, config, dr
|
|
|
364
282
|
}
|
|
365
283
|
}
|
|
366
284
|
}
|
|
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)
|
|
285
|
+
if (piModelResolutionFailures.length === 0)
|
|
375
286
|
return;
|
|
376
287
|
const index = sections.findIndex((section) => section.id === "runtime");
|
|
377
288
|
if (index < 0)
|
|
@@ -382,63 +293,14 @@ async function applyRequestModelOverrideCompatibilityChecks(sections, config, dr
|
|
|
382
293
|
status: "error",
|
|
383
294
|
details: [
|
|
384
295
|
...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
|
-
]),
|
|
296
|
+
"Per-trigger Pi model overrides must resolve through providers.local or Pi's exact built-in catalog before execution.",
|
|
297
|
+
...piModelResolutionFailures,
|
|
433
298
|
],
|
|
434
299
|
};
|
|
435
300
|
}
|
|
436
301
|
function isUnknownRecord(value) {
|
|
437
302
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
438
303
|
}
|
|
439
|
-
function hasAllowAllOnlyToolPolicy(tools) {
|
|
440
|
-
return isAllowAllTools(tools.allowedTools) && tools.disallowedTools.length === 0;
|
|
441
|
-
}
|
|
442
304
|
/** Adapter send tools each channel owns; an allowed entry needs BOTH the tool AND the enabled channel. */
|
|
443
305
|
const CHANNEL_OWNED_SEND_TOOLS = {
|
|
444
306
|
slack: ["SlackSendMessage"],
|
|
@@ -511,45 +373,28 @@ function runtimeSection(config) {
|
|
|
511
373
|
const details = [];
|
|
512
374
|
let status = "ok";
|
|
513
375
|
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
376
|
for (const route of routes) {
|
|
533
377
|
try {
|
|
534
|
-
const support = describeMonoRuntimeSupport(route.model
|
|
378
|
+
const support = describeMonoRuntimeSupport(route.model);
|
|
535
379
|
const resolutionIssue = piModelResolutionIssue(config, route.model);
|
|
536
|
-
const
|
|
537
|
-
if (
|
|
538
|
-
details.push(`${route.label} ${
|
|
380
|
+
const effortWarning = runtimeRouteEffortWarning(config, route);
|
|
381
|
+
if (resolutionIssue === undefined) {
|
|
382
|
+
details.push(`${route.label} ${displayReferenceOf(route.model)} runs on ${support.backend.label} ` +
|
|
539
383
|
`(effort: ${route.effort ?? "provider default"}).`);
|
|
540
384
|
}
|
|
541
385
|
else {
|
|
542
386
|
status = "error";
|
|
543
|
-
details.push(`${route.label} ${
|
|
387
|
+
details.push(`${route.label} ${displayReferenceOf(route.model)}: ${resolutionIssue}.`);
|
|
544
388
|
}
|
|
545
|
-
if (
|
|
546
|
-
status
|
|
547
|
-
|
|
389
|
+
if (effortWarning !== undefined) {
|
|
390
|
+
if (status !== "error")
|
|
391
|
+
status = "waiting";
|
|
392
|
+
details.push(effortWarning);
|
|
548
393
|
}
|
|
549
394
|
}
|
|
550
395
|
catch (error) {
|
|
551
396
|
status = "error";
|
|
552
|
-
details.push(`${route.label} ${
|
|
397
|
+
details.push(`${route.label} ${displayReferenceOf(route.model)}: ${displayReason(error)}.`);
|
|
553
398
|
}
|
|
554
399
|
}
|
|
555
400
|
return { id: "runtime", label: "Runtime", status, details };
|
|
@@ -557,48 +402,52 @@ function runtimeSection(config) {
|
|
|
557
402
|
function configuredRuntimeRouteChecks(config) {
|
|
558
403
|
const primary = {
|
|
559
404
|
label: "Primary model",
|
|
405
|
+
configPath: "runtime.effort",
|
|
560
406
|
model: config.runtime.model,
|
|
561
407
|
...(config.runtime.effort === undefined ? {} : { effort: config.runtime.effort }),
|
|
562
|
-
executionMode: config.runtime.executionMode,
|
|
563
408
|
};
|
|
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
409
|
return [
|
|
575
410
|
primary,
|
|
576
|
-
...(config.runtime.
|
|
411
|
+
...(config.runtime.fallbacks ?? []).map((fallback, index) => ({
|
|
577
412
|
label: "Fallback model",
|
|
578
|
-
|
|
579
|
-
|
|
413
|
+
configPath: `runtime.fallbacks[${index}].effort`,
|
|
414
|
+
model: fallback.model,
|
|
415
|
+
...(fallback.effort === undefined ? {} : { effort: fallback.effort }),
|
|
580
416
|
})),
|
|
581
417
|
];
|
|
582
418
|
}
|
|
583
|
-
function
|
|
419
|
+
function runtimeRouteEffortWarning(config, route) {
|
|
584
420
|
if (route.effort === undefined)
|
|
585
421
|
return undefined;
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
422
|
+
// Always go through the shared resolver. Deriving the ladder from
|
|
423
|
+
// `thinkingLevelMap`'s KEYS treated an override table as the complete
|
|
424
|
+
// supported set: claude-fable-5 maps only {off, xhigh, max}, which yielded
|
|
425
|
+
// [none, xhigh, max] and made a perfectly valid `effort: medium` warn and
|
|
426
|
+
// recommend xhigh. The resolver reports [minimal, low, medium, high, xhigh,
|
|
427
|
+
// max] for that model, and it is the same one the catalog and pickers use --
|
|
428
|
+
// doctor disagreeing with what the selector offers is its own bug.
|
|
429
|
+
const resolved = resolveAdvertisedModelEffort(route.model, {
|
|
430
|
+
...(config.providers?.local === undefined ? {} : { localProviders: config.providers.local }),
|
|
431
|
+
});
|
|
432
|
+
// A NON-REASONING route has no advertised ladder at all, so the level-list
|
|
433
|
+
// check below returns clean and `effort: high` was reported ready. Keep this
|
|
434
|
+
// ahead of that check: a local model can declare `reasoning_mode: "none"`
|
|
435
|
+
// WITH `reasoning_levels`, and "this route does not reason" is the accurate
|
|
436
|
+
// diagnosis there, not "pick another level".
|
|
437
|
+
if (resolved.reasoning === false && route.effort !== "none") {
|
|
438
|
+
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.`;
|
|
439
|
+
}
|
|
440
|
+
const advertised = resolved.effortLevels
|
|
441
|
+
?.filter((level) => EFFORT_LEVELS.includes(level));
|
|
442
|
+
if (advertised === undefined || advertised.length === 0 || advertised.includes(route.effort))
|
|
443
|
+
return undefined;
|
|
444
|
+
const configuredIndex = EFFORT_LEVELS.indexOf(route.effort);
|
|
445
|
+
const nearest = advertised.reduce((best, candidate) => {
|
|
446
|
+
const candidateIndex = EFFORT_LEVELS.indexOf(candidate);
|
|
447
|
+
const bestIndex = EFFORT_LEVELS.indexOf(best);
|
|
448
|
+
return Math.abs(candidateIndex - configuredIndex) < Math.abs(bestIndex - configuredIndex) ? candidate : best;
|
|
449
|
+
});
|
|
450
|
+
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
451
|
}
|
|
603
452
|
/**
|
|
604
453
|
* Mirrors the Pi runtime's actual model-resolution boundary. Mono-agent only
|
|
@@ -607,17 +456,14 @@ function runtimeRouteEffortIssue(config, route) {
|
|
|
607
456
|
* provider, `resolvePiRuntimeModel` performs an exact built-in catalog lookup.
|
|
608
457
|
*/
|
|
609
458
|
function piModelResolutionIssue(config, model) {
|
|
610
|
-
if (model.sdk !== "pi" || model.provider === undefined) {
|
|
611
|
-
return undefined;
|
|
612
|
-
}
|
|
613
459
|
const localProvider = config.providers?.local?.find((provider) => provider.id === model.provider);
|
|
614
460
|
if (localProvider !== undefined) {
|
|
615
461
|
if (localProvider.enabled === false) {
|
|
616
|
-
return `provider \`${model.provider}\` is disabled in providers.local`;
|
|
462
|
+
return `provider \`${displayText(model.provider)}\` is disabled in providers.local`;
|
|
617
463
|
}
|
|
618
464
|
const localModel = localProvider.models?.find((candidate) => candidate.name === model.model || candidate.alias === model.model);
|
|
619
465
|
if (localModel?.enabled === false) {
|
|
620
|
-
return `model \`${model.model}\` is disabled in providers.local for provider \`${model.provider}\``;
|
|
466
|
+
return `model \`${displayText(model.model)}\` is disabled in providers.local for provider \`${displayText(model.provider)}\``;
|
|
621
467
|
}
|
|
622
468
|
return undefined;
|
|
623
469
|
}
|
|
@@ -625,7 +471,14 @@ function piModelResolutionIssue(config, model) {
|
|
|
625
471
|
&& getPiBuiltinModels(model.provider).some((candidate) => candidate.id === model.model)) {
|
|
626
472
|
return undefined;
|
|
627
473
|
}
|
|
628
|
-
|
|
474
|
+
// This is doctor's own validate-time diagnostic, not the runtime failure
|
|
475
|
+
// string. The one that must stay byte-identical for `NON_RETRYABLE_PROVIDER_RE`
|
|
476
|
+
// is built in agent-runtime's `ai/providers/pi-models.js`; nothing matches
|
|
477
|
+
// against this text. So the reference is bounded here, which matters now that
|
|
478
|
+
// the parser imposes no length ceiling and a legitimately long id reaches this
|
|
479
|
+
// line whole.
|
|
480
|
+
const reference = displayText(`${model.provider}:${model.model}`);
|
|
481
|
+
return (`pi model not found: ${reference}; no matching providers.local entry exists and Pi's built-in catalog has no exact model. ` +
|
|
629
482
|
"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
483
|
}
|
|
631
484
|
function isPiBuiltinProvider(provider) {
|
|
@@ -641,229 +494,21 @@ async function readPiAuthProviders(path) {
|
|
|
641
494
|
return inspection;
|
|
642
495
|
return { status: "ok", providers: inspection.auth };
|
|
643
496
|
}
|
|
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
497
|
/**
|
|
838
498
|
* Checks that every referenced model (primary, fallbacks, and the agent-host memory LLM)
|
|
839
499
|
* has discoverable credentials, so a keyless or expired-OAuth provider is caught at
|
|
840
500
|
* `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").
|
|
501
|
+
* that broke memory capture for ~10 days when an auth-store OAuth token quietly expired.
|
|
844
502
|
*
|
|
845
|
-
* The
|
|
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.
|
|
848
|
-
*
|
|
849
|
-
* - Pi providers: inspect the auth store (`piAuthPath`) and the config's own
|
|
503
|
+
* The read-only check inspects the auth store (`piAuthPath`) and the config's own
|
|
850
504
|
* `providers.local` custom providers. A custom/local provider follows its declared
|
|
851
505
|
* `apiKey` / `apiKeyEnv` contract instead of Pi OAuth; an OAuth provider absent from
|
|
852
506
|
* 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
507
|
*
|
|
863
508
|
* `waiting` (never `error`) keeps the verdict non-fatal, mirroring the Ollama/Phoenix
|
|
864
509
|
* probes — the goal is visibility, not blocking start.
|
|
865
510
|
*/
|
|
866
|
-
async function credentialsSection(config, env,
|
|
511
|
+
async function credentialsSection(config, env, verifiedCredentialModelRefs = [], staticTriggerRefs = []) {
|
|
867
512
|
const refs = [
|
|
868
513
|
{ label: "Primary", ref: config.runtime.model },
|
|
869
514
|
...configuredRuntimeRouteChecks(config).slice(1).map((route) => ({
|
|
@@ -872,7 +517,7 @@ async function credentialsSection(config, env, cwd, liveness, verifiedCredential
|
|
|
872
517
|
})),
|
|
873
518
|
...staticTriggerRefs,
|
|
874
519
|
];
|
|
875
|
-
if (config.memory?.llm
|
|
520
|
+
if (config.memory?.llm?.provider === "agent-host") {
|
|
876
521
|
try {
|
|
877
522
|
refs.push({ label: "Memory LLM", ref: parseMonoRuntimeModelReference(config.memory.llm.model) });
|
|
878
523
|
}
|
|
@@ -880,9 +525,7 @@ async function credentialsSection(config, env, cwd, liveness, verifiedCredential
|
|
|
880
525
|
// A malformed memory model reference is surfaced by the memory/runtime shape checks.
|
|
881
526
|
}
|
|
882
527
|
}
|
|
883
|
-
const authenticatedRefs = refs
|
|
884
|
-
|| (r.ref.sdk === "opencode" && typeof r.ref.provider === "string")
|
|
885
|
-
|| isSdkAuthName(r.ref.sdk));
|
|
528
|
+
const authenticatedRefs = refs;
|
|
886
529
|
if (authenticatedRefs.length === 0) {
|
|
887
530
|
return {
|
|
888
531
|
id: "credentials",
|
|
@@ -895,102 +538,19 @@ async function credentialsSection(config, env, cwd, liveness, verifiedCredential
|
|
|
895
538
|
let status = "ok";
|
|
896
539
|
const verified = new Set(verifiedCredentialModelRefs);
|
|
897
540
|
for (const { label, ref } of authenticatedRefs) {
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
541
|
+
// The set holds whole canonical references, so the LOOKUP key stays whole;
|
|
542
|
+
// only the printed form is bounded.
|
|
543
|
+
if (verified.has(referenceOf(ref))) {
|
|
544
|
+
details.push(`${label} ${displayReferenceOf(ref)}: credentials verified by a successful live model check.`);
|
|
901
545
|
}
|
|
902
546
|
}
|
|
903
547
|
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);
|
|
548
|
+
if (unverifiedRefs.length > 0) {
|
|
549
|
+
const piStatus = await appendPiCredentialDetails(config, unverifiedRefs, details, env);
|
|
909
550
|
if (piStatus === "waiting") {
|
|
910
551
|
status = "waiting";
|
|
911
552
|
}
|
|
912
553
|
}
|
|
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
554
|
return { id: "credentials", label: "Provider credentials", status, details };
|
|
995
555
|
}
|
|
996
556
|
/**
|
|
@@ -1014,34 +574,38 @@ async function appendPiCredentialDetails(config, piRefs, details, env) {
|
|
|
1014
574
|
}
|
|
1015
575
|
for (const { label, ref } of piRefs) {
|
|
1016
576
|
const provider = ref.provider;
|
|
1017
|
-
|
|
1018
|
-
|
|
577
|
+
// Display-only from here down: `provider` is still the lookup key into the
|
|
578
|
+
// auth store and providers.local, `providerLabel` and `refStr` are what the
|
|
579
|
+
// detail lines quote back at the operator.
|
|
580
|
+
const providerLabel = displayText(provider);
|
|
581
|
+
const refStr = displayReferenceOf(ref);
|
|
582
|
+
const loginCommand = piAuthRecoveryCommand(providerLabel, authPath);
|
|
1019
583
|
const localProvider = localProviders.get(provider);
|
|
1020
584
|
if (localProvider !== undefined) {
|
|
1021
585
|
if (localProvider.enabled === false) {
|
|
1022
586
|
status = "waiting";
|
|
1023
|
-
details.push(`[WARN] ${label} ${refStr}: provider \`${
|
|
587
|
+
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
588
|
}
|
|
1025
589
|
else if (localProvider.apiKey !== undefined) {
|
|
1026
|
-
details.push(`${label} ${refStr}: provider \`${
|
|
590
|
+
details.push(`${label} ${refStr}: provider \`${providerLabel}\` configured via config providers.local (API key configured); credential detected, live model verification is still pending.`);
|
|
1027
591
|
}
|
|
1028
592
|
else if (localProvider.apiKeyEnv !== undefined) {
|
|
1029
593
|
if (hasNonEmptyCredentialValue(env[localProvider.apiKeyEnv])) {
|
|
1030
|
-
details.push(`${label} ${refStr}: provider \`${
|
|
594
|
+
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
595
|
}
|
|
1032
596
|
else {
|
|
1033
597
|
status = "waiting";
|
|
1034
|
-
details.push(`[WARN] ${label} ${refStr}: provider \`${
|
|
598
|
+
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
599
|
}
|
|
1036
600
|
}
|
|
1037
601
|
else {
|
|
1038
|
-
details.push(`${label} ${refStr}: provider \`${
|
|
602
|
+
details.push(`${label} ${refStr}: provider \`${providerLabel}\` configured via config providers.local (keyless local provider; no API key declared).`);
|
|
1039
603
|
}
|
|
1040
604
|
continue;
|
|
1041
605
|
}
|
|
1042
606
|
const apiKeyEnv = PI_API_KEY_ENV_BY_PROVIDER[provider];
|
|
1043
607
|
if (apiKeyEnv !== undefined && hasNonEmptyCredentialValue(env[apiKeyEnv])) {
|
|
1044
|
-
details.push(`${label} ${refStr}: Pi API-key credential for \`${
|
|
608
|
+
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
609
|
continue;
|
|
1046
610
|
}
|
|
1047
611
|
if (authInspection?.status === "unsafe") {
|
|
@@ -1056,37 +620,37 @@ async function appendPiCredentialDetails(config, piRefs, details, env) {
|
|
|
1056
620
|
if (entry === undefined) {
|
|
1057
621
|
status = "waiting";
|
|
1058
622
|
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 \`${
|
|
623
|
+
? `[WARN] ${label} ${refStr}: no Pi credentials found for provider \`${providerLabel}\` in the auth store. Authenticate it with \`${loginCommand}\`, or set providers.piAuthPath.`
|
|
624
|
+
: `[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
625
|
continue;
|
|
1062
626
|
}
|
|
1063
627
|
const isOAuth = entry.type === "oauth";
|
|
1064
628
|
const isApiKey = entry.type === "api_key";
|
|
1065
629
|
if (isApiKey && !hasNonEmptyCredentialValue(entry.key)) {
|
|
1066
630
|
status = "waiting";
|
|
1067
|
-
details.push(`[WARN] ${label} ${refStr}: stored API-key credential for \`${
|
|
631
|
+
details.push(`[WARN] ${label} ${refStr}: stored API-key credential for \`${providerLabel}\` has no usable key. Run \`${loginCommand}\`; no secret value was displayed.`);
|
|
1068
632
|
continue;
|
|
1069
633
|
}
|
|
1070
634
|
if (isOAuth && !hasNonEmptyCredentialValue(entry.access) && !hasNonEmptyCredentialValue(entry.refresh)) {
|
|
1071
635
|
status = "waiting";
|
|
1072
|
-
details.push(`[WARN] ${label} ${refStr}: stored OAuth credential for \`${
|
|
636
|
+
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
637
|
continue;
|
|
1074
638
|
}
|
|
1075
639
|
if (!isOAuth && !isApiKey) {
|
|
1076
640
|
status = "waiting";
|
|
1077
|
-
details.push(`[WARN] ${label} ${refStr}: stored credential for \`${
|
|
641
|
+
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
642
|
continue;
|
|
1079
643
|
}
|
|
1080
644
|
const expired = typeof entry.expires === "number" && entry.expires < now;
|
|
1081
645
|
const whenNote = typeof entry.expires === "number" ? ` ${new Date(entry.expires).toISOString()}` : "";
|
|
1082
646
|
if (isOAuth && expired) {
|
|
1083
647
|
status = "waiting";
|
|
1084
|
-
details.push(`[WARN] ${label} ${refStr}: OAuth token for \`${
|
|
648
|
+
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
649
|
continue;
|
|
1086
650
|
}
|
|
1087
651
|
details.push(isOAuth
|
|
1088
|
-
? `${label} ${refStr}: OAuth credentials for \`${
|
|
1089
|
-
: `${label} ${refStr}: API key credentials for \`${
|
|
652
|
+
? `${label} ${refStr}: OAuth credentials for \`${providerLabel}\` present (token valid${whenNote}); credential detected, live model verification is still pending.`
|
|
653
|
+
: `${label} ${refStr}: API key credentials for \`${providerLabel}\` present; credential detected, live model verification is still pending.`);
|
|
1090
654
|
}
|
|
1091
655
|
return status;
|
|
1092
656
|
}
|
|
@@ -1110,6 +674,8 @@ function hasNonEmptyCredentialValue(value) {
|
|
|
1110
674
|
async function contextSection(config, cwd) {
|
|
1111
675
|
const details = [];
|
|
1112
676
|
let status = "ok";
|
|
677
|
+
const activeSelectedSkills = activeProjectSkillSelections(config.context.selectedSkills);
|
|
678
|
+
const retiredSelectedSkills = config.context.selectedSkills.filter(isRetiredProjectSkillName);
|
|
1113
679
|
if (await pathExists(config.context.identityPath)) {
|
|
1114
680
|
details.push(`Identity: ${config.context.identityPath}`);
|
|
1115
681
|
}
|
|
@@ -1121,10 +687,19 @@ async function contextSection(config, cwd) {
|
|
|
1121
687
|
status = "error";
|
|
1122
688
|
details.push(`Soul file is missing: ${config.context.soulPath}`);
|
|
1123
689
|
}
|
|
1124
|
-
|
|
690
|
+
for (const skill of retiredSelectedSkills) {
|
|
691
|
+
if (status === "ok")
|
|
692
|
+
status = "waiting";
|
|
693
|
+
details.push(`Skill \`${skill}\` is retired and ignored. Remove it from context.selectedSkills or ` +
|
|
694
|
+
"MONO_AGENT_SELECTED_SKILLS, then run `mono-agent install-skill --project --check`." +
|
|
695
|
+
(config.context.skillDisclosure === "index"
|
|
696
|
+
? " Skill disclosure runs as `full` until the retired selector is removed."
|
|
697
|
+
: ""));
|
|
698
|
+
}
|
|
699
|
+
if (activeSelectedSkills.length > 0 && config.context.skillsRoot !== undefined) {
|
|
1125
700
|
if (await pathExists(config.context.skillsRoot)) {
|
|
1126
701
|
details.push(`Skills root: ${config.context.skillsRoot}`);
|
|
1127
|
-
for (const skill of
|
|
702
|
+
for (const skill of activeSelectedSkills) {
|
|
1128
703
|
const skillPath = join(config.context.skillsRoot, skill, "SKILL.md");
|
|
1129
704
|
if (await pathExists(skillPath)) {
|
|
1130
705
|
details.push(`Skill \`${skill}\`: ${skillPath}`);
|
|
@@ -1140,7 +715,7 @@ async function contextSection(config, cwd) {
|
|
|
1140
715
|
details.push(`Skills root is missing: ${config.context.skillsRoot}`);
|
|
1141
716
|
}
|
|
1142
717
|
}
|
|
1143
|
-
else if (
|
|
718
|
+
else if (activeSelectedSkills.length > 0) {
|
|
1144
719
|
status = "error";
|
|
1145
720
|
details.push("Skills are selected but context.skillsRoot is not set.");
|
|
1146
721
|
}
|
|
@@ -1175,7 +750,15 @@ function memoryConsolidationCronIssue(expression) {
|
|
|
1175
750
|
}
|
|
1176
751
|
async function memorySection(config, cwd, env, liveness, allowFilesystemWrites, preferAppPluginInstall) {
|
|
1177
752
|
if (config.memory === undefined) {
|
|
1178
|
-
return {
|
|
753
|
+
return {
|
|
754
|
+
id: "memory",
|
|
755
|
+
label: "Memory",
|
|
756
|
+
status: "disabled",
|
|
757
|
+
details: [
|
|
758
|
+
"No memory configured.",
|
|
759
|
+
"Chronological journal: disabled because no local memory backend is configured.",
|
|
760
|
+
],
|
|
761
|
+
};
|
|
1179
762
|
}
|
|
1180
763
|
// External backend (e.g. supermemory): mode/embeddings/llm are bujo-only and
|
|
1181
764
|
// ignored, so validate the plugin-owned shape before any soft liveness probe.
|
|
@@ -1221,6 +804,9 @@ async function memorySection(config, cwd, env, liveness, allowFilesystemWrites,
|
|
|
1221
804
|
sm.apiKey === undefined
|
|
1222
805
|
? "Auth: no API key configured (keyless — works only if the instance allows it)."
|
|
1223
806
|
: "Auth: API key configured.",
|
|
807
|
+
config.memory.recallTool?.enabled === false
|
|
808
|
+
? "Explicit memory read tools are disabled by memory.recallTool.enabled. Chronological journal browsing is also unsupported by Supermemory."
|
|
809
|
+
: "Chronological journal: unsupported by Supermemory; MemoryJournal is not offered.",
|
|
1224
810
|
];
|
|
1225
811
|
if (!liveness) {
|
|
1226
812
|
details.push("Supermemory liveness probe skipped; ingestion is async.");
|
|
@@ -1238,6 +824,9 @@ async function memorySection(config, cwd, env, liveness, allowFilesystemWrites,
|
|
|
1238
824
|
}
|
|
1239
825
|
const details = [
|
|
1240
826
|
`Mode: ${config.memory.mode}, path: ${config.memory.path}, writeMode: ${config.memory.writeMode}.`,
|
|
827
|
+
config.memory.recallTool?.enabled === false
|
|
828
|
+
? `Explicit memory read tools are disabled by memory.recallTool.enabled; the local ${config.memory.mode} tier supports chronology but MemoryJournal is not offered.`
|
|
829
|
+
: `Chronological journal: supported by the local ${config.memory.mode} tier when tool policy allows MemoryJournal.`,
|
|
1241
830
|
];
|
|
1242
831
|
let status = "ok";
|
|
1243
832
|
if (config.memory.llm !== undefined) {
|
|
@@ -1248,12 +837,12 @@ async function memorySection(config, cwd, env, liveness, allowFilesystemWrites,
|
|
|
1248
837
|
const resolutionIssue = piModelResolutionIssue(config, model);
|
|
1249
838
|
if (resolutionIssue !== undefined) {
|
|
1250
839
|
status = "error";
|
|
1251
|
-
details.push(`Agent-host memory LLM ${
|
|
840
|
+
details.push(`Agent-host memory LLM ${displayReferenceOf(model)}: ${resolutionIssue}.`);
|
|
1252
841
|
}
|
|
1253
842
|
}
|
|
1254
843
|
catch (error) {
|
|
1255
844
|
status = "error";
|
|
1256
|
-
details.push(`Agent-host memory LLM ${config.memory.llm.model}: ${
|
|
845
|
+
details.push(`Agent-host memory LLM ${displayText(config.memory.llm.model)}: ${displayReason(error)}.`);
|
|
1257
846
|
}
|
|
1258
847
|
}
|
|
1259
848
|
}
|
|
@@ -1573,12 +1162,12 @@ async function localEmbeddingLivenessWarnings(embeddings, apiKey) {
|
|
|
1573
1162
|
}
|
|
1574
1163
|
if (provider === "ollama" && /HTTP 404/u.test(reason)) {
|
|
1575
1164
|
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.`,
|
|
1165
|
+
`[WARN] Ollama embedding model ${displayText(embeddings.model)} could not be proved at ${endpoint} (${reason}); ` +
|
|
1166
|
+
`run \`ollama pull ${displayText(embeddings.model)}\` and verify its embedding capability.`,
|
|
1578
1167
|
];
|
|
1579
1168
|
}
|
|
1580
1169
|
return [
|
|
1581
|
-
`[WARN] ${label} embedding readiness failed for ${embeddings.model} at ${endpoint} (${reason}). ` +
|
|
1170
|
+
`[WARN] ${label} embedding readiness failed for ${displayText(embeddings.model)} at ${endpoint} (${reason}). ` +
|
|
1582
1171
|
"Verify the selected model, authentication, and configured dimension.",
|
|
1583
1172
|
];
|
|
1584
1173
|
}
|
|
@@ -1620,8 +1209,8 @@ function memoryLlmLabel(llm) {
|
|
|
1620
1209
|
return "none";
|
|
1621
1210
|
}
|
|
1622
1211
|
return llm.provider === "ollama"
|
|
1623
|
-
? `ollama:${llm.model}`
|
|
1624
|
-
: `agent-host:${llm.model
|
|
1212
|
+
? `ollama:${displayText(llm.model)}`
|
|
1213
|
+
: `agent-host:${displayText(llm.model)}`;
|
|
1625
1214
|
}
|
|
1626
1215
|
async function toolsSection(config, input) {
|
|
1627
1216
|
const details = [];
|
|
@@ -1650,6 +1239,53 @@ async function toolsSection(config, input) {
|
|
|
1650
1239
|
details.push(`Allowed tools: ${allowedTools.join(", ")}.`);
|
|
1651
1240
|
let mcpNoteAdded = false;
|
|
1652
1241
|
for (const name of allowedTools) {
|
|
1242
|
+
if (isMemoryJournalToolPolicyName(name)) {
|
|
1243
|
+
// This app-owned MCP surface must be checked before the generic MCP
|
|
1244
|
+
// branch, using the same deny-wins spellings as runtime composition.
|
|
1245
|
+
if (!isMemoryJournalToolAllowed(config.tools)) {
|
|
1246
|
+
status = "waiting";
|
|
1247
|
+
details.push(`${name} is listed but tool policy still resolves to denied (deny wins, including the mcp__mono-agent-memory-journal__* spelling) - the tool will not be offered.`);
|
|
1248
|
+
}
|
|
1249
|
+
else if (config.memory === undefined) {
|
|
1250
|
+
status = "waiting";
|
|
1251
|
+
details.push(`${name} is in allowedTools but no memory block is configured - chronological journal browsing will not work.`);
|
|
1252
|
+
}
|
|
1253
|
+
else if (config.memory.recallTool?.enabled === false) {
|
|
1254
|
+
status = "waiting";
|
|
1255
|
+
details.push(`${name} is in allowedTools but memory.recallTool.enabled is off - explicit memory read tools will not work.`);
|
|
1256
|
+
}
|
|
1257
|
+
else if ((config.memory.backend ?? "bujo") === "supermemory") {
|
|
1258
|
+
status = "waiting";
|
|
1259
|
+
details.push(`${name} is in allowedTools but the Supermemory backend exposes no chronological journal surface - the tool will not be offered.`);
|
|
1260
|
+
}
|
|
1261
|
+
continue;
|
|
1262
|
+
}
|
|
1263
|
+
if (isRememberToolPolicyName(name)) {
|
|
1264
|
+
// Reconcile with the runtime's own resolution, which honours the
|
|
1265
|
+
// `mcp__mono-agent-memory-write__*` spellings and deny-wins. This check
|
|
1266
|
+
// must precede the generic MCP branch below.
|
|
1267
|
+
if (!isRememberToolAllowed(config.tools)) {
|
|
1268
|
+
status = "waiting";
|
|
1269
|
+
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.`);
|
|
1270
|
+
continue;
|
|
1271
|
+
}
|
|
1272
|
+
// Unlike MemoryRecall, Remember IS allowlist-gated, so listing it is
|
|
1273
|
+
// correct and required under a restrictive policy. It still needs a
|
|
1274
|
+
// configured memory block with the write surface left enabled.
|
|
1275
|
+
if (config.memory === undefined) {
|
|
1276
|
+
status = "waiting";
|
|
1277
|
+
details.push(`${name} is in allowedTools but no memory block is configured - durable writes will not work. Configure memory (or remove this entry).`);
|
|
1278
|
+
}
|
|
1279
|
+
else if (config.memory.rememberTool?.enabled === false) {
|
|
1280
|
+
status = "waiting";
|
|
1281
|
+
details.push(`${name} is in allowedTools but memory.rememberTool.enabled is off - durable writes will not work. Enable memory.rememberTool (or remove this entry).`);
|
|
1282
|
+
}
|
|
1283
|
+
else if (config.memory.backend === "supermemory") {
|
|
1284
|
+
status = "waiting";
|
|
1285
|
+
details.push(`${name} is in allowedTools but the supermemory backend exposes no durable write surface - the tool will not be offered.`);
|
|
1286
|
+
}
|
|
1287
|
+
continue;
|
|
1288
|
+
}
|
|
1653
1289
|
if (isMcpToolName(name)) {
|
|
1654
1290
|
// MCP tool names are owned by their servers; we cannot verify them offline.
|
|
1655
1291
|
if (!mcpNoteAdded) {
|
|
@@ -1685,45 +1321,6 @@ async function toolsSection(config, input) {
|
|
|
1685
1321
|
// Under allow-all the disallow list is already folded into the "except" clause above.
|
|
1686
1322
|
details.push(`Disallowed tools: ${config.tools.disallowedTools.join(", ")}.`);
|
|
1687
1323
|
}
|
|
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
1324
|
let configuredMcpServerNames = [];
|
|
1728
1325
|
let configuredMcpServers = {};
|
|
1729
1326
|
if (config.tools.mcpConfigPath !== undefined) {
|
|
@@ -1781,7 +1378,6 @@ async function toolsSection(config, input) {
|
|
|
1781
1378
|
const adapterSendTools = await resolveAdapterSendToolsSettings(input, {
|
|
1782
1379
|
allowedTools: config.tools.allowedTools,
|
|
1783
1380
|
disallowedTools: config.tools.disallowedTools,
|
|
1784
|
-
suppressInteractionTools: directOpenCodeModels.length > 0,
|
|
1785
1381
|
});
|
|
1786
1382
|
if (adapterSendTools === undefined) {
|
|
1787
1383
|
details.push("No adapter-derived send tools enabled.");
|
|
@@ -1789,19 +1385,13 @@ async function toolsSection(config, input) {
|
|
|
1789
1385
|
else {
|
|
1790
1386
|
details.push(`Adapter send tools: ${adapterSendToolNames(adapterSendTools).join(", ")}.`);
|
|
1791
1387
|
}
|
|
1792
|
-
const blockedAdapterEndpoints = await adapterSendToolNetworkPolicyWarnings(config, input, adapterSendTools
|
|
1388
|
+
const blockedAdapterEndpoints = await adapterSendToolNetworkPolicyWarnings(config, input, adapterSendTools);
|
|
1793
1389
|
if (blockedAdapterEndpoints.length > 0) {
|
|
1794
1390
|
if (status !== "error") {
|
|
1795
1391
|
status = "waiting";
|
|
1796
1392
|
}
|
|
1797
1393
|
details.push(...blockedAdapterEndpoints);
|
|
1798
1394
|
}
|
|
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
1395
|
// Subagents: the Agent tool is registered only when BOTH the capability is
|
|
1806
1396
|
// enabled and the tool is allowed, so surface either half being missing.
|
|
1807
1397
|
const subagents = config.subagents;
|
|
@@ -1824,14 +1414,6 @@ async function toolsSection(config, input) {
|
|
|
1824
1414
|
}
|
|
1825
1415
|
else {
|
|
1826
1416
|
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
1417
|
}
|
|
1836
1418
|
for (const tool of subagents.inline?.allowedTools ?? []) {
|
|
1837
1419
|
if (!isKnownToolName(tool) && !isMcpToolName(tool)) {
|
|
@@ -1881,31 +1463,61 @@ async function toolsSection(config, input) {
|
|
|
1881
1463
|
return { id: "tools", label: "Tools & MCP", status, details };
|
|
1882
1464
|
}
|
|
1883
1465
|
const MIN_AGENT_BROWSER_VERSION = [0, 33, 1];
|
|
1466
|
+
function formatSearchBackendChain(backends) {
|
|
1467
|
+
if (backends.length <= 1)
|
|
1468
|
+
return backends[0] ?? "none";
|
|
1469
|
+
return `${backends.slice(0, -1).join(", ")}, then ${backends.at(-1)}`;
|
|
1470
|
+
}
|
|
1884
1471
|
async function webToolsSection(config, input, liveness) {
|
|
1885
1472
|
const web = config.tools.web;
|
|
1886
1473
|
const search = web?.search ?? {
|
|
1887
1474
|
backend: "auto",
|
|
1475
|
+
maxRequestsPerRun: 4,
|
|
1888
1476
|
codex: { model: "gpt-5.6-luna" },
|
|
1889
1477
|
};
|
|
1890
1478
|
const fetchConfig = web?.fetch ?? { render: "never", browserCommand: "agent-browser" };
|
|
1891
|
-
const details = [
|
|
1479
|
+
const details = [
|
|
1480
|
+
`WebSearch backend: ${search.backend}.`,
|
|
1481
|
+
`WebSearch request budget: ${search.maxRequestsPerRun ?? 4} per logical run.`,
|
|
1482
|
+
`Web coordination: ${web?.coordination ?? "process"}.`,
|
|
1483
|
+
];
|
|
1892
1484
|
let status = "ok";
|
|
1893
|
-
if (
|
|
1485
|
+
if (web?.coordination === "host") {
|
|
1486
|
+
try {
|
|
1487
|
+
const shared = await inspectWebControl();
|
|
1488
|
+
details.push(shared.exists ? "Host web control state is valid." : "Host web control state will be initialized on first request.");
|
|
1489
|
+
}
|
|
1490
|
+
catch {
|
|
1491
|
+
status = "waiting";
|
|
1492
|
+
details.push("Host web control state is unavailable; web requests will fail closed. Inspect with mono-agent web-control status.");
|
|
1493
|
+
}
|
|
1494
|
+
}
|
|
1495
|
+
const searxngEndpoint = search.searxng?.endpoint ?? search.endpoint;
|
|
1496
|
+
const autoEligibleBackends = [
|
|
1497
|
+
...(search.ollama === undefined ? [] : ["configured Ollama"]),
|
|
1498
|
+
...(searxngEndpoint === undefined ? [] : ["configured SearXNG"]),
|
|
1499
|
+
"Codex",
|
|
1500
|
+
"keyless",
|
|
1501
|
+
];
|
|
1502
|
+
const autoAfterOllama = formatSearchBackendChain(autoEligibleBackends.slice(1));
|
|
1503
|
+
if (searxngEndpoint === undefined) {
|
|
1894
1504
|
details.push(search.backend === "keyless"
|
|
1895
1505
|
? "SearXNG is not configured; keyless search is enabled."
|
|
1896
1506
|
: search.backend === "codex"
|
|
1897
1507
|
? "SearXNG is not configured; strict Codex subscription search is enabled."
|
|
1898
1508
|
: search.backend === "searxng"
|
|
1899
1509
|
? "SearXNG is not configured."
|
|
1900
|
-
:
|
|
1510
|
+
: search.ollama === undefined
|
|
1511
|
+
? "SearXNG is not configured; auto mode starts with Codex subscription search, then keyless search."
|
|
1512
|
+
: "SearXNG is not configured; auto mode uses configured Ollama, then Codex subscription search, then keyless search.");
|
|
1901
1513
|
}
|
|
1902
1514
|
else {
|
|
1903
|
-
details.push(`SearXNG endpoint: ${
|
|
1515
|
+
details.push(`SearXNG endpoint: ${searxngEndpoint}.`);
|
|
1904
1516
|
if (!liveness) {
|
|
1905
1517
|
details.push("SearXNG liveness was not probed.");
|
|
1906
1518
|
}
|
|
1907
1519
|
else {
|
|
1908
|
-
const probe = await probeSearxngEndpoint(
|
|
1520
|
+
const probe = await probeSearxngEndpoint(searxngEndpoint);
|
|
1909
1521
|
if (probe.ok) {
|
|
1910
1522
|
details.push("SearXNG JSON search probe succeeded.");
|
|
1911
1523
|
}
|
|
@@ -1913,14 +1525,42 @@ async function webToolsSection(config, input, liveness) {
|
|
|
1913
1525
|
status = "waiting";
|
|
1914
1526
|
details.push(`[WARN] SearXNG JSON search probe failed (${probe.reason}). ` +
|
|
1915
1527
|
(search.backend === "auto"
|
|
1916
|
-
? "Start the local companion or fix tools.web.search.endpoint; auto mode can still fall back to Codex subscription search, then keyless search."
|
|
1917
|
-
: "Start the local companion or fix tools.web.search.endpoint; strict SearXNG mode has no fallback."));
|
|
1528
|
+
? "Start the local companion or fix tools.web.search.searxng.endpoint; auto mode can still fall back to Codex subscription search, then keyless search."
|
|
1529
|
+
: "Start the local companion or fix tools.web.search.searxng.endpoint; strict SearXNG mode has no fallback."));
|
|
1918
1530
|
}
|
|
1919
1531
|
}
|
|
1920
1532
|
}
|
|
1533
|
+
if (search.backend === "ollama" || (search.backend === "auto" && search.ollama !== undefined)) {
|
|
1534
|
+
const ollama = search.ollama;
|
|
1535
|
+
if (ollama === undefined) {
|
|
1536
|
+
status = "waiting";
|
|
1537
|
+
details.push("[WARN] Ollama Web Search is selected but its resolved configuration is missing.");
|
|
1538
|
+
}
|
|
1539
|
+
else if (!liveness) {
|
|
1540
|
+
details.push(`Ollama Web Search origin: ${ollama.baseUrl}. ${search.backend === "ollama" ? "Strict Ollama mode has no fallback." : `Auto mode advances to ${autoAfterOllama} when Ollama is unavailable.`}`);
|
|
1541
|
+
details.push("Ollama Web Search liveness was not probed.");
|
|
1542
|
+
}
|
|
1543
|
+
else {
|
|
1544
|
+
details.push(`Ollama Web Search origin: ${ollama.baseUrl}. ${search.backend === "ollama" ? "Strict Ollama mode has no fallback." : `Auto mode advances to ${autoAfterOllama} when Ollama is unavailable.`}`);
|
|
1545
|
+
const probe = await probeOllamaWebSearch(ollama);
|
|
1546
|
+
if (probe.ok)
|
|
1547
|
+
details.push("Ollama Web Search JSON probe succeeded.");
|
|
1548
|
+
else {
|
|
1549
|
+
status = "waiting";
|
|
1550
|
+
details.push(`[WARN] Ollama Web Search probe failed (${probe.reason}). ${search.backend === "ollama" ? "Strict Ollama mode has no fallback." : `Auto mode can still advance to ${autoAfterOllama}.`}`);
|
|
1551
|
+
}
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
else if (search.ollama !== undefined) {
|
|
1555
|
+
details.push(`Ollama Web Search is configured but inactive because backend ${search.backend} is strict.`);
|
|
1556
|
+
}
|
|
1921
1557
|
if (search.backend === "auto") {
|
|
1922
1558
|
const codexModel = search.codex?.model ?? "gpt-5.6-luna";
|
|
1923
|
-
|
|
1559
|
+
const beforeCodex = autoEligibleBackends.slice(0, autoEligibleBackends.indexOf("Codex"));
|
|
1560
|
+
const codexPosition = beforeCodex.length === 0
|
|
1561
|
+
? "as the first eligible backend"
|
|
1562
|
+
: `after ${formatSearchBackendChain(beforeCodex)}`;
|
|
1563
|
+
details.push(`Codex subscription fallback model: ${codexModel}; readiness is checked lazily when auto mode reaches it ${codexPosition}.`);
|
|
1924
1564
|
}
|
|
1925
1565
|
else if (search.backend === "codex") {
|
|
1926
1566
|
const codexModel = search.codex?.model ?? "gpt-5.6-luna";
|
|
@@ -1960,6 +1600,95 @@ async function webToolsSection(config, input, liveness) {
|
|
|
1960
1600
|
}
|
|
1961
1601
|
return { id: "web-tools", label: "Web search & fetch", status, details };
|
|
1962
1602
|
}
|
|
1603
|
+
async function probeOllamaWebSearch(config) {
|
|
1604
|
+
const ctrl = new AbortController();
|
|
1605
|
+
const timer = setTimeout(() => { ctrl.abort(); }, LIVENESS_PROBE_TIMEOUT_MS);
|
|
1606
|
+
const official = config.baseUrl === "https://ollama.com";
|
|
1607
|
+
const paths = official ? ["/api/web_search"] : ["/api/experimental/web_search", "/api/web_search"];
|
|
1608
|
+
try {
|
|
1609
|
+
for (let index = 0; index < paths.length; index += 1) {
|
|
1610
|
+
const response = await fetch(`${config.baseUrl}${paths[index]}`, {
|
|
1611
|
+
method: "POST",
|
|
1612
|
+
headers: {
|
|
1613
|
+
Accept: "application/json",
|
|
1614
|
+
"Content-Type": "application/json",
|
|
1615
|
+
...(official ? { Authorization: `Bearer ${config.apiKey}` } : {}),
|
|
1616
|
+
},
|
|
1617
|
+
body: JSON.stringify({ query: "mono-agent doctor", max_results: 1 }),
|
|
1618
|
+
redirect: "error",
|
|
1619
|
+
signal: ctrl.signal,
|
|
1620
|
+
});
|
|
1621
|
+
if (!official && index === 0 && [404, 405].includes(response.status)) {
|
|
1622
|
+
await response.body?.cancel();
|
|
1623
|
+
continue;
|
|
1624
|
+
}
|
|
1625
|
+
if ([401, 403].includes(response.status)) {
|
|
1626
|
+
await response.body?.cancel();
|
|
1627
|
+
return { ok: false, reason: official
|
|
1628
|
+
? `hosted authentication failed; check ${config.apiKeyEnv}`
|
|
1629
|
+
: "the local proxy requires sign-in; run ollama signin or repair its proxy authentication" };
|
|
1630
|
+
}
|
|
1631
|
+
if (!response.ok) {
|
|
1632
|
+
await response.body?.cancel();
|
|
1633
|
+
return { ok: false, reason: `HTTP ${response.status}` };
|
|
1634
|
+
}
|
|
1635
|
+
const declared = Number(response.headers.get("content-length"));
|
|
1636
|
+
if (Number.isFinite(declared) && declared > OLLAMA_WEB_SEARCH_PROBE_MAX_BYTES) {
|
|
1637
|
+
await response.body?.cancel();
|
|
1638
|
+
return { ok: false, reason: `response exceeded ${OLLAMA_WEB_SEARCH_PROBE_MAX_BYTES} bytes` };
|
|
1639
|
+
}
|
|
1640
|
+
const bytes = await readBoundedProbeResponse(response, OLLAMA_WEB_SEARCH_PROBE_MAX_BYTES);
|
|
1641
|
+
let body;
|
|
1642
|
+
try {
|
|
1643
|
+
body = JSON.parse(new TextDecoder().decode(bytes));
|
|
1644
|
+
}
|
|
1645
|
+
catch {
|
|
1646
|
+
return { ok: false, reason: "response was not valid JSON" };
|
|
1647
|
+
}
|
|
1648
|
+
if (!body || typeof body !== "object" || !Array.isArray(body.results)) {
|
|
1649
|
+
return { ok: false, reason: "response did not contain a results array" };
|
|
1650
|
+
}
|
|
1651
|
+
return { ok: true, reason: "" };
|
|
1652
|
+
}
|
|
1653
|
+
return { ok: false, reason: "this Ollama version supports neither local Web Search endpoint" };
|
|
1654
|
+
}
|
|
1655
|
+
catch (error) {
|
|
1656
|
+
return { ok: false, reason: error instanceof Error ? error.message : String(error) };
|
|
1657
|
+
}
|
|
1658
|
+
finally {
|
|
1659
|
+
clearTimeout(timer);
|
|
1660
|
+
}
|
|
1661
|
+
}
|
|
1662
|
+
async function readBoundedProbeResponse(response, maxBytes) {
|
|
1663
|
+
const reader = response.body?.getReader();
|
|
1664
|
+
if (reader === undefined)
|
|
1665
|
+
throw new Error("response body could not be read safely");
|
|
1666
|
+
const chunks = [];
|
|
1667
|
+
let total = 0;
|
|
1668
|
+
try {
|
|
1669
|
+
while (true) {
|
|
1670
|
+
const next = await reader.read();
|
|
1671
|
+
if (next.done)
|
|
1672
|
+
break;
|
|
1673
|
+
total += next.value.byteLength;
|
|
1674
|
+
if (total > maxBytes) {
|
|
1675
|
+
await reader.cancel();
|
|
1676
|
+
throw new Error(`response exceeded ${maxBytes} bytes`);
|
|
1677
|
+
}
|
|
1678
|
+
chunks.push(next.value);
|
|
1679
|
+
}
|
|
1680
|
+
}
|
|
1681
|
+
finally {
|
|
1682
|
+
reader.releaseLock();
|
|
1683
|
+
}
|
|
1684
|
+
const joined = new Uint8Array(total);
|
|
1685
|
+
let offset = 0;
|
|
1686
|
+
for (const chunk of chunks) {
|
|
1687
|
+
joined.set(chunk, offset);
|
|
1688
|
+
offset += chunk.byteLength;
|
|
1689
|
+
}
|
|
1690
|
+
return joined;
|
|
1691
|
+
}
|
|
1963
1692
|
async function probeSearxngEndpoint(endpoint) {
|
|
1964
1693
|
const ctrl = new AbortController();
|
|
1965
1694
|
const timer = setTimeout(() => { ctrl.abort(); }, LIVENESS_PROBE_TIMEOUT_MS);
|
|
@@ -2001,14 +1730,14 @@ async function probeSearxngEndpoint(endpoint) {
|
|
|
2001
1730
|
}
|
|
2002
1731
|
}
|
|
2003
1732
|
async function readAgentBrowserVersion(command, input) {
|
|
2004
|
-
const run = input.
|
|
1733
|
+
const run = input.statusExecFile ?? defaultDoctorStatusExecFile;
|
|
2005
1734
|
try {
|
|
2006
1735
|
const { stdout } = await run(command, ["--version"], {
|
|
2007
1736
|
cwd: input.cwd,
|
|
2008
1737
|
env: Object.fromEntries(["PATH", "LANG", "LC_ALL", "LC_CTYPE", "SHELL", "TMPDIR", "TMP", "TEMP"]
|
|
2009
1738
|
.flatMap((key) => typeof input.env[key] === "string" ? [[key, input.env[key]]] : [])),
|
|
2010
|
-
timeout:
|
|
2011
|
-
maxBuffer:
|
|
1739
|
+
timeout: DOCTOR_STATUS_TIMEOUT_MS,
|
|
1740
|
+
maxBuffer: DOCTOR_STATUS_MAX_BUFFER_BYTES,
|
|
2012
1741
|
encoding: "utf8",
|
|
2013
1742
|
});
|
|
2014
1743
|
const match = /(?:^|\s)v?(\d+)\.(\d+)\.(\d+)(?:\s|$)/u.exec(stdout.trim());
|
|
@@ -2166,7 +1895,7 @@ async function processJobsSection(config, input) {
|
|
|
2166
1895
|
`Owner-only local state: ${settings.stateDir}.`,
|
|
2167
1896
|
`Concurrency: ${String(settings.maxConcurrent)} global, ${String(settings.maxActivePerConversation)} per conversation, ${String(settings.maxQueued)} queued.`,
|
|
2168
1897
|
`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.
|
|
1898
|
+
`Runtime availability: Pi-native Exec/Bash only; configured primary provider is ${displayText(config.runtime.model.provider)}.`,
|
|
2170
1899
|
];
|
|
2171
1900
|
const inspection = await inspectProcessJobState(input.cwd, settings.stateDir);
|
|
2172
1901
|
return {
|
|
@@ -3205,7 +2934,7 @@ function formatContinuationHost(host) {
|
|
|
3205
2934
|
* needs the remote adapter API (or the app-owned interaction bridge) admitted
|
|
3206
2935
|
* by sandbox.network.
|
|
3207
2936
|
*/
|
|
3208
|
-
async function adapterSendToolNetworkPolicyWarnings(config, input, settings
|
|
2937
|
+
async function adapterSendToolNetworkPolicyWarnings(config, input, settings) {
|
|
3209
2938
|
if (config.sandbox === undefined || config.sandbox.mode !== "native") {
|
|
3210
2939
|
return [];
|
|
3211
2940
|
}
|
|
@@ -3225,7 +2954,7 @@ async function adapterSendToolNetworkPolicyWarnings(config, input, settings, sup
|
|
|
3225
2954
|
url: settings.telegram.apiRoot ?? "https://api.telegram.org",
|
|
3226
2955
|
});
|
|
3227
2956
|
}
|
|
3228
|
-
const askUserAllowed =
|
|
2957
|
+
const askUserAllowed = isAdapterSendToolAllowed("AskUser", {
|
|
3229
2958
|
allowedTools: config.tools.allowedTools,
|
|
3230
2959
|
disallowedTools: config.tools.disallowedTools,
|
|
3231
2960
|
});
|
|
@@ -3273,100 +3002,7 @@ function endpointHost(url) {
|
|
|
3273
3002
|
return url;
|
|
3274
3003
|
}
|
|
3275
3004
|
}
|
|
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
3005
|
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
3006
|
if (config.sandbox === undefined) {
|
|
3371
3007
|
return { id: "sandbox", label: "Sandbox", status: "disabled", details: ["No sandbox policy configured."] };
|
|
3372
3008
|
}
|
|
@@ -3394,44 +3030,6 @@ async function sandboxSection(config, engine) {
|
|
|
3394
3030
|
details,
|
|
3395
3031
|
};
|
|
3396
3032
|
}
|
|
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
3033
|
async function channelSection(driver, input) {
|
|
3436
3034
|
const id = `channel:${driver.id}`;
|
|
3437
3035
|
try {
|
|
@@ -3464,6 +3062,44 @@ async function channelSection(driver, input) {
|
|
|
3464
3062
|
function referenceOf(model) {
|
|
3465
3063
|
return modelReferenceKey(model);
|
|
3466
3064
|
}
|
|
3065
|
+
/**
|
|
3066
|
+
* Bounded, single-line rendering of operator-supplied text for a diagnostic.
|
|
3067
|
+
*
|
|
3068
|
+
* `referenceOf` is the canonical KEY -- it is compared against
|
|
3069
|
+
* `verifiedCredentialModelRefs`, which carries whole references -- so it must
|
|
3070
|
+
* stay unbounded. What gets PRINTED must not be. The parser deliberately has no
|
|
3071
|
+
* length ceiling any more (a reference cannot be truncated into validity, so a
|
|
3072
|
+
* ceiling there could only refuse a model that runs), which means acceptance
|
|
3073
|
+
* stopped being a length guarantee: a 500,000-byte reference parses today and
|
|
3074
|
+
* every one of these lines is echoed by `mono-agent validate`, the daemon log
|
|
3075
|
+
* and launchd's captured stdout. Escaping comes with the clamp, so a reference
|
|
3076
|
+
* carrying a newline also cannot forge a detail line that reads as doctor's own.
|
|
3077
|
+
*/
|
|
3078
|
+
function displayText(value) {
|
|
3079
|
+
return sanitizeModelReferenceText(value, MODEL_REFERENCE_ECHO_MAX_BYTES);
|
|
3080
|
+
}
|
|
3081
|
+
/** Bounded display form of a model reference. Never use it as a map or set key. */
|
|
3082
|
+
function displayReferenceOf(model) {
|
|
3083
|
+
return displayText(referenceOf(model));
|
|
3084
|
+
}
|
|
3085
|
+
/**
|
|
3086
|
+
* Bounded display form of a thrown reason. A reason gets the larger budget because it is one
|
|
3087
|
+
* fixed repair sentence plus at most one echo of the value.
|
|
3088
|
+
*
|
|
3089
|
+
* Unwrap one layer first, as `modelReferenceReason` in @mono-agent/config and `reasonOf` in
|
|
3090
|
+
* trigger-overrides already do: the adapter's `message` wraps a 32-byte generic prefix around
|
|
3091
|
+
* a reason ALREADY at the full reason budget, so clamping the wrapped form spends the budget
|
|
3092
|
+
* on the prefix and pays for it out of the tail -- which is where the kernel parser puts the
|
|
3093
|
+
* concrete repair whenever it quotes the operator's value first.
|
|
3094
|
+
*/
|
|
3095
|
+
function displayReason(error) {
|
|
3096
|
+
const reason = error instanceof RuntimeAdapterError && typeof error.details.reason === "string"
|
|
3097
|
+
? error.details.reason
|
|
3098
|
+
: error instanceof Error
|
|
3099
|
+
? error.message
|
|
3100
|
+
: String(error);
|
|
3101
|
+
return sanitizeModelReferenceText(reason, MODEL_REFERENCE_REASON_MAX_BYTES);
|
|
3102
|
+
}
|
|
3467
3103
|
async function pathExists(path) {
|
|
3468
3104
|
try {
|
|
3469
3105
|
await stat(path);
|