@sema-agent/core 5.2.0 → 5.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -47,12 +47,7 @@ export function reduceToolEnd(state, details) {
47
47
  : [];
48
48
  });
49
49
  const allDone = shaped.length > 0 && shaped.every((t) => t.status === "completed");
50
- state.todo = allDone
51
- ? { statusCounts: {}, items: [] }
52
- : {
53
- statusCounts: countStatuses(shaped),
54
- items: shaped.slice(0, PROJECTION_ITEMS_MAX),
55
- };
50
+ state.todo = allDone ? { items: [] } : { items: shaped.slice(0, PROJECTION_ITEMS_MAX) };
56
51
  }
57
52
  else if (d.type === "task") {
58
53
  const map = state.taskItems ?? (state.taskItems = new Map());
@@ -120,12 +115,6 @@ export function rebaseCadenceWindows(state, clock) {
120
115
  state.toolSearchLastUseTurn = clock;
121
116
  state.lastToolSearchReminderTurn = clock;
122
117
  }
123
- function countStatuses(items) {
124
- const counts = {};
125
- for (const t of items)
126
- counts[t.status] = (counts[t.status] ?? 0) + 1;
127
- return counts;
128
- }
129
118
  const EMPTY = Object.freeze([]);
130
119
  export function collectDueAttachments(state, inp) {
131
120
  let out;
@@ -0,0 +1,5 @@
1
+ import { type BackgroundAgentStore } from "../background-agent-store.js";
2
+ import { type ContractAssertionRunner } from "./contract-harness.js";
3
+ export declare const BACKGROUND_AGENT_CONTRACT_SCOPE = "tenant-A";
4
+ export declare function backgroundAgentStoreContract(mk: () => BackgroundAgentStore, runAssertion?: ContractAssertionRunner): Promise<void>;
5
+ export declare function backgroundAgentStoreScopesContract(mk: () => BackgroundAgentStore, runAssertion?: ContractAssertionRunner): Promise<void>;
@@ -0,0 +1,213 @@
1
+ import { strict as assert } from "node:assert";
2
+ import { STALE_RUNNING_REAP_ATTRIBUTION, } from "../background-agent-store.js";
3
+ import { beginContract } from "./contract-harness.js";
4
+ export const BACKGROUND_AGENT_CONTRACT_SCOPE = "tenant-A";
5
+ function record(over = {}) {
6
+ return {
7
+ handle: "a0000000000000001",
8
+ scope: BACKGROUND_AGENT_CONTRACT_SCOPE,
9
+ owner: "task-1",
10
+ sessionScoped: false,
11
+ parentSessionId: "sess-1",
12
+ writerId: "writer-1",
13
+ spawnedAt: 1_000,
14
+ updatedAt: 1_000,
15
+ status: "running",
16
+ rev: 0,
17
+ ...over,
18
+ };
19
+ }
20
+ async function withStores(mk, fn) {
21
+ const live = [];
22
+ const make = () => {
23
+ const s = mk();
24
+ live.push(s);
25
+ return s;
26
+ };
27
+ try {
28
+ await fn(make);
29
+ }
30
+ finally {
31
+ for (const s of live.splice(0))
32
+ s.close?.();
33
+ }
34
+ }
35
+ async function held(store, handle, scope = BACKGROUND_AGENT_CONTRACT_SCOPE) {
36
+ const row = await store.get(handle, scope);
37
+ assert.notEqual(row, null, `row ${handle} should exist in scope ${scope}`);
38
+ return row;
39
+ }
40
+ export async function backgroundAgentStoreContract(mk, runAssertion) {
41
+ const { run: runRaw, settle } = beginContract(runAssertion);
42
+ const run = (name, fn) => runRaw(name, () => withStores(mk, fn));
43
+ const S = BACKGROUND_AGENT_CONTRACT_SCOPE;
44
+ run("put is create-once and scope-partitioned; get round-trips the row and is null cross-scope", async (make) => {
45
+ const store = make();
46
+ await store.put(record({ handle: "a1", finalOutput: "result-text" }));
47
+ await assert.rejects(store.put(record({ handle: "a1" })), (e) => e.code === "agent_record.already_exists", "a second put under the same (handle, scope) must throw the typed already_exists error");
48
+ await store.put(record({ handle: "a1", scope: "tenant-B" }));
49
+ const row = await held(store, "a1");
50
+ assert.equal(row.finalOutput, "result-text");
51
+ assert.equal(row.owner, "task-1");
52
+ assert.equal(await store.get("a1", "tenant-C"), null, "an unrelated scope must not see the row");
53
+ assert.equal(await store.get("a-missing", S), null);
54
+ });
55
+ run("put refuses a scopeless row (a row with no tenant partition would be world-readable)", async (make) => {
56
+ const store = make();
57
+ await assert.rejects(store.put(record({ scope: "" })), "an empty scope must be refused loudly, not stored");
58
+ });
59
+ run("get returns DETACHED copies: mutating a read never reaches the stored row", async (make) => {
60
+ const store = make();
61
+ await store.put(record({ handle: "a1", usage: { totalTokens: 5 } }));
62
+ const first = await held(store, "a1");
63
+ first.status = "failed";
64
+ first.usage.totalTokens = 999;
65
+ const second = await held(store, "a1");
66
+ assert.equal(second.status, "running");
67
+ assert.equal(second.usage?.totalTokens, 5);
68
+ });
69
+ run("update is a rev-CAS: stale rev loses, wrong scope loses, the winner bumps rev by EXACTLY 1", async (make) => {
70
+ const store = make();
71
+ await store.put(record({ handle: "a1" }));
72
+ const row = await held(store, "a1");
73
+ assert.equal(await store.update("a1", "tenant-B", { ...row, status: "completed" }, { rev: row.rev }), false, "wrong scope");
74
+ assert.equal(await store.update("a1", S, { ...row, status: "completed" }, { rev: row.rev + 5 }), false, "rev ahead");
75
+ assert.equal(await store.update("a-missing", S, { ...row, status: "completed" }, { rev: row.rev }), false, "missing row");
76
+ assert.equal(await store.update("a1", S, { ...row, status: "completed" }, { rev: row.rev }), true);
77
+ const after = await held(store, "a1");
78
+ assert.equal(after.status, "completed");
79
+ assert.equal(after.rev, row.rev + 1);
80
+ assert.equal(await store.update("a1", S, { ...row, status: "failed" }, { rev: row.rev }), false);
81
+ assert.equal((await held(store, "a1")).status, "completed");
82
+ });
83
+ run("update keeps the KEY authoritative: a payload naming another handle/scope cannot repartition the row", async (make) => {
84
+ const store = make();
85
+ await store.put(record({ handle: "a1" }));
86
+ const row = await held(store, "a1");
87
+ assert.equal(await store.update("a1", S, { ...row, handle: "a-other", scope: "tenant-B", status: "failed" }, { rev: row.rev }), true);
88
+ const after = await held(store, "a1");
89
+ assert.equal(after.handle, "a1", "the key's handle wins over the payload's");
90
+ assert.equal(after.scope, S, "scope is the partition key and an update must never rewrite it");
91
+ assert.equal(await store.get("a-other", "tenant-B"), null, "no second row may appear");
92
+ });
93
+ run("updateIf keeps the rev guard AND adds the field guards (status / checkpoint token / claim id)", async (make) => {
94
+ const store = make();
95
+ await store.put(record({ handle: "a1", status: "parked", parkedCheckpointToken: "cp-1" }));
96
+ const row = await held(store, "a1");
97
+ assert.equal(await store.updateIf("a1", S, { ...row, status: "running" }, { rev: row.rev + 3 }), false, "stale rev");
98
+ assert.equal(await store.updateIf("a1", S, { ...row, status: "running" }, { rev: row.rev, status: "running" }), false, "status mismatch");
99
+ assert.equal(await store.updateIf("a1", S, { ...row, status: "running" }, { rev: row.rev, status: "parked", parkedCheckpointToken: "cp-OTHER" }), false, "checkpoint token mismatch");
100
+ assert.equal(await store.updateIf("a1", S, { ...row, status: "running" }, { rev: row.rev, parkedCheckpointToken: null }), false, "null must mean 'this field is absent', and it is present");
101
+ assert.equal(await store.updateIf("a1", S, { ...row, status: "running" }, { rev: row.rev, parkClaimId: "claim-1" }), false, "claim id expected but absent");
102
+ const next = { ...row, status: "running", parkClaimId: "claim-1" };
103
+ assert.equal(await store.updateIf("a1", S, next, { rev: row.rev, status: "parked", parkedCheckpointToken: "cp-1", parkClaimId: null }), true, "matching guards (incl. null-as-absent on the claim id) must win");
104
+ const after = await held(store, "a1");
105
+ assert.equal(after.status, "running");
106
+ assert.equal(after.parkClaimId, "claim-1");
107
+ assert.equal(after.rev, row.rev + 1);
108
+ });
109
+ run("listBySession anchors on (sessionScoped ? owner : parentSessionId) OR rootSessionId, newest-first", async (make) => {
110
+ const store = make();
111
+ await store.put(record({ handle: "a1", spawnedAt: 1_000, finalOutput: "SECRET", summary: "s", error: "e", recentSteps: [] }));
112
+ await store.put(record({ handle: "a2", owner: "sess-1", sessionScoped: true, parentSessionId: undefined, spawnedAt: 2_000 }));
113
+ await store.put(record({ handle: "a3", parentSessionId: "sess-mid", rootSessionId: "sess-1", spawnedAt: 3_000 }));
114
+ await store.put(record({ handle: "a4", parentSessionId: "sess-other", spawnedAt: 4_000 }));
115
+ const rows = await store.listBySession(S, "sess-1");
116
+ assert.deepEqual(rows.map((r) => r.handle), ["a3", "a2", "a1"], "newest-first by spawnedAt");
117
+ for (const row of rows) {
118
+ for (const field of ["finalOutput", "summary", "error", "recentSteps", "editedFiles"]) {
119
+ assert.equal(Object.hasOwn(row, field), false, `list projections must not carry ${field}`);
120
+ }
121
+ }
122
+ assert.deepEqual((await store.listBySession(S, "sess-1", { limit: 2 })).map((r) => r.handle), ["a3", "a2"], "limit keeps the NEWEST n");
123
+ assert.deepEqual(await store.listBySession("tenant-B", "sess-1"), [], "another tenant sees nothing");
124
+ assert.deepEqual((await store.listBySession(S, "sess-mid")).map((r) => r.handle), ["a3"], "the intermediate anchor still lists its direct child");
125
+ });
126
+ run("listBySession/listByScope filter one status; listByScope enumerates the whole tenant", async (make) => {
127
+ const store = make();
128
+ await store.put(record({ handle: "a1", status: "running", spawnedAt: 1_000 }));
129
+ await store.put(record({ handle: "a2", status: "completed", spawnedAt: 2_000 }));
130
+ await store.put(record({ handle: "a3", status: "completed", parentSessionId: "sess-other", spawnedAt: 3_000 }));
131
+ await store.put(record({ handle: "a4", status: "completed", scope: "tenant-B", spawnedAt: 4_000 }));
132
+ assert.deepEqual((await store.listByScope(S)).map((r) => r.handle), ["a3", "a2", "a1"], "scope-wide, newest-first");
133
+ assert.deepEqual((await store.listByScope(S, { status: "completed" })).map((r) => r.handle), ["a3", "a2"]);
134
+ assert.deepEqual((await store.listByScope(S, { status: "completed", limit: 1 })).map((r) => r.handle), ["a3"]);
135
+ assert.deepEqual((await store.listBySession(S, "sess-1", { status: "completed" })).map((r) => r.handle), ["a2"]);
136
+ assert.deepEqual((await store.listByScope("tenant-B")).map((r) => r.handle), ["a4"], "tenants are separate ledgers");
137
+ });
138
+ run("delete is row-precise and CONDITIONAL when a rev is supplied (a concurrent revival must win)", async (make) => {
139
+ const store = make();
140
+ await store.put(record({ handle: "a1", status: "completed" }));
141
+ await store.put(record({ handle: "a2", status: "completed" }));
142
+ const row = await held(store, "a1");
143
+ assert.equal(await store.delete("a1", S, { rev: row.rev + 1 }), false, "a stale rev must not delete");
144
+ assert.notEqual(await store.get("a1", S), null);
145
+ assert.equal(await store.delete("a1", "tenant-B"), false, "cross-scope delete is a miss");
146
+ assert.equal(await store.delete("a1", S, { rev: row.rev }), true);
147
+ assert.equal(await store.get("a1", S), null);
148
+ assert.equal(await store.delete("a1", S), false, "deleting a gone row reports false, not true");
149
+ assert.equal(await store.delete("a2", S), true, "an unconditional delete still works");
150
+ });
151
+ run("reap with NO policy is a no-op (retention is always explicit)", async (make) => {
152
+ const store = make();
153
+ await store.put(record({ handle: "a1", status: "completed", settledAt: 1_000 }));
154
+ assert.equal(await store.reap(S, 100_000), 0);
155
+ assert.equal(await store.reap(S, 100_000, {}), 0);
156
+ assert.notEqual(await store.get("a1", S), null);
157
+ });
158
+ run("reap deletes terminal rows by age and by keep-bound; running and parked rows survive BOTH", async (make) => {
159
+ const store = make();
160
+ await store.put(record({ handle: "a1", status: "completed", spawnedAt: 1_000, settledAt: 1_000 }));
161
+ await store.put(record({ handle: "a2", status: "running", spawnedAt: 1_000, updatedAt: 1_000 }));
162
+ await store.put(record({ handle: "a3", status: "parked", spawnedAt: 1_000, updatedAt: 1_000, parkedCheckpointToken: "cp-1" }));
163
+ assert.equal(await store.reap(S, 100_000, { maxAgeMs: 10 }), 1, "only the terminal row ages out");
164
+ assert.equal(await store.get("a1", S), null);
165
+ assert.equal((await held(store, "a2")).status, "running");
166
+ assert.equal((await held(store, "a3")).status, "parked");
167
+ for (const i of [1, 2, 3])
168
+ await store.put(record({ handle: `b${i}`, status: "completed", spawnedAt: 1_000 + i, settledAt: 2_000 + i }));
169
+ assert.equal(await store.reap(S, 100_000, { keep: 2 }), 1);
170
+ assert.deepEqual((await store.listByScope(S, { status: "completed" })).map((r) => r.handle), ["b3", "b2"]);
171
+ assert.notEqual(await store.get("a2", S), null);
172
+ assert.notEqual(await store.get("a3", S), null);
173
+ });
174
+ run("reap's stale-running arm flips to a terminal `failed` WITH the shared attribution, and never overwrites real failure text", async (make) => {
175
+ const store = make();
176
+ await store.put(record({ handle: "a1", status: "running", updatedAt: 1_000 }));
177
+ await store.put(record({ handle: "a2", status: "running", updatedAt: 1_000, error: "the child reported a real failure" }));
178
+ assert.equal(await store.reap(S, 100_000, { staleRunningMaxAgeMs: 50_000 }), 2);
179
+ const flipped = await held(store, "a1");
180
+ assert.equal(flipped.status, "failed", "an interrupted host's row settles as a terminal, not a perpetual running");
181
+ assert.equal(flipped.stoppedBy, "system");
182
+ assert.equal(flipped.settledAt, 100_000);
183
+ assert.equal(flipped.updatedAt, 100_000);
184
+ assert.equal(flipped.error, STALE_RUNNING_REAP_ATTRIBUTION);
185
+ assert.equal(flipped.summary, STALE_RUNNING_REAP_ATTRIBUTION);
186
+ assert.equal((await held(store, "a2")).error, "the child reported a real failure");
187
+ await store.put(record({ handle: "a3", status: "running", updatedAt: 99_000 }));
188
+ assert.equal(await store.reap(S, 100_000, { staleRunningMaxAgeMs: 50_000 }), 0);
189
+ assert.equal((await held(store, "a3")).status, "running");
190
+ });
191
+ run("reap counts each ROW once even when the same sweep flips it AND removes it", async (make) => {
192
+ const store = make();
193
+ await store.put(record({ handle: "a1", status: "running", spawnedAt: 1_000, updatedAt: 1_000 }));
194
+ assert.equal(await store.reap(S, 100_000, { staleRunningMaxAgeMs: 50_000, keep: 0 }), 1, "one row changed, so the count is 1");
195
+ assert.equal(await store.get("a1", S), null, "and it really is gone (flipped, then trimmed)");
196
+ });
197
+ await settle();
198
+ }
199
+ export async function backgroundAgentStoreScopesContract(mk, runAssertion) {
200
+ const { run: runRaw, settle } = beginContract(runAssertion);
201
+ const run = (name, fn) => runRaw(name, () => withStores(mk, fn));
202
+ run("listScopes enumerates the scopes that currently HOLD rows, deduplicated and sorted", async (make) => {
203
+ const store = make();
204
+ assert.equal(typeof store.listScopes, "function", "this layer is only for backends that implement listScopes");
205
+ await store.put(record({ handle: "a1", scope: "tenant-B" }));
206
+ await store.put(record({ handle: "a2", scope: "tenant-A" }));
207
+ await store.put(record({ handle: "a3", scope: "tenant-A" }));
208
+ assert.deepEqual(await store.listScopes(), ["tenant-A", "tenant-B"], "deduplicated and sorted — a retention loop drives itself off this");
209
+ await store.delete("a1", "tenant-B");
210
+ assert.deepEqual(await store.listScopes(), ["tenant-A"]);
211
+ });
212
+ await settle();
213
+ }
@@ -1,5 +1,5 @@
1
1
  import { type BackgroundAgentRecord, type BackgroundAgentStore } from "./background-agent-store.js";
2
- import { type StopSource, type TaskAccess, type UnifiedTaskResult, type BackgroundAgentTaskHandle, type DurableAgentCore, type ParkedClaimTicket, type RegisterBackgroundAgentInput } from "./task-registry-shared.js";
2
+ import { type StopSource, type TaskAccess, type TaskRetrievalStatus, type UnifiedTaskOutput, type UnifiedTaskResult, type BackgroundAgentTaskHandle, type DurableAgentCore, type ParkedClaimTicket, type RegisterBackgroundAgentInput } from "./task-registry-shared.js";
3
3
  import { type ToolResultStore } from "./tool-result-store.js";
4
4
  export declare function ensureDurableHeartbeatLane(core: DurableAgentCore): void;
5
5
  export declare function durableAgentWriteLane(handle: BackgroundAgentTaskHandle, patch: Partial<BackgroundAgentRecord>, clear?: readonly (keyof BackgroundAgentRecord)[]): void;
@@ -126,6 +126,19 @@ export declare function notFoundRunningAgentsTail(footer: {
126
126
  named: string[];
127
127
  background: string[];
128
128
  }): string;
129
+ export interface AgentPollDetailsInput {
130
+ taskId: string;
131
+ status: UnifiedTaskOutput["status"];
132
+ retrievalStatus: TaskRetrievalStatus;
133
+ seq?: number;
134
+ stoppedBy?: StopSource;
135
+ error?: string;
136
+ errorCode?: string;
137
+ errorRetryable?: boolean;
138
+ resultIsPartial?: boolean;
139
+ completionId?: string;
140
+ }
141
+ export declare function buildAgentPollDetails(input: AgentPollDetailsInput): UnifiedTaskOutput;
129
142
  export declare function serveDurableAgentRowLane(row: BackgroundAgentRecord): UnifiedTaskResult;
130
143
  export declare function spillClippedAgentResult(handle: BackgroundAgentTaskHandle, full: string, clipped: string, store: ToolResultStore | undefined, sessionId: string | undefined): Promise<string>;
131
144
  export declare function pollBackgroundAgentLane(handle: BackgroundAgentTaskHandle, deadline?: number, signal?: AbortSignal, oneShot?: boolean, store?: ToolResultStore, sessionId?: string): Promise<UnifiedTaskResult>;
@@ -1008,7 +1008,7 @@ export function notFoundRunningAgentsTail(footer) {
1008
1008
  return ((footer.named.length > 0 ? `. Running named agents: ${footer.named.join(", ")}` : "") +
1009
1009
  (footer.background.length > 0 ? `. Running background agents: ${footer.background.join(", ")}` : ""));
1010
1010
  }
1011
- function buildAgentPollDetails(input) {
1011
+ export function buildAgentPollDetails(input) {
1012
1012
  const failed = input.status === "failed";
1013
1013
  return {
1014
1014
  task_id: input.taskId,
@@ -197,6 +197,19 @@ export interface McpServerSpec {
197
197
  irreversibility?: "always" | "never";
198
198
  }>;
199
199
  }
200
+ export interface A2aServerSpec {
201
+ name: string;
202
+ url: string;
203
+ cardUrl?: string;
204
+ headers?: Record<string, string>;
205
+ principalHeader?: string;
206
+ allowSkills?: string[];
207
+ toolAxes?: Record<string, {
208
+ effect?: ToolEffect;
209
+ egress?: boolean;
210
+ irreversibility?: "always" | "never";
211
+ }>;
212
+ }
200
213
  export interface McpElicitRequest {
201
214
  server: string;
202
215
  message: string;
@@ -296,6 +309,7 @@ export interface TaskSpec {
296
309
  enableFork?: boolean;
297
310
  shellGate?: "off" | "always" | "classify";
298
311
  mcp?: McpServerSpec[];
312
+ a2a?: A2aServerSpec[];
299
313
  skills?: SkillSpec[];
300
314
  backgroundScope?: "task" | "session";
301
315
  envFacts?: {
@@ -562,7 +576,7 @@ export type TaskEvent = ({
562
576
  reason?: string;
563
577
  } & TaskEventIdentity) | ({
564
578
  type: "steering_injected";
565
- source: "deadline_nudge" | "finalize" | "todo_reminder" | "task_reminder" | "tool_search_usage_reminder" | "changed_files" | "plan_mode" | "date_change" | "instructions_change" | "budget_usd" | "background_tasks" | "tools_delta" | "agent_listing" | "skills_listing" | "mcp_instructions" | "mcp_dropped_tools";
579
+ source: "deadline_nudge" | "finalize" | "todo_reminder" | "task_reminder" | "tool_search_usage_reminder" | "changed_files" | "plan_mode" | "date_change" | "instructions_change" | "workflow_size_guideline_change" | "budget_usd" | "background_tasks" | "tools_delta" | "agent_listing" | "skills_listing" | "mcp_instructions" | "mcp_dropped_tools";
566
580
  preview: string;
567
581
  } & TaskEventIdentity) | ({
568
582
  type: "diagnostics";
@@ -772,7 +786,7 @@ export interface RunnerDeps {
772
786
  hooks?: import("./hooks.js").Hooks;
773
787
  allowImageUrl?: (url: string) => boolean;
774
788
  onError?: (err: unknown, context: {
775
- phase: "compaction" | "prompt-cache" | "prompt-constitution" | "degraded" | "config" | "memory" | "mcp" | "interrupt-reconcile" | "suggestions" | "rewind" | "hook";
789
+ phase: "compaction" | "prompt-cache" | "prompt-constitution" | "degraded" | "config" | "memory" | "mcp" | "a2a" | "interrupt-reconcile" | "suggestions" | "rewind" | "hook";
776
790
  sessionId: string;
777
791
  classification?: string;
778
792
  }) => void;
package/dist/index.d.ts CHANGED
@@ -40,7 +40,8 @@ export { decideAutoPromote, deriveTripwire, FROZEN_DENYLIST_FLOOR, type AutoProm
40
40
  export { runCascade, type CascadeRung, type CascadeConfig, type CascadeAttempt, type CascadeRunResult, type GateVerdict, } from "./agents/cascade.js";
41
41
  export { pgQuery, mysqlQuery, sqliteQuery } from "./tools/sql-adapters.js";
42
42
  export { materializeMcpTools, MCP_PREFIX, type MaterializedMcp, type McpServerStatus, type McpRefreshResult } from "./core/mcp.js";
43
- export { PROTOCOL_TABLE, MCP_NAMESPACE, protocolOf, type ProtocolNamespace, type ProtocolId } from "./core/protocol-table.js";
43
+ export { materializeA2aTools, A2aRpcError, type MaterializedA2a, type A2aPeerStatus, type A2aRefreshResult, type A2aToolAxis } from "./core/a2a.js";
44
+ export { PROTOCOL_TABLE, MCP_NAMESPACE, A2A_NAMESPACE, protocolOf, type ProtocolNamespace, type ProtocolId } from "./core/protocol-table.js";
44
45
  export { InMemorySessionPolicyStore, SessionPolicyError, loosenReasons, normalizeRules, stripRev, type SessionPolicyStore, type SessionPermissionRules, type StoredSessionRules, type SessionRulesRecord, type PutRulesOptions, } from "./core/session-policy-store.js";
45
46
  export { SAFETY_MERGE_CONFORMANCE_CORPUS, type SafetyMergeVector } from "./core/safety-merge-corpus.js";
46
47
  export { SAFETY_AXIS_VOCABULARY } from "./core/safety-axis-vocab.js";
@@ -163,8 +164,12 @@ export { sessionRepoContract } from "./core/store-contracts/session-repo-contrac
163
164
  export { toolResultStoreContract } from "./core/store-contracts/tool-result-store-contract.js";
164
165
  export { fileSnapshotStoreContract } from "./core/store-contracts/file-snapshot-store-contract.js";
165
166
  export { MAILBOX_CONTRACT_SCOPE, mailboxStoreContract, mailboxAckOwnershipContract, mailboxBundledOnlyContract, } from "./core/store-contracts/mailbox-store-contract.js";
166
- export { type BackgroundAgentStore, type BackgroundAgentRecord, type BackgroundAgentRowSummary, type BackgroundAgentUsage, canAccessAgentRecord, BackgroundAgentStoreError, InMemoryBackgroundAgentStore, reconcileParkedAgents, } from "./core/background-agent-store.js";
167
+ export { BACKGROUND_AGENT_CONTRACT_SCOPE, backgroundAgentStoreContract, backgroundAgentStoreScopesContract, } from "./core/store-contracts/background-agent-store-contract.js";
168
+ export { type BackgroundAgentStore, type BackgroundAgentRecord, type BackgroundAgentRowSummary, type BackgroundAgentUsage, canAccessAgentRecord, BackgroundAgentStoreError, InMemoryBackgroundAgentStore, reconcileParkedAgents, queryBackgroundAgents, type BackgroundAgentQuery, } from "./core/background-agent-store.js";
169
+ export { serveDurableAgentRowLane, buildAgentPollDetails, type AgentPollDetailsInput, } from "./core/task-registry-agent.js";
170
+ export type { UnifiedTaskResult } from "./core/task-registry-shared.js";
167
171
  export { FileBackgroundAgentStore, type FileBackgroundAgentStoreOptions } from "./stores/file/background-agent-store.js";
172
+ export { A2A_TASK_STATES, type A2ATaskState, type A2ATaskStateReversal, type A2ATaskStateReversalFaithful, type A2ATaskStateReversalLossy, toA2ATaskState, fromA2ATaskState, } from "./core/a2a-task-state.js";
168
173
  export { type WorkflowJournalStore, type WorkflowJournalEntry, InMemoryWorkflowJournalStore, callKeyOrdinal, JOURNAL_OVERSIZE_ERROR_CODE, } from "./core/workflow-journal-store.js";
169
174
  export { untrustedEgressForHuman, redactHostLeaks, redactSecrets, boundedRedactedSummary } from "./core/untrusted-egress.js";
170
175
  export type { RedactionFinding, RedactionReport, RedactionConfidence } from "./core/untrusted-egress.js";
@@ -193,7 +198,7 @@ export { retryBackoffMs, parseRetryAfter } from "./brain/retry.js";
193
198
  export { type BrainTimeoutConfig } from "./brain/timeout.js";
194
199
  export { createAssistantMessageEventStream } from "./internal/llm.js";
195
200
  export type { AssistantMessage, AssistantMessageEvent, CompleteSimpleFn, Context, DocumentContent, ImageContent, Message, StopReason, StreamFn, TextContent, ThinkingContent, ToolCall, ToolResultMessage, Usage, UserMessage, } from "./internal/llm.js";
196
- export type { AgentDefinition, BeforeWriteHook, BeforeWriteRequest, BeforeWriteResult, HandsBandOptions, Brain, BrainStatus, BrainStatusPhase, ImageInput, McpElicitRequest, McpElicitResponse, McpServerSpec, OnElicit, Model, ModelRef, ProjectMemoryLoad, RunnerDeps, RuntimeCaps, BackgroundChildEvent, SkillManifest, SkillSpec, TaskEvent, TaskEventIdentity, ToolActivity, TaskResult, RemoteEnvFailureNote, TaskSpec, TaskStatus, TaskStream, CompactOutcome, ThinkingLevel, ToolExecuteContext, ToolReturn, ToolSpec, ToolEffect, WorkflowGovernanceBaseline, } from "./core/types.js";
201
+ export type { AgentDefinition, BeforeWriteHook, BeforeWriteRequest, BeforeWriteResult, HandsBandOptions, Brain, BrainStatus, BrainStatusPhase, ImageInput, McpElicitRequest, McpElicitResponse, McpServerSpec, A2aServerSpec, OnElicit, Model, ModelRef, ProjectMemoryLoad, RunnerDeps, RuntimeCaps, BackgroundChildEvent, SkillManifest, SkillSpec, TaskEvent, TaskEventIdentity, ToolActivity, TaskResult, RemoteEnvFailureNote, TaskSpec, TaskStatus, TaskStream, CompactOutcome, ThinkingLevel, ToolExecuteContext, ToolReturn, ToolSpec, ToolEffect, WorkflowGovernanceBaseline, } from "./core/types.js";
197
202
  export { Type } from "typebox";
198
203
  export type { TSchema, Static } from "typebox";
199
204
  export { explainPromptAssembly, describeDefaultPack, type DefaultPackDescription, type ExplainInput } from "./prompt-assembly/explain.js";
package/dist/index.js CHANGED
@@ -37,7 +37,8 @@ export { decideAutoPromote, deriveTripwire, FROZEN_DENYLIST_FLOOR, } from "./cor
37
37
  export { runCascade, } from "./agents/cascade.js";
38
38
  export { pgQuery, mysqlQuery, sqliteQuery } from "./tools/sql-adapters.js";
39
39
  export { materializeMcpTools, MCP_PREFIX } from "./core/mcp.js";
40
- export { PROTOCOL_TABLE, MCP_NAMESPACE, protocolOf } from "./core/protocol-table.js";
40
+ export { materializeA2aTools, A2aRpcError } from "./core/a2a.js";
41
+ export { PROTOCOL_TABLE, MCP_NAMESPACE, A2A_NAMESPACE, protocolOf } from "./core/protocol-table.js";
41
42
  export { InMemorySessionPolicyStore, SessionPolicyError, loosenReasons, normalizeRules, stripRev, } from "./core/session-policy-store.js";
42
43
  export { SAFETY_MERGE_CONFORMANCE_CORPUS } from "./core/safety-merge-corpus.js";
43
44
  export { SAFETY_AXIS_VOCABULARY } from "./core/safety-axis-vocab.js";
@@ -149,8 +150,11 @@ export { sessionRepoContract } from "./core/store-contracts/session-repo-contrac
149
150
  export { toolResultStoreContract } from "./core/store-contracts/tool-result-store-contract.js";
150
151
  export { fileSnapshotStoreContract } from "./core/store-contracts/file-snapshot-store-contract.js";
151
152
  export { MAILBOX_CONTRACT_SCOPE, mailboxStoreContract, mailboxAckOwnershipContract, mailboxBundledOnlyContract, } from "./core/store-contracts/mailbox-store-contract.js";
152
- export { canAccessAgentRecord, BackgroundAgentStoreError, InMemoryBackgroundAgentStore, reconcileParkedAgents, } from "./core/background-agent-store.js";
153
+ export { BACKGROUND_AGENT_CONTRACT_SCOPE, backgroundAgentStoreContract, backgroundAgentStoreScopesContract, } from "./core/store-contracts/background-agent-store-contract.js";
154
+ export { canAccessAgentRecord, BackgroundAgentStoreError, InMemoryBackgroundAgentStore, reconcileParkedAgents, queryBackgroundAgents, } from "./core/background-agent-store.js";
155
+ export { serveDurableAgentRowLane, buildAgentPollDetails, } from "./core/task-registry-agent.js";
153
156
  export { FileBackgroundAgentStore } from "./stores/file/background-agent-store.js";
157
+ export { A2A_TASK_STATES, toA2ATaskState, fromA2ATaskState, } from "./core/a2a-task-state.js";
154
158
  export { InMemoryWorkflowJournalStore, callKeyOrdinal, JOURNAL_OVERSIZE_ERROR_CODE, } from "./core/workflow-journal-store.js";
155
159
  export { untrustedEgressForHuman, redactHostLeaks, redactSecrets, boundedRedactedSummary } from "./core/untrusted-egress.js";
156
160
  export { MemoryRosterStore, FileRosterStore } from "./agents/roster-store.js";
@@ -5,6 +5,7 @@ export const EVENT_PROMPT_REGISTRY = new Map([
5
5
  { kind: "plan_mode", carrier: "message.user-prefix", trust: "operator", dedupe: "replace-by-key", defaultPolicy: "off", rendererRef: "turn-attachments.ts#PLAN_MODE_FULL_BODY" },
6
6
  { kind: "date_change", carrier: "message.user-prefix", trust: "operator", dedupe: "replace-by-key", defaultPolicy: "always", rendererRef: "turn-attachments.ts#renderDateChange" },
7
7
  { kind: "instructions_change", carrier: "message.user-prefix", trust: "operator", dedupe: "replace-by-key", maxBytes: 512, defaultPolicy: "always", rendererRef: "turn-attachments.ts#collectInstructionsChange" },
8
+ { kind: "workflow_size_guideline_change", carrier: "message.user-prefix", trust: "operator", dedupe: "replace-by-key", defaultPolicy: "always", rendererRef: "runtask.ts#workflowSizeGuidelineChangeNotice" },
8
9
  { kind: "budget_usd", carrier: "message.user-prefix", trust: "operator", dedupe: "replace-by-key", defaultPolicy: "off", rendererRef: "turn-attachments.ts#renderBudgetUsd" },
9
10
  { kind: "background_tasks", carrier: "message.user-prefix", trust: "operator", dedupe: "replace-by-key", defaultPolicy: "off", rendererRef: "turn-attachments.ts#renderBackgroundTasks" },
10
11
  { kind: "tools_delta", carrier: "message.user-prefix", trust: "external", dedupe: "replace-by-key", defaultPolicy: "off", rendererRef: "turn-attachments.ts#renderToolsDelta" },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sema-agent/core",
3
- "version": "5.2.0",
3
+ "version": "5.3.0",
4
4
  "description": "Stateless, task-oriented AI agent core",
5
5
  "type": "module",
6
6
  "license": "BUSL-1.1",