feat(setup): Claude-assisted error recovery with resume-at-step retry
When a setup step fails — whether hard via fail() or soft via the
"What's left" / "Skipping the first chat" notes — offer to ask Claude
to diagnose. On consent, spawn `claude -p --output-format stream-json`
with a scrolling 3-line action window ("Reading x", "Running y") so
the 1–4 minute investigations feel active rather than hung. No hard
timeout: debugging can take time, Ctrl-C is the escape hatch.
The prompt is minimal: one-paragraph framing, failed step name + msg +
hint, and a list of file references (not contents). Claude's Read/Grep
tools fetch what they need. A per-step map in claude-assist.ts gives
the most relevant files per step; the rest is README + auto.ts +
logs/setup.log + the per-step raw log.
Claude responds with REASON + COMMAND lines. We show the reason in a
clack note, prefill the command via setup/run-suggested.sh (bash 4+
readline, 3.x fallback to Enter-to-run), and eval on the user's
confirm.
When the user runs a fix, fail() now offers to retry the failing step
rather than aborting. setup/logs.ts tracks successfully-completed step
names in-memory; fail() threads those as NANOCLAW_SKIP on a spawnSync
retry, so the child picks up exactly where the parent left off — no
rebuilding containers or reinstalling OneCLI.
Other polish in this change:
- fitToWidth + dimWrap in lib/theme.ts to prevent long spinner labels
from soft-wrapping (each terminal row stacks a stale copy otherwise).
- Shorter container step label ("Preparing your assistant's sandbox…")
so it fits on narrow terminals.
- Wordmark anchored in the clack intro line on every run.
- All 25 existing fail() call sites updated to await fail(...) since
fail is now async.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -70,7 +70,7 @@ export async function runTelegramChannel(displayName: string): Promise<void> {
|
||||
},
|
||||
);
|
||||
if (!install.ok) {
|
||||
fail(
|
||||
await fail(
|
||||
'telegram-install',
|
||||
"Couldn't connect Telegram.",
|
||||
'See logs/setup-steps/ for details, then retry setup.',
|
||||
@@ -79,7 +79,7 @@ export async function runTelegramChannel(displayName: string): Promise<void> {
|
||||
|
||||
const pair = await runPairTelegram();
|
||||
if (!pair.ok) {
|
||||
fail(
|
||||
await fail(
|
||||
'pair-telegram',
|
||||
"Couldn't pair with Telegram.",
|
||||
'Re-run setup to try again.',
|
||||
@@ -89,7 +89,7 @@ export async function runTelegramChannel(displayName: string): Promise<void> {
|
||||
const platformId = pair.terminal?.fields.PLATFORM_ID;
|
||||
const pairedUserId = pair.terminal?.fields.PAIRED_USER_ID;
|
||||
if (!platformId || !pairedUserId) {
|
||||
fail(
|
||||
await fail(
|
||||
'pair-telegram',
|
||||
'Pairing completed but came back incomplete.',
|
||||
'Re-run setup to try again.',
|
||||
@@ -118,7 +118,7 @@ export async function runTelegramChannel(displayName: string): Promise<void> {
|
||||
},
|
||||
);
|
||||
if (!init.ok) {
|
||||
fail(
|
||||
await fail(
|
||||
'init-first-agent',
|
||||
`Couldn't finish connecting ${agentName}.`,
|
||||
'You can retry later with `/manage-channels`.',
|
||||
@@ -188,7 +188,7 @@ async function validateTelegramToken(token: string): Promise<string> {
|
||||
setupLog.step('telegram-validate', 'failed', Date.now() - start, {
|
||||
ERROR: reason,
|
||||
});
|
||||
fail(
|
||||
await fail(
|
||||
'telegram-validate',
|
||||
"Telegram didn't accept that token.",
|
||||
'Copy the token again from @BotFather and try setup once more.',
|
||||
@@ -200,7 +200,7 @@ async function validateTelegramToken(token: string): Promise<string> {
|
||||
setupLog.step('telegram-validate', 'failed', Date.now() - start, {
|
||||
ERROR: message,
|
||||
});
|
||||
fail(
|
||||
await fail(
|
||||
'telegram-validate',
|
||||
"Couldn't reach Telegram.",
|
||||
'Check your internet connection and retry setup.',
|
||||
|
||||
Reference in New Issue
Block a user