diff --git a/src/agents/pi-embedded-runner/compact.ts b/src/agents/pi-embedded-runner/compact.ts index 2a7c839455e..d8c23135bc9 100644 --- a/src/agents/pi-embedded-runner/compact.ts +++ b/src/agents/pi-embedded-runner/compact.ts @@ -103,6 +103,7 @@ import { getDmHistoryLimitFromSessionKey, limitHistoryTurns } from "./history.js import { resolveGlobalLane, resolveSessionLane } from "./lanes.js"; import { log } from "./logger.js"; import { buildEmbeddedMessageActionDiscoveryInput } from "./message-action-discovery-input.js"; +import { readPiModelContextTokens } from "./model-context-tokens.js"; import { buildModelAliasLines, resolveModelAsync } from "./model.js"; import { sanitizeSessionHistory, validateReplayTurns } from "./replay-history.js"; import { buildEmbeddedSandboxInfo } from "./sandbox-info.js"; @@ -443,7 +444,7 @@ export async function compactEmbeddedPiSessionDirect( cfg: params.config, provider, modelId, - modelContextTokens: runtimeModelWithContext.contextTokens, + modelContextTokens: readPiModelContextTokens(runtimeModel), modelContextWindow: runtimeModelWithContext.contextWindow, defaultTokens: DEFAULT_CONTEXT_TOKENS, }); @@ -1035,7 +1036,7 @@ export async function compactEmbeddedPiSession( cfg: params.config, provider: ceProvider, modelId: ceModelId, - modelContextTokens: ceRuntimeModel?.contextTokens, + modelContextTokens: readPiModelContextTokens(ceModel), modelContextWindow: ceRuntimeModel?.contextWindow, defaultTokens: DEFAULT_CONTEXT_TOKENS, }); diff --git a/src/agents/pi-embedded-runner/model-context-tokens.ts b/src/agents/pi-embedded-runner/model-context-tokens.ts new file mode 100644 index 00000000000..1aa623d3f41 --- /dev/null +++ b/src/agents/pi-embedded-runner/model-context-tokens.ts @@ -0,0 +1,10 @@ +import type { Api, Model } from "@mariozechner/pi-ai"; + +type PiModelWithOptionalContextTokens = Model & { + contextTokens?: number; +}; + +export function readPiModelContextTokens(model: Model | null | undefined): number | undefined { + const value = (model as PiModelWithOptionalContextTokens | null | undefined)?.contextTokens; + return typeof value === "number" && Number.isFinite(value) ? value : undefined; +} diff --git a/src/agents/pi-embedded-runner/run/setup.ts b/src/agents/pi-embedded-runner/run/setup.ts index adf493409fc..f5494832b1a 100644 --- a/src/agents/pi-embedded-runner/run/setup.ts +++ b/src/agents/pi-embedded-runner/run/setup.ts @@ -12,6 +12,7 @@ import { import { DEFAULT_CONTEXT_TOKENS } from "../../defaults.js"; import { FailoverError } from "../../failover-error.js"; import { log } from "../logger.js"; +import { readPiModelContextTokens } from "../model-context-tokens.js"; type HookContext = { agentId?: string; @@ -107,7 +108,7 @@ export function resolveEffectiveRuntimeModel(params: { cfg: params.cfg, provider: params.provider, modelId: params.modelId, - modelContextTokens: params.runtimeModel.contextTokens, + modelContextTokens: readPiModelContextTokens(params.runtimeModel), modelContextWindow: params.runtimeModel.contextWindow, defaultTokens: DEFAULT_CONTEXT_TOKENS, }); diff --git a/src/channels/config-presence.test.ts b/src/channels/config-presence.test.ts index ee5785e04af..76fb098fdbc 100644 --- a/src/channels/config-presence.test.ts +++ b/src/channels/config-presence.test.ts @@ -56,4 +56,19 @@ describe("config presence", () => { expectedConfigured: false, }); }); + + it("detects env-only channel config", () => { + const stateDir = makeTempStateDir(); + const env = { + OPENCLAW_STATE_DIR: stateDir, + MATRIX_ACCESS_TOKEN: "token", + } as NodeJS.ProcessEnv; + + expectPotentialConfiguredChannelCase({ + cfg: {}, + env, + expectedIds: ["matrix"], + expectedConfigured: true, + }); + }); }); diff --git a/src/channels/config-presence.ts b/src/channels/config-presence.ts index 1d3f0680088..78ce5760a8a 100644 --- a/src/channels/config-presence.ts +++ b/src/channels/config-presence.ts @@ -1,4 +1,8 @@ +import fs from "node:fs"; +import os from "node:os"; import type { OpenClawConfig } from "../config/config.js"; +import { resolveStateDir } from "../config/paths.js"; +import { listBundledChannelPluginIds } from "./plugins/bundled-ids.js"; import { listBundledChannelPlugins } from "./plugins/bundled.js"; const IGNORED_CHANNEL_CONFIG_KEYS = new Set(["defaults", "modelByChannel"]); @@ -19,12 +23,16 @@ export function hasMeaningfulChannelConfig(value: unknown): boolean { } function listConfiguredChannelEnvPrefixes(): Array<[prefix: string, channelId: string]> { - return listBundledChannelPlugins().map((plugin) => [ - `${plugin.id.replace(/[^a-z0-9]+/gi, "_").toUpperCase()}_`, - plugin.id, + return listBundledChannelPluginIds().map((channelId) => [ + `${channelId.replace(/[^a-z0-9]+/gi, "_").toUpperCase()}_`, + channelId, ]); } +function hasPersistedChannelState(env: NodeJS.ProcessEnv): boolean { + return fs.existsSync(resolveStateDir(env, os.homedir)); +} + export function listPotentialConfiguredChannelIds( cfg: OpenClawConfig, env: NodeJS.ProcessEnv = process.env, @@ -53,9 +61,11 @@ export function listPotentialConfiguredChannelIds( } } } - for (const plugin of listBundledChannelPlugins()) { - if (plugin.config?.hasPersistedAuthState?.({ cfg, env })) { - configuredChannelIds.add(plugin.id); + if (hasPersistedChannelState(env)) { + for (const plugin of listBundledChannelPlugins()) { + if (plugin.config?.hasPersistedAuthState?.({ cfg, env })) { + configuredChannelIds.add(plugin.id); + } } } return [...configuredChannelIds]; @@ -71,6 +81,9 @@ function hasEnvConfiguredChannel(cfg: OpenClawConfig, env: NodeJS.ProcessEnv): b return true; } } + if (!hasPersistedChannelState(env)) { + return false; + } return listBundledChannelPlugins().some((plugin) => Boolean(plugin.config?.hasPersistedAuthState?.({ cfg, env })), ); diff --git a/src/channels/plugins/bundled-ids.ts b/src/channels/plugins/bundled-ids.ts new file mode 100644 index 00000000000..7bccb4c7717 --- /dev/null +++ b/src/channels/plugins/bundled-ids.ts @@ -0,0 +1,13 @@ +import { listBundledPluginMetadata } from "../../plugins/bundled-plugin-metadata.js"; + +export const BUNDLED_CHANNEL_PLUGIN_IDS = listBundledPluginMetadata({ + includeChannelConfigs: false, + includeSyntheticChannelConfigs: false, +}) + .filter(({ manifest }) => Array.isArray(manifest.channels) && manifest.channels.length > 0) + .map(({ manifest }) => manifest.id) + .toSorted((left, right) => left.localeCompare(right)); + +export function listBundledChannelPluginIds(): string[] { + return [...BUNDLED_CHANNEL_PLUGIN_IDS]; +}