#!/usr/bin/env node /** * progress.mjs — 任务进度四态上报(AgentTaskStatus 表) * * 四态: ack → running → done | failed,running 期间心跳保鲜。 * 幂等: 同 agentId+taskId 覆盖更新(重复上报不产生脏数据)。 * * 用法: * node progress.mjs ack --agent --task [--name <任务名>] [--note <备注>] * node progress.mjs running --agent --task [--note <备注>] * node progress.mjs done --agent --task [--note <结果备注>] * node progress.mjs failed --agent --task --note <失败根因> * node progress.mjs heartbeat --agent --task * * 纪律: running 状态 30s 无心跳 = 疑似死亡,调度层应主动查,不靠"以为还在跑"。 */ import { resolveCredentials, failNoCredentials, wrapDate, upsertObject, queryObjects, } from './parse-client.mjs'; const TABLE = 'AgentTaskStatus'; const VALID = ['ack', 'running', 'done', 'failed']; function parseArgs(argv) { const state = argv[0]; const args = { state }; for (let i = 1; i < argv.length; i += 2) { const key = (argv[i] || '').replace(/^--/, ''); args[key] = argv[i + 1]; } return args; } const args = parseArgs(process.argv.slice(2)); const isHeartbeat = args.state === 'heartbeat'; if (!VALID.includes(args.state) && !isHeartbeat) { console.error(`用法: node progress.mjs --agent --task [--name <任务名>] [--note <备注>]`); process.exit(1); } if (!args.agent || !args.task) { console.error('[task-progress] --agent 与 --task 必填(幂等键 agentId+taskId)'); process.exit(1); } const cred = resolveCredentials(); if (!cred) failNoCredentials(); const now = new Date(); const where = { agentId: args.agent, taskId: args.task }; // 心跳:只刷 heartbeatAt,不动状态字段 if (isHeartbeat) { const existing = await queryObjects(TABLE, cred, { where, limit: 1 }); if (existing.length === 0) { console.error(`[task-progress] 心跳目标不存在(先 ack): ${args.agent}/${args.task}`); process.exit(1); } const r = await upsertObject(TABLE, cred, where, { heartbeatAt: wrapDate(now) }); console.log(JSON.stringify({ ok: true, op: 'heartbeat', objectId: r.objectId, at: now.toISOString() })); process.exit(0); } // 四态上报:字段组装 const data = { agentId: args.agent, taskId: args.task, status: args.state, heartbeatAt: wrapDate(now), }; if (args.name) data.taskName = args.name; if (args.note) data.resultNote = args.note; if (args.state === 'ack') data.startedAt = wrapDate(now); if (args.state === 'done' || args.state === 'failed') data.endedAt = wrapDate(now); const r = await upsertObject(TABLE, cred, where, data); console.log(JSON.stringify({ ok: true, op: 'progress', state: args.state, agentId: args.agent, taskId: args.task, objectId: r.objectId, created: r.created, at: now.toISOString(), }));