Files
EJClaw/runners/agent-runner/src/session-command.ts

152 lines
4.2 KiB
TypeScript

import { query } from '@anthropic-ai/claude-agent-sdk';
import { compactBoundaryFromMessage } from './compaction-boundary.js';
import { getClaudeCliPath } from './claude-cli.js';
import { createPreCompactHook } from './runner-hooks.js';
import {
buildCompactionOutput,
type RunnerCompaction,
writeOutput,
} from './output-protocol.js';
export interface RunSessionCommandArgs {
prompt: string;
sessionId?: string;
cwd: string;
sdkEnv: Record<string, string | undefined>;
abortController: AbortController;
assistantName?: string;
groupDir: string;
groupFolder: string;
hostTasksDir: string;
log: (message: string) => void;
}
export async function runSessionCommand({
prompt,
sessionId,
cwd,
sdkEnv,
abortController,
assistantName,
groupDir,
groupFolder,
hostTasksDir,
log,
}: RunSessionCommandArgs): Promise<void> {
let slashSessionId: string | undefined;
let compactBoundarySeen = false;
let slashCompaction: RunnerCompaction | undefined;
let hadError = false;
let resultEmitted = false;
try {
for await (const message of query({
prompt,
options: {
pathToClaudeCodeExecutable: getClaudeCliPath(log),
cwd,
resume: sessionId,
systemPrompt: undefined,
allowedTools: [],
env: sdkEnv,
permissionMode: 'bypassPermissions' as const,
allowDangerouslySkipPermissions: true,
settingSources: ['project', 'user'] as const,
abortController,
hooks: {
PreCompact: [
{
hooks: [
createPreCompactHook({
assistantName,
groupDir,
groupFolder,
hostTasksDir,
log,
writeOutput,
}),
],
},
],
},
},
})) {
const msgType =
message.type === 'system'
? `system/${(message as { subtype?: string }).subtype}`
: message.type;
log(`[slash-cmd] type=${msgType}`);
if (message.type === 'system' && message.subtype === 'init') {
slashSessionId = message.session_id;
log(`Session after slash command: ${slashSessionId}`);
}
const observedCompaction = compactBoundaryFromMessage(message, log);
if (observedCompaction) {
compactBoundarySeen = true;
slashCompaction = observedCompaction;
}
if (message.type === 'result') {
const resultSubtype = (message as { subtype?: string }).subtype;
const textResult =
'result' in message ? (message as { result?: string }).result : null;
if (resultSubtype?.startsWith('error')) {
hadError = true;
writeOutput({
status: 'error',
result: null,
error: textResult || 'Session command failed.',
newSessionId: slashSessionId,
...buildCompactionOutput(slashCompaction),
});
} else {
writeOutput({
status: 'success',
result: textResult || 'Conversation compacted.',
newSessionId: slashSessionId,
...buildCompactionOutput(slashCompaction),
});
}
resultEmitted = true;
}
}
} catch (err) {
hadError = true;
const errorMsg = err instanceof Error ? err.message : String(err);
log(`Slash command error: ${errorMsg}`);
writeOutput({ status: 'error', result: null, error: errorMsg });
}
log(
`Slash command done. compactBoundarySeen=${compactBoundarySeen}, hadError=${hadError}`,
);
if (!hadError && !compactBoundarySeen) {
log(
'WARNING: compact_boundary was not observed. Compaction may not have completed.',
);
}
if (!resultEmitted && !hadError) {
writeOutput({
status: 'success',
result: compactBoundarySeen
? 'Conversation compacted.'
: 'Compaction requested but compact_boundary was not observed.',
newSessionId: slashSessionId,
...buildCompactionOutput(slashCompaction),
});
} else if (!hadError) {
writeOutput({
status: 'success',
result: null,
newSessionId: slashSessionId,
...buildCompactionOutput(slashCompaction),
});
}
}