mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-26 13:42:16 +00:00
fix(cli): keep channel add plugin install noninteractive
# Conflicts: # CHANGELOG.md
This commit is contained in:
parent
7e13f3f514
commit
9089e6b595
8 changed files with 46 additions and 9 deletions
|
|
@ -20,6 +20,7 @@ Docs: https://docs.openclaw.ai
|
|||
|
||||
### Fixes
|
||||
|
||||
- Plugins/CLI: let flag-driven `openclaw channels add` install the selected channel plugin from its default source without opening an interactive prompt, fixing published npm Telegram setup in stdin-closed automation. Thanks @codex.
|
||||
- Onboarding/setup: keep first-run config reads, plugin compatibility notices, and post-model sanity checks on cold metadata paths unless the user chooses to browse all models, avoiding full plugin/runtime catalog work between prompts. Thanks @shakkernerd.
|
||||
- Onboarding/auth: run manifest-owned provider auth choices through scoped setup providers so selecting OpenAI Codex browser/device auth no longer loads every provider runtime before OAuth starts. Thanks @shakkernerd.
|
||||
- Onboarding/auth: keep the post-auth default-model policy lookup on manifest/setup metadata so the next prompt appears without loading broad provider runtime. Thanks @shakkernerd.
|
||||
|
|
|
|||
|
|
@ -59,6 +59,8 @@ Common non-interactive add surfaces include:
|
|||
- Tlon fields: `--ship`, `--url`, `--code`, `--group-channels`, `--dm-allowlist`, `--auto-discover-channels`
|
||||
- `--use-env` for default-account env-backed auth where supported
|
||||
|
||||
If a channel plugin needs to be installed during a flag-driven add command, OpenClaw uses the channel's default install source without opening the interactive plugin install prompt.
|
||||
|
||||
When you run `openclaw channels add` without flags, the interactive wizard can prompt:
|
||||
|
||||
- account ids per selected channel
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { Command } from "commander";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { captureEnv } from "../test-utils/env.js";
|
||||
|
|
@ -139,17 +142,19 @@ function parseFirstJsonRuntimeLine<T>() {
|
|||
|
||||
describe("daemon-cli coverage", () => {
|
||||
let envSnapshot: ReturnType<typeof captureEnv>;
|
||||
let tmpDir: string;
|
||||
|
||||
beforeEach(() => {
|
||||
daemonProgram = createDaemonProgram();
|
||||
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-daemon-cli-"));
|
||||
envSnapshot = captureEnv([
|
||||
"OPENCLAW_STATE_DIR",
|
||||
"OPENCLAW_CONFIG_PATH",
|
||||
"OPENCLAW_GATEWAY_PORT",
|
||||
"OPENCLAW_PROFILE",
|
||||
]);
|
||||
process.env.OPENCLAW_STATE_DIR = "/tmp/openclaw-cli-state";
|
||||
process.env.OPENCLAW_CONFIG_PATH = "/tmp/openclaw-cli-state/openclaw.json";
|
||||
process.env.OPENCLAW_STATE_DIR = tmpDir;
|
||||
process.env.OPENCLAW_CONFIG_PATH = path.join(tmpDir, "openclaw.json");
|
||||
delete process.env.OPENCLAW_GATEWAY_PORT;
|
||||
delete process.env.OPENCLAW_PROFILE;
|
||||
serviceReadCommand.mockResolvedValue(null);
|
||||
|
|
@ -160,6 +165,7 @@ describe("daemon-cli coverage", () => {
|
|||
|
||||
afterEach(() => {
|
||||
envSnapshot.restore();
|
||||
fs.rmSync(tmpDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it("probes gateway status by default", async () => {
|
||||
|
|
|
|||
|
|
@ -430,6 +430,27 @@ describe("ensureChannelSetupPluginInstalled", () => {
|
|||
);
|
||||
});
|
||||
|
||||
it("uses the bundled default install source without prompting in non-interactive mode", async () => {
|
||||
const runtime = makeRuntime();
|
||||
const { prompter, select } = makeSkipInstallPrompter();
|
||||
const cfg: OpenClawConfig = { update: { channel: "beta" } };
|
||||
mockBundledChatSource();
|
||||
|
||||
const result = await ensureChannelSetupPluginInstalled({
|
||||
cfg,
|
||||
entry: baseEntry,
|
||||
prompter,
|
||||
runtime,
|
||||
promptInstall: false,
|
||||
});
|
||||
|
||||
expect(select).not.toHaveBeenCalled();
|
||||
expect(result.installed).toBe(true);
|
||||
expect(result.cfg.plugins?.load?.paths).toContain(
|
||||
bundledPluginRootAt("/opt/openclaw", "bundled-chat"),
|
||||
);
|
||||
});
|
||||
|
||||
it("does not default to bundled local path when an external catalog overrides the npm spec", async () => {
|
||||
const runtime = makeRuntime();
|
||||
const { prompter, select } = makeSkipInstallPrompter();
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ export async function ensureChannelSetupPluginInstalled(params: {
|
|||
prompter: WizardPrompter;
|
||||
runtime: RuntimeEnv;
|
||||
workspaceDir?: string;
|
||||
promptInstall?: boolean;
|
||||
}): Promise<InstallResult> {
|
||||
const result = await ensureOnboardingPluginInstalled({
|
||||
cfg: params.cfg,
|
||||
|
|
@ -48,6 +49,7 @@ export async function ensureChannelSetupPluginInstalled(params: {
|
|||
prompter: params.prompter,
|
||||
runtime: params.runtime,
|
||||
workspaceDir: params.workspaceDir,
|
||||
...(params.promptInstall !== undefined ? { promptInstall: params.promptInstall } : {}),
|
||||
});
|
||||
return {
|
||||
cfg: result.cfg,
|
||||
|
|
|
|||
|
|
@ -501,7 +501,7 @@ describe("channelsAddCommand", () => {
|
|||
);
|
||||
|
||||
expect(ensureChannelSetupPluginInstalled).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ entry: catalogEntry }),
|
||||
expect.objectContaining({ entry: catalogEntry, promptInstall: false }),
|
||||
);
|
||||
expect(loadChannelSetupPluginRegistrySnapshotForChannel).toHaveBeenCalledTimes(1);
|
||||
expect(loadChannelSetupPluginRegistrySnapshotForChannel).toHaveBeenCalledWith(
|
||||
|
|
|
|||
|
|
@ -311,6 +311,7 @@ export async function channelsAddCommand(
|
|||
prompter,
|
||||
runtime,
|
||||
workspaceDir,
|
||||
promptInstall: false,
|
||||
});
|
||||
nextConfig = result.cfg;
|
||||
if (!result.installed) {
|
||||
|
|
|
|||
|
|
@ -422,6 +422,7 @@ export async function ensureOnboardingPluginInstalled(params: {
|
|||
prompter: WizardPrompter;
|
||||
runtime: RuntimeEnv;
|
||||
workspaceDir?: string;
|
||||
promptInstall?: boolean;
|
||||
}): Promise<OnboardingPluginInstallResult> {
|
||||
const { entry, prompter, runtime, workspaceDir } = params;
|
||||
let next = params.cfg;
|
||||
|
|
@ -442,12 +443,15 @@ export async function ensureOnboardingPluginInstalled(params: {
|
|||
bundledLocalPath,
|
||||
hasNpmSpec: Boolean(npmSpec),
|
||||
});
|
||||
const choice = await promptInstallChoice({
|
||||
entry,
|
||||
localPath,
|
||||
defaultChoice,
|
||||
prompter,
|
||||
});
|
||||
const choice =
|
||||
params.promptInstall === false
|
||||
? defaultChoice
|
||||
: await promptInstallChoice({
|
||||
entry,
|
||||
localPath,
|
||||
defaultChoice,
|
||||
prompter,
|
||||
});
|
||||
|
||||
if (choice === "skip") {
|
||||
return {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue