Merge branch 'main' into fix/scoped-container-reaper
This commit is contained in:
@@ -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 30–60 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);
|
||||||
|
|||||||
@@ -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 },
|
||||||
|
|||||||
Reference in New Issue
Block a user