Governed substrate for autonomous agents: scoped identity (passports), audited actions, MCP workspace. Infra IPs and secrets redacted for public release.
112 lines
4 KiB
JavaScript
112 lines
4 KiB
JavaScript
/**
|
|
* AgentJobService — Shared helpers, constants, and internal status update functions.
|
|
*
|
|
* Used by create.js, query.js, webhook.js, and lifecycle.js.
|
|
*/
|
|
|
|
import { dbRun, isPostgres } from '../../database/connection.js';
|
|
|
|
// ─── TodoWrite → Plan Bridge (Ticket #81861) ────────────────────
|
|
// Claude Code agents use TodoWrite instead of manage_plan.
|
|
// Map TodoWrite status values to manage_plan status values.
|
|
export function mapTodoStatus(status) {
|
|
if (!status) return 'pending';
|
|
const s = String(status).toLowerCase().trim();
|
|
if (s === 'in-progress' || s === 'in_progress') return 'in_progress';
|
|
if (s === 'completed') return 'completed';
|
|
if (s === 'blocked') return 'blocked';
|
|
return 'pending';
|
|
}
|
|
|
|
// ─── CONSTANTS ───────────────────────────────────────────────────
|
|
|
|
export const JOB_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes (matches Claude CLI timeout)
|
|
export const MAX_ATTEMPTS = 3;
|
|
export const MAX_JOB_RESTART_RETRIES = 3; // Max auto-restarts for a single conversation after server restarts
|
|
export const MAX_RECOVERY_REDISPATCH = 2; // Max CLI processes to spawn during startup recovery (prevents server overload)
|
|
|
|
export const JOB_STATUS = {
|
|
PENDING: 'pending',
|
|
PROCESSING: 'processing',
|
|
COMPLETED: 'completed',
|
|
FAILED: 'failed',
|
|
CANCELLED: 'cancelled',
|
|
};
|
|
|
|
// Ticket table & state constants (mirror ChainHandoffService to avoid circular import)
|
|
export const TICKETS_TABLE_ID = 1708;
|
|
export const TICKET_STATE = {
|
|
BACKLOG: 24275,
|
|
IN_PROGRESS: 24276,
|
|
REVIEW: 24277,
|
|
};
|
|
export const MAX_RESTART_RETRIES = 3;
|
|
|
|
// ─── HELPERS ─────────────────────────────────────────────────────
|
|
|
|
export function safeParse(val, fallback = {}) {
|
|
if (typeof val === 'object' && val !== null) return val;
|
|
if (typeof val === 'string') {
|
|
try { return JSON.parse(val); } catch { return fallback; }
|
|
}
|
|
return fallback;
|
|
}
|
|
|
|
// ─── JOB STATUS UPDATES ─────────────────────────────────────────
|
|
|
|
/**
|
|
* Update job status
|
|
*/
|
|
export async function updateJobStatus(jobId, status) {
|
|
if (status === JOB_STATUS.PROCESSING) {
|
|
await dbRun(
|
|
isPostgres()
|
|
? `UPDATE agent_jobs SET status = $1, started_at = NOW(), attempts = attempts + 1 WHERE id = $2`
|
|
: `UPDATE agent_jobs SET status = ?, started_at = datetime('now'), attempts = attempts + 1 WHERE id = ?`,
|
|
[status, jobId]
|
|
);
|
|
} else {
|
|
await dbRun(
|
|
isPostgres()
|
|
? `UPDATE agent_jobs SET status = $1 WHERE id = $2`
|
|
: `UPDATE agent_jobs SET status = ? WHERE id = ?`,
|
|
[status, jobId]
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Mark job as completed with result
|
|
*/
|
|
export async function completeJob(jobId, resultMessage, metadata = {}) {
|
|
await dbRun(
|
|
isPostgres()
|
|
? `UPDATE agent_jobs SET status = $1, result_message = $2, result_metadata = $3, completed_at = NOW() WHERE id = $4`
|
|
: `UPDATE agent_jobs SET status = ?, result_message = ?, result_metadata = ?, completed_at = datetime('now') WHERE id = ?`,
|
|
[JOB_STATUS.COMPLETED, resultMessage, JSON.stringify(metadata), jobId]
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Mark job as failed
|
|
*/
|
|
export async function failJob(jobId, errorMessage) {
|
|
await dbRun(
|
|
isPostgres()
|
|
? `UPDATE agent_jobs SET status = $1, error_message = $2, completed_at = NOW() WHERE id = $3`
|
|
: `UPDATE agent_jobs SET status = ?, error_message = ?, completed_at = datetime('now') WHERE id = ?`,
|
|
[JOB_STATUS.FAILED, errorMessage, jobId]
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Update attempt count
|
|
*/
|
|
export async function updateJobAttempts(jobId, attempts) {
|
|
await dbRun(
|
|
isPostgres()
|
|
? `UPDATE agent_jobs SET attempts = $1, status = 'pending' WHERE id = $2`
|
|
: `UPDATE agent_jobs SET attempts = ?, status = 'pending' WHERE id = ?`,
|
|
[attempts, jobId]
|
|
);
|
|
}
|