refactor(agent-runner): decouple provider interface from Claude specifics

Reshape AgentProvider so provider-specific assumptions stop leaking into
the generic layer. No change to what reaches sdkQuery() — same values,
different plumbing.

- QueryInput: opaque `continuation` replaces `sessionId` + `resumeAt`;
  `systemContext.instructions` replaces ambiguous `systemPrompt`;
  `mcpServers`, `env`, `additionalDirectories` move to `ProviderOptions`
  at construction time.
- AgentProvider gains `isSessionInvalid(err)` and
  `supportsNativeSlashCommands` so the poll-loop stops regex-matching
  Claude error strings and gates passthrough slash commands per provider.
- ClaudeProvider owns `CLAUDE_CODE_AUTO_COMPACT_WINDOW` and the
  stale-session regex internally.
- ProviderEvent.activity kept and documented as the liveness signal
  (fires on every SDK message so the idle timer stays honest during
  long tool runs); init carries `continuation` instead of `sessionId`.
- poll-loop drops mcpServers/env/systemPrompt from its config; admin
  user id now passed explicitly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
gavrielc
2026-04-13 10:25:29 +03:00
parent e07158e194
commit b63dd186df
8 changed files with 156 additions and 114 deletions

View File

@@ -40,19 +40,18 @@ const GLOBAL_CLAUDE_MD = '/workspace/global/CLAUDE.md';
async function main(): Promise<void> {
const providerName = (process.env.AGENT_PROVIDER || 'claude') as ProviderName;
const assistantName = process.env.NANOCLAW_ASSISTANT_NAME;
const adminUserId = process.env.NANOCLAW_ADMIN_USER_ID;
log(`Starting v2 agent-runner (provider: ${providerName})`);
const provider = createProvider(providerName, { assistantName });
// Load global CLAUDE.md as additional system context, then append destinations addendum
let systemPrompt: string | undefined;
let instructions: string | undefined;
if (fs.existsSync(GLOBAL_CLAUDE_MD)) {
systemPrompt = fs.readFileSync(GLOBAL_CLAUDE_MD, 'utf-8');
instructions = fs.readFileSync(GLOBAL_CLAUDE_MD, 'utf-8');
log('Loaded global CLAUDE.md');
}
const addendum = buildSystemPromptAddendum();
systemPrompt = systemPrompt ? `${systemPrompt}\n\n${addendum}` : addendum;
instructions = instructions ? `${instructions}\n\n${addendum}` : addendum;
// Discover additional directories mounted at /workspace/extra/*
const additionalDirectories: string[] = [];
@@ -73,12 +72,6 @@ async function main(): Promise<void> {
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const mcpServerPath = path.join(__dirname, 'mcp-tools', 'index.js');
// SDK env
const env: Record<string, string | undefined> = {
...process.env,
CLAUDE_CODE_AUTO_COMPACT_WINDOW: '165000',
};
// Build MCP servers config: nanoclaw built-in + any additional from host
const mcpServers: Record<string, { command: string; args: string[]; env: Record<string, string> }> = {
nanoclaw: {
@@ -105,13 +98,18 @@ async function main(): Promise<void> {
}
}
const provider = createProvider(providerName, {
assistantName,
mcpServers,
env: { ...process.env },
additionalDirectories: additionalDirectories.length > 0 ? additionalDirectories : undefined,
});
await runPollLoop({
provider,
cwd: CWD,
mcpServers,
systemPrompt,
env,
additionalDirectories: additionalDirectories.length > 0 ? additionalDirectories : undefined,
systemContext: { instructions },
adminUserId,
});
}