feat: auto-suspend tasks on quota errors, add Claude progress tracking

- Auto-suspend tasks after 3 consecutive quota/auth errors with
  retry-after date parsing and Discord notification
- Add tool_progress and tool_use_summary streaming from Claude Agent SDK
- Prefix progress messages with invisible marker so bots ignore them
- Fix misleading provider label in codex service logs
This commit is contained in:
Eyejoker
2026-03-23 17:03:17 +09:00
parent 42d721a9ec
commit 77e79505eb
8 changed files with 320 additions and 32 deletions

View File

@@ -214,6 +214,14 @@ function createSchema(database: Database.Database): void {
/* column already exists */
}
try {
database.exec(
`ALTER TABLE scheduled_tasks ADD COLUMN suspended_until TEXT`,
);
} catch {
/* column already exists */
}
database.exec(`
UPDATE scheduled_tasks
SET agent_type = COALESCE(
@@ -928,7 +936,12 @@ export function updateTask(
updates: Partial<
Pick<
ScheduledTask,
'prompt' | 'schedule_type' | 'schedule_value' | 'next_run' | 'status'
| 'prompt'
| 'schedule_type'
| 'schedule_value'
| 'next_run'
| 'status'
| 'suspended_until'
>
>,
): void {
@@ -955,6 +968,10 @@ export function updateTask(
fields.push('status = ?');
values.push(updates.status);
}
if (updates.suspended_until !== undefined) {
fields.push('suspended_until = ?');
values.push(updates.suspended_until);
}
if (fields.length === 0) return;
@@ -1005,10 +1022,11 @@ export function getDueTasks(
`
SELECT * FROM scheduled_tasks
WHERE status = 'active' AND agent_type = ? AND next_run IS NOT NULL AND next_run <= ?
AND (suspended_until IS NULL OR suspended_until <= ?)
ORDER BY next_run
`,
)
.all(agentType, now) as ScheduledTask[];
.all(agentType, now, now) as ScheduledTask[];
}
export function updateTaskAfterRun(
@@ -1042,6 +1060,25 @@ export function logTaskRun(log: TaskRunLog): void {
);
}
export function getRecentConsecutiveErrors(
taskId: string,
limit: number = 5,
): string[] {
const rows = db
.prepare(
`SELECT status, error FROM task_run_logs
WHERE task_id = ? ORDER BY run_at DESC LIMIT ?`,
)
.all(taskId, limit) as Array<{ status: string; error: string | null }>;
const errors: string[] = [];
for (const row of rows) {
if (row.status !== 'error' || !row.error) break;
errors.push(row.error);
}
return errors;
}
// --- Router state accessors ---
export function getRouterState(key: string): string | undefined {