UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

78 lines (77 loc) • 3.5 kB
import { deliverEngineNotice } from "../types.js"; export function streamHaltVerbs(input) { const { spec, live, ready, orTimeout, readyTimeoutMs: READY_TIMEOUT_MS, run, reapSuspended, runner } = input; let destroyOnce; const halt = async () => { const haltRefused = (msg) => { const e = new Error(`cannot halt: ${msg}`); e.code = "steering.not_running"; return e; }; if (live.resultValue) throw haltRefused("the task has already finished"); const h = live.handle ?? (await orTimeout(ready)); if (!h) throw haltRefused("the task is not running"); const apply = () => { const abortOwnedBeforeHalt = h.abortController.signal.aborted; const receipt = h.loop.ended ? { turnCut: false, accepted: false } : h.harness.halt(); h.loop.callerStop.stop(); if (receipt.accepted && !abortOwnedBeforeHalt) { h.loop.userHalted = true; } else if (!h.loop.userHalted) { deliverEngineNotice(runner.deps.onNotice, { code: "task.halt_unconsumed", message: "a user halt arrived while the run was already ending for its own reason: it cut no turn and stopped no " + "boundary — the run's own ending stands, and the result will not carry haltedByUser for this halt.", detail: { sessionId: h.sessionId, runId: h.runId, ...(spec.taskId !== undefined ? { taskId: spec.taskId } : {}), }, }); } if (receipt.turnCut) { deliverEngineNotice(runner.deps.onNotice, { code: "task.turn_interrupted", message: "a bare user halt cut the running turn: in-flight work was cut at a manufactured boundary " + "(finished tool calls keep their real results; never-started ones settle as interrupted) and " + "the run is collecting to a clean, resumable stop — no further model turn will start.", detail: { cause: "user_halt", sessionId: h.sessionId, runId: h.runId, ...(spec.taskId !== undefined ? { taskId: spec.taskId } : {}), }, }); } return { turnCut: receipt.turnCut }; }; const birthDeadline = Date.now() + READY_TIMEOUT_MS; while (live.resultValue === undefined) { try { return apply(); } catch (e) { if (!(e instanceof Error && e.code === "invalid_state")) throw e; } if (Date.now() >= birthDeadline) throw haltRefused("the task is no longer running"); await new Promise((r) => setTimeout(r, 10)); } throw haltRefused("the task has already finished"); }; const destroy = () => (destroyOnce ??= (async () => { const h = live.handle ?? (await orTimeout(ready)); if (h) { h.abortController.abort(); void h.harness.abort(); h.loop.callerStop.stop(); } await orTimeout(run.catch(() => { })); await reapSuspended(); })()); return { halt, destroy }; }