Merge pull request #791 from roshanasingh4/fix/788-implicit-delivery-leak

Prevent onboarding TUI from auto-delivering to lastProvider/lastTo
This commit is contained in:
Peter Steinberger
2026-01-12 19:39:05 +00:00
committed by GitHub
7 changed files with 82 additions and 7 deletions

View File

@@ -33,6 +33,7 @@
- Google: apply patched pi-ai `google-gemini-cli` function call handling (strips ids) after upgrading to pi-ai 0.43.0.
- Auto-reply: elevated/reasoning toggles now enqueue system events so the model sees the mode change immediately.
- Tools: keep `image` available in sandbox and fail over when image models return empty output (fixes “(no text returned)”).
- Onboarding: TUI defaults to `deliver: false` to avoid cross-provider auto-delivery leaks; onboarding spawns the TUI with explicit `deliver: false`. (#791 — thanks @roshanasingh4)
## 2026.1.11

View File

@@ -5,12 +5,12 @@ import {
agentsDeleteCommand,
agentsListCommand,
} from "../commands/agents.js";
import { dashboardCommand } from "../commands/dashboard.js";
import {
CONFIGURE_WIZARD_SECTIONS,
configureCommand,
configureCommandWithSections,
} from "../commands/configure.js";
import { dashboardCommand } from "../commands/dashboard.js";
import { doctorCommand } from "../commands/doctor.js";
import { healthCommand } from "../commands/health.js";
import { messageCommand } from "../commands/message.js";

View File

@@ -1,4 +1,4 @@
import { describe, expect, it, vi, beforeEach } from "vitest";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { dashboardCommand } from "./dashboard.js";
@@ -94,7 +94,10 @@ describe("dashboardCommand", () => {
it("prints SSH hint when browser cannot open", async () => {
mockSnapshot("shhhh");
mocks.copyToClipboard.mockResolvedValue(false);
mocks.detectBrowserOpenSupport.mockResolvedValue({ ok: false, reason: "ssh" });
mocks.detectBrowserOpenSupport.mockResolvedValue({
ok: false,
reason: "ssh",
});
mocks.formatControlUiSshHint.mockReturnValue("ssh hint");
await dashboardCommand(runtime);

View File

@@ -1,6 +1,9 @@
import { resolveGatewayPort, readConfigFileSnapshot } from "../config/config.js";
import { defaultRuntime } from "../runtime.js";
import {
readConfigFileSnapshot,
resolveGatewayPort,
} from "../config/config.js";
import type { RuntimeEnv } from "../runtime.js";
import { defaultRuntime } from "../runtime.js";
import {
copyToClipboard,
detectBrowserOpenSupport,
@@ -33,7 +36,9 @@ export async function dashboardCommand(
runtime.log(`Dashboard URL: ${authedUrl}`);
const copied = await copyToClipboard(authedUrl).catch(() => false);
runtime.log(copied ? "Copied to clipboard." : "Copy to clipboard unavailable.");
runtime.log(
copied ? "Copied to clipboard." : "Copy to clipboard unavailable.",
);
let opened = false;
let hint: string | undefined;

View File

@@ -199,7 +199,7 @@ export async function runTui(opts: TuiOptions) {
let isConnected = false;
let toolsExpanded = false;
let showThinking = false;
const deliverDefault = opts.deliver ?? true;
const deliverDefault = opts.deliver ?? false;
const autoMessage = opts.message?.trim();
let autoMessageSent = false;
let sessionInfo: SessionInfo = {};

View File

@@ -1,5 +1,9 @@
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { describe, expect, it, vi } from "vitest";
import { DEFAULT_BOOTSTRAP_FILENAME } from "../agents/workspace.js";
import type { RuntimeEnv } from "../runtime.js";
import { runOnboardingWizard } from "./onboarding.js";
import type { WizardPrompter } from "./prompts.js";
@@ -117,4 +121,64 @@ describe("runOnboardingWizard", () => {
expect(healthCommand).not.toHaveBeenCalled();
expect(runTui).not.toHaveBeenCalled();
});
it("launches TUI without auto-delivery when hatching", async () => {
runTui.mockClear();
const workspaceDir = await fs.mkdtemp(
path.join(os.tmpdir(), "clawdbot-onboard-"),
);
await fs.writeFile(
path.join(workspaceDir, DEFAULT_BOOTSTRAP_FILENAME),
"{}",
);
const confirm: WizardPrompter["confirm"] = vi.fn(async (opts) => {
if (opts.message === "Do you want to hatch your bot now?") return true;
return opts.initialValue ?? false;
});
const prompter: WizardPrompter = {
intro: vi.fn(async () => {}),
outro: vi.fn(async () => {}),
note: vi.fn(async () => {}),
select: vi.fn(async () => "quickstart"),
multiselect: vi.fn(async () => []),
text: vi.fn(async () => ""),
confirm,
progress: vi.fn(() => ({ update: vi.fn(), stop: vi.fn() })),
};
const runtime: RuntimeEnv = {
log: vi.fn(),
error: vi.fn(),
exit: vi.fn((code: number) => {
throw new Error(`exit:${code}`);
}),
};
await runOnboardingWizard(
{
flow: "quickstart",
mode: "local",
workspace: workspaceDir,
authChoice: "skip",
skipProviders: true,
skipSkills: true,
skipHealth: true,
installDaemon: false,
},
runtime,
prompter,
);
expect(runTui).toHaveBeenCalledWith(
expect.objectContaining({
deliver: false,
message: "Wake up, my friend!",
}),
);
await fs.rm(workspaceDir, { recursive: true, force: true });
});
});

View File

@@ -796,6 +796,8 @@ export async function runOnboardingWizard(
token: authMode === "token" ? gatewayToken : undefined,
password:
authMode === "password" ? baseConfig.gateway?.auth?.password : "",
// Safety: onboarding TUI should not auto-deliver to lastProvider/lastTo.
deliver: false,
message: "Wake up, my friend!",
});
}