mirror of
https://github.com/qwibitai/nanoclaw.git
synced 2026-06-12 18:11:51 +08:00
feat(setup): Yes-default + session-persist on claude-assist, quieter first-chat
Three UX tweaks after watching a user walk through setup: 1. Claude-assist "Run this command?" now defaults to Yes. After Claude has already been asked to diagnose + explained the fix, the vast majority of users want to run it — the No-default added friction without proportional safety. 2. claude-assist persists its session across failures in one setup run. First invocation captures session_id from the stream-json init event; subsequent invocations pass --resume <id>. Claude sees prior failures as conversation history instead of treating each hiccup as a blank-slate ticket. 3. First-chat flow no longer drops the user into a free-text chat loop by default. Instead: explain what the ping/pong check is doing, wait for the pong, then offer "Continue with setup" (recommended, default) or "Pause here and chat with your agent from the terminal" (opt-in). The free-text loop is still reachable, just not the default path. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+24
-1
@@ -246,10 +246,33 @@ async function main(): Promise<void> {
|
||||
);
|
||||
}
|
||||
if (!skip.has('first-chat')) {
|
||||
p.log.message(
|
||||
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.",
|
||||
4,
|
||||
),
|
||||
);
|
||||
const ping = await confirmAssistantResponds();
|
||||
if (ping === 'ok') {
|
||||
phEmit('first_chat_ready');
|
||||
await runFirstChat();
|
||||
const next = ensureAnswer(
|
||||
await p.select({
|
||||
message: 'What next?',
|
||||
options: [
|
||||
{
|
||||
value: 'continue',
|
||||
label: 'Continue with setup',
|
||||
hint: 'recommended',
|
||||
},
|
||||
{
|
||||
value: 'chat',
|
||||
label: 'Pause here and chat with your agent from the terminal',
|
||||
},
|
||||
],
|
||||
}),
|
||||
) as 'continue' | 'chat';
|
||||
setupLog.userInput('first_chat_choice', next);
|
||||
if (next === 'chat') await runFirstChat();
|
||||
} else {
|
||||
phEmit('first_chat_failed', { reason: ping });
|
||||
renderPingFailureNote(ping);
|
||||
|
||||
+40
-15
@@ -115,7 +115,7 @@ export async function offerClaudeAssist(
|
||||
const run = ensureAnswer(
|
||||
await p.confirm({
|
||||
message: 'Run this command? (you can edit it before executing)',
|
||||
initialValue: false,
|
||||
initialValue: true,
|
||||
}),
|
||||
);
|
||||
if (!run) return false;
|
||||
@@ -279,18 +279,24 @@ async function queryClaudeUnderSpinner(
|
||||
// No hard timeout — debugging can take a long time, and the cost of
|
||||
// cutting Claude off mid-investigation is worse than letting the
|
||||
// spinner run. The user can Ctrl-C if they want to abort.
|
||||
const child = spawn(
|
||||
'claude',
|
||||
[
|
||||
'-p',
|
||||
'--output-format',
|
||||
'stream-json',
|
||||
'--verbose',
|
||||
'--permission-mode',
|
||||
'bypassPermissions',
|
||||
],
|
||||
{ cwd: projectRoot, stdio: ['pipe', 'pipe', 'pipe'] },
|
||||
);
|
||||
//
|
||||
// Resume the same session on repeat invocations so Claude carries
|
||||
// context across failures in one setup run.
|
||||
const claudeArgs = [
|
||||
'-p',
|
||||
'--output-format',
|
||||
'stream-json',
|
||||
'--verbose',
|
||||
'--permission-mode',
|
||||
'bypassPermissions',
|
||||
];
|
||||
if (claudeSessionId) {
|
||||
claudeArgs.push('--resume', claudeSessionId);
|
||||
}
|
||||
const child = spawn('claude', claudeArgs, {
|
||||
cwd: projectRoot,
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
});
|
||||
|
||||
child.stdout.on('data', (c: Buffer) => {
|
||||
lineBuf += c.toString('utf-8');
|
||||
@@ -301,6 +307,16 @@ async function queryClaudeUnderSpinner(
|
||||
if (!line.trim()) continue;
|
||||
try {
|
||||
const event = JSON.parse(line) as StreamEvent;
|
||||
// Capture the session id on the very first claude invocation of
|
||||
// this process so later calls can --resume it.
|
||||
if (
|
||||
!claudeSessionId &&
|
||||
event.type === 'system' &&
|
||||
event.subtype === 'init' &&
|
||||
typeof event.session_id === 'string'
|
||||
) {
|
||||
claudeSessionId = event.session_id;
|
||||
}
|
||||
handleStreamEvent(event, {
|
||||
setAction: (a) => {
|
||||
actions.push(a);
|
||||
@@ -335,10 +351,14 @@ async function queryClaudeUnderSpinner(
|
||||
}
|
||||
|
||||
// Minimal shape of the stream-json events we care about. Claude emits
|
||||
// many more, but we only read tool_use blocks (for breadcrumbs) and text
|
||||
// blocks (to reassemble the final REASON/COMMAND answer).
|
||||
// many more, but we only read tool_use blocks (for breadcrumbs), text
|
||||
// blocks (to reassemble the final REASON/COMMAND answer), and the
|
||||
// session_id on the init event so follow-up invocations can resume the
|
||||
// same conversation.
|
||||
interface StreamEvent {
|
||||
type: string;
|
||||
subtype?: string;
|
||||
session_id?: string;
|
||||
message?: {
|
||||
content?: Array<
|
||||
| { type: 'text'; text: string }
|
||||
@@ -347,6 +367,11 @@ interface StreamEvent {
|
||||
};
|
||||
}
|
||||
|
||||
// The session id from the first claude-assist invocation in this process.
|
||||
// Subsequent invocations pass `--resume <id>` so Claude sees prior failures
|
||||
// as conversation history instead of treating each failure in isolation.
|
||||
let claudeSessionId: string | null = null;
|
||||
|
||||
function handleStreamEvent(
|
||||
event: StreamEvent,
|
||||
cb: { setAction: (a: string) => void; appendText: (t: string) => void },
|
||||
|
||||
Reference in New Issue
Block a user