@adonis-agora/agent
Version:
Governed, durable-backed AI agent (chat + tool-calling + governance) for AdonisJS — part of the Agora ecosystem.
106 lines • 5.76 kB
TypeScript
import type { AgentDepsFactory } from '../agent-deps-factory.js';
import { type ChatQueueService } from '../chat-queue-service.js';
import { type HumanReply } from '../elicitation.js';
import type { AgentRunner, AgentRunStartOptions } from '../spi/agent-runner.js';
import type { AgentStore } from '../spi/agent-store.js';
import type { AgentRunInput } from '../types.js';
/**
* Runs the agent turn in-process — the default runner (`durable: false`). A HITL wait resolves a
* pending promise keyed run-namespaced by `${runId}:${toolCallId}`, so one run's reply can never
* satisfy another's pending action.
*
* Sub-agent delegation runs a nested loop on a transient sub-thread, and that nested run parks on a
* human exactly as a top-level one does — under its OWN runId, which is what makes it answerable:
* the pending row it writes carries that runId, and its frames are forwarded into the top-level
* ancestor's stream (the only stream anyone subscribed to) carrying it too. The mirror of the
* durable runner's `sinkRunId`.
*
* Single-replica only — durable is the scaled path (deferred).
*/
export declare class InlineAgentRunner implements AgentRunner {
private readonly factory;
private readonly store;
/** The thread message queue this runner drains when a thread's own turn settles. */
private readonly queue?;
private readonly pending;
/**
* The input of each top-level run this process is running — what {@link isRunActive} answers
* from, and what a cancel settles the thread (and its queue) of.
*/
private readonly live;
/** Runs someone asked to stop; the loop observes it at its next safe point. */
private readonly cancelled;
/**
* Aborted by {@link cancel}: what cuts short the model call or tool a stopped run is in (the
* loop's `abortSignal` hook). Keyed like {@link cancelled}, and dropped with it.
*/
private readonly aborts;
/** The detached delegates this process is running, by runId — what a Stop on one settles. */
private readonly detached;
constructor(factory: AgentDepsFactory, store: AgentStore,
/** The thread message queue this runner drains when a thread's own turn settles. */
queue?: ChatQueueService | undefined);
/**
* In-process, so exact: a run this process is not running is not running anywhere — the process
* that was running it is gone (a restart), and a thread it still holds is a stale claim.
*/
isRunActive(runId: string): Promise<boolean>;
start(input: AgentRunInput, options?: AgentRunStartOptions): Promise<{
runId: string;
}>;
/**
* Move the thread past a settling TOP-LEVEL run — to the next queued message, which starts here,
* or to a paused/empty queue — and write the resulting `queue` frame into this run's stream. A
* no-op without a queue-capable store, and for a run that is not a thread's own turn.
*
* Never throws: the run is settling either way, and a queue that could not be advanced is picked
* up by the next send or resume on the thread.
*/
private settleQueue;
/**
* Deliver a human's reply to whichever wait this run is parked on.
*
* An approval wait takes a {@link Decision} only. Answers addressed there are refused rather than
* delivered: `approved` is absent on them, and the tool-call row would record a rejection the
* person never made. The refusal leaves the wait intact, so the approval is still there to make.
*
* The other direction IS delivered — a question set parks as a `pending_approval` action, so an
* operator pressing Approve on it is expected, and the loop reads that as "confirmed the
* pre-picked answers".
*/
signal(runId: string, toolCallId: string, reply: HumanReply): Promise<void>;
/** The signal {@link cancel} aborts for `runId` — one per run, shared by its awaited delegates. */
private abortSignalFor;
/** Drop a settled run's Stop: its flag and its abort controller. */
private forgetCancel;
cancel(runId: string): Promise<void>;
private topLevelHooks;
/**
* The two waits a run parks on, keyed by the run that is parked. Identical for a top-level run and
* for a delegated one: `signal(runId, …)` finds either, because the pending map belongs to the
* runner rather than to a run.
*/
private humanHooks;
/** Hold a run at `key` until a human's reply arrives through {@link InlineAgentRunner.signal}. */
/**
* {@link park} for an approval, bounded by the policy's time to live when it has one: the timer
* settles the wait as `expired` — the same Decision the durable runner builds from its signal
* timeout — and a decision that arrives first disarms it.
*/
private parkApproval;
private park;
/**
* Delegate WITHOUT waiting: a nested loop nobody awaits, on its own scratch thread and its OWN
* stream, which posts its answer back into the delegating thread when it lands (`deliverTo`,
* delivered by the loop). The calling turn gets the run id straight back and finishes.
*
* No `sinkRunId`, unlike {@link runNested}: forwarding into the stream the human is watching would
* write into a turn that has already ended. It parks on a human under its own runId, which is what
* `tool-call/approve` resolves the pending row to; and it is stoppable on that id (the receipt
* carries it), which settles the delegation as `cancelled` in the thread that asked.
*/
private startDetached;
/** Delegate to another agent as a nested in-process run (a transient sub-thread). */
private runNested;
}
//# sourceMappingURL=inline-agent-runner.d.ts.map