@sema-agent/core
Version:
Stateless, task-oriented AI agent core
189 lines (188 loc) • 11.7 kB
JavaScript
import { Type } from "typebox";
import { defineTool } from "../core/tools.js";
import { inlineUntrusted } from "../core/untrusted-text.js";
import { formatPeerNameRef } from "./cross-session-ref.js";
import { entryAccessible } from "./roster-store.js";
import { mintPeerSessionCandidates, PEER_PRESENCE_MAX_CHARS, peerSessionBoxHandle } from "./peer-directory.js";
import { PEER_LANE_DEFAULTS } from "./peer-admission.js";
import { toolFace } from "../core/tool-catalog-entries.js";
import { thrownText } from "../core/thrown-value.js";
import { assertNoRetiredKeys } from "../core/retired-keys.js";
import { agentActivityClause, agentApprovalClause, formatAgo, readAgentState } from "../core/agent-state.js";
export const LIST_AGENTS_TOOL_NAME = "ListAgents";
export const LIST_AGENTS_TOOL_ALIAS = "ListPeers";
export const LIST_AGENTS_MAX_RESULT_CHARS = 10_000;
export const PEER_LISTING_FRESH_WINDOW_MS = PEER_LANE_DEFAULTS.listingFreshWindowMs;
export function isFreshPeerRow(r, now, windowMs = PEER_LISTING_FRESH_WINDOW_MS) {
if (r.liveness !== "live")
return false;
if (r.tempo === "active" || r.tempo === "blocked")
return true;
return now - r.updatedAt <= windowMs;
}
export { formatAgo };
export function cwdTail(cwd) {
const parts = cwd.split(/[\\/]+/).filter((p) => p !== "");
return parts.slice(-2).join("/");
}
const AGENT_ROW_STATE = {
pending: "running",
running: "running",
parked: "parked",
completed: "finished",
failed: "failed",
killed: "killed",
cancelled: "cancelled",
};
function agentRowState(status) {
return Object.hasOwn(AGENT_ROW_STATE, status) ? AGENT_ROW_STATE[status] : "unknown";
}
const PEER_ROW_STATE = {
active: "busy",
blocked: "busy",
idle: "idle",
};
function peerRowState(r) {
if (r.liveness === "dead")
return "offline";
const tempo = r.tempo;
return tempo !== undefined && Object.hasOwn(PEER_ROW_STATE, tempo) ? PEER_ROW_STATE[tempo] : "unknown";
}
function renderPresence(p) {
const parts = [...(p.agent !== undefined ? [`agent=${inlineUntrusted(p.agent, PEER_PRESENCE_MAX_CHARS)}`] : []), ...(p.intent !== undefined ? [`intent=${inlineUntrusted(p.intent, PEER_PRESENCE_MAX_CHARS)}`] : [])];
return `self: ${parts.join(", ")}`;
}
const RETIRED_LIST_AGENTS_OPTION_KEYS = {
includeOffline: "the model asks for stale and offline rows per call with the `includeAll` tool parameter (a superset: stale + offline)",
};
export function createListAgentsTool(opts) {
assertNoRetiredKeys(opts, RETIRED_LIST_AGENTS_OPTION_KEYS, "ListAgentsToolOptions");
const peers = opts.peerDirectory !== undefined;
const windowMs = opts.freshWindowMs ?? PEER_LISTING_FRESH_WINDOW_MS;
return defineTool({
name: LIST_AGENTS_TOOL_NAME,
...toolFace("list-agents"),
isConcurrencySafe: () => true,
description: `List the agents${peers ? " and peer sessions" : ""} you can message with SendMessage. ` +
(peers
? `Peer sessions are other sessions of this engine running for the same user, shown as \`name [ref] · state · active <N> ago · <directory> · self: agent=…, intent=…\`; send to one with its name (or \`name [ref]\` ` +
`when a name is ambiguous, or its \`session.<id>\` address). By default only FRESH peers are listed (working now, or active within the last ${formatAgo(windowMs)}); ` +
`pass includeAll: true to also list stale and offline ones. A peer that is offline or unlisted still accepts messages — they are parked ` +
`durably in its session box and delivered when it next runs. `
: `Peer sessions of this engine are not addressable on this mount (no session directory). `) +
`Background agents you spawned are listed with their task_id, their status, when they last did anything, and — for one parked on a durable approval — that approval's card id, state and deadline. ` +
`A ref is only valid against the listing (or disambiguation error) you read it from — do not invent one.`,
parameters: Type.Object({
includeAll: Type.Optional(Type.Boolean({ description: "Also list stale (no activity within the freshness window) and offline peer sessions. Default false." })),
}),
execute: async (args) => {
const includeAll = args.includeAll === true;
const now = opts.now?.() ?? Date.now();
const rows = [];
let peerRows = [];
let directoryFault;
if (opts.peerDirectory !== undefined) {
try {
peerRows = await opts.peerDirectory.listPeerSessions({ scope: opts.self.scope });
}
catch (e) {
directoryFault = thrownText(e);
}
}
for (const c of mintPeerSessionCandidates(peerRows, { sessionId: opts.self.sessionId })) {
const r = c.record;
if (r.sessionId.toLowerCase() === opts.self.sessionId.toLowerCase())
continue;
if (r.liveness === "deleted")
continue;
if (!includeAll && !isFreshPeerRow(r, now, windowMs))
continue;
const presence = { ...(r.agent !== undefined ? { agent: r.agent } : {}), ...(r.intent !== undefined ? { intent: r.intent } : {}) };
rows.push({
kind: "peer_session",
name: r.name,
ref: c.ref,
address: peerSessionBoxHandle(r.sessionId),
state: peerRowState(r),
...(r.liveness === "live" ? { lastActiveAt: r.updatedAt } : r.diedAt !== undefined ? { offlineAt: r.diedAt } : {}),
...(r.cwd !== undefined ? { cwd: cwdTail(r.cwd) } : {}),
...(presence.agent !== undefined || presence.intent !== undefined ? { presence } : {}),
});
}
let registryCapped = false;
const agentStateOf = (id, view) => {
const src = opts.registry?.agentStateSource?.(id, view);
return src === undefined ? undefined : readAgentState(src, opts.checkpointSeat, opts.now?.() ?? Date.now());
};
const views = [opts.access, opts.parentAccess].filter((a) => a !== undefined);
const seenIds = new Set();
if (opts.registry !== undefined) {
for (const view of views) {
const listed = opts.registry.list(view, { limit: 500 });
registryCapped ||= listed.length >= 500;
for (const t of listed) {
if (t.type !== "background_agent" || seenIds.has(t.task_id))
continue;
seenIds.add(t.task_id);
const full = opts.registry.getAccessibleTask(t.task_id, view);
const stateRead = agentStateOf(t.task_id, view);
const agentState = stateRead instanceof Promise ? await stateRead : stateRead;
rows.push({
kind: "background_agent",
name: full?.name ?? t.description ?? t.task_id,
address: t.task_id,
state: agentRowState(agentState?.status ?? t.status),
...(agentState !== undefined ? { agentState } : {}),
});
}
}
}
if (opts.roster !== undefined && views.length > 0) {
try {
for (const e of await opts.roster.list()) {
const view = views.find((v) => entryAccessible(e, v));
if (view === undefined || seenIds.has(e.agentId))
continue;
seenIds.add(e.agentId);
const live = opts.registry?.getAccessibleTask(e.agentId, view);
const stateRead = live === undefined ? undefined : agentStateOf(e.agentId, view);
const agentState = stateRead instanceof Promise ? await stateRead : stateRead;
rows.push({
kind: "roster",
name: e.name,
address: e.agentId,
state: live === undefined ? "unknown" : agentRowState(agentState?.status ?? live.status),
...(agentState !== undefined ? { agentState } : {}),
});
}
}
catch {
}
}
const tier = opts.continuationTier ?? "none";
const rosterLabel = tier === "full" ? "agent (durable record)" : tier === "rows" ? "agent (record kept; conversation not kept across restarts)" : "agent (recorded name)";
const finishedReach = tier === "full"
? "while its session is retained or a durable record exists"
: tier === "rows"
? "while its session is retained or this engine process still holds its conversation"
: "while its session is retained";
const lines = rows.map((r) => {
const label = r.kind === "peer_session" ? formatPeerNameRef(inlineUntrusted(r.name, 64), r.ref) : inlineUntrusted(r.name, 64);
const age = r.kind !== "peer_session" ? undefined : r.lastActiveAt !== undefined ? `active ${formatAgo(now - r.lastActiveAt)} ago` : r.offlineAt !== undefined ? `offline ${formatAgo(now - r.offlineAt)} ago` : "offline";
const activity = r.agentState !== undefined ? agentActivityClause(r.agentState) : undefined;
const approval = r.agentState?.approval !== undefined ? agentApprovalClause(r.agentState.approval, r.agentState.readAt) : undefined;
const cols = [label, r.state, age, activity, approval, r.cwd !== undefined ? inlineUntrusted(r.cwd, 64) : undefined, r.presence !== undefined ? renderPresence(r.presence) : undefined, r.kind === "peer_session" ? "peer session" : r.kind === "roster" ? rosterLabel : "agent", r.address];
return cols.filter((c) => c !== undefined).join(" · ");
});
const finishedCount = rows.filter((r) => r.state === "finished").length;
const header = rows.length === 0 ? "No agents or peer sessions are listed right now." : `${rows.length} listed:`;
const finishedNote = finishedCount > 0 ? `\n(${finishedCount} finished agent(s): continuable by SendMessage only ${finishedReach} — otherwise SendMessage answers with an honest refusal)` : "";
const fault = (directoryFault !== undefined ? `\n(peer-session directory unreadable: ${inlineUntrusted(directoryFault, 200)} — peer rows may be missing)` : "") +
(registryCapped ? "\n(the live task list is at its 500-row window: older background agents may be missing from this listing — address them by task_id)" : "");
let content = `${header}${lines.length > 0 ? `\n${lines.join("\n")}` : ""}${finishedNote}${fault}`;
if (content.length > LIST_AGENTS_MAX_RESULT_CHARS)
content = `${content.slice(0, LIST_AGENTS_MAX_RESULT_CHARS)}\n[listing truncated: ${content.length} chars total]`;
return { content, details: { type: "list-agents", rows, ...(directoryFault !== undefined ? { directoryFault: true } : {}) } };
},
});
}