UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

167 lines (166 loc) • 8.82 kB
import { createPeerInboundChainRef, createPeerSelfRef } from "../../agents/peer-admission.js"; import { boundInputHashOf } from "../canonical-json.js"; import { createLiveNotificationLane, DurableAppendObligations, injectionLaneOf, isDelegatedAgentTerminal, isTerminalTaskNotification, renderTaskNotificationXml, routeOrParkNotification, SystemInjectionQueue, taskNotificationDedupKey } from "../task-notification.js"; import { ToolDetachHub } from "../tool-detach.js"; import { pendingContentAskCallId } from "./decide-continuation.js"; export function runNotificationLane(input) { const obligations = new DurableAppendObligations(); return obligations.run(() => runNotificationLaneAs(input, obligations)); } async function runNotificationLaneAs(input, obligations) { const { spec, queue, resume, internals, taskIdRef, holdSessionLock, runner, self, sessions, sessionReadStates, sessionModeSeats, pendingSessionNotifications, prepareTask, next } = input; const prepareResume = resume ? { leafId: resume.cp.leafId, suspendedBatch: new Set(resume.cp.pendingAction.kind === "tool_approval" ? resume.cp.pendingAction.batchToolCallIds : []), seed: resume.cp.state, outcome: resume.outcome, ...(resume.legMods !== undefined ? { legMods: resume.legMods } : {}), priorSuspendCount: resume.cp.suspendCount, priorLedger: resume.cp.resourceLedger, priorHumanReview: resume.cp.humanReview, workspaceHandle: resume.cp.state.workspaceHandle, executesApprovedAction: resume.cp.pendingAction.kind === "tool_approval" && resume.outcome.gate !== "wake" && resume.outcome.decision === "allow", ...(resume.outcome.gate !== "wake" && resume.outcome.decision === "allow" && pendingContentAskCallId(resume.cp) !== undefined ? { redeemedContentAskCallId: pendingContentAskCallId(resume.cp), redeemedContentAskQuestionsHash: boundInputHashOf(resume.cp.pendingAction.args?.questions), } : {}), } : undefined; const taskNotificationQueue = new SystemInjectionQueue(); const detachHub = internals?.detachHub ?? new ToolDetachHub(); let notificationHarness; let notificationIdent = () => ({}); let notificationLaneLive = true; let notificationSessionId; let descendantAnchorDisclosed = false; const parkTaskNotification = (payload, priority, onDisposition) => { laneSeat.handOff(payload); const ownSession = notificationSessionId; if (ownSession !== undefined) routeOrParkNotification(pendingSessionNotifications, ownSession, payload, priority, onDisposition); else onDisposition?.("parked"); if (!isDelegatedAgentTerminal(payload)) return; const uplink = internals?.parentNotify; if (uplink !== undefined) { try { uplink(payload, { priority }); } catch (e) { runner.deps.onError?.(e, { phase: "degraded", sessionId: notificationSessionId ?? "", classification: "descendant-terminal-uplink" }); } return; } const rootAnchor = internals?.rootSessionId; if (rootAnchor !== undefined && rootAnchor !== notificationSessionId) { routeOrParkNotification(pendingSessionNotifications, rootAnchor, payload, priority); return; } if (internals?.parentSessionId !== undefined && !descendantAnchorDisclosed) { descendantAnchorDisclosed = true; runner.deps.onError?.(new Error(`background-agent terminal ${payload.task_id} stayed on this run's own session — the delegation tree's root session is not reachable from this run (no uplink, no root anchor)`), { phase: "degraded", sessionId: notificationSessionId ?? "", classification: "descendant-terminal-unrouted" }); } }; const unsubscribeTaskNotifications = taskNotificationQueue.subscribe((item) => { queue.push({ type: "task_notification", notification: item.payload, priority: item.priority, ...notificationIdent() }); if (notificationHarness && prepared.batchHaltRef.current === undefined) { const xml = renderTaskNotificationXml(item.payload); const noteOptions = { provenance: "engine-note", enginePayload: item.payload, engineNoteKey: taskNotificationDedupKey(item.payload), ...(item.payload.task_type !== "external" && isTerminalTaskNotification(item.payload) ? { capPreferred: true } : {}), }; const deliver = injectionLaneOf(item.priority) === "followUp" ? notificationHarness.followUp(xml, noteOptions) : notificationHarness.steer(xml, { ...noteOptions, ...(item.priority === "now" ? { immediate: true } : {}) }); void deliver.then(() => item.onDisposition?.("queued"), () => { parkTaskNotification(item.payload, item.priority, item.onDisposition); }); } else { parkTaskNotification(item.payload, item.priority, item.onDisposition); } }); const upstreamTaskNotification = internals?.onTaskNotification; const deliveredAtTurnOpen = new Set(); const laneSeat = createLiveNotificationLane({ queue: taskNotificationQueue, canTakeNow: () => notificationLaneLive && notificationHarness?.acceptsInjections === true, isLive: () => notificationLaneLive, alreadyDelivered: (key) => deliveredAtTurnOpen.has(key), }); const takeOnThisLane = laneSeat.take; const takeAsDestination = laneSeat.takeAsDestination; const injectTaskNotification = (notification, opts) => { const priority = opts?.priority ?? "later"; return new Promise((resolve) => { const own = takeOnThisLane(notification, priority, resolve); if (own === "duplicate") resolve("dropped_duplicate"); else if (own === "pending") resolve("queued"); else if (own === "unavailable") parkTaskNotification(notification, priority, resolve); }); }; const peerSelfRef = internals?.peerSelfRef ?? createPeerSelfRef(internals?.registryScope ?? spec.principal ?? "default"); const peerInboundChainRef = internals?.peerInboundChainRef ?? createPeerInboundChainRef(); const modelCatalog = runner.deps.models; const prepared = await prepareTask(spec, runner.deps, sessions, prepareResume, { ...(internals ?? {}), peerSelfRef, peerInboundChainRef, detachHub, sessionReadStates, sessionModeSeats, onTaskNotification: (notification, opts) => { try { upstreamTaskNotification?.(notification, opts); } catch { } return injectTaskNotification(notification, opts); }, }, self, taskIdRef, holdSessionLock); pendingSessionNotifications.bindSessionBox(prepared.sessionId, prepared.sessionBox); const notificationLane = { get harness() { return notificationHarness; }, set harness(h) { notificationHarness = h; }, get sessionId() { return notificationSessionId; }, set sessionId(s) { if (notificationSessionId !== undefined) pendingSessionNotifications.liveLanes.unregister(notificationSessionId, takeAsDestination); notificationSessionId = s; if (s !== undefined && notificationLaneLive) pendingSessionNotifications.liveLanes.register(s, takeAsDestination); }, get ident() { return notificationIdent; }, set ident(f) { notificationIdent = f; }, get live() { return notificationLaneLive; }, set live(v) { notificationLaneLive = v; if (notificationSessionId === undefined) return; if (v) { pendingSessionNotifications.liveLanes.register(notificationSessionId, takeAsDestination); } else { pendingSessionNotifications.liveLanes.unregister(notificationSessionId, takeAsDestination); laneSeat.release(); } }, }; try { return await next({ prepared, injectTaskNotification, deliveredAtTurnOpen, peerInboundChainRef, modelCatalog, notificationLane, unsubscribeTaskNotifications }); } finally { if (notificationSessionId !== undefined) pendingSessionNotifications.liveLanes.unregister(notificationSessionId, takeAsDestination); await obligations.settle(); } }