UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

632 lines (631 loc) • 29.3 kB
import { createHash } from "node:crypto"; import { Type } from "typebox"; import { getToolContract } from "../prompt-assembly/tool-catalog.js"; import { protocolOf } from "./protocol-table.js"; import { describeToolCatalog } from "./tool-registry.js"; import { RENDER_HINT_ACTIVITY_MAX_CHARS, RENDER_HINT_MAX_CHARS, RENDER_HINT_MAX_LIST, RESERVED_APPROVAL_CARDS, isToolApprovalCard, TOOL_APPROVAL_CARDS, TOOL_CARD_ID_MAX_CHARS, TOOL_CONTRACT_MAX_CHARS, TOOL_FAMILIES, TOOL_KEYS_MAX, TOOL_KEY_MAX_CHARS, TOOL_MOUNT_TAGS, TOOL_WIRE_NAME_MAX_CHARS, TOOL_PATH_ABSENCES, TOOL_PATH_BASES, } from "./tool-face.js"; import { ASK_USER_QUESTION_TOOL_NAME } from "./ask-question.js"; import { PRESENT_PLAN_TOOL_NAME } from "./present-plan-tool.js"; import { wireOf } from "./tool-name-readings.js"; function lit(values) { return Type.Union(values.map((v) => Type.Literal(v))); } const Name = Type.String({ minLength: 1, maxLength: TOOL_WIRE_NAME_MAX_CHARS }); const Short = Type.String({ maxLength: TOOL_CONTRACT_MAX_CHARS }); const Key = Type.String({ maxLength: TOOL_KEY_MAX_CHARS }); const Digest16 = Type.String({ minLength: 16, maxLength: 16 }); export const ROSTER_SOURCES = Object.freeze(["builtin", "caller", "host", "mcp", "a2a", "external", "synthetic"]); export const ToolRosterRenderHints = Type.Object({ userFacingName: Type.Optional(Type.String({ maxLength: RENDER_HINT_MAX_CHARS })), activity: Type.Optional(Type.String({ maxLength: RENDER_HINT_ACTIVITY_MAX_CHARS })), summaryParams: Type.Optional(Type.Array(Key, { maxItems: RENDER_HINT_MAX_LIST })), resultCards: Type.Optional(Type.Array(Key, { maxItems: RENDER_HINT_MAX_LIST })), approvalCard: Type.Optional(lit(TOOL_APPROVAL_CARDS)), }, { additionalProperties: false }); export const ToolRosterEntry = Type.Object({ name: Name, aliases: Type.Array(Name, { uniqueItems: true }), source: lit(ROSTER_SOURCES), origin: Type.Optional(Type.Object({ peer: Name, declaredBy: Type.Optional(lit(["center", "file", "request"])) }, { additionalProperties: false })), contract: Type.Object({ contractId: Short, implementationRevision: Short }, { additionalProperties: false }), shapeDigest: Digest16, wireSchemaDigest: Digest16, cardId: Type.String({ maxLength: TOOL_CARD_ID_MAX_CHARS }), capabilityId: Digest16, inputKeys: Type.Array(Type.Object({ name: Key, type: lit(["string", "number", "integer", "boolean", "object", "array", "null", "mixed"]) }, { additionalProperties: false }), { maxItems: TOOL_KEYS_MAX }), effect: lit(["read", "write", "idempotent"]), effectProvenance: lit(["declared", "caller", "synthetic", "default"]), offload: Type.Boolean(), offloadThresholdChars: Type.Optional(Type.Integer({ minimum: 0 })), compactable: Type.Boolean(), budgetExempt: Type.Boolean(), egress: Type.Boolean(), egressProvenance: lit(["declared", "caller", "default"]), irreversibility: Type.Optional(lit(["never", "maybe", "always"])), reversibilityProbe: Type.Boolean(), contentOrigin: lit(["local", "execution", "external"]), contentOriginProvenance: lit(["declared", "server", "exempted", "default"]), family: lit(TOOL_FAMILIES), pathTarget: Type.Optional(Type.Object({ param: Key, aliases: Type.Array(Key), access: lit(["read", "create", "edit"]), skillScopeEligible: Type.Boolean(), base: Type.Optional(lit(TOOL_PATH_BASES)), absent: Type.Optional(lit(TOOL_PATH_ABSENCES)), patternParam: Type.Optional(Key), }, { additionalProperties: false })), ruleFace: Type.Object({ primaryParams: Type.Array(Key), params: Type.Array(Key) }, { additionalProperties: false }), approvalPreview: Type.Boolean(), validateInput: Type.Boolean(), deferred: Type.Boolean(), alwaysLoad: Type.Boolean(), modelGate: Type.Optional(Short), renderHints: Type.Optional(ToolRosterRenderHints), mountedBy: lit(TOOL_MOUNT_TAGS), }, { additionalProperties: false }); export const ToolRoster = Type.Object({ schemaVersion: Type.Literal(1), count: Type.Integer({ minimum: 0 }), entries: Type.Array(ToolRosterEntry, { maxItems: 2048 }), digest: Digest16, }, { additionalProperties: false }); export const ToolRosterDelta = Type.Object({ fromDigest: Digest16, roster: ToolRoster, summary: Type.Object({ added: Type.Array(Name), removed: Type.Array(Name), changed: Type.Array(Name) }, { additionalProperties: false }), }, { additionalProperties: false }); export function rosterEntryNamed(roster, toolName) { return roster.entries.find((r) => r.name === toolName) ?? roster.entries.find((r) => r.aliases.includes(toolName)); } export function rosterToolName(roster, name) { return (roster === undefined ? undefined : rosterEntryNamed(roster, name)?.name) ?? name; } function namesResolvingTo(roster, e) { return [e.name, ...e.aliases.filter((a) => a !== e.name && rosterToolName(roster, a) === e.name)]; } export { ruleNamesOf } from "./tool-name-readings.js"; const RESERVED_CARD_OWNER = { question: () => ASK_USER_QUESTION_TOOL_NAME, plan: () => PRESENT_PLAN_TOOL_NAME, }; export function engineCardOf(call) { const wire = wireOf("card.reserved", call); return RESERVED_APPROVAL_CARDS.find((card) => RESERVED_CARD_OWNER[card]() === wire); } export function approvalCardOf(call, declared) { return engineCardOf(call) ?? (declared !== undefined && !RESERVED_APPROVAL_CARDS.includes(declared) ? declared : "generic"); } export function approvalCardMemberOf(call) { const card = call.face?.approvalCard; return card !== undefined ? { approvalCard: card } : {}; } export function toolCallFaceOf(roster, toolName) { if (roster === undefined) return undefined; const e = rosterEntryNamed(roster, toolName); if (e === undefined) return undefined; const resolving = namesResolvingTo(roster, e); return { names: resolving.includes(toolName) ? [toolName, ...resolving.filter((n) => n !== toolName)] : resolving, family: e.family, ...(e.pathTarget !== undefined ? { pathTarget: e.pathTarget } : {}), capabilityId: e.capabilityId, contractId: e.contract.contractId, shapeDigest: e.shapeDigest, implementationRevision: e.contract.implementationRevision, approvalCard: approvalCardOf({ toolName: e.name }, e.renderHints?.approvalCard), }; } export function parkedToolIdentity(roster, toolName) { if (roster === undefined) return {}; const e = rosterEntryNamed(roster, toolName); if (e === undefined) return {}; return { capabilityId: e.capabilityId, contractId: e.contract.contractId, shapeDigest: e.shapeDigest, implementationRevision: e.contract.implementationRevision, rosterDigest: roster.digest }; } export function parkedToolFace(roster, req) { if (roster === undefined) return {}; const e = rosterEntryNamed(roster, req.toolName); if (e === undefined) return {}; const judged = req.face?.names; return { family: e.family, ...(e.pathTarget !== undefined ? { pathTarget: e.pathTarget } : {}), names: judged !== undefined && judged.includes(req.toolName) ? [...judged] : namesResolvingTo(roster, e) }; } export function parkedRowLiveFace(roster, row) { const spelled = row.names?.[0]; const under = spelled !== undefined && roster !== undefined && rosterToolName(roster, spelled) === row.toolName ? spelled : row.toolName; return toolCallFaceOf(roster, under); } export function parkedCallFaceOf(pending) { if (pending.family === undefined || pending.capabilityId === undefined || pending.contractId === undefined || pending.shapeDigest === undefined || pending.implementationRevision === undefined) return undefined; return { ...(pending.names !== undefined ? { names: pending.names } : {}), family: pending.family, ...(pending.pathTarget !== undefined ? { pathTarget: pending.pathTarget } : {}), capabilityId: pending.capabilityId, contractId: pending.contractId, shapeDigest: pending.shapeDigest, implementationRevision: pending.implementationRevision, }; } export function callFaceSeat(face) { return face !== undefined ? { face } : {}; } export function toolIdentityOf(entry) { return { capabilityId: entry.capabilityId, contractId: entry.contract.contractId, shapeDigest: entry.shapeDigest, implementationRevision: entry.contract.implementationRevision }; } function canonical(node) { if (Array.isArray(node)) return `[${node.map(canonical).join(",")}]`; if (node === null || typeof node !== "object") return JSON.stringify(node) ?? "null"; const keys = Object.keys(node).sort(); return `{${keys.map((k) => `${JSON.stringify(k)}:${canonical(node[k])}`).join(",")}}`; } function sha16(text) { return createHash("sha256").update(text).digest("hex").slice(0, 16); } export function mintCapabilityId(t) { return sha16(canonical({ source: t.source, peer: t.peer, name: t.name, contractId: t.contractId })); } export function rosterDigestOf(entries) { return sha16(canonical(entries)); } const SCALAR_TYPES = new Set(["string", "number", "integer", "boolean"]); function schemaTypeOf(node) { if (node === null || typeof node !== "object") return { type: "mixed", scalar: false }; const s = node; const branches = s["anyOf"] ?? s["oneOf"]; if (Array.isArray(branches) && branches.length > 0) { const parts = branches.map(schemaTypeOf); const scalar = parts.every((p) => p.scalar); const first = parts[0].type; return parts.every((p) => p.type === first) ? { type: first, scalar } : { type: "mixed", scalar }; } if (s["const"] !== undefined) { const t = typeof s["const"]; return t === "string" || t === "number" || t === "boolean" ? { type: t, scalar: true } : { type: "mixed", scalar: false }; } const en = s["enum"]; if (Array.isArray(en) && en.length > 0) { const kinds = new Set(en.map((v) => typeof v)); if (kinds.size === 1) { const t = [...kinds][0]; if (t === "string" || t === "number" || t === "boolean") return { type: t, scalar: true }; } return { type: "mixed", scalar: [...kinds].every((k) => k === "string" || k === "number" || k === "boolean") }; } const t = s["type"]; if (typeof t === "string") { if (SCALAR_TYPES.has(t)) return { type: t, scalar: true }; if (t === "object" || t === "array" || t === "null") return { type: t, scalar: false }; } if (Array.isArray(t)) return { type: "mixed", scalar: t.every((x) => typeof x === "string" && SCALAR_TYPES.has(x)) }; if (s["properties"] !== undefined) return { type: "object", scalar: false }; if (s["items"] !== undefined) return { type: "array", scalar: false }; return { type: "mixed", scalar: false }; } export function inputKeysOf(schema) { const keys = []; const scalar = new Set(); const props = schema?.properties; if (props !== null && typeof props === "object" && !Array.isArray(props)) { for (const [name, sub] of Object.entries(props)) { const t = schemaTypeOf(sub); keys.push({ name, type: t.type }); if (t.scalar) scalar.add(name); } } return { keys, scalar }; } export function schemaKeyBoundProblem(schema) { const props = schema?.properties; if (props === null || typeof props !== "object" || Array.isArray(props)) return undefined; const names = Object.keys(props); if (names.length > TOOL_KEYS_MAX) return `the schema declares ${names.length} top-level properties; a roster row carries at most ${TOOL_KEYS_MAX}`; const long = names.find((n) => n.length > TOOL_KEY_MAX_CHARS); if (long !== undefined) return `the schema property ${JSON.stringify(long.slice(0, 40) + "…")} is ${long.length} characters; a roster key is at most ${TOOL_KEY_MAX_CHARS}`; return undefined; } export function rosterMemberBoundProblem(m) { if (typeof m.name !== "string") return { code: "config.tool_roster_bound", message: `tool has no string name (got ${m.name === null ? "null" : typeof m.name}); every roster row is keyed by a string \`name\`` }; const over = (label, value, bound) => `${label} ${JSON.stringify(value.slice(0, 40) + "…")} is ${value.length} characters; the roster's bound is ${bound}`; const of = (text) => `tool ${JSON.stringify(m.name.slice(0, 40))}: ${text}`; if (m.name.length > TOOL_WIRE_NAME_MAX_CHARS) return { code: "config.tool_name_too_long", message: over("tool name", m.name, TOOL_WIRE_NAME_MAX_CHARS) }; const alias = m.aliases?.find((a) => a.length > TOOL_WIRE_NAME_MAX_CHARS); if (alias !== undefined) return { code: "config.tool_name_too_long", message: of(over("alias", alias, TOOL_WIRE_NAME_MAX_CHARS)) }; if (m.peer !== undefined && m.peer.length > TOOL_WIRE_NAME_MAX_CHARS) return { code: "config.tool_name_too_long", message: of(over("peer", m.peer, TOOL_WIRE_NAME_MAX_CHARS)) }; for (const [label, v] of [["contractId", m.contractId], ["implementationRevision", m.implementationRevision]]) { if (v !== undefined && v.length > TOOL_CONTRACT_MAX_CHARS) return { code: "config.tool_contract_too_long", message: of(over(`contract ${label}`, v, TOOL_CONTRACT_MAX_CHARS)) }; } if (m.modelGate !== undefined && m.modelGate.length > TOOL_CONTRACT_MAX_CHARS) return { code: "config.tool_roster_bound", message: of(over("modelGate", m.modelGate, TOOL_CONTRACT_MAX_CHARS)) }; if (m.cardId !== undefined && m.cardId.length > TOOL_CARD_ID_MAX_CHARS) return { code: "config.tool_roster_bound", message: of(over("cardId", m.cardId, TOOL_CARD_ID_MAX_CHARS)) }; return undefined; } export function ruleFaceOfInstance(schema, declared, pathTarget) { const { scalar } = inputKeysOf(schema); const derived = new Set(scalar); if (pathTarget !== undefined) { derived.add(pathTarget.param); for (const a of pathTarget.aliases ?? []) derived.add(a); } const primary = declared?.primaryParams !== undefined ? [...declared.primaryParams] : pathTarget !== undefined ? [pathTarget.param, ...(pathTarget.aliases ?? [])] : []; for (const p of primary) derived.add(p); const params = declared?.params !== undefined ? declared.params.filter((p) => derived.has(p)) : [...derived]; const ordered = [...new Set([...params, ...primary.filter((p) => params.includes(p) || declared?.params === undefined)])]; return { primaryParams: primary, params: ordered }; } function capRenderHints(h) { const out = {}; if (typeof h.userFacingName === "string") out.userFacingName = h.userFacingName; if (typeof h.activity === "string") out.activity = h.activity; if (Array.isArray(h.summaryParams)) out.summaryParams = h.summaryParams.filter((s) => typeof s === "string"); if (Array.isArray(h.resultCards)) out.resultCards = h.resultCards.filter((s) => typeof s === "string"); if (isToolApprovalCard(h.approvalCard)) out.approvalCard = h.approvalCard; return Object.keys(out).length > 0 ? out : undefined; } export function survivorsByName(mounted) { const winner = new Map(); for (const t of mounted) winner.set(t.name, t); return winner; } export const faceOf = (t) => t; function configError(code, message) { const e = new Error(message); e.code = code; return e; } export class RosterBuilder { #tools = []; #engineLane = []; #meta = new WeakMap(); #effects; constructor(effects) { this.#effects = effects; } get tools() { return this.#tools; } get length() { return this.#tools.length; } metaOf(tool) { return this.#meta.get(tool); } mount(tool, meta) { this.#admit(tool, meta); this.#tools.push(tool); } mountEngineLane(tool, meta) { this.#admit(tool, meta); this.#engineLane.push(tool); } #admit(tool, meta) { if ((meta.source === "mcp" || meta.source === "a2a" || meta.source === "external") !== (meta.origin !== undefined)) { throw configError("config.tool_roster_origin", `tool ${JSON.stringify(tool.name)}: a ${meta.source} mount ${meta.origin === undefined ? "requires" : "must not carry"} an origin`); } if (!TOOL_MOUNT_TAGS.includes(meta.mountedBy)) throw configError("config.tool_roster_mount_tag", `tool ${JSON.stringify(tool.name)}: mount tag ${JSON.stringify(meta.mountedBy)} is outside the closed set`); this.#meta.set(tool, meta); const eff = faceOf(tool).effect; if (eff !== undefined && meta.mountedBy !== "scenario") this.#effects.set(tool.name, eff); } mountAll(tools, meta) { for (const t of tools) this.mount(t, meta); } unmountWhere(pred) { const removed = []; for (let i = this.#tools.length - 1; i >= 0; i--) { const t = this.#tools[i]; if (pred(t, i)) { this.#tools.splice(i, 1); removed.unshift(t); } } return removed; } replaceAt(index, tool) { const prior = this.#tools[index]; if (prior === undefined) throw new Error(`roster: no tool at index ${index}`); const meta = this.#meta.get(prior); this.#tools[index] = tool; if (meta !== undefined) this.#meta.set(tool, meta); } replaceDomain(prefix, fresh, meta) { let anchor = -1; for (let i = this.#tools.length - 1; i >= 0; i--) { if (this.#tools[i].name.startsWith(prefix)) { anchor = i; this.#tools.splice(i, 1); } } for (const t of fresh) { this.#meta.set(t, meta); const eff = faceOf(t).effect; if (eff !== undefined) this.#effects.set(t.name, eff); } if (anchor >= 0) this.#tools.splice(anchor, 0, ...fresh); else this.#tools.push(...fresh); return { anchor }; } snapshot(facts) { const all = [...this.#tools, ...this.#engineLane]; const winner = survivorsByName(all); const entries = []; const emitted = new Set(); for (const t of all) { if (emitted.has(t.name)) continue; emitted.add(t.name); const survivor = winner.get(t.name); const meta = this.#meta.get(survivor) ?? { source: "caller", mountedBy: "scenario" }; entries.push(mintRosterEntry(survivor, meta, facts)); } const roster = { schemaVersion: 1, count: entries.length, entries, digest: rosterDigestOf(entries) }; const worn = rosterEngineNamesProblem(roster); if (worn !== undefined) throw configError("config.tool_face_invalid", worn); return roster; } } let catalogOwners; function catalogOwnerOf(name) { if (catalogOwners === undefined) { const owners = new Map(); for (const e of describeToolCatalog()) { owners.set(e.name, e.name); for (const a of e.face.aliases ?? []) if (!owners.has(a)) owners.set(a, e.name); } catalogOwners = owners; } return catalogOwners.get(name); } export function rosterEngineNamesProblem(roster) { for (const e of roster.entries) { const resolving = namesResolvingTo(roster, e); const worn = new Map(); for (const n of resolving) { const owner = catalogOwnerOf(n); if (owner !== undefined && !worn.has(owner)) worn.set(owner, n); } if (worn.size >= 2) { return `tool ${JSON.stringify(e.name)} answers to the names of ${worn.size} engine tools (${[...worn.values()].map((n) => JSON.stringify(n)).join(", ")}): a tool may answer to at most one engine tool's names — keep one of those aliases`; } } return undefined; } export function callerMountSource(tool) { return getToolContract(tool, "caller").origin === "core" ? "builtin" : "caller"; } export function rosterCardId(source, toolName, peer, protocolTool, contractId) { return source === "builtin" || source === "synthetic" ? `builtin:${toolName}` : source === "mcp" || source === "a2a" ? `${source}:${peer}/${protocolTool}` : `${source === "external" ? "external" : "caller"}:${contractId}`; } export function callerCardIdOf(tool) { const t = tool; return rosterCardId(callerMountSource(t), t.name, null, t.name, getToolContract(t, "caller").contractId); } export function mintRosterEntry(tool, meta, facts) { const face = faceOf(tool); const fallbackOrigin = meta.source === "builtin" ? "core" : meta.source === "synthetic" ? "synthetic" : meta.source === "mcp" || meta.source === "a2a" ? meta.source : "caller"; const d = getToolContract(tool, fallbackOrigin); const peer = meta.origin?.peer ?? null; const protocolTool = peer !== null ? protocolOf(tool.name)?.parse(tool.name)?.tool ?? tool.name : tool.name; const cardId = rosterCardId(meta.source, tool.name, peer, protocolTool, d.contractId); const { keys: inputKeys } = inputKeysOf(tool.parameters); const declaredEffect = face.effect; const foldedEffect = facts.effects.get(tool.name); const effect = declaredEffect ?? foldedEffect ?? "write"; const effectProvenance = declaredEffect !== undefined ? (meta.source === "synthetic" ? "synthetic" : "declared") : foldedEffect !== undefined && (meta.source === "mcp" || meta.source === "a2a") ? "caller" : "default"; const egress = face.egress === true || facts.egress.has(tool.name); const egressProvenance = face.egress === true ? "declared" : egress ? "caller" : "default"; const irreversibility = face.irreversibility ?? facts.irreversibilityTier.get(tool.name); const reversibilityProbe = face.reversibilityProbe !== undefined || facts.reversibilityProbes.has(tool.name); const contentOrigin = face.contentOrigin ?? (facts.trustedTools.has(tool.name) ? "local" : "external"); const contentOriginProvenance = face.contentOrigin !== undefined ? (meta.source === "mcp" || meta.source === "a2a" ? "server" : "declared") : facts.trustedTools.has(tool.name) ? "exempted" : "default"; const pathTarget = face.pathTarget; const rf = ruleFaceOfInstance(tool.parameters, face.ruleFace, pathTarget); const hints = face.renderHints !== undefined ? capRenderHints(face.renderHints) : undefined; const bound = rosterMemberBoundProblem({ name: tool.name, aliases: tool.aliases, ...(peer !== null ? { peer } : {}), contractId: d.contractId, implementationRevision: d.implementationRevision, ...(typeof face.modelGate === "string" ? { modelGate: face.modelGate } : {}), cardId, }); if (bound !== undefined) throw configError(bound.code, bound.message); return { name: tool.name, aliases: [...new Set(tool.aliases ?? [])], source: meta.source, ...(meta.origin !== undefined ? { origin: { peer: meta.origin.peer, ...(meta.origin.declaredBy !== undefined ? { declaredBy: meta.origin.declaredBy } : {}) } } : {}), contract: { contractId: d.contractId, implementationRevision: d.implementationRevision }, shapeDigest: d.shapeDigest, wireSchemaDigest: d.wireSchemaDigest, cardId, capabilityId: mintCapabilityId({ source: meta.source, peer, name: tool.name, contractId: d.contractId }), inputKeys, effect, effectProvenance, offload: face.offload !== false, ...(typeof face.offloadThresholdChars === "number" && Number.isFinite(face.offloadThresholdChars) && face.offloadThresholdChars >= 0 ? { offloadThresholdChars: Math.floor(face.offloadThresholdChars) } : {}), compactable: face.compactable === true, budgetExempt: face.budgetExempt === true, egress, egressProvenance, ...(irreversibility !== undefined ? { irreversibility } : {}), reversibilityProbe, contentOrigin, contentOriginProvenance, family: face.family !== undefined && TOOL_FAMILIES.includes(face.family) ? face.family : "other", ...(pathTarget !== undefined ? { pathTarget: { param: pathTarget.param, aliases: [...(pathTarget.aliases ?? [])], access: pathTarget.access, skillScopeEligible: pathTarget.skillScopeEligible === true, ...(pathTarget.base !== undefined ? { base: pathTarget.base } : {}), ...(pathTarget.absent !== undefined ? { absent: pathTarget.absent } : {}), ...(pathTarget.patternParam !== undefined ? { patternParam: pathTarget.patternParam } : {}), }, } : {}), ruleFace: rf, approvalPreview: typeof tool.approvalPreview === "function", validateInput: typeof tool.validateInput === "function", deferred: facts.deferred.has(tool.name), alwaysLoad: face.alwaysLoad === true || face.mcpAlwaysLoad === true, ...(typeof face.modelGate === "string" ? { modelGate: face.modelGate } : {}), ...(hints !== undefined ? { renderHints: hints } : {}), mountedBy: meta.mountedBy, }; } export class ToolRosterDeltaSeat { #current; #snapshot; #pending = []; #sink; arm(current, snapshot) { this.#current = current; this.#snapshot = snapshot; } get current() { return this.#current; } republish() { if (this.#current === undefined || this.#snapshot === undefined) return undefined; const next = this.#snapshot(); const delta = diffToolRoster(this.#current, next); this.#current = next; if (this.#sink !== undefined) this.#sink(delta); else { this.#pending.push(delta); if (this.#pending.length > 64) this.#pending.shift(); } return delta; } subscribe(sink) { this.#sink = sink; const pending = this.#pending; this.#pending = []; for (const d of pending) sink(d); } unsubscribe() { this.#sink = undefined; } drain() { const pending = this.#pending; this.#pending = []; return pending; } } export function ruleFacesFromRoster(roster) { const byName = new Map(roster.entries.map((e) => [e.name, e])); for (const e of roster.entries) for (const a of e.aliases) if (!byName.has(a)) byName.set(a, e); return (toolName) => { const e = byName.get(toolName); if (e === undefined) return undefined; const params = new Set(e.ruleFace.params); return { primaryParams: e.ruleFace.primaryParams, params: e.ruleFace.params, nonScalar: e.inputKeys.filter((k) => !params.has(k.name) && (k.type === "object" || k.type === "array")).map((k) => k.name), pathTargeting: e.pathTarget !== undefined, }; }; } export function diffToolRoster(prev, next) { const before = new Map(prev.entries.map((e) => [e.name, canonical(e)])); const after = new Map(next.entries.map((e) => [e.name, canonical(e)])); const added = []; const removed = []; const changed = []; for (const [name, row] of after) { const old = before.get(name); if (old === undefined) added.push(name); else if (old !== row) changed.push(name); } for (const name of before.keys()) if (!after.has(name)) removed.push(name); return { fromDigest: prev.digest, roster: next, summary: { added, removed, changed } }; } export function judgeParkedToolIdentity(pending, row) { const carried = pending.capabilityId !== undefined || pending.contractId !== undefined || pending.shapeDigest !== undefined || pending.implementationRevision !== undefined; if (row === undefined) return "absent"; if (!carried) return "name_only"; const id = toolIdentityOf(row); return pending.capabilityId === id.capabilityId && pending.contractId === id.contractId && pending.shapeDigest === id.shapeDigest && pending.implementationRevision === id.implementationRevision ? "match" : "mismatch"; } export function offendingResultCard(hints, cardId, engineCards) { for (const c of hints?.resultCards ?? []) { if (typeof c !== "string") continue; if (c.startsWith(`${cardId}/`)) continue; if (c.startsWith("builtin:") || engineCards.has(c) || !c.includes("/")) return c; return c; } return undefined; }