feat: add seq cursor, work_items, provider fallback, and delivery reliability

Major reliability improvements to the message processing pipeline:

- Add messages.seq monotonic cursor replacing timestamp-based cursors,
  preventing message loss from timestamp collisions with LIMIT queries
- Add work_items table separating agent production from delivery,
  enabling delivery retry without re-running the agent
- Propagate Discord send failures instead of silently swallowing them
- Add Claude 429 → Kimi K2.5 automatic provider fallback with cooldown
- Fix follow-up turn state reset in live index.ts path (not just
  message-runtime.ts) to prevent final output from being lost
- Add restart context tracking for graceful restart announcements
- Lazy migration from timestamp cursors to seq cursors for existing data

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Eyejoker
2026-03-23 00:11:32 +09:00
parent dd15585941
commit b647502a10
19 changed files with 2782 additions and 209 deletions

View File

@@ -156,6 +156,8 @@ function prepareGroupEnvironment(
// Build environment variables for the runner process
const envVars = readEnvFile([
'ANTHROPIC_API_KEY',
'ANTHROPIC_AUTH_TOKEN',
'ANTHROPIC_BASE_URL',
'CLAUDE_CODE_OAUTH_TOKEN',
'CLAUDE_MODEL',
'CLAUDE_THINKING',
@@ -340,6 +342,8 @@ args = [${JSON.stringify(mementoSseUrl)}, "--header", ${JSON.stringify(`Authoriz
// Sanitize secrets: prevent API keys from leaking to codex subprocesses
delete env.ANTHROPIC_API_KEY;
delete env.ANTHROPIC_AUTH_TOKEN;
delete env.ANTHROPIC_BASE_URL;
delete env.CLAUDE_CODE_OAUTH_TOKEN;
env.CODEX_HOME = sessionCodexDir;
@@ -349,6 +353,19 @@ args = [${JSON.stringify(mementoSseUrl)}, "--header", ${JSON.stringify(`Authoriz
env.ANTHROPIC_API_KEY =
envVars.ANTHROPIC_API_KEY || process.env.ANTHROPIC_API_KEY || '';
}
if (
envVars.ANTHROPIC_AUTH_TOKEN ||
process.env.ANTHROPIC_AUTH_TOKEN
) {
env.ANTHROPIC_AUTH_TOKEN =
envVars.ANTHROPIC_AUTH_TOKEN ||
process.env.ANTHROPIC_AUTH_TOKEN ||
'';
}
if (envVars.ANTHROPIC_BASE_URL || process.env.ANTHROPIC_BASE_URL) {
env.ANTHROPIC_BASE_URL =
envVars.ANTHROPIC_BASE_URL || process.env.ANTHROPIC_BASE_URL || '';
}
if (
envVars.CLAUDE_CODE_OAUTH_TOKEN ||
process.env.CLAUDE_CODE_OAUTH_TOKEN
@@ -384,6 +401,7 @@ export async function runAgentProcess(
input: AgentInput,
onProcess: (proc: ChildProcess, processName: string) => void,
onOutput?: (output: AgentOutput) => Promise<void>,
envOverrides?: Record<string, string>,
): Promise<AgentOutput> {
const startTime = Date.now();
const { env, groupDir, runnerDir } = prepareGroupEnvironment(
@@ -391,6 +409,13 @@ export async function runAgentProcess(
input.isMain,
input.chatJid,
);
// Apply provider fallback overrides (e.g. Kimi env vars when Claude is in cooldown)
if (envOverrides) {
for (const [key, value] of Object.entries(envOverrides)) {
if (value) env[key] = value;
}
}
if (input.runId) {
env.NANOCLAW_RUN_ID = input.runId;
}