Merge branch 'main' into fix/scoped-container-reaper

This commit is contained in:
gavrielc
2026-04-23 12:52:32 +03:00
committed by GitHub
2 changed files with 64 additions and 16 deletions

View File

@@ -246,10 +246,33 @@ async function main(): Promise<void> {
); );
} }
if (!skip.has('first-chat')) { 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 3060 seconds while the sandbox warms up.",
4,
),
);
const ping = await confirmAssistantResponds(); const ping = await confirmAssistantResponds();
if (ping === 'ok') { if (ping === 'ok') {
phEmit('first_chat_ready'); 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 { } else {
phEmit('first_chat_failed', { reason: ping }); phEmit('first_chat_failed', { reason: ping });
renderPingFailureNote(ping); renderPingFailureNote(ping);

View File

@@ -115,7 +115,7 @@ export async function offerClaudeAssist(
const run = ensureAnswer( const run = ensureAnswer(
await p.confirm({ await p.confirm({
message: 'Run this command? (you can edit it before executing)', message: 'Run this command? (you can edit it before executing)',
initialValue: false, initialValue: true,
}), }),
); );
if (!run) return false; if (!run) return false;
@@ -279,18 +279,24 @@ async function queryClaudeUnderSpinner(
// No hard timeout — debugging can take a long time, and the cost of // No hard timeout — debugging can take a long time, and the cost of
// cutting Claude off mid-investigation is worse than letting the // cutting Claude off mid-investigation is worse than letting the
// spinner run. The user can Ctrl-C if they want to abort. // spinner run. The user can Ctrl-C if they want to abort.
const child = spawn( //
'claude', // Resume the same session on repeat invocations so Claude carries
[ // context across failures in one setup run.
'-p', const claudeArgs = [
'--output-format', '-p',
'stream-json', '--output-format',
'--verbose', 'stream-json',
'--permission-mode', '--verbose',
'bypassPermissions', '--permission-mode',
], 'bypassPermissions',
{ cwd: projectRoot, stdio: ['pipe', 'pipe', 'pipe'] }, ];
); if (claudeSessionId) {
claudeArgs.push('--resume', claudeSessionId);
}
const child = spawn('claude', claudeArgs, {
cwd: projectRoot,
stdio: ['pipe', 'pipe', 'pipe'],
});
child.stdout.on('data', (c: Buffer) => { child.stdout.on('data', (c: Buffer) => {
lineBuf += c.toString('utf-8'); lineBuf += c.toString('utf-8');
@@ -301,6 +307,16 @@ async function queryClaudeUnderSpinner(
if (!line.trim()) continue; if (!line.trim()) continue;
try { try {
const event = JSON.parse(line) as StreamEvent; 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, { handleStreamEvent(event, {
setAction: (a) => { setAction: (a) => {
actions.push(a); actions.push(a);
@@ -335,10 +351,14 @@ async function queryClaudeUnderSpinner(
} }
// Minimal shape of the stream-json events we care about. Claude emits // 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 // many more, but we only read tool_use blocks (for breadcrumbs), text
// blocks (to reassemble the final REASON/COMMAND answer). // 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 { interface StreamEvent {
type: string; type: string;
subtype?: string;
session_id?: string;
message?: { message?: {
content?: Array< content?: Array<
| { type: 'text'; text: string } | { 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( function handleStreamEvent(
event: StreamEvent, event: StreamEvent,
cb: { setAction: (a: string) => void; appendText: (t: string) => void }, cb: { setAction: (a: string) => void; appendText: (t: string) => void },