@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.
@@ -1,3 +1,4 @@
1
+ import { type ProtocolId } from "./protocol-table.js";
1
2
  import type { AgentTool } from "../internal/harness-types.js";
2
3
  import type { ImageContent, TextContent } from "../internal/llm.js";
3
4
  import { type McpImageResizer } from "./image-downsample.js";
@@ -69,14 +70,20 @@ export declare function mcpIdleTimeoutMs(kind: "stdio" | "http"): number;
69
70
  export declare function describeMcpSpecErrorCode(code: unknown): string | undefined;
70
71
  export declare function collapseMcpErrorPrefix(message: string): string;
71
72
  export declare function networkErrorCode(err: unknown, depth?: number): string | undefined;
72
- export declare function normalizeMcpName(name: string): string;
73
- export declare function clampNameSegment(seg: string, max?: number): string;
73
+ export interface HttpTransportFailure {
74
+ condition: string;
75
+ delivered: "no" | "unknown";
76
+ httpStatus?: number;
77
+ }
78
+ export declare function describeHttpTransportFailure(err: unknown): HttpTransportFailure | undefined;
79
+ export { normalizeNameSegment as normalizeMcpName, clampNameSegment } from "./protocol-naming.js";
74
80
  export * from "./image-downsample.js";
75
81
  export declare function inferCompactSchema(value: unknown, depth?: number): string;
76
- export declare function resolveMcpHttpHeaders(t: {
82
+ export interface ProtocolHttpAuth {
77
83
  headers?: Record<string, string>;
78
84
  principalHeader?: string;
79
- }, principal?: string): Record<string, string> | undefined;
85
+ }
86
+ export declare function resolveProtocolHttpHeaders(protocol: ProtocolId, t: ProtocolHttpAuth, principal?: string): Record<string, string> | undefined;
80
87
  interface McpContentItem {
81
88
  type: string;
82
89
  text?: string;
package/dist/core/mcp.js CHANGED
@@ -1,5 +1,5 @@
1
- import { createHash } from "node:crypto";
2
1
  import { MCP_NAMESPACE } from "./protocol-table.js";
2
+ import { mintNamespacePrefix, mintNamespacedToolName, normalizeNameSegment } from "./protocol-naming.js";
3
3
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
4
4
  import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
5
5
  import { StreamableHTTPClientTransport, StreamableHTTPError } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
@@ -12,7 +12,6 @@ import { sliceHeadSafe, sliceTailSafe } from "./surrogate-safe-slice.js";
12
12
  import { truncateError } from "./tool-errors.js";
13
13
  import { delimitUntrusted, inlineUntrusted, sanitizeUntrustedText } from "./untrusted-text.js";
14
14
  import { validateJsonSchemaShape } from "./runner/strict-output-schema.js";
15
- const NAME_SEP = "__";
16
15
  export const MCP_PREFIX = MCP_NAMESPACE.prefix;
17
16
  const MCP_OUTPUT_TOKENS_DEFAULT = 25_000;
18
17
  const MCP_CHARS_PER_TOKEN = 4;
@@ -198,7 +197,7 @@ export function networkErrorCode(err, depth = 0) {
198
197
  }
199
198
  return networkErrorCode(err.cause, depth + 1);
200
199
  }
201
- function describeHttpTransportFailure(err) {
200
+ export function describeHttpTransportFailure(err) {
202
201
  if (err instanceof McpError)
203
202
  return undefined;
204
203
  if (err instanceof StreamableHTTPError) {
@@ -297,19 +296,7 @@ function throwDeadServer(server, what) {
297
296
  e.details = { server };
298
297
  throw e;
299
298
  }
300
- export function normalizeMcpName(name) {
301
- const charset = name.replace(/[^a-zA-Z0-9_-]/g, "_");
302
- return clampNameSegment(charset);
303
- }
304
- export function clampNameSegment(seg, max = 64) {
305
- if (seg === "")
306
- return "_";
307
- if (seg.length <= max)
308
- return seg;
309
- const digest = createHash("sha256").update(seg).digest("hex").slice(0, 8);
310
- const keep = max - digest.length - 1;
311
- return keep > 0 ? `${seg.slice(0, keep)}_${digest}` : digest.slice(0, max);
312
- }
299
+ export { normalizeNameSegment as normalizeMcpName, clampNameSegment } from "./protocol-naming.js";
313
300
  export * from "./image-downsample.js";
314
301
  const MCP_BLOB_DIR = join(tmpdir(), "sema-mcp-blobs");
315
302
  function extensionForMime(mimeType) {
@@ -345,7 +332,7 @@ function extensionForMime(mimeType) {
345
332
  async function persistMcpBlob(base64Data, mimeType, server) {
346
333
  const bytes = Buffer.from(base64Data, "base64");
347
334
  const ext = extensionForMime(mimeType);
348
- const filePath = join(MCP_BLOB_DIR, `mcp-${normalizeMcpName(server ?? "server")}-blob-${Date.now()}-${Math.random().toString(36).slice(2, 8)}.${ext}`);
335
+ const filePath = join(MCP_BLOB_DIR, `mcp-${normalizeNameSegment(server ?? "server")}-blob-${Date.now()}-${Math.random().toString(36).slice(2, 8)}.${ext}`);
349
336
  try {
350
337
  await mkdir(MCP_BLOB_DIR, { recursive: true, mode: 0o700 });
351
338
  const st = await lstat(MCP_BLOB_DIR);
@@ -409,10 +396,10 @@ export function inferCompactSchema(value, depth = 2) {
409
396
  }
410
397
  return typeof value;
411
398
  }
412
- export function resolveMcpHttpHeaders(t, principal) {
399
+ export function resolveProtocolHttpHeaders(protocol, t, principal) {
413
400
  if (t.principalHeader && principal !== undefined) {
414
401
  if (/[\r\n]/.test(principal))
415
- throw new Error("mcp: principal must not contain CR/LF (header injection guard)");
402
+ throw new Error(`${protocol}: principal must not contain CR/LF (header injection guard)`);
416
403
  return { ...t.headers, [t.principalHeader]: principal };
417
404
  }
418
405
  return t.headers;
@@ -426,7 +413,7 @@ function buildTransport(spec, principal) {
426
413
  env: t.env,
427
414
  });
428
415
  }
429
- const headers = resolveMcpHttpHeaders(t, principal);
416
+ const headers = resolveProtocolHttpHeaders(MCP_NAMESPACE.id, t, principal);
430
417
  return new StreamableHTTPClientTransport(new URL(t.url), {
431
418
  requestInit: headers ? { headers } : undefined,
432
419
  });
@@ -667,7 +654,7 @@ export async function materializeMcpTools(specs, principal, onElicit, imageResiz
667
654
  droppedTools,
668
655
  statuses,
669
656
  refresh: async (server) => {
670
- const prefixOf = (name) => `${MCP_PREFIX}${normalizeMcpName(name)}${NAME_SEP}`;
657
+ const prefixOf = (name) => mintNamespacePrefix(MCP_NAMESPACE, name);
671
658
  const targets = server !== undefined ? serverHandles.filter((h) => h.name === server) : serverHandles;
672
659
  const results = [];
673
660
  if (server !== undefined && targets.length === 0) {
@@ -1149,14 +1136,14 @@ function intakeListedTools(listed, spec, client, health, imageResizer) {
1149
1136
  }
1150
1137
  const normalized = normalizeMcpToolSchema(t.inputSchema);
1151
1138
  let effectiveInputSchema = t.inputSchema;
1152
- let effectiveDescription = t.description;
1139
+ let effectiveDescription = t.description !== undefined ? sanitizeUntrustedText(t.description) : undefined;
1153
1140
  if (normalized.outcome === "drop") {
1154
1141
  dropped.push({ tool: inlineUntrusted(t.name), reason: inlineUntrusted(normalized.reason, 240) });
1155
1142
  continue;
1156
1143
  }
1157
1144
  if (normalized.outcome === "normalized") {
1158
1145
  effectiveInputSchema = normalized.schema;
1159
- effectiveDescription = normalized.note + (t.description ? `\n\n${t.description}` : "");
1146
+ effectiveDescription = normalized.note + (effectiveDescription ? `\n\n${effectiveDescription}` : "");
1160
1147
  }
1161
1148
  const schemaProblem = mcpToolSchemaProblem(effectiveInputSchema);
1162
1149
  if (schemaProblem !== undefined) {
@@ -1164,14 +1151,7 @@ function intakeListedTools(listed, spec, client, health, imageResizer) {
1164
1151
  continue;
1165
1152
  }
1166
1153
  const remoteName = t.name;
1167
- const namespacedName = (() => {
1168
- const TOOL_MIN = 16;
1169
- const fixed = MCP_PREFIX.length + NAME_SEP.length;
1170
- const serverBudget = Math.max(1, 64 - fixed - TOOL_MIN);
1171
- const server = clampNameSegment(normalizeMcpName(spec.name), serverBudget);
1172
- const prefix = `${MCP_PREFIX}${server}${NAME_SEP}`;
1173
- return `${prefix}${clampNameSegment(normalizeMcpName(remoteName), Math.max(1, 64 - prefix.length))}`;
1174
- })();
1154
+ const namespacedName = mintNamespacedToolName(MCP_NAMESPACE, spec.name, remoteName);
1175
1155
  const hintAxis = mcpAxisFor(namespacedName, t.annotations);
1176
1156
  const axis = applyCallerAxisOverride(namespacedName, hintAxis, spec.toolAxes?.[remoteName]);
1177
1157
  if (axis)
@@ -0,0 +1,7 @@
1
+ import type { ProtocolNamespace } from "./protocol-table.js";
2
+ export declare const TOOL_NAME_MAX_CHARS = 64;
3
+ export declare const MINTED_TOOL_SEGMENT_MIN_CHARS = 16;
4
+ export declare function normalizeNameSegment(name: string): string;
5
+ export declare function clampNameSegment(seg: string, max?: number): string;
6
+ export declare function mintNamespacePrefix(ns: ProtocolNamespace, peer: string): string;
7
+ export declare function mintNamespacedToolName(ns: ProtocolNamespace, peer: string, tool: string): string;
@@ -0,0 +1,32 @@
1
+ import { createHash } from "node:crypto";
2
+ export const TOOL_NAME_MAX_CHARS = 64;
3
+ export const MINTED_TOOL_SEGMENT_MIN_CHARS = 16;
4
+ const SEGMENT_DIGEST_CHARS = 8;
5
+ const NAME_SEP = "__";
6
+ export function normalizeNameSegment(name) {
7
+ return clampNameSegment(name.replace(/[^a-zA-Z0-9_-]/g, "_"));
8
+ }
9
+ const SEGMENT_DIGEST_JOIN = "-";
10
+ export function clampNameSegment(seg, max = TOOL_NAME_MAX_CHARS) {
11
+ if (seg === "")
12
+ return "_";
13
+ if (seg.length <= max)
14
+ return seg;
15
+ const digest = createHash("sha256").update(seg).digest("hex").slice(0, SEGMENT_DIGEST_CHARS);
16
+ const keep = max - digest.length - SEGMENT_DIGEST_JOIN.length;
17
+ return keep > 0 ? `${seg.slice(0, keep)}${SEGMENT_DIGEST_JOIN}${digest}` : digest.slice(0, max);
18
+ }
19
+ function settlePeerSegment(clamped) {
20
+ const folded = clamped.replace(/_{2,}/g, "_").replace(/_+$/, "");
21
+ if (folded !== "")
22
+ return folded;
23
+ return createHash("sha256").update(clamped).digest("hex").slice(0, SEGMENT_DIGEST_CHARS);
24
+ }
25
+ export function mintNamespacePrefix(ns, peer) {
26
+ const peerBudget = Math.max(1, TOOL_NAME_MAX_CHARS - ns.prefix.length - NAME_SEP.length - MINTED_TOOL_SEGMENT_MIN_CHARS);
27
+ return `${ns.prefix}${settlePeerSegment(clampNameSegment(normalizeNameSegment(peer), peerBudget))}${NAME_SEP}`;
28
+ }
29
+ export function mintNamespacedToolName(ns, peer, tool) {
30
+ const prefix = mintNamespacePrefix(ns, peer);
31
+ return `${prefix}${clampNameSegment(normalizeNameSegment(tool), Math.max(1, TOOL_NAME_MAX_CHARS - prefix.length))}`;
32
+ }
@@ -1,4 +1,4 @@
1
- export type ProtocolId = "mcp";
1
+ export type ProtocolId = "mcp" | "a2a";
2
2
  export interface ProtocolPeerTool {
3
3
  peer: string;
4
4
  tool: string;
@@ -11,7 +11,9 @@ export interface ProtocolNamespace<P extends string = string> {
11
11
  displayGroupKey(name: string): string;
12
12
  }
13
13
  declare const MCP_PREFIX_NAME: "mcp__";
14
+ declare const A2A_PREFIX_NAME: "a2a__";
14
15
  export declare const MCP_NAMESPACE: ProtocolNamespace<typeof MCP_PREFIX_NAME>;
16
+ export declare const A2A_NAMESPACE: ProtocolNamespace<typeof A2A_PREFIX_NAME>;
15
17
  export declare const PROTOCOL_TABLE: readonly ProtocolNamespace[];
16
18
  export declare function protocolOf(name: string): ProtocolNamespace | undefined;
17
19
  export {};
@@ -1,37 +1,42 @@
1
1
  const NAME_SEP = "__";
2
+ function makeProtocolNamespace(id, prefix) {
3
+ const parse = (name) => {
4
+ if (!name.startsWith(prefix))
5
+ return undefined;
6
+ const rest = name.slice(prefix.length);
7
+ const sep = rest.indexOf(NAME_SEP);
8
+ if (sep <= 0)
9
+ return undefined;
10
+ const tool = rest.slice(sep + NAME_SEP.length);
11
+ if (tool.length === 0)
12
+ return undefined;
13
+ return { peer: rest.slice(0, sep), tool };
14
+ };
15
+ const makeName = (peer, tool) => {
16
+ if (peer.length === 0 || tool.length === 0) {
17
+ throw new Error(`protocol table (${id}): peer and tool must both be non-empty (got peer=${JSON.stringify(peer)}, tool=${JSON.stringify(tool)})`);
18
+ }
19
+ if (peer.includes(NAME_SEP)) {
20
+ throw new Error(`protocol table (${id}): peer must not contain the ${NAME_SEP} separator (got ${JSON.stringify(peer)}) — the composed name would parse back as a DIFFERENT peer`);
21
+ }
22
+ return `${prefix}${peer}${NAME_SEP}${tool}`;
23
+ };
24
+ return {
25
+ id,
26
+ prefix,
27
+ makeName,
28
+ parse,
29
+ displayGroupKey: (name) => {
30
+ const p = parse(name);
31
+ return p === undefined ? name : `${prefix}${p.peer}${NAME_SEP}*`;
32
+ },
33
+ };
34
+ }
2
35
  const MCP_PREFIX_NAME = `mcp${NAME_SEP}`;
3
- const parseMcpName = (name) => {
4
- if (!name.startsWith(MCP_PREFIX_NAME))
5
- return undefined;
6
- const rest = name.slice(MCP_PREFIX_NAME.length);
7
- const sep = rest.indexOf(NAME_SEP);
8
- if (sep <= 0)
9
- return undefined;
10
- const tool = rest.slice(sep + NAME_SEP.length);
11
- if (tool.length === 0)
12
- return undefined;
13
- return { peer: rest.slice(0, sep), tool };
14
- };
15
- const makeMcpName = (peer, tool) => {
16
- if (peer.length === 0 || tool.length === 0) {
17
- throw new Error(`protocol table (mcp): peer and tool must both be non-empty (got peer=${JSON.stringify(peer)}, tool=${JSON.stringify(tool)})`);
18
- }
19
- if (peer.includes(NAME_SEP)) {
20
- throw new Error(`protocol table (mcp): peer must not contain the ${NAME_SEP} separator (got ${JSON.stringify(peer)}) — the composed name would parse back as a DIFFERENT peer`);
21
- }
22
- return `${MCP_PREFIX_NAME}${peer}${NAME_SEP}${tool}`;
23
- };
24
- export const MCP_NAMESPACE = {
25
- id: "mcp",
26
- prefix: MCP_PREFIX_NAME,
27
- makeName: makeMcpName,
28
- parse: parseMcpName,
29
- displayGroupKey: (name) => {
30
- const p = parseMcpName(name);
31
- return p === undefined ? name : `${MCP_PREFIX_NAME}${p.peer}${NAME_SEP}*`;
32
- },
33
- };
34
- export const PROTOCOL_TABLE = [MCP_NAMESPACE];
36
+ const A2A_PREFIX_NAME = `a2a${NAME_SEP}`;
37
+ export const MCP_NAMESPACE = makeProtocolNamespace("mcp", MCP_PREFIX_NAME);
38
+ export const A2A_NAMESPACE = makeProtocolNamespace("a2a", A2A_PREFIX_NAME);
39
+ export const PROTOCOL_TABLE = [MCP_NAMESPACE, A2A_NAMESPACE];
35
40
  export function protocolOf(name) {
36
41
  return PROTOCOL_TABLE.find((ns) => name.startsWith(ns.prefix));
37
42
  }
@@ -1,6 +1,7 @@
1
1
  import { AgentHarness, type ThinkingLevel } from "../../internal/harness.js";
2
2
  import type { Model } from "../../internal/llm.js";
3
3
  import { type MaterializedMcp } from "../mcp.js";
4
+ import { type MaterializedA2a } from "../a2a.js";
4
5
  import type { HarvestReport, MemorySessionHandle } from "../memory-engine/types.js";
5
6
  import { StoredSession } from "../session.js";
6
7
  import type { SessionStore } from "../session.js";
@@ -15,6 +16,7 @@ import type { MemoryEngine } from "../memory-engine/engine.js";
15
16
  import { type ToolManifestRow } from "../../prompt-assembly/tool-catalog.js";
16
17
  import type { TaskNotificationPayload } from "../task-notification.js";
17
18
  import { type CwdRef } from "../../tools/fs/index.js";
19
+ import { type WorkflowSizeGuideline } from "../../orchestration/workflow-size-guideline.js";
18
20
  import type { Runner } from "./runtask.js";
19
21
  import { type CheckpointGate, type CheckpointState, type CheckpointToken, type ResourceLedger, type ResourceLimitReason } from "../checkpoint-store.js";
20
22
  import type { ActiveWorktreeSession, AgentMessage, AgentTool, ExecutionEnv } from "../../internal/harness.js";
@@ -36,6 +38,7 @@ export interface Prepared {
36
38
  thinking?: ThinkingLevel;
37
39
  compModel?: Model;
38
40
  mcp: MaterializedMcp;
41
+ a2a?: MaterializedA2a;
39
42
  blockedRef: BlockedRef;
40
43
  outputRef: OutputRef;
41
44
  abortController: AbortController;
@@ -186,6 +189,10 @@ export interface Prepared {
186
189
  path: string;
187
190
  contentHash: string | null;
188
191
  }>;
192
+ workflowSizeGuideline?: {
193
+ legGuideline: WorkflowSizeGuideline;
194
+ current: () => WorkflowSizeGuideline;
195
+ };
189
196
  detectExternalChanges?: (maxFiles: number) => Promise<{
190
197
  changed: Array<{
191
198
  path: string;
@@ -9,6 +9,7 @@ import { buildAutoModePrompt, renderAutoModeAction, renderAutoModeWindow } from
9
9
  import { resolveModel, resolveTaskModel, roleModelIfSet } from "../roles.js";
10
10
  import { primaryActivityArg } from "../arg-summary.js";
11
11
  import { materializeMcpTools } from "../mcp.js";
12
+ import { materializeA2aTools } from "../a2a.js";
12
13
  import { Type } from "typebox";
13
14
  import { brainToRuntime } from "../runtime.js";
14
15
  import { StoredSession, isSessionConflict, hasSessionFork } from "../session.js";
@@ -30,7 +31,7 @@ import { CacheBreakDetector, toolsToFingerprintInputs } from "../cache-break-det
30
31
  import { reservedCollisions, reservedFor } from "../../brain/request-params.js";
31
32
  import { defineTool, isDefineToolProduct } from "../tools.js";
32
33
  import { RETIRED_TOOL_NAMES } from "../tool-name-aliases.js";
33
- import { protocolOf } from "../protocol-table.js";
34
+ import { protocolOf, PROTOCOL_TABLE } from "../protocol-table.js";
34
35
  import { pathToUri } from "../lsp-protocol.js";
35
36
  import { DEFAULT_TOOL_RESULT_THRESHOLD_CHARS, createOffloadPersist, firstPartyOffloadPolicy, InMemoryToolResultStore, RunnerSharedToolResultStore, ScopedToolResultStore, isVolatileOffloadStore, OFFLOAD_TOOL_NAME, createReadToolResultTool, withToolResultOffload, } from "../tool-result-store.js";
36
37
  import { OUTPUT_TOOL_NAME, REPORT_FINDINGS_TOOL_NAME, SKILL_CONTENT_MAX_CHARS, SKILL_TOOL_NAME, createOutputTool, createReportBlockedTool, createReportFindingsTool, createSkillTool, normalizeSkills } from "./synthetic-tools.js";
@@ -64,6 +65,7 @@ import { createSchedulerTools } from "../../tools/scheduler-tools.js";
64
65
  import { createPresentPlanTool, createEnterPlanModeTool, PRESENT_PLAN_TOOL_NAME, ENTER_PLAN_MODE_TOOL_NAME } from "../present-plan-tool.js";
65
66
  import { isSelfOrchestrationActive, selfOrchestrationFailClosedReason } from "../../orchestration/workflow-script-runner.js";
66
67
  import { createRunWorkflowTool, RUN_WORKFLOW_TOOL_NAME } from "../../orchestration/run-workflow-tool.js";
68
+ import { resolveWorkflowSizeGuideline } from "../../orchestration/workflow-size-guideline.js";
67
69
  import { createLspTool, gitCheckIgnoreFilter, resolveLspPath } from "../lsp.js";
68
70
  import { resolveKey } from "../../tools/fs/safety.js";
69
71
  import { BINDING_CHECKPOINT_VERSION, mintCheckpointToken, RESOURCE_CHECKPOINT_VERSION, WALLTIME_CHECKPOINT_VERSION, buildRiskDescriptor, debitLedger, remainingBudgetMicroUsd, } from "../checkpoint-store.js";
@@ -82,6 +84,7 @@ function sanitizedTtlMs(ttlMs) {
82
84
  const ungatedWarnedShapes = new WeakMap();
83
85
  const advisedPolicyNames = new WeakMap();
84
86
  const ADVISED_KEYS_CAP = 64;
87
+ const NAMESPACED_NAME_SHAPES = PROTOCOL_TABLE.map((ns) => `${ns.prefix}<peer>__<tool>`).join(", ");
85
88
  export const DEFAULT_IRREVERSIBLE_SCOPE = "irreversible";
86
89
  export function checkpointScopeOf(spec) {
87
90
  return spec.durableApproval?.scope || spec.principal || DEFAULT_IRREVERSIBLE_SCOPE;
@@ -261,7 +264,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
261
264
  let shellGatedMonitor = false;
262
265
  for (const t of spec.tools ?? []) {
263
266
  if (t.name.includes("__")) {
264
- const e = new Error(`Tool name "${t.name}" is invalid: "__" is reserved for the MCP namespace (mcp__<server>__<tool>) and must not appear in a caller tool name.`);
267
+ const e = new Error(`Tool name "${t.name}" is invalid: "__" is reserved for the engine's protocol tool namespaces (${NAMESPACED_NAME_SHAPES}) and must not appear in a caller tool name.`);
265
268
  e.code = "config.tool_name_invalid";
266
269
  throw e;
267
270
  }
@@ -479,7 +482,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
479
482
  return maybeOffload(tool, policy);
480
483
  return maybeOffload(tool, explicitGlobalThreshold === undefined ? policy : undefined);
481
484
  };
482
- const mcpOffload = (tool) => {
485
+ const remoteToolOffload = (tool) => {
483
486
  if (explicitGlobalThreshold !== undefined)
484
487
  return maybeOffload(tool);
485
488
  return maybeOffload(tool, { offloadThresholdChars: tool.mcpMaxResultSizeChars ?? 50_000 });
@@ -492,6 +495,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
492
495
  }
493
496
  let ownedEnv;
494
497
  let mcp;
498
+ let a2a;
495
499
  if (internals?.requestedCwd !== undefined && deps.executionEnvFactory === undefined) {
496
500
  await settleTeardownLeg(() => forgetOnThrow(), "forgetOnThrow (cwd-unsupported leg)", (err) => deps.onError?.(err, { phase: "config", sessionId }));
497
501
  const e = new Error(`Agent cwd "${internals.requestedCwd}" cannot take effect: this deployment has no executionEnvFactory (a static execution environment cannot be re-rooted per agent). Drop the cwd parameter or deploy a factory.`);
@@ -969,8 +973,11 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
969
973
  `(per-principal entitlement governance, not a misconfiguration — the run_workflow tool is not mounted)`), { phase: "config", sessionId });
970
974
  }
971
975
  let workflowToolsActive = false;
976
+ let workflowSizeGuideline;
972
977
  if (selfOrchestrationActive && runnerSelf && deps.workflowScriptRunner && deps.workflowGovernanceBaseline) {
973
978
  workflowToolsActive = true;
979
+ const currentSizeGuideline = () => resolveWorkflowSizeGuideline(deps.workflowLimits?.sizeGuideline).size;
980
+ workflowSizeGuideline = { legGuideline: currentSizeGuideline(), current: currentSizeGuideline };
974
981
  toolEffects.set(RUN_WORKFLOW_TOOL_NAME, "write");
975
982
  tools.push(await createRunWorkflowTool({
976
983
  runner: runnerSelf,
@@ -1093,7 +1100,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1093
1100
  throw e;
1094
1101
  }
1095
1102
  }
1096
- tools.push(...mcp.tools.map((t) => mcpOffload(t)));
1103
+ tools.push(...mcp.tools.map((t) => remoteToolOffload(t)));
1097
1104
  const rebuildHarnessToolsRef = {};
1098
1105
  const toolCallGateArmedRef = { armed: false };
1099
1106
  if (spec.mcp?.length) {
@@ -1121,7 +1128,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1121
1128
  const pushable = r.tools.filter((t) => !excludedSet.has(t.name));
1122
1129
  const excludedNow = r.tools.filter((t) => excludedSet.has(t.name)).map((t) => t.name);
1123
1130
  try {
1124
- foldMcpAxes((r.axes ?? []).filter((a) => !excludedSet.has(a.name)));
1131
+ foldProtocolAxes((r.axes ?? []).filter((a) => !excludedSet.has(a.name)), "MCP");
1125
1132
  }
1126
1133
  catch (foldErr) {
1127
1134
  lines.push(`${r.server}: failed (${foldErr instanceof Error ? foldErr.message : String(foldErr)})`);
@@ -1135,7 +1142,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1135
1142
  tools.splice(i, 1);
1136
1143
  }
1137
1144
  }
1138
- tools.push(...pushable.map((t) => mcpOffload(t)));
1145
+ tools.push(...pushable.map((t) => remoteToolOffload(t)));
1139
1146
  changed = true;
1140
1147
  const detail = [];
1141
1148
  const shownAdded = r.added.filter((n) => !excludedSet.has(n));
@@ -1165,7 +1172,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1165
1172
  });
1166
1173
  toolEffects.set("RefreshMcpTools", "read");
1167
1174
  }
1168
- const foldMcpAxes = (axes) => {
1175
+ const foldProtocolAxes = (axes, protocolLabel) => {
1169
1176
  for (const axis of axes) {
1170
1177
  if (axis.irreversibility === "always") {
1171
1178
  irreversibilityTier.set(axis.name, "always");
@@ -1173,7 +1180,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1173
1180
  }
1174
1181
  if (axis.egress) {
1175
1182
  if (axis.effect !== undefined && axis.effect !== "write") {
1176
- const e = new Error(`MCP tool "${axis.name}" resolves to egress:true with effect:"${axis.effect}" — an egress tool (external write) must have effect:"write". Clear egress (toolAxes egress:false) if it is a pure read, or set effect:"write".`);
1183
+ const e = new Error(`${protocolLabel} tool "${axis.name}" resolves to egress:true with effect:"${axis.effect}" — an egress tool (external write) must have effect:"write". Clear egress (toolAxes egress:false) if it is a pure read, or set effect:"write".`);
1177
1184
  e.code = "config.egress_requires_write_effect";
1178
1185
  throw e;
1179
1186
  }
@@ -1183,7 +1190,25 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1183
1190
  toolEffects.set(axis.name, axis.effect ?? "write");
1184
1191
  }
1185
1192
  };
1186
- foldMcpAxes(mcp.toolAxes);
1193
+ foldProtocolAxes(mcp.toolAxes, "MCP");
1194
+ a2a = spec.a2a?.length
1195
+ ? await materializeA2aTools(spec.a2a, spec.principal, abortController.signal)
1196
+ : { tools: [], toolAxes: [], warnings: [], statuses: [], refresh: async () => [], dispose: async () => { } };
1197
+ for (const w of a2a.warnings)
1198
+ deps.onError?.(w, { phase: "a2a", sessionId });
1199
+ {
1200
+ const callerNames = new Set((spec.tools ?? []).flatMap((t) => [t.name, ...(t.aliases ?? [])]));
1201
+ const clash = a2a.tools.find((t) => callerNames.has(t.name));
1202
+ if (clash) {
1203
+ await a2a.dispose();
1204
+ await mcp.dispose();
1205
+ const e = new Error(`Tool name "${clash.name}" is reserved by an injected A2A tool — a caller tool of the same name would silently shadow it.`);
1206
+ e.code = "config.reserved_tool_name";
1207
+ throw e;
1208
+ }
1209
+ }
1210
+ tools.push(...a2a.tools.map((t) => remoteToolOffload(t)));
1211
+ foldProtocolAxes(a2a.toolAxes, "A2A");
1187
1212
  const callCapOn = spec.limits?.callCapByDeadline !== false;
1188
1213
  const callCapRef = spec.limits?.timeoutSec &&
1189
1214
  spec.limits.timeoutSec > 0 &&
@@ -1861,11 +1886,11 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1861
1886
  }
1862
1887
  }
1863
1888
  const userToolNames = (spec.tools ?? []).map((t) => t.name);
1864
- const mcpToolNames = mcp.tools.map((t) => t.name);
1889
+ const protocolToolNames = [...mcp.tools.map((t) => t.name), ...a2a.tools.map((t) => t.name)];
1865
1890
  const deferred = classifyDeferred({
1866
1891
  specs: spec.tools ?? [],
1867
- mcpToolNames,
1868
- fullTools: tools.filter((t) => userToolNames.includes(t.name) || mcpToolNames.includes(t.name)),
1892
+ protocolToolNames,
1893
+ fullTools: tools.filter((t) => userToolNames.includes(t.name) || protocolToolNames.includes(t.name)),
1869
1894
  deferMode: deps.deferMode,
1870
1895
  model,
1871
1896
  deferNames: (toolFaceSnapshot.defer ?? []).filter((n) => tools.some((t) => t.name === n)),
@@ -2211,7 +2236,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2211
2236
  throw err;
2212
2237
  }
2213
2238
  if (n.includes("__") && protocolOf(n) === undefined) {
2214
- const err = new Error(`tool policy ${kind}-list entry "${n}" is a pre-prefix MCP tool name and matches nothing in this run's roster — MCP tools are named "mcp__<server>__<tool>" and legacy-name normalization was removed (RB-476-A), so this entry would silently guard nothing. Prefix the entry with "mcp__".`);
2239
+ const err = new Error(`tool policy ${kind}-list entry "${n}" carries the "__" namespace separator but no protocol prefix, and matches nothing in this run's roster — protocol tools are named ${NAMESPACED_NAME_SHAPES}, and legacy-name normalization was removed (RB-476-A), so this entry would silently guard nothing. Prefix the entry with the owning protocol's marker.`);
2215
2240
  err.code = "config.legacy_tool_name";
2216
2241
  throw err;
2217
2242
  }
@@ -3504,12 +3529,16 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3504
3529
  : undefined;
3505
3530
  overheadState.promptChars = systemPrompt.length;
3506
3531
  const preparedHolder = {};
3507
- const buildPrepared = () => ({ harness, session, sessionId, taskRootPath, model, thinking, compModel, mcp: mcp, blockedRef, outputRef, abortController, conflictRef, blockedToolCalls, nestedStats, ...(rewindNotes.length > 0 ? { rewindNotes } : {}), cwdRef: handsCwdRef, ...(worktreeSessionRef !== undefined ? { worktreeSessionRef } : {}), ...(workspaceStateSettle !== undefined ? { workspaceStateSettle } : {}), denyNarrowingPolicy, ...(basePolicyForResumeEdit !== undefined ? { basePolicyForResumeEdit } : {}), releaseSignal, cacheBreakDetector, cacheFingerprint, promptManifest, epochDeclaredSections, activeTools, ...(deferred.size > 0 ? { deferredToolNames: deferred } : {}), ownedEnv, suspendRef, reviewRef, remoteEnvFailures, reviewRequestRef, suspendLoopRef, suspendForResource, ...(callCapRef ? { callCapRef } : {}), ...(cutKills ? { cutKills } : {}), ...(callCapRef ? { callIssuedAtRef } : {}), suspendForReview, resourceLedger: priorLedger, liveSpendRef, humanReviewRef, now, tools, toolEffects, promptOverheadTokens, readTaskFile, recentlyReadFiles, normalizeAttachmentPath, isDedupStubResult, ...(onCompactionApplied ? { onCompactionApplied } : {}), compactionReuseRef, trimPressureRef, ...(memoryEngineSession ? { memoryEngineSession } : {}), ...(subagentRetain ? { subagentRetain } : {}), ...(lspDiagnostics && nudgeLspOnEdit ? { lspDiagnostics: { registry: lspDiagnostics, nudge: nudgeLspOnEdit } } : {}), planModeRef, ...(dateChange ? { dateChange } : {}), ...(instructionSources ? { instructionSources } : {}), ...(detectExternalChanges ? { detectExternalChanges } : {}), ...(toolsDeltaRef ? { toolsDeltaRef } : {}), ...(agentListing ? { agentListing } : {}), ...(skillsListing ? { skillsListing } : {}), announcedListingsRef, listBackgroundTasks, ...(turnSnapshotRef.current !== undefined ? { turnSnapshot: turnSnapshotRef.current } : {}), ...(centerCompactionCandidate !== undefined ? { centerCompactionCandidate } : {}) });
3532
+ const buildPrepared = () => ({ harness, session, sessionId, taskRootPath, model, thinking, compModel, mcp: mcp, ...(a2a !== undefined && a2a.tools.length > 0 ? { a2a } : {}), blockedRef, outputRef, abortController, conflictRef, blockedToolCalls, nestedStats, ...(rewindNotes.length > 0 ? { rewindNotes } : {}), cwdRef: handsCwdRef, ...(worktreeSessionRef !== undefined ? { worktreeSessionRef } : {}), ...(workspaceStateSettle !== undefined ? { workspaceStateSettle } : {}), denyNarrowingPolicy, ...(basePolicyForResumeEdit !== undefined ? { basePolicyForResumeEdit } : {}), releaseSignal, cacheBreakDetector, cacheFingerprint, promptManifest, epochDeclaredSections, activeTools, ...(deferred.size > 0 ? { deferredToolNames: deferred } : {}), ownedEnv, suspendRef, reviewRef, remoteEnvFailures, reviewRequestRef, suspendLoopRef, suspendForResource, ...(callCapRef ? { callCapRef } : {}), ...(cutKills ? { cutKills } : {}), ...(callCapRef ? { callIssuedAtRef } : {}), suspendForReview, resourceLedger: priorLedger, liveSpendRef, humanReviewRef, now, tools, toolEffects, promptOverheadTokens, readTaskFile, recentlyReadFiles, normalizeAttachmentPath, isDedupStubResult, ...(onCompactionApplied ? { onCompactionApplied } : {}), compactionReuseRef, trimPressureRef, ...(memoryEngineSession ? { memoryEngineSession } : {}), ...(subagentRetain ? { subagentRetain } : {}), ...(lspDiagnostics && nudgeLspOnEdit ? { lspDiagnostics: { registry: lspDiagnostics, nudge: nudgeLspOnEdit } } : {}), planModeRef, ...(dateChange ? { dateChange } : {}), ...(instructionSources ? { instructionSources } : {}), ...(workflowSizeGuideline ? { workflowSizeGuideline } : {}), ...(detectExternalChanges ? { detectExternalChanges } : {}), ...(toolsDeltaRef ? { toolsDeltaRef } : {}), ...(agentListing ? { agentListing } : {}), ...(skillsListing ? { skillsListing } : {}), announcedListingsRef, listBackgroundTasks, ...(turnSnapshotRef.current !== undefined ? { turnSnapshot: turnSnapshotRef.current } : {}), ...(centerCompactionCandidate !== undefined ? { centerCompactionCandidate } : {}) });
3508
3533
  const prepared = buildPrepared();
3509
3534
  preparedHolder.current = prepared;
3510
3535
  return prepared;
3511
3536
  }
3512
3537
  catch (prepareErr) {
3538
+ if (a2a) {
3539
+ const a2aHandle = a2a;
3540
+ await settleTeardownLeg(() => a2aHandle.dispose(), "a2a.dispose (prepare-throw leg)", (e) => deps.onError?.(e, { phase: "config", sessionId }));
3541
+ }
3513
3542
  if (mcp) {
3514
3543
  const mcpHandle = mcp;
3515
3544
  await settleTeardownLeg(() => mcpHandle.dispose(), "mcp.dispose (prepare-throw leg)", (e) => deps.onError?.(e, { phase: "config", sessionId }));
@@ -40,6 +40,7 @@ import { refuseOutOfContractDecision, toolPolicyNameSets } from "../tool-policy.
40
40
  import { defaultTaskRegistry } from "../task-registry.js";
41
41
  import { discloseDroppedPending, PendingSessionNotifications, renderTaskNotificationXml, SystemInjectionQueue, taskNotificationDedupKey } from "../task-notification.js";
42
42
  import { ToolDetachHub } from "../tool-detach.js";
43
+ import { workflowSizeGuidelineChangeNotice } from "../../orchestration/workflow-size-guideline.js";
43
44
  import { DEFAULT_MAX_TURNS } from "../../config/defaults.js";
44
45
  import { structuredFrom, toolOutputFrom } from "./tool-output-projection.js";
45
46
  export { DEFAULT_MAX_TURNS };
@@ -51,7 +52,7 @@ function createRunState() {
51
52
  budget: { remainingMicroUsd: undefined, maxCostMicroUsd: undefined, overBudget: () => false, streamCancel: false, callOutputChars: 0, lastStreamBudgetCheck: 0, projectedOverBudget: () => false },
52
53
  turn: { callStartAt: undefined, firstTokenAt: undefined, turnUsage: undefined, turnUsageMissing: false, turnStopReason: undefined, lastTurnHadToolCalls: false, toolBatch: [] },
53
54
  counters: { nudgesSent: 0, nudgeIdx: 0, finalizeInjected: false, walltimeSyncBackstopFired: false, compactionFloor: 0, trimForceBackoff: false, callCutoffs: 0, repetitionCuts: 0, repetitionSpared: 0, repetitionEvents: [], REPETITION_EVENTS_CAP: 0, preemptIgnoredReported: false, wroteThisRun: false, finalVerifyInjections: 0, groundingSignalPreR9: false, groundingSignalPostR9: false, cadenceTurns: 0 },
54
- attach: { attachmentsCfg: undefined, agentListingOn: false, skillsListingOn: false, attachState: undefined, dateState: undefined, instrProbe: undefined, instrState: undefined, attachmentsInjected: 0 },
55
+ attach: { attachmentsCfg: undefined, agentListingOn: false, skillsListingOn: false, attachState: undefined, dateState: undefined, instrProbe: undefined, instrState: undefined, sizeGuidelineState: undefined, attachmentsInjected: 0 },
55
56
  };
56
57
  }
57
58
  const MAX_CONSECUTIVE_COMPACTION_FAILURES = 3;
@@ -314,7 +315,10 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
314
315
  }
315
316
  let boundaryAttachmentBytes = 0;
316
317
  let attachmentsPayload;
317
- if ((rs.attach.dateState !== undefined || rs.attach.instrState !== undefined || rs.attach.attachState !== undefined) &&
318
+ if ((rs.attach.dateState !== undefined ||
319
+ rs.attach.instrState !== undefined ||
320
+ rs.attach.sizeGuidelineState !== undefined ||
321
+ rs.attach.attachState !== undefined) &&
318
322
  !boundarySteered &&
319
323
  !rs.counters.finalizeInjected &&
320
324
  rs.counters.finalVerifyInjections === 0 &&
@@ -457,6 +461,16 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
457
461
  rs.attach.dateState.announcedDate = today;
458
462
  }
459
463
  }
464
+ if (rs.attach.sizeGuidelineState !== undefined && rs.turn.lastTurnHadToolCalls) {
465
+ const guideline = rs.attach.sizeGuidelineState.current();
466
+ if (guideline !== rs.attach.sizeGuidelineState.announcedGuideline) {
467
+ due.splice(due.length > 0 && due[0].source === "date_change" ? 1 : 0, 0, {
468
+ source: "workflow_size_guideline_change",
469
+ body: workflowSizeGuidelineChangeNotice(guideline),
470
+ });
471
+ rs.attach.sizeGuidelineState.announcedGuideline = guideline;
472
+ }
473
+ }
460
474
  if (rs.attach.instrState !== undefined && rs.attach.instrProbe !== undefined && rs.turn.lastTurnHadToolCalls) {
461
475
  let probed = null;
462
476
  try {
@@ -1770,6 +1784,10 @@ export class Runner {
1770
1784
  rs.attach.instrProbe !== undefined && prepared.instructionSources !== undefined && prepared.instructionSources.length > 0
1771
1785
  ? { lastAnnouncedHash: new Map(prepared.instructionSources.map((s) => [s.path, s.contentHash])) }
1772
1786
  : undefined;
1787
+ rs.attach.sizeGuidelineState =
1788
+ prepared.workflowSizeGuideline !== undefined
1789
+ ? { announcedGuideline: prepared.workflowSizeGuideline.legGuideline, current: prepared.workflowSizeGuideline.current }
1790
+ : undefined;
1773
1791
  rs.counters.cadenceTurns = 0;
1774
1792
  rs.turn.lastTurnHadToolCalls = false;
1775
1793
  if (rs.attach.attachState !== undefined && rs.attach.attachmentsCfg?.backgroundTasks === true) {
@@ -2620,6 +2638,7 @@ export class Runner {
2620
2638
  catch (err) {
2621
2639
  if (errorCodeOf(err) === "resume.tool_unavailable") {
2622
2640
  await settleTeardownLeg(() => prepared.mcp.dispose(), "mcp.dispose (tool_unavailable leg)", (e) => this.deps.onError?.(e, { phase: "config", sessionId: prepared.sessionId }));
2641
+ await settleTeardownLeg(() => prepared.a2a?.dispose(), "a2a.dispose (tool_unavailable leg)", (e) => this.deps.onError?.(e, { phase: "config", sessionId: prepared.sessionId }));
2623
2642
  await settleTeardownLeg(() => (prepared.ownedEnv && hasDestroy(prepared.ownedEnv) ? prepared.ownedEnv.destroy() : undefined), "ownedEnv.destroy (tool_unavailable leg)", (e) => this.deps.onError?.(e, { phase: "config", sessionId: prepared.sessionId }));
2624
2643
  throw err;
2625
2644
  }
@@ -3198,12 +3217,12 @@ export class Runner {
3198
3217
  if (outcome.gate === "policy_ask") {
3199
3218
  const boundTo = cp.pendingAction.kind === "tool_approval" ? cp.pendingAction.toolCallId : undefined;
3200
3219
  if (outcome.boundCallId !== boundTo) {
3201
- throw new CheckpointError("checkpoint.invalid_outcome", `resume boundCallId "${outcome.boundCallId}" does not match the checkpoint's pending tool call "${boundTo ?? "(none)"}" — the decision-action binding (design/80 D-1) failed; refusing to apply a decision bound to a different action`);
3220
+ throw new CheckpointError("checkpoint.invalid_outcome", `resume boundCallId "${outcome.boundCallId}" does not match the checkpoint's pending tool call "${boundTo ?? "(none)"}" — the decision-action binding (design/80 D-1) failed; refusing to apply a decision bound to a different action`, { field: "boundCallId" });
3202
3221
  }
3203
3222
  const boundHash = cp.pendingAction.kind === "tool_approval" ? cp.pendingAction.boundInputHash : undefined;
3204
3223
  if (boundHash !== undefined) {
3205
3224
  if (outcome.boundInputHash !== boundHash) {
3206
- throw new CheckpointError("checkpoint.invalid_outcome", "resume boundInputHash does not match the checkpoint's pending tool call input — the decision-action input binding (design/80 D-1 §2) failed; refusing to apply an approval bound to a different input (TOCTOU re-mint guard)");
3225
+ throw new CheckpointError("checkpoint.invalid_outcome", "resume boundInputHash does not match the checkpoint's pending tool call input — the decision-action input binding (design/80 D-1 §2) failed; refusing to apply an approval bound to a different input (TOCTOU re-mint guard)", { field: "boundInputHash" });
3207
3226
  }
3208
3227
  }
3209
3228
  else if (checkpointVersionOf(cp) >= BINDING_CHECKPOINT_VERSION) {
@@ -3520,6 +3539,14 @@ export class Runner {
3520
3539
  catch (err) {
3521
3540
  this.deps.onError?.(err, { phase: "mcp", sessionId: prepared.sessionId });
3522
3541
  }
3542
+ if (prepared.a2a !== undefined) {
3543
+ try {
3544
+ await prepared.a2a.dispose();
3545
+ }
3546
+ catch (err) {
3547
+ this.deps.onError?.(err, { phase: "a2a", sessionId: prepared.sessionId });
3548
+ }
3549
+ }
3523
3550
  return comp;
3524
3551
  }
3525
3552
  async teardownOwnedEnv(prepared) {
@@ -14,7 +14,7 @@ export declare function deferHint(description: string, max?: number): string;
14
14
  export declare function safeName(name: string): string;
15
15
  export declare function classifyDeferred(opts: {
16
16
  specs: ReadonlyArray<ToolSpec>;
17
- mcpToolNames: ReadonlyArray<string>;
17
+ protocolToolNames: ReadonlyArray<string>;
18
18
  fullTools: ReadonlyArray<ToolFingerprintInput>;
19
19
  deferMode?: "auto";
20
20
  model: Model;
@@ -35,7 +35,7 @@ export function classifyDeferred(opts) {
35
35
  if (s.defer === true && !pinned.has(s.name))
36
36
  deferred.add(s.name);
37
37
  }
38
- for (const name of opts.mcpToolNames)
38
+ for (const name of opts.protocolToolNames)
39
39
  if (!pinned.has(name))
40
40
  deferred.add(name);
41
41
  for (const name of opts.deferNames ?? [])
@@ -15,7 +15,7 @@ export declare const TOOL_SEARCH_REMINDER_CONFIG: {
15
15
  export declare const CHANGED_FILES_MAX = 20;
16
16
  export declare const CHANGED_FILES_MTIME_EPS_MS = 2000;
17
17
  export declare const ATTACHMENT_BYTE_CAP: number;
18
- export type AttachmentSource = "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";
18
+ export type AttachmentSource = "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";
19
19
  export interface AgentListingEntry {
20
20
  name: string;
21
21
  description: string;
@@ -31,7 +31,6 @@ export interface TurnAttachment {
31
31
  body: string;
32
32
  }
33
33
  export interface ListProjection {
34
- statusCounts: Record<string, number>;
35
34
  items: Array<{
36
35
  content: string;
37
36
  status: string;