mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 16:40:49 +00:00
fix: route read-only channel detection
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { resolveAgentWorkspaceDir, resolveDefaultAgentId } from "../../agents/agent-scope.js";
|
||||
import type { OpenClawConfig } from "../../config/types.openclaw.js";
|
||||
import {
|
||||
listConfiguredChannelIdsForPluginScope,
|
||||
listConfiguredChannelIdsForReadOnlyScope,
|
||||
resolveDiscoverableScopedChannelPluginIds,
|
||||
} from "../../plugins/channel-plugin-ids.js";
|
||||
import { loadOpenClawPlugins } from "../../plugins/loader.js";
|
||||
@@ -141,7 +141,7 @@ export function listReadOnlyChannelPluginsForConfig(
|
||||
});
|
||||
const configuredChannelIds = [
|
||||
...new Set(
|
||||
listConfiguredChannelIdsForPluginScope({
|
||||
listConfiguredChannelIdsForReadOnlyScope({
|
||||
config: cfg,
|
||||
workspaceDir,
|
||||
env,
|
||||
|
||||
@@ -10,6 +10,7 @@ const mocks = vi.hoisted(() => ({
|
||||
readConfigFileSnapshot: vi.fn(async () => ({ path: "/tmp/openclaw.json" })),
|
||||
requireValidConfigSnapshot: vi.fn(),
|
||||
listChannelPlugins: vi.fn(),
|
||||
listConfiguredChannelIdsForReadOnlyScope: vi.fn((_params: unknown) => ["discord"]),
|
||||
withProgress: vi.fn(async (_opts: unknown, run: () => Promise<unknown>) => await run()),
|
||||
}));
|
||||
|
||||
@@ -33,8 +34,9 @@ vi.mock("../config/config.js", () => ({
|
||||
readConfigFileSnapshot: () => mocks.readConfigFileSnapshot(),
|
||||
}));
|
||||
|
||||
vi.mock("../channels/config-presence.js", () => ({
|
||||
listPotentialConfiguredChannelIds: () => ["discord"],
|
||||
vi.mock("../plugins/channel-plugin-ids.js", () => ({
|
||||
listConfiguredChannelIdsForReadOnlyScope: (params: unknown) =>
|
||||
mocks.listConfiguredChannelIdsForReadOnlyScope(params),
|
||||
}));
|
||||
|
||||
vi.mock("./channels/shared.js", () => ({
|
||||
@@ -192,6 +194,8 @@ describe("channelsStatusCommand SecretRef fallback flow", () => {
|
||||
mocks.readConfigFileSnapshot.mockClear();
|
||||
mocks.requireValidConfigSnapshot.mockReset();
|
||||
mocks.listChannelPlugins.mockReset();
|
||||
mocks.listConfiguredChannelIdsForReadOnlyScope.mockClear();
|
||||
mocks.listConfiguredChannelIdsForReadOnlyScope.mockReturnValue(["discord"]);
|
||||
mocks.withProgress.mockClear();
|
||||
mocks.listChannelPlugins.mockReturnValue([createTokenOnlyPlugin()]);
|
||||
});
|
||||
@@ -259,6 +263,12 @@ describe("channelsStatusCommand SecretRef fallback flow", () => {
|
||||
await channelsStatusCommand({ json: true, probe: false }, runtime as never);
|
||||
|
||||
expect(mocks.listChannelPlugins).not.toHaveBeenCalled();
|
||||
expect(mocks.listConfiguredChannelIdsForReadOnlyScope).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
config: expect.objectContaining({ secretResolved: true }),
|
||||
includePersistedAuthState: false,
|
||||
}),
|
||||
);
|
||||
const payload = JSON.parse(logs.at(-1) ?? "{}");
|
||||
expect(payload).toEqual(
|
||||
expect.objectContaining({
|
||||
|
||||
150
src/commands/channels.status.external-env.test.ts
Normal file
150
src/commands/channels.status.external-env.test.ts
Normal file
@@ -0,0 +1,150 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { afterAll, afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
import {
|
||||
cleanupPluginLoaderFixturesForTest,
|
||||
EMPTY_PLUGIN_SCHEMA,
|
||||
makeTempDir,
|
||||
resetPluginLoaderTestStateForTest,
|
||||
useNoBundledPlugins,
|
||||
} from "../plugins/loader.test-fixtures.js";
|
||||
import { withEnvAsync } from "../test-utils/env.js";
|
||||
import { channelsStatusCommand } from "./channels/status.js";
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
callGateway: vi.fn(),
|
||||
readConfigFileSnapshot: vi.fn(async () => ({ path: "/tmp/openclaw.json" })),
|
||||
requireValidConfigSnapshot: vi.fn(),
|
||||
resolveCommandConfigWithSecrets: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("../gateway/call.js", () => ({
|
||||
callGateway: (opts: unknown) => mocks.callGateway(opts),
|
||||
}));
|
||||
|
||||
vi.mock("../config/config.js", () => ({
|
||||
readConfigFileSnapshot: () => mocks.readConfigFileSnapshot(),
|
||||
}));
|
||||
|
||||
vi.mock("../cli/command-config-resolution.js", () => ({
|
||||
resolveCommandConfigWithSecrets: (opts: unknown) => mocks.resolveCommandConfigWithSecrets(opts),
|
||||
}));
|
||||
|
||||
vi.mock("./channels/shared.js", () => ({
|
||||
requireValidConfigSnapshot: (runtime: unknown) => mocks.requireValidConfigSnapshot(runtime),
|
||||
formatChannelAccountLabel: ({ channel, accountId }: { channel: string; accountId: string }) =>
|
||||
`${channel} ${accountId}`,
|
||||
appendBaseUrlBit: () => undefined,
|
||||
appendEnabledConfiguredLinkedBits: () => undefined,
|
||||
appendModeBit: () => undefined,
|
||||
appendTokenSourceBits: () => undefined,
|
||||
buildChannelAccountLine: () => "",
|
||||
}));
|
||||
|
||||
vi.mock("../cli/progress.js", () => ({
|
||||
withProgress: async (_opts: unknown, run: () => Promise<unknown>) => await run(),
|
||||
}));
|
||||
|
||||
function writeExternalEnvChannelPlugin() {
|
||||
useNoBundledPlugins();
|
||||
const pluginDir = makeTempDir();
|
||||
const fullMarker = path.join(pluginDir, "full-loaded.txt");
|
||||
fs.writeFileSync(
|
||||
path.join(pluginDir, "package.json"),
|
||||
JSON.stringify(
|
||||
{
|
||||
name: "@example/openclaw-external-env-channel",
|
||||
version: "1.0.0",
|
||||
openclaw: {
|
||||
extensions: ["./index.cjs"],
|
||||
},
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
"utf-8",
|
||||
);
|
||||
fs.writeFileSync(
|
||||
path.join(pluginDir, "openclaw.plugin.json"),
|
||||
JSON.stringify(
|
||||
{
|
||||
id: "external-env-channel-plugin",
|
||||
configSchema: EMPTY_PLUGIN_SCHEMA,
|
||||
channels: ["external-env-channel"],
|
||||
channelEnvVars: {
|
||||
"external-env-channel": ["EXTERNAL_ENV_CHANNEL_TOKEN"],
|
||||
},
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
"utf-8",
|
||||
);
|
||||
fs.writeFileSync(
|
||||
path.join(pluginDir, "index.cjs"),
|
||||
`require("node:fs").writeFileSync(${JSON.stringify(fullMarker)}, "loaded", "utf-8");`,
|
||||
"utf-8",
|
||||
);
|
||||
return { pluginDir, fullMarker };
|
||||
}
|
||||
|
||||
function createRuntimeCapture() {
|
||||
const logs: string[] = [];
|
||||
const errors: string[] = [];
|
||||
const runtime = {
|
||||
log: (message: unknown) => logs.push(String(message)),
|
||||
error: (message: unknown) => errors.push(String(message)),
|
||||
exit: (_code?: number) => undefined,
|
||||
};
|
||||
return { runtime, logs, errors };
|
||||
}
|
||||
|
||||
describe("channelsStatusCommand external env-only channel fallback", () => {
|
||||
beforeEach(() => {
|
||||
mocks.callGateway.mockReset();
|
||||
mocks.callGateway.mockRejectedValue(new Error("gateway closed"));
|
||||
mocks.readConfigFileSnapshot.mockClear();
|
||||
mocks.requireValidConfigSnapshot.mockReset();
|
||||
mocks.resolveCommandConfigWithSecrets.mockReset();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
resetPluginLoaderTestStateForTest();
|
||||
});
|
||||
|
||||
it("reports env-only external manifest channels in JSON fallback without full runtime load", async () => {
|
||||
const { pluginDir, fullMarker } = writeExternalEnvChannelPlugin();
|
||||
const config = {
|
||||
plugins: {
|
||||
load: { paths: [pluginDir] },
|
||||
allow: ["external-env-channel-plugin"],
|
||||
},
|
||||
} as OpenClawConfig;
|
||||
mocks.requireValidConfigSnapshot.mockResolvedValue(config);
|
||||
mocks.resolveCommandConfigWithSecrets.mockResolvedValue({
|
||||
resolvedConfig: config,
|
||||
effectiveConfig: config,
|
||||
diagnostics: [],
|
||||
});
|
||||
const { runtime, logs } = createRuntimeCapture();
|
||||
|
||||
await withEnvAsync({ EXTERNAL_ENV_CHANNEL_TOKEN: "token" }, async () => {
|
||||
await channelsStatusCommand({ json: true, probe: false }, runtime as never);
|
||||
});
|
||||
|
||||
expect(fs.existsSync(fullMarker)).toBe(false);
|
||||
const payload = JSON.parse(logs.at(-1) ?? "{}");
|
||||
expect(payload).toEqual(
|
||||
expect.objectContaining({
|
||||
gatewayReachable: false,
|
||||
configOnly: true,
|
||||
configuredChannels: ["external-env-channel"],
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
cleanupPluginLoaderFixturesForTest();
|
||||
});
|
||||
@@ -1,4 +1,3 @@
|
||||
import { listPotentialConfiguredChannelIds } from "../../channels/config-presence.js";
|
||||
import { resolveCommandConfigWithSecrets } from "../../cli/command-config-resolution.js";
|
||||
import { formatCliCommand } from "../../cli/command-format.js";
|
||||
import { getConfiguredChannelsCommandSecretTargetIds } from "../../cli/command-secret-targets.js";
|
||||
@@ -7,6 +6,7 @@ import { readConfigFileSnapshot } from "../../config/config.js";
|
||||
import { callGateway } from "../../gateway/call.js";
|
||||
import { collectChannelStatusIssues } from "../../infra/channels-status-issues.js";
|
||||
import { formatTimeAgo } from "../../infra/format-time/format-relative.ts";
|
||||
import { listConfiguredChannelIdsForReadOnlyScope } from "../../plugins/channel-plugin-ids.js";
|
||||
import { defaultRuntime, type RuntimeEnv, writeRuntimeJson } from "../../runtime.js";
|
||||
import { formatDocsLink } from "../../terminal/links.js";
|
||||
import { theme } from "../../terminal/theme.js";
|
||||
@@ -196,9 +196,11 @@ export async function channelsStatusCommand(
|
||||
path: snapshot.path,
|
||||
mode,
|
||||
},
|
||||
configuredChannels: listPotentialConfiguredChannelIds(resolvedConfig, process.env, {
|
||||
configuredChannels: listConfiguredChannelIdsForReadOnlyScope({
|
||||
config: resolvedConfig,
|
||||
env: process.env,
|
||||
includePersistedAuthState: false,
|
||||
}).toSorted(),
|
||||
}),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { hasPotentialConfiguredChannels } from "../channels/config-presence.js";
|
||||
import type { OpenClawConfig } from "../config/types.js";
|
||||
import type { collectChannelStatusIssues as collectChannelStatusIssuesFn } from "../infra/channels-status-issues.js";
|
||||
import { resolveOsSummary } from "../infra/os-summary.js";
|
||||
import type { UpdateCheckResult } from "../infra/update-check.js";
|
||||
import { hasConfiguredChannelsForReadOnlyScope } from "../plugins/channel-plugin-ids.js";
|
||||
import type { RuntimeEnv } from "../runtime.js";
|
||||
import type { buildChannelsTable as buildChannelsTableFn } from "./status-all/channels.js";
|
||||
import type { getAgentLocalStatuses as getAgentLocalStatusesFn } from "./status.agent-local.js";
|
||||
@@ -178,7 +178,7 @@ export async function collectStatusScanOverview(params: {
|
||||
params.progress?.tick();
|
||||
const hasConfiguredChannels = params.resolveHasConfiguredChannels
|
||||
? params.resolveHasConfiguredChannels(cfg)
|
||||
: hasPotentialConfiguredChannels(cfg);
|
||||
: hasConfiguredChannelsForReadOnlyScope({ config: cfg });
|
||||
const osSummary = resolveOsSummary();
|
||||
const bootstrap = await createStatusScanCoreBootstrap<
|
||||
Awaited<ReturnType<typeof getAgentLocalStatusesFn>>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { hasPotentialConfiguredChannels } from "../channels/config-presence.js";
|
||||
import type { OpenClawConfig } from "../config/types.js";
|
||||
import { hasConfiguredChannelsForReadOnlyScope } from "../plugins/channel-plugin-ids.js";
|
||||
import type { RuntimeEnv } from "../runtime.js";
|
||||
import { executeStatusScanFromOverview } from "./status.scan-execute.ts";
|
||||
import {
|
||||
@@ -12,9 +13,7 @@ type StatusJsonScanPolicy = {
|
||||
commandName: string;
|
||||
allowMissingConfigFastPath?: boolean;
|
||||
includeChannelSummary?: boolean;
|
||||
resolveHasConfiguredChannels: (
|
||||
cfg: Parameters<typeof hasPotentialConfiguredChannels>[0],
|
||||
) => boolean;
|
||||
resolveHasConfiguredChannels: (cfg: OpenClawConfig) => boolean;
|
||||
resolveMemory: Parameters<typeof executeStatusScanFromOverview>[0]["resolveMemory"];
|
||||
};
|
||||
|
||||
@@ -60,7 +59,9 @@ export async function scanStatusJsonFast(
|
||||
allowMissingConfigFastPath: true,
|
||||
includeChannelSummary: false,
|
||||
resolveHasConfiguredChannels: (cfg) =>
|
||||
hasPotentialConfiguredChannels(cfg, process.env, {
|
||||
hasConfiguredChannelsForReadOnlyScope({
|
||||
config: cfg,
|
||||
env: process.env,
|
||||
includePersistedAuthState: false,
|
||||
}),
|
||||
resolveMemory: async ({ cfg, agentStatus, memoryPlugin }) =>
|
||||
|
||||
@@ -182,6 +182,36 @@ export async function loadStatusScanModuleForTest(
|
||||
vi.doMock("../channels/config-presence.js", () => ({
|
||||
hasPotentialConfiguredChannels: mocks.hasPotentialConfiguredChannels,
|
||||
}));
|
||||
vi.doMock("../plugins/channel-plugin-ids.js", () => ({
|
||||
hasConfiguredChannelsForReadOnlyScope: (params: {
|
||||
config: OpenClawConfig;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
includePersistedAuthState?: boolean;
|
||||
}) =>
|
||||
Boolean(
|
||||
mocks.hasPotentialConfiguredChannels(
|
||||
params.config,
|
||||
params.env,
|
||||
params.includePersistedAuthState === undefined
|
||||
? undefined
|
||||
: { includePersistedAuthState: params.includePersistedAuthState },
|
||||
),
|
||||
),
|
||||
listConfiguredChannelIdsForReadOnlyScope: (params: {
|
||||
config: OpenClawConfig;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
includePersistedAuthState?: boolean;
|
||||
}) =>
|
||||
mocks.hasPotentialConfiguredChannels(
|
||||
params.config,
|
||||
params.env,
|
||||
params.includePersistedAuthState === undefined
|
||||
? undefined
|
||||
: { includePersistedAuthState: params.includePersistedAuthState },
|
||||
)
|
||||
? ["mock-channel"]
|
||||
: [],
|
||||
}));
|
||||
|
||||
vi.doMock("../config/io.js", () => ({
|
||||
readBestEffortConfig: mocks.readBestEffortConfig,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { hasPotentialConfiguredChannels } from "../channels/config-presence.js";
|
||||
import { withProgress } from "../cli/progress.js";
|
||||
import { hasConfiguredChannelsForReadOnlyScope } from "../plugins/channel-plugin-ids.js";
|
||||
import { buildPluginCompatibilitySnapshotNotices } from "../plugins/status.js";
|
||||
import type { RuntimeEnv } from "../runtime.js";
|
||||
import { executeStatusScanFromOverview } from "./status.scan-execute.ts";
|
||||
@@ -25,7 +25,8 @@ export async function scanStatus(
|
||||
_runtime,
|
||||
{
|
||||
commandName: "status --json",
|
||||
resolveHasConfiguredChannels: (cfg) => hasPotentialConfiguredChannels(cfg),
|
||||
resolveHasConfiguredChannels: (cfg) =>
|
||||
hasConfiguredChannelsForReadOnlyScope({ config: cfg }),
|
||||
resolveMemory: async ({ cfg, agentStatus, memoryPlugin }) =>
|
||||
await resolveStatusMemoryStatusSnapshot({
|
||||
cfg,
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const statusSummaryMocks = vi.hoisted(() => ({
|
||||
hasPotentialConfiguredChannels: vi.fn(() => true),
|
||||
hasConfiguredChannelsForReadOnlyScope: vi.fn(() => true),
|
||||
buildChannelSummary: vi.fn(async () => ["ok"]),
|
||||
}));
|
||||
|
||||
vi.mock("../channels/config-presence.js", () => ({
|
||||
hasPotentialConfiguredChannels: statusSummaryMocks.hasPotentialConfiguredChannels,
|
||||
vi.mock("../plugins/channel-plugin-ids.js", () => ({
|
||||
hasConfiguredChannelsForReadOnlyScope: statusSummaryMocks.hasConfiguredChannelsForReadOnlyScope,
|
||||
}));
|
||||
|
||||
vi.mock("./status.summary.runtime.js", () => ({
|
||||
@@ -125,7 +125,7 @@ describe("getStatusSummary", () => {
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
statusSummaryMocks.hasPotentialConfiguredChannels.mockReturnValue(true);
|
||||
statusSummaryMocks.hasConfiguredChannelsForReadOnlyScope.mockReturnValue(true);
|
||||
statusSummaryMocks.buildChannelSummary.mockResolvedValue(["ok"]);
|
||||
});
|
||||
|
||||
@@ -140,12 +140,15 @@ describe("getStatusSummary", () => {
|
||||
});
|
||||
|
||||
it("skips channel summary imports when no channels are configured", async () => {
|
||||
statusSummaryMocks.hasPotentialConfiguredChannels.mockReturnValue(false);
|
||||
statusSummaryMocks.hasConfiguredChannelsForReadOnlyScope.mockReturnValue(false);
|
||||
|
||||
const summary = await getStatusSummary();
|
||||
|
||||
expect(summary.channelSummary).toEqual([]);
|
||||
expect(summary.linkChannel).toBeUndefined();
|
||||
expect(statusSummaryMocks.hasConfiguredChannelsForReadOnlyScope).toHaveBeenCalledWith({
|
||||
config: {},
|
||||
});
|
||||
expect(buildChannelSummary).not.toHaveBeenCalled();
|
||||
expect(resolveLinkChannelContext).not.toHaveBeenCalled();
|
||||
});
|
||||
@@ -155,7 +158,7 @@ describe("getStatusSummary", () => {
|
||||
|
||||
expect(summary.channelSummary).toEqual([]);
|
||||
expect(summary.linkChannel).toBeUndefined();
|
||||
expect(statusSummaryMocks.hasPotentialConfiguredChannels).not.toHaveBeenCalled();
|
||||
expect(statusSummaryMocks.hasConfiguredChannelsForReadOnlyScope).not.toHaveBeenCalled();
|
||||
expect(buildChannelSummary).not.toHaveBeenCalled();
|
||||
expect(resolveLinkChannelContext).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { DEFAULT_CONTEXT_TOKENS, DEFAULT_MODEL, DEFAULT_PROVIDER } from "../agents/defaults.js";
|
||||
import { hasPotentialConfiguredChannels } from "../channels/config-presence.js";
|
||||
import { resolveMainSessionKey } from "../config/sessions/main-session.js";
|
||||
import { resolveStorePath } from "../config/sessions/paths.js";
|
||||
import { readSessionStoreReadOnly } from "../config/sessions/store-read.js";
|
||||
@@ -8,6 +7,7 @@ import type { OpenClawConfig } from "../config/types.js";
|
||||
import { listGatewayAgentsBasic } from "../gateway/agent-list.js";
|
||||
import { resolveHeartbeatSummaryForAgent } from "../infra/heartbeat-summary.js";
|
||||
import { peekSystemEvents } from "../infra/system-events.js";
|
||||
import { hasConfiguredChannelsForReadOnlyScope } from "../plugins/channel-plugin-ids.js";
|
||||
import { parseAgentSessionKey } from "../routing/session-key.js";
|
||||
import { createLazyRuntimeSurface } from "../shared/lazy-runtime.js";
|
||||
import { resolveRuntimeServiceVersion } from "../version.js";
|
||||
@@ -118,7 +118,11 @@ export async function getStatusSummary(
|
||||
resolveSessionModelRef,
|
||||
} = await loadStatusSummaryRuntimeModule();
|
||||
const cfg = options.config ?? (await loadConfigIoModule()).loadConfig();
|
||||
const needsChannelPlugins = includeChannelSummary && hasPotentialConfiguredChannels(cfg);
|
||||
const needsChannelPlugins =
|
||||
includeChannelSummary &&
|
||||
hasConfiguredChannelsForReadOnlyScope({
|
||||
config: cfg,
|
||||
});
|
||||
const linkContext = needsChannelPlugins
|
||||
? await loadLinkChannelModule().then(({ resolveLinkChannelContext }) =>
|
||||
resolveLinkChannelContext(cfg),
|
||||
|
||||
@@ -19,6 +19,8 @@ vi.mock("./manifest-registry.js", async (importOriginal) => {
|
||||
});
|
||||
|
||||
import {
|
||||
hasConfiguredChannelsForReadOnlyScope,
|
||||
listConfiguredChannelIdsForReadOnlyScope,
|
||||
resolveConfiguredChannelPluginIds,
|
||||
resolveGatewayStartupPluginIds,
|
||||
} from "./channel-plugin-ids.js";
|
||||
@@ -634,3 +636,48 @@ describe("resolveConfiguredChannelPluginIds", () => {
|
||||
).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("listConfiguredChannelIdsForReadOnlyScope", () => {
|
||||
beforeEach(() => {
|
||||
listPotentialConfiguredChannelIds.mockReset().mockReturnValue([]);
|
||||
hasPotentialConfiguredChannels.mockReset().mockReturnValue(false);
|
||||
loadPluginManifestRegistry.mockReset().mockReturnValue(createManifestRegistryFixture());
|
||||
});
|
||||
|
||||
it("uses manifest env vars as read-only configured channel triggers", () => {
|
||||
expect(
|
||||
listConfiguredChannelIdsForReadOnlyScope({
|
||||
config: {
|
||||
plugins: {
|
||||
allow: ["external-env-channel-plugin"],
|
||||
},
|
||||
} as OpenClawConfig,
|
||||
workspaceDir: "/tmp",
|
||||
env: {
|
||||
EXTERNAL_ENV_CHANNEL_TOKEN: "token",
|
||||
} as NodeJS.ProcessEnv,
|
||||
includePersistedAuthState: false,
|
||||
}),
|
||||
).toEqual(["external-env-channel"]);
|
||||
});
|
||||
|
||||
it("uses manifest env vars for read-only channel presence checks", () => {
|
||||
listPotentialConfiguredChannelIds.mockReturnValue([]);
|
||||
hasPotentialConfiguredChannels.mockReturnValue(false);
|
||||
|
||||
expect(
|
||||
hasConfiguredChannelsForReadOnlyScope({
|
||||
config: {
|
||||
plugins: {
|
||||
allow: ["external-env-channel-plugin"],
|
||||
},
|
||||
} as OpenClawConfig,
|
||||
workspaceDir: "/tmp",
|
||||
env: {
|
||||
EXTERNAL_ENV_CHANNEL_TOKEN: "token",
|
||||
} as NodeJS.ProcessEnv,
|
||||
includePersistedAuthState: false,
|
||||
}),
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { resolveAgentWorkspaceDir, resolveDefaultAgentId } from "../agents/agent-scope.js";
|
||||
import { collectConfiguredAgentHarnessRuntimes } from "../agents/harness-runtimes.js";
|
||||
import { listPotentialConfiguredChannelIds } from "../channels/config-presence.js";
|
||||
import {
|
||||
hasPotentialConfiguredChannels,
|
||||
listPotentialConfiguredChannelIds,
|
||||
} from "../channels/config-presence.js";
|
||||
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
import {
|
||||
resolveMemoryDreamingConfig,
|
||||
@@ -111,6 +115,52 @@ export function listConfiguredChannelIdsForPluginScope(params: {
|
||||
].toSorted((left, right) => left.localeCompare(right));
|
||||
}
|
||||
|
||||
export function listConfiguredChannelIdsForReadOnlyScope(params: {
|
||||
config: OpenClawConfig;
|
||||
workspaceDir?: string;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
cache?: boolean;
|
||||
includePersistedAuthState?: boolean;
|
||||
manifestRecords?: readonly PluginManifestRecord[];
|
||||
}): string[] {
|
||||
const env = params.env ?? process.env;
|
||||
const workspaceDir =
|
||||
params.workspaceDir ??
|
||||
resolveAgentWorkspaceDir(params.config, resolveDefaultAgentId(params.config));
|
||||
return listConfiguredChannelIdsForPluginScope({
|
||||
config: params.config,
|
||||
workspaceDir,
|
||||
env,
|
||||
cache: params.cache,
|
||||
includePersistedAuthState: params.includePersistedAuthState,
|
||||
manifestRecords: params.manifestRecords,
|
||||
});
|
||||
}
|
||||
|
||||
export function hasConfiguredChannelsForReadOnlyScope(params: {
|
||||
config: OpenClawConfig;
|
||||
workspaceDir?: string;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
cache?: boolean;
|
||||
includePersistedAuthState?: boolean;
|
||||
manifestRecords?: readonly PluginManifestRecord[];
|
||||
}): boolean {
|
||||
const env = params.env ?? process.env;
|
||||
if (
|
||||
hasPotentialConfiguredChannels(params.config, env, {
|
||||
includePersistedAuthState: params.includePersistedAuthState,
|
||||
})
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return (
|
||||
listConfiguredChannelIdsForReadOnlyScope({
|
||||
...params,
|
||||
env,
|
||||
}).length > 0
|
||||
);
|
||||
}
|
||||
|
||||
function isChannelPluginEligibleForScopedOwnership(params: {
|
||||
plugin: PluginManifestRecord;
|
||||
normalizedConfig: ReturnType<typeof normalizePluginsConfig>;
|
||||
|
||||
Reference in New Issue
Block a user