@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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,26 @@
1
1
  # Changelog
2
2
 
3
+ ## 5.3.0 (2026-08-03)
4
+
5
+ _The A2A arc: client+server legs land end-to-end (all additive), the workflow-size notice lane closes its channel, and three naming/containment defects fix. Major stays locked at 5._
6
+
7
+ ### Fixed
8
+
9
+ - Protocol naming (ticket #10): a minted peer segment can no longer parse back as a DIFFERENT peer — adjacent out-of-charset characters used to normalize into the `__` separator (`"prod. db"` → `prod__db`, read back as peer `prod`), and a clamp could splice one in at the digest join. The peer leg folds `_` runs after the clamp (an all-separator peer degrades to a readable digest), the digest joins with `-`; only names for peers over the assembly budget change spelling. The tool segment is untouched (`repo__delete` round-trips as before).
10
+ - MCP tool `description` is neutralized (`sanitizeUntrustedText`) before it reaches the model-facing tool card (ticket #11) — it was the one remote text with no neutralization at all (instructions are sanitized+fenced, drop reasons inlined, error bodies fenced); the text is otherwise verbatim (no fence: that would poison every tool table for the honest majority).
11
+ - MCP refresh receipt `prefix` (ticket #9): the refresh leg recomputed the server prefix without the assembly budget, so a server name surviving normalization but not the 64-char budget produced a prefix matching none of the registered names — the splice domain then removed nothing and the fresh tools mounted alongside the stale ones under the same names. Minting (normalize + budget + clamp) is now single-sourced in `protocol-naming.ts`; the intake and the receipt call the same arithmetic.
12
+
13
+ ### Added
14
+
15
+ - `workflow_size_guideline_change` turn-boundary frame: a mid-run retune of `RunnerDeps.workflowLimits.sizeGuideline` now reaches the model at the next tool-calling boundary (the Workflow tool card is a per-mount snapshot and never re-renders). Same discipline as the `date_change`/`instructions_change` lanes: continuation-gated (a retune landing on a text-final turn never revives a finished run), fixed one-sentence frame above the bundle byte cap, comparison on the RESOLVED guideline (writing down the value already in force is not a change), armed only when the Workflow tool is mounted. `TaskEvent.steering_injected.source` gains the member (additive).
16
+ - Internal cleanup: the todo projection's dead `statusCounts` field is gone (computed on every TodoWrite, never consumed by any model-facing renderer).
17
+ - `toA2ATaskState` / `fromA2ATaskState` / `A2A_TASK_STATES` (exported): the pure projection between the durable background-agent row status and the A2A TaskState vocabulary. Forward is total (`killed` splits on `stoppedBy`: user/parent → `canceled`, anything else → `failed`); reverse is 9→5 and returns a discriminated `{lossy, status, note?}` — `lossy: false` holds exactly for the states that survive the round trip (pinned as a property test). Serving layers must still gate every row through `canAccessAgentRecord`.
18
+ - `queryBackgroundAgents` / `serveDurableAgentRowLane` / `buildAgentPollDetails` (+ their named input/result types) reach the package root — the row-serving primitives an A2A-style host endpoint composes, previously module-internal.
19
+ - `backgroundAgentStoreContract` + `backgroundAgentStoreScopesContract`: the contract kit the store family was missing — third-party `BackgroundAgentStore` backends can now run the same assertion set the bundled InMemory/File backends pass (rev-CAS, guarded updateIf, filter semantics, stale-running reap attribution; the optional `listScopes` layer is opt-in so an unimplemented method cannot skip silently).
20
+ - `TaskSpec.a2a` + `A2aServerSpec` + `materializeA2aTools` / `MaterializedA2a` (exported) + `onError` phase `"a2a"`: the A2A client leg — declared peers' agent cards are discovered (`agent-card.json` with legacy fallback, explicit `cardUrl` override), their skills mount as task-scoped tools (`a2a__<peer>__<skill>`; the protocol gives skills no input schema, so the engine synthesizes `{message, taskId?, contextId?}` and folds tags/examples into the sanitized, bounded description), calls ride JSON-RPC `message/send` with terminal-state polling under dual clocks, and every request declares `A2A-Version: 1.0`. Both legal reply shapes are honored (a direct Message and a polled Task); `input-required`/`auth-required` return immediately with the continuation keys instead of burning the ceiling. Default axes are `egress` + `write` (a remote agent is an external write; the caller's `toolAxes` is the only lowering channel), success bodies come back fenced as untrusted data (a deliberate divergence from the MCP success path — the sender is an agent, not a data source), and a `FilePart` uri is shown as text, never fetched. One unreachable peer never bricks its healthy sibling.
21
+ - `A2A_NAMESPACE` (exported): the protocol table's second entry — the `a2a__<peer>__<skill>` name shape, parse/makeName/displayGroupKey under the same destructuring-safe contract as MCP (both entries now built by one factory). The reserved-name and policy-audit messages now enumerate every table prefix instead of spelling `mcp__` by hand.
22
+ - `CheckpointError.detail` (additive, optional): structured discrimination where the code alone is ambiguous — `detail.field` names WHICH decision-action binding failed on `checkpoint.invalid_outcome` (`"boundCallId"`: the action you decided on was replaced, re-fetch the pending list; `"boundInputHash"`: the input you reviewed changed under the same action, re-review). `code` remains the only required discriminant.
23
+
3
24
  ## 5.2.0 (2026-08-03)
4
25
 
5
26
  _The v5.0.1..v5.1.0 full-window dual-lens review's repair batch (67 findings, all dispositioned; see docs/REVIEW-BACKLOG.md). Theme: the 5.1.0 retirements now refuse loudly EVERYWHERE they promised to, and the new disclosure seats reach the deployments they were built for._
@@ -0,0 +1,15 @@
1
+ import type { BackgroundAgentRecord } from "./background-agent-store.js";
2
+ export declare const A2A_TASK_STATES: readonly ["submitted", "working", "input-required", "completed", "canceled", "failed", "rejected", "auth-required", "unknown"];
3
+ export type A2ATaskState = (typeof A2A_TASK_STATES)[number];
4
+ export declare function toA2ATaskState(record: Pick<BackgroundAgentRecord, "status" | "stoppedBy">): A2ATaskState;
5
+ export interface A2ATaskStateReversalFaithful {
6
+ lossy: false;
7
+ status: BackgroundAgentRecord["status"];
8
+ }
9
+ export interface A2ATaskStateReversalLossy {
10
+ lossy: true;
11
+ status: BackgroundAgentRecord["status"];
12
+ note: string;
13
+ }
14
+ export type A2ATaskStateReversal = A2ATaskStateReversalFaithful | A2ATaskStateReversalLossy;
15
+ export declare function fromA2ATaskState(state: A2ATaskState): A2ATaskStateReversal;
@@ -0,0 +1,68 @@
1
+ export const A2A_TASK_STATES = [
2
+ "submitted",
3
+ "working",
4
+ "input-required",
5
+ "completed",
6
+ "canceled",
7
+ "failed",
8
+ "rejected",
9
+ "auth-required",
10
+ "unknown",
11
+ ];
12
+ const CANCELLING_STOP_SOURCES = new Set(["user", "parent"]);
13
+ export function toA2ATaskState(record) {
14
+ switch (record.status) {
15
+ case "running":
16
+ return "working";
17
+ case "parked":
18
+ return "input-required";
19
+ case "completed":
20
+ return "completed";
21
+ case "failed":
22
+ return "failed";
23
+ case "killed":
24
+ return record.stoppedBy !== undefined && CANCELLING_STOP_SOURCES.has(record.stoppedBy) ? "canceled" : "failed";
25
+ }
26
+ }
27
+ export function fromA2ATaskState(state) {
28
+ switch (state) {
29
+ case "working":
30
+ return { lossy: false, status: "running" };
31
+ case "input-required":
32
+ return { lossy: false, status: "parked" };
33
+ case "completed":
34
+ return { lossy: false, status: "completed" };
35
+ case "failed":
36
+ return { lossy: false, status: "failed" };
37
+ case "submitted":
38
+ return {
39
+ lossy: true,
40
+ status: "running",
41
+ note: "the durable ledger has no accepted-but-not-yet-started state — a row exists only once its child is running, so the distinction between submitted and working is dropped.",
42
+ };
43
+ case "canceled":
44
+ return {
45
+ lossy: true,
46
+ status: "killed",
47
+ note: "the cancellation attribution lives in the row's stoppedBy field, which a state alone cannot carry: a killed row WITHOUT stoppedBy in (user, parent) projects back as failed, not canceled.",
48
+ };
49
+ case "rejected":
50
+ return {
51
+ lossy: true,
52
+ status: "failed",
53
+ note: "rejection happens before a row exists (admission-time), so it is recorded as a failed terminal — the fact that the task was never admitted at all is lost.",
54
+ };
55
+ case "auth-required":
56
+ return {
57
+ lossy: true,
58
+ status: "failed",
59
+ note: "auth-required is a resumable waiting state with no durable seat here (parked is bound to an approval-checkpoint token, which this state has none of), so it lands as a failed terminal and is NOT resumable through this mapping.",
60
+ };
61
+ case "unknown":
62
+ return {
63
+ lossy: true,
64
+ status: "failed",
65
+ note: "unknown is a read-projection state (an outcome this engine instance cannot see), not a durable outcome; writing it back asserts a terminal failure the row itself never recorded.",
66
+ };
67
+ }
68
+ }
@@ -0,0 +1,42 @@
1
+ import type { AgentTool } from "../internal/harness-types.js";
2
+ import type { A2aServerSpec, ToolEffect } from "./types.js";
3
+ export declare const A2A_SKILL_DESCRIPTION_MAX_CHARS = 2048;
4
+ export interface A2aToolAxis {
5
+ name: string;
6
+ irreversibility?: "always";
7
+ egress?: true;
8
+ effect?: ToolEffect;
9
+ }
10
+ export interface A2aPeerStatus {
11
+ name: string;
12
+ status: "ready" | "failed";
13
+ agentName?: string;
14
+ agentDescription?: string;
15
+ endpoint?: string;
16
+ toolNames?: string[];
17
+ error?: string;
18
+ }
19
+ export interface A2aRefreshResult {
20
+ peer: string;
21
+ prefix: string;
22
+ status: "refreshed" | "failed" | "disposed";
23
+ toolCount: number;
24
+ added: string[];
25
+ removed: string[];
26
+ tools?: AgentTool[];
27
+ axes?: A2aToolAxis[];
28
+ error?: string;
29
+ }
30
+ export interface MaterializedA2a {
31
+ tools: AgentTool[];
32
+ toolAxes: A2aToolAxis[];
33
+ warnings: Error[];
34
+ statuses: A2aPeerStatus[];
35
+ refresh: (peer?: string) => Promise<A2aRefreshResult[]>;
36
+ dispose: () => Promise<void>;
37
+ }
38
+ export declare class A2aRpcError extends Error {
39
+ readonly code: number;
40
+ constructor(peer: string, method: string, code: number, message: string);
41
+ }
42
+ export declare function materializeA2aTools(specs: readonly A2aServerSpec[], principal?: string, signal?: AbortSignal): Promise<MaterializedA2a>;