diff --git a/scripts/delete-cli-agent.ts b/scripts/delete-cli-agent.ts new file mode 100644 index 0000000..8e947cb --- /dev/null +++ b/scripts/delete-cli-agent.ts @@ -0,0 +1,74 @@ +/** + * Delete the scratch CLI agent created during setup's ping-pong test. + * + * Removes the agent group, its messaging_group_agents wiring, any + * agent_destinations rows, and the groups// directory. Leaves the + * CLI messaging group intact so it can be reused for a new agent. + * + * Usage: + * pnpm exec tsx scripts/delete-cli-agent.ts --folder + */ +import fs from 'fs'; +import path from 'path'; + +import { DATA_DIR } from '../src/config.js'; +import { getAgentGroupByFolder, deleteAgentGroup } from '../src/db/agent-groups.js'; +import { initDb } from '../src/db/connection.js'; +import { runMigrations } from '../src/db/migrations/index.js'; + +interface Args { + folder: string; +} + +function parseArgs(): Args { + const argv = process.argv.slice(2); + let folder = ''; + for (let i = 0; i < argv.length; i++) { + if (argv[i] === '--folder' && argv[i + 1]) folder = argv[++i]; + } + if (!folder) { + console.error('usage: pnpm exec tsx scripts/delete-cli-agent.ts --folder '); + process.exit(1); + } + return { folder }; +} + +const args = parseArgs(); + +const db = initDb(path.join(DATA_DIR, 'v2.db')); +runMigrations(db); + +const ag = getAgentGroupByFolder(args.folder); +if (!ag) { + console.log(`No agent group with folder "${args.folder}" — nothing to delete.`); + process.exit(0); +} + +// Delete all rows referencing this agent group, in dependency order. +const fkTables = [ + 'messaging_group_agents', + 'agent_destinations', + 'agent_group_members', + 'pending_sender_approvals', + 'channel_registrations', + 'user_roles', + 'sessions', +]; +for (const table of fkTables) { + const exists = db + .prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name=?") + .get(table); + if (exists) { + db.prepare(`DELETE FROM ${table} WHERE agent_group_id = ?`).run(ag.id); + } +} + +deleteAgentGroup(ag.id); + +// Remove the groups// directory. +const groupDir = path.join(process.cwd(), 'groups', args.folder); +if (fs.existsSync(groupDir)) { + fs.rmSync(groupDir, { recursive: true }); +} + +console.log(`Deleted agent group ${ag.id} (${args.folder}).`); diff --git a/setup/auto.ts b/setup/auto.ts index 392bc13..e46a639 100644 --- a/setup/auto.ts +++ b/setup/auto.ts @@ -55,6 +55,7 @@ import { ensureAnswer, fail, runQuietChild, runQuietStep } from './lib/runner.js import { emit as phEmit } from './lib/diagnostics.js'; import { accentGreen, brandBody, brandBold, brandChip, dimWrap, fitToWidth, note, wrapForGutter } from './lib/theme.js'; import { isValidTimezone } from '../src/timezone.js'; +import { normalizeName } from '../src/modules/agent-to-agent/db/agent-destinations.js'; const CLI_AGENT_NAME = 'Terminal Agent'; const RUN_START = Date.now(); @@ -349,8 +350,8 @@ async function main(): Promise { const res = await runQuietStep( 'cli-agent', { - running: 'Bringing your assistant online…', - done: 'Assistant wired up.', + running: 'Preparing connection test…', + done: 'Ready to test.', }, ['--display-name', displayName!, '--agent-name', CLI_AGENT_NAME], ); @@ -365,7 +366,7 @@ async function main(): Promise { p.log.message( brandBody( dimWrap( - "Your assistant runs in an isolated sandbox. I'm going to send it a quick test message (ping) and wait for a reply (pong) to confirm it's responding. First startup typically takes 30–60 seconds while the sandbox warms up.", + 'Checking your assistant can respond — first startup takes 30–60 seconds.', 4, ), ), @@ -373,6 +374,10 @@ async function main(): Promise { const ping = await confirmAssistantResponds(); if (ping === 'ok') { phEmit('first_chat_ready'); + const scratchFolder = `cli-with-${normalizeName(displayName!)}`; + spawnSync('pnpm', ['exec', 'tsx', 'scripts/delete-cli-agent.ts', '--folder', scratchFolder], { + stdio: 'ignore', + }); const next = ensureAnswer( await brightSelect<'continue' | 'chat'>({ message: 'What next?', @@ -390,7 +395,23 @@ async function main(): Promise { }), ) as 'continue' | 'chat'; setupLog.userInput('first_chat_choice', next); - if (next === 'chat') await runFirstChat(); + if (next === 'chat') { + const terminalAgentName = `${displayName!}'s Terminal`; + const createRes = await runQuietChild( + 'create-terminal-agent', + 'pnpm', + ['exec', 'tsx', 'scripts/init-cli-agent.ts', '--display-name', displayName!, '--agent-name', terminalAgentName], + { running: `Creating ${terminalAgentName}…`, done: `${terminalAgentName} is ready.` }, + ); + if (!createRes.ok) { + await fail( + 'create-terminal-agent', + `Couldn't create ${terminalAgentName}.`, + 'You can retry later with `pnpm exec tsx scripts/init-cli-agent.ts`.', + ); + } + await runFirstChat(); + } } else { phEmit('first_chat_failed', { reason: ping }); renderPingFailureNote(ping); @@ -592,7 +613,7 @@ async function confirmAssistantResponds(): Promise { const elapsed = Math.round((Date.now() - start) / 1000); const suffix = ` (${elapsed}s)`; if (result === 'ok') { - s.stop(`${k.bold(fitToWidth('Your assistant is ready.', suffix))}${k.dim(suffix)}`); + s.stop(`${k.bold(fitToWidth('Connection verified.', suffix))}${k.dim(suffix)}`); } else { const msg = result === 'socket_error' ? "Couldn't reach the NanoClaw service." : "Your assistant didn't reply in time.";