E2E 결함 수정: Agent 절대 번들 URL, HAL 작업 재사용(실패 숨김 방지), 도메인 탐침 재개 키, 작업 엔진 동시 실행(부모/자식 교착 해소), 외부 Agent 자리표시자, JobError 4xx, 포털 HAL 이벤트·health 수신, link-portal 공유 비밀

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
zzabisagent-lab
2026-10-26 13:13:39 +00:00
co-authored by Claude Fable 5.1
parent 7662d06009
commit 98f4182bbb
8 changed files with 991 additions and 853 deletions
+26
View File
@@ -0,0 +1,26 @@
/**
* portal/webhooks/api.mjs — HAL → 포털 이벤트 수신과 포털 건강 확인.
*
* POST /internal/hal-events HAL 이 작업 상태 변화를 밀어 넣는다(lib/serviceauth, 키 id 'hal'). 포털 쪽 작업은 5초 주기로 HAL 을 다시 묻는 구조이므로
* 여기서는 "그 HAL 작업을 기다리는 포털 작업" 의 next_run_at 을 지금으로 당겨 바로 깨운다(둘 중 빠른 쪽, docs/HAL-JOBS.md §6).
* GET /api/health HAL 모듈 설명자가 부르는 건강 확인(버전·DB·작업 엔진).
*/
import { verifyCtx } from '../../lib/serviceauth.mjs';
import { HttpError } from '../../lib/http.mjs';
import { nowIso } from '../../lib/db.mjs';
export async function register(app, deps) {
const { store } = deps;
app.get('/api/health', (ctx) => { let db = false; try { db = !!store.get('SELECT 1 AS ok').ok; } catch (_) {} return { ok: db, version: deps.version, db, jobs: deps.jobs?.kinds().length || 0, time: nowIso() }; });
app.post('/internal/hal-events', async (ctx) => {
const r = await verifyCtx(ctx, (id) => (id === 'hal' ? deps.vault.get('service:hal') : null));
if (!r.ok) throw new HttpError(401, 'service_auth_failed', `서비스 인증 실패(${r.reason})`);
const ev = await ctx.body(); const halJobId = String(ev.halJobId || ev.id || ''); if (!halJobId) return { ok: true, woken: 0 };
// 기다리는 포털 작업: 단계 결과에 halJobId 를 적어 둔 작업(halJobFor) 또는 deployments.hal_job_id 로 연결된 작업
const now = nowIso(); let woken = 0;
woken += store.run('UPDATE jobs SET next_run_at=?, updated_at=? WHERE status=\'queued\' AND next_run_at>? AND steps_json LIKE ?', now, now, now, `%${halJobId}%`).changes;
const dep = store.get('SELECT job_id FROM deployments WHERE hal_job_id=?', halJobId);
if (dep && dep.job_id) woken += store.run('UPDATE jobs SET next_run_at=?, updated_at=? WHERE id=? AND status=\'queued\' AND next_run_at>?', now, now, dep.job_id, now).changes;
return { ok: true, woken };
});
}