@sema-agent/core 7.5.2 → 7.6.1

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.
Files changed (138) hide show
  1. package/CHANGELOG.md +67 -0
  2. package/dist/agents/agent-transcript-tool.d.ts +2 -2
  3. package/dist/agents/cascade.d.ts +4 -5
  4. package/dist/agents/cascade.js +12 -10
  5. package/dist/agents/repair-loop.d.ts +7 -5
  6. package/dist/agents/repair-loop.js +13 -15
  7. package/dist/agents/retain-ledger.d.ts +2 -3
  8. package/dist/agents/send-message-tool.d.ts +2 -2
  9. package/dist/agents/session-util.d.ts +2 -2
  10. package/dist/agents/subagent.d.ts +27 -46
  11. package/dist/agents/subagent.js +119 -105
  12. package/dist/agents/suspend-guard.d.ts +31 -19
  13. package/dist/agents/suspend-guard.js +14 -8
  14. package/dist/agents/teacher.d.ts +2 -2
  15. package/dist/agents/teacher.js +9 -9
  16. package/dist/agents/team.d.ts +6 -5
  17. package/dist/agents/team.js +10 -8
  18. package/dist/agents/verify.d.ts +8 -9
  19. package/dist/agents/verify.js +17 -17
  20. package/dist/core/a2a.js +2 -1
  21. package/dist/core/agent-definition.d.ts +172 -0
  22. package/dist/core/agent-definition.js +1 -0
  23. package/dist/core/ask-origin.d.ts +60 -7
  24. package/dist/core/ask-origin.js +26 -1
  25. package/dist/core/checkpoint-store.d.ts +78 -76
  26. package/dist/core/checkpoint-store.js +17 -1
  27. package/dist/core/delegation-frames.d.ts +298 -0
  28. package/dist/core/delegation-frames.js +21 -0
  29. package/dist/core/engine-notice.d.ts +555 -0
  30. package/dist/core/engine-notice.js +55 -0
  31. package/dist/core/gate-fold.d.ts +12 -0
  32. package/dist/core/gate-fold.js +158 -0
  33. package/dist/core/gate-lanes.d.ts +93 -0
  34. package/dist/core/gate-lanes.js +626 -0
  35. package/dist/core/gate-outcome.d.ts +189 -0
  36. package/dist/core/gate-outcome.js +70 -0
  37. package/dist/core/hands-band.d.ts +134 -0
  38. package/dist/core/hands-band.js +1 -0
  39. package/dist/core/hooks.d.ts +22 -177
  40. package/dist/core/hooks.js +53 -851
  41. package/dist/core/mcp-failure.d.ts +142 -0
  42. package/dist/core/mcp-failure.js +145 -0
  43. package/dist/core/mcp-server-spec.d.ts +217 -0
  44. package/dist/core/mcp-server-spec.js +1 -0
  45. package/dist/core/mcp.d.ts +21 -77
  46. package/dist/core/mcp.js +76 -150
  47. package/dist/core/model-seat.d.ts +99 -0
  48. package/dist/core/model-seat.js +1 -0
  49. package/dist/core/pause-registry.d.ts +131 -0
  50. package/dist/core/pause-registry.js +27 -0
  51. package/dist/core/reminder-mint.d.ts +10 -0
  52. package/dist/core/reminder-mint.js +3 -0
  53. package/dist/core/runner/assemble-result.d.ts +32 -41
  54. package/dist/core/runner/assemble-result.js +55 -74
  55. package/dist/core/runner/contracts.d.ts +427 -69
  56. package/dist/core/runner/denial-limit-arms.d.ts +1 -1
  57. package/dist/core/runner/denial-limit-arms.js +3 -3
  58. package/dist/core/runner/gate-exit.d.ts +242 -0
  59. package/dist/core/runner/gate-exit.js +124 -0
  60. package/dist/core/runner/park-commit.d.ts +17 -23
  61. package/dist/core/runner/park-commit.js +14 -15
  62. package/dist/core/runner/prepare-ask-lane.d.ts +0 -3
  63. package/dist/core/runner/prepare-ask-lane.js +3 -5
  64. package/dist/core/runner/prepare-boundary-parks.d.ts +3 -6
  65. package/dist/core/runner/prepare-boundary-parks.js +3 -3
  66. package/dist/core/runner/prepare-caps-and-workflow.d.ts +2 -7
  67. package/dist/core/runner/prepare-caps-and-workflow.js +1 -1
  68. package/dist/core/runner/prepare-delegation-surface.d.ts +2 -7
  69. package/dist/core/runner/prepare-gate-stations.d.ts +4 -7
  70. package/dist/core/runner/prepare-gate-stations.js +29 -54
  71. package/dist/core/runner/prepare-inherited-gate.js +1 -1
  72. package/dist/core/runner/prepare-memory.d.ts +44 -26
  73. package/dist/core/runner/prepare-park-ask.d.ts +2 -4
  74. package/dist/core/runner/prepare-park-ask.js +5 -5
  75. package/dist/core/runner/prepare-task.d.ts +2 -2
  76. package/dist/core/runner/prepare-task.js +8 -9
  77. package/dist/core/runner/prepare-wiring-manifest.d.ts +7 -15
  78. package/dist/core/runner/prepare-wiring-manifest.js +9 -10
  79. package/dist/core/runner/runtask.d.ts +20 -102
  80. package/dist/core/runner/runtask.js +119 -121
  81. package/dist/core/runner/terminal-projection.d.ts +22 -0
  82. package/dist/core/runner/terminal-projection.js +28 -0
  83. package/dist/core/runner-deps.d.ts +1416 -0
  84. package/dist/core/runner-deps.js +1 -0
  85. package/dist/core/runtime-caps.d.ts +164 -0
  86. package/dist/core/runtime-caps.js +1 -0
  87. package/dist/core/store-contracts/checkpoint-store-contract.d.ts +4 -1
  88. package/dist/core/store-contracts/checkpoint-store-contract.js +8 -2
  89. package/dist/core/task-event.d.ts +910 -0
  90. package/dist/core/task-event.js +1 -0
  91. package/dist/core/task-limits.d.ts +110 -0
  92. package/dist/core/task-limits.js +1 -0
  93. package/dist/core/task-result.d.ts +809 -0
  94. package/dist/core/task-result.js +1 -0
  95. package/dist/core/task-spec.d.ts +1370 -0
  96. package/dist/core/task-spec.js +1 -0
  97. package/dist/core/task-stream.d.ts +382 -0
  98. package/dist/core/task-stream.js +1 -0
  99. package/dist/core/terminal-cause.d.ts +137 -0
  100. package/dist/core/terminal-cause.js +9 -0
  101. package/dist/core/tool-policy.d.ts +43 -139
  102. package/dist/core/tool-policy.js +79 -112
  103. package/dist/core/tool-spec.d.ts +1174 -0
  104. package/dist/core/tool-spec.js +1 -0
  105. package/dist/core/types.d.ts +27 -7789
  106. package/dist/core/types.js +2 -76
  107. package/dist/core/warm-resume.d.ts +2 -2
  108. package/dist/core/wiring-manifest.d.ts +6 -3
  109. package/dist/core/workflow-journal-store.js +3 -4
  110. package/dist/engine/harness/agent-harness.d.ts +1 -1
  111. package/dist/index.d.ts +12 -8
  112. package/dist/index.js +9 -6
  113. package/dist/orchestration/builtin-workflows.d.ts +2 -2
  114. package/dist/orchestration/builtin-workflows.js +1 -1
  115. package/dist/orchestration/goal.d.ts +2 -2
  116. package/dist/orchestration/goal.js +8 -7
  117. package/dist/orchestration/run-spec.d.ts +2 -2
  118. package/dist/orchestration/run-spec.js +5 -3
  119. package/dist/orchestration/run-workflow-tool.d.ts +4 -4
  120. package/dist/orchestration/run-workflow-tool.js +4 -4
  121. package/dist/orchestration/workflow-governance.d.ts +4 -4
  122. package/dist/orchestration/workflow-governance.js +4 -2
  123. package/dist/orchestration/workflow-primitives.d.ts +1 -1
  124. package/dist/orchestration/workflow-primitives.js +1 -1
  125. package/dist/orchestration/workflow.d.ts +15 -4
  126. package/dist/orchestration/workflow.js +64 -39
  127. package/dist/prompts/supervisor.d.ts +1 -1
  128. package/dist/prompts/supervisor.js +3 -3
  129. package/dist/scenarios/scenario-registry.d.ts +3 -3
  130. package/dist/scenarios/scenario-registry.js +1 -1
  131. package/dist/scenarios/teacher-quickstart.d.ts +2 -2
  132. package/dist/server/http.d.ts +2 -2
  133. package/dist/stores/file/fs-atomic.d.ts +88 -12
  134. package/dist/stores/file/fs-atomic.js +184 -55
  135. package/dist/stores/file/index.d.ts +1 -0
  136. package/dist/stores/file/index.js +1 -0
  137. package/package.json +3 -1
  138. package/test/export-surface.snapshot.json +82 -22
@@ -1,13 +1,13 @@
1
- import type { TaskResult } from "../core/types.js";
1
+ import type { PausedCause, TaskResult, TerminalCause } from "../core/types.js";
2
2
  /**
3
- * design/45 §11 Q6 — the **durable-suspend hard boundary** (v1). A `status:"suspended"` result is only
4
- * safe to handle at the **top-level `runTask`** boundary: the orchestrators (cascade / verify / teacher /
5
- * team) run their work as *nested* `runTask`s, and none of them can drive a resume from the inside. If a
6
- * nested task suspends on a durable approval gate, the orchestrator must **fail fast** — not silently
7
- * treat it as an ordinary failure (which would escalate/re-run side effects) and not release its pinned
8
- * session (which would orphan the checkpoint). It maps `suspended → failed` + `errorCode:
9
- * "unexpected.suspended"` and **passes the `checkpointToken`/`checkpointGate` straight through** (via the
10
- * spread) so the top-level caller can still resume.
3
+ * design/45 §11 Q6 — the **durable-suspend hard boundary** (v1). A `paused` terminal is only safe to
4
+ * handle at the **top-level `runTask`** boundary: the orchestrators (cascade / verify / teacher / team)
5
+ * run their work as *nested* `runTask`s, and none of them can drive a resume from the inside. If a nested
6
+ * task pauses on a durable gate, the orchestrator must **fail fast** — not silently treat it as an
7
+ * ordinary failure (which would escalate/re-run side effects) and not release its pinned session (which
8
+ * would orphan the checkpoint). It maps `paused → failed` with the code `"unexpected.suspended"` (an
9
+ * approval / resource pause) or `"unexpected.needs_review"` (a review pause) and **carries the pause's own
10
+ * cause on the failed terminal's `nestedPause` seat** so the top-level caller can still resume.
11
11
  *
12
12
  * The structural prevention is upstream — an orchestrator should run nested tasks under a **non-durable**
13
13
  * approval policy (e.g. the verifier already forces `handsReadOnly` + headless auto-deny, DESIGN#5) so a
@@ -15,20 +15,32 @@ import type { TaskResult } from "../core/types.js";
15
15
  * a hard boundary, or a deployment wires durable-suspend into verify and produces silent errors."
16
16
  */
17
17
  export declare const UNEXPECTED_SUSPENDED = "unexpected.suspended";
18
- /** design/80 D-B: the `needs_review` durable-pause family (dry-run `needs_review` AND `plan_review`) hits the
19
- * SAME nested hard boundary as `suspended` — a distinct code so a caller can tell a review pause from an approval. */
18
+ /** design/80 D-B: the review durable-pause family (dry-run `needs_review` AND `plan_review`) hits the SAME
19
+ * nested hard boundary as an approval pause — a distinct code so a caller can tell a review pause from an
20
+ * approval. */
20
21
  export declare const UNEXPECTED_NEEDS_REVIEW = "unexpected.needs_review";
21
22
  /**
22
- * design/80 D-B: the durable-pause hard boundary covers BOTH `suspended` (an approval gate) AND `needs_review`
23
- * (a dry-run review OR a `plan_review` plan-gate). Both persist a checkpoint + PIN their session
24
- * (prepare-task.ts), and a nested orchestrator can't drive their resume — so it must surface the
25
- * `checkpointToken` and NOT release the pinned session (releasing it orphans the checkpoint). The `types.ts`
26
- * TaskStatus doc mandates orchestrators map an unexpected `needs_review` the same hard-boundary way as `suspended`.
23
+ * design/80 D-B: the durable-pause hard boundary covers EVERY `paused` cause — an approval gate, a resource
24
+ * slice, a dry-run review, a `plan_review` plan-gate. All of them persist a checkpoint + PIN their session,
25
+ * and a nested orchestrator can't drive their resume — so it must surface the pause and NOT release the
26
+ * pinned session (releasing it orphans the checkpoint). A type guard, so the caller reads the token/gate
27
+ * off the narrowed cause.
27
28
  */
28
- export declare function isDurablePause(status: TaskResult["status"]): boolean;
29
+ export declare function isDurablePause(terminal: TerminalCause): terminal is PausedCause;
30
+ /** A result whose terminal is known to be a pause — what {@link isDurablePause} narrows a result's
31
+ * `terminal` to, spelled as a type so an orchestrator can HOLD a paused result without re-checking. */
32
+ export type PausedResult = TaskResult & {
33
+ terminal: PausedCause;
34
+ };
29
35
  /**
30
- * If `result` durably paused (suspended OR needs_review), return it mapped to a `failed` result carrying the
31
- * checkpoint token; otherwise return it unchanged. The orchestrator should also **stop** (not escalate/re-run)
36
+ * Which family a pause belongs to its gate kind's registry row: `"suspended"` (an approval gate, a
37
+ * resource slice, a platform park) or `"needs_review"` (a dry-run or plan review). The same row the
38
+ * status word is projected from; read here by the orchestrators that name the family in their own faces.
39
+ */
40
+ export declare function pauseFamily(t: PausedCause): "suspended" | "needs_review";
41
+ /**
42
+ * If `result` durably paused, return it mapped to a `failed` result whose terminal carries the pause on
43
+ * `nestedPause`; otherwise return it unchanged. The orchestrator should also **stop** (not escalate/re-run)
32
44
  * and **not release** the session when this maps a pause — the checkpoint still references that session.
33
45
  */
34
46
  export declare function mapNestedSuspend(result: TaskResult): TaskResult;
@@ -1,18 +1,24 @@
1
+ import { PAUSE_REGISTRY } from "../core/pause-registry.js";
1
2
  export const UNEXPECTED_SUSPENDED = "unexpected.suspended";
2
3
  export const UNEXPECTED_NEEDS_REVIEW = "unexpected.needs_review";
3
- export function isDurablePause(status) {
4
- return status === "suspended" || status === "needs_review";
4
+ export function isDurablePause(terminal) {
5
+ return terminal.kind === "paused";
6
+ }
7
+ export function pauseFamily(t) {
8
+ return PAUSE_REGISTRY[t.gate.kind].taskStatus;
5
9
  }
6
10
  export function mapNestedSuspend(result) {
7
- if (!isDurablePause(result.status)) {
11
+ const t = result.terminal;
12
+ if (!isDurablePause(t)) {
8
13
  return result;
9
14
  }
15
+ const word = pauseFamily(t);
16
+ const code = word === "needs_review" ? UNEXPECTED_NEEDS_REVIEW : UNEXPECTED_SUSPENDED;
17
+ const message = `a nested task durably paused ("${word}") on a HITL gate; durable suspend/resume and plan/` +
18
+ "dry-run review are only supported at the top-level runTask (design/45 v1 / design/80). Resume it via " +
19
+ "the pause's token, or run the orchestrated task under a non-durable policy so it auto-adjudicates instead of pausing.";
10
20
  return {
11
21
  ...result,
12
- status: "failed",
13
- errorCode: result.status === "needs_review" ? UNEXPECTED_NEEDS_REVIEW : UNEXPECTED_SUSPENDED,
14
- errorMessage: `a nested task durably paused ("${result.status}") on a HITL gate; durable suspend/resume and plan/` +
15
- "dry-run review are only supported at the top-level runTask (design/45 v1 / design/80). Resume it via " +
16
- "checkpointToken, or run the orchestrated task under a non-durable policy so it auto-adjudicates instead of pausing.",
22
+ terminal: { kind: "failed", code, message, nestedPause: t },
17
23
  };
18
24
  }
@@ -1,4 +1,4 @@
1
- import type { Runner } from "../core/runner/runtask.js";
1
+ import type { RunnerSelfSeat } from "../core/runner/contracts.js";
2
2
  import { type StrategyOrigin, type StrategyStore, type StrategyStoreIncident } from "../core/strategy-store.js";
3
3
  import type { ModelRef, TaskResult, TaskSpec } from "../core/types.js";
4
4
  /**
@@ -156,4 +156,4 @@ export declare function parseTeacherAdvice(text: string): TeacherAdvice;
156
156
  * completed output (catches "passes but semantically wrong"). Correction-then-takeover, with hard
157
157
  * cost guards (escalation cap, teacher-spend ratio, bounded teacher turns).
158
158
  */
159
- export declare function runWithTeacher(runner: Runner, studentSpec: TaskSpec, teacher?: TeacherConfig): Promise<TeacherRunResult>;
159
+ export declare function runWithTeacher(runner: RunnerSelfSeat, studentSpec: TaskSpec, teacher?: TeacherConfig): Promise<TeacherRunResult>;
@@ -119,7 +119,7 @@ export async function runWithTeacher(runner, studentSpec, teacher = {}) {
119
119
  }
120
120
  }
121
121
  const result = await runTeacherCore(runner, spec, teacher);
122
- if (store && scope && result.status === "completed" && result.escalations.length > 0) {
122
+ if (store && scope && result.terminal.kind === "completed" && result.escalations.length > 0) {
123
123
  const advice = result.escalations[result.escalations.length - 1].teacher;
124
124
  if (advice.strategy && advice.confidence >= (teacher.minConfidenceToStore ?? 2)) {
125
125
  try {
@@ -271,7 +271,7 @@ async function runTeacherCore(runner, studentSpec, teacher) {
271
271
  ...(studentSpec.restoreGatedTools !== undefined
272
272
  ? { restoreGatedTools: Array.isArray(studentSpec.restoreGatedTools) ? [...studentSpec.restoreGatedTools] : studentSpec.restoreGatedTools }
273
273
  : {}),
274
- ...(studentSpec.checkpointStore === null ? { checkpointStore: null } : {}),
274
+ ...(studentSpec.checkpointStore === "disabled" ? { checkpointStore: "disabled" } : {}),
275
275
  };
276
276
  const helperBase = () => teacher.helperModel
277
277
  ? { ...inheritedDeploymentConfig, model: teacher.helperModel }
@@ -407,9 +407,8 @@ async function runTeacherCore(runner, studentSpec, teacher) {
407
407
  const syntheticAborted = (sid) => ({
408
408
  taskId: sid ?? "aborted",
409
409
  sessionId: sid ?? "aborted",
410
- status: "failed",
410
+ terminal: { kind: "failed", message: "student run did not settle promptly after stuck-abort" },
411
411
  result: "",
412
- errorMessage: "student run did not settle promptly after stuck-abort",
413
412
  stats: { turns: 0, tokens: 0 },
414
413
  });
415
414
  const abortFallback = (sid) => {
@@ -451,7 +450,7 @@ async function runTeacherCore(runner, studentSpec, teacher) {
451
450
  toolLog.length = 0;
452
451
  const { result, stuck, recent } = await runStudent();
453
452
  sessionId = result.sessionId;
454
- if (isDurablePause(result.status)) {
453
+ if (isDurablePause(result.terminal)) {
455
454
  return finalize(mapNestedSuspend(result));
456
455
  }
457
456
  let trigger;
@@ -459,10 +458,10 @@ async function runTeacherCore(runner, studentSpec, teacher) {
459
458
  if (stuck) {
460
459
  trigger = "stuck";
461
460
  }
462
- else if (result.status === "blocked") {
461
+ else if (result.terminal.kind === "blocked") {
463
462
  trigger = "blocked";
464
463
  }
465
- else if (result.status === "failed") {
464
+ else if (result.terminal.kind === "failed") {
466
465
  trigger = "failed";
467
466
  }
468
467
  else if (!verifyOutput) {
@@ -482,7 +481,8 @@ async function runTeacherCore(runner, studentSpec, teacher) {
482
481
  return finalize(result);
483
482
  }
484
483
  if (trigger === "failed" &&
485
- /truncat|max[ _-]?tokens?|maximum tokens?|token (limit|budget)|context (length|window)|no answer text|(consumed|exceeded).{0,12}budget/i.test(result.errorMessage ?? "")) {
484
+ result.terminal.kind === "failed" &&
485
+ /truncat|max[ _-]?tokens?|maximum tokens?|token (limit|budget)|context (length|window)|no answer text|(consumed|exceeded).{0,12}budget/i.test(result.terminal.message ?? "")) {
486
486
  return finalize(result);
487
487
  }
488
488
  if (escalations.length >= maxEscalations) {
@@ -528,7 +528,7 @@ async function runTeacherCore(runner, studentSpec, teacher) {
528
528
  signal: extSignal,
529
529
  });
530
530
  addTeacher(r);
531
- if (isDurablePause(r.status)) {
531
+ if (isDurablePause(r.terminal)) {
532
532
  return finalize(mapNestedSuspend(r));
533
533
  }
534
534
  return finalize(r);
@@ -1,5 +1,6 @@
1
- import type { Runner } from "../core/runner/runtask.js";
2
- import type { McpServerSpec, ModelRef, ModelRole, TaskResult, TaskSpec, ToolSpec } from "../core/types.js";
1
+ import type { RunnerSelfSeat } from "../core/runner/contracts.js";
2
+ import type { McpServerSpec, ModelRef, ModelRole, TaskSpec, ToolSpec } from "../core/types.js";
3
+ import type { CheckpointGate, CheckpointToken } from "../core/checkpoint-store.js";
3
4
  export interface TeamMember {
4
5
  /** The member's role / specialty, e.g. "安全评审" or "performance". */
5
6
  role: string;
@@ -58,7 +59,7 @@ export type TeamEvent = {
58
59
  type: "done";
59
60
  };
60
61
  export interface TeamDiscussionOptions {
61
- runner: Runner;
62
+ runner: RunnerSelfSeat;
62
63
  /** Default model for members / synthesizer without their own. If omitted, members resolve the
63
64
  * `team` role and the synthesizer the `synthesize` role (both fall back to `default`). */
64
65
  model?: ModelRef;
@@ -206,8 +207,8 @@ export interface TeamResult {
206
207
  * the discussion STOPS (no synthesis on a half-done team) and surfaces the resume capability so the caller
207
208
  * can resume the paused member via the token, then re-run. `conclusionValid` is false in this case. */
208
209
  durablePause?: boolean;
209
- checkpointToken?: TaskResult["checkpointToken"];
210
- checkpointGate?: TaskResult["checkpointGate"];
210
+ checkpointToken?: CheckpointToken;
211
+ checkpointGate?: CheckpointGate;
211
212
  }
212
213
  /**
213
214
  * Run a multi-round discussion across several role-specialized agents, then synthesize a conclusion.
@@ -1,5 +1,6 @@
1
1
  import { resolveToolSubset } from "./tool-filter.js";
2
2
  import { mapNestedSuspend, isDurablePause } from "./suspend-guard.js";
3
+ import { terminalProjection } from "../core/runner/terminal-projection.js";
3
4
  import { sanitizeUntrustedText } from "../core/untrusted-text.js";
4
5
  const DISCUSSION_WRAPPER_TAGS = ["statement", "discussion", "discussion-final", "earlier-discussion-summary", "previous-summary", "topic"];
5
6
  const RETIRED_WRAPPER_TAGS = ["team-discussion", "team-discussion-final"];
@@ -87,7 +88,7 @@ export async function runTeamDiscussion(opts) {
87
88
  return {
88
89
  status: "failed",
89
90
  result: conclusion,
90
- errorCode: p.errorCode,
91
+ errorCode: terminalProjection(mapNestedSuspend(p).terminal).errorCode,
91
92
  conclusion,
92
93
  conclusionValid: false,
93
94
  transcript,
@@ -96,8 +97,8 @@ export async function runTeamDiscussion(opts) {
96
97
  failures,
97
98
  transcriptCompactions,
98
99
  durablePause: true,
99
- checkpointToken: p.checkpointToken,
100
- checkpointGate: p.checkpointGate,
100
+ checkpointToken: p.terminal.token,
101
+ checkpointGate: p.terminal.gate,
101
102
  };
102
103
  };
103
104
  const runTurn = async (objective, systemPrompt, modelRef, role, tools, mcp) => {
@@ -122,16 +123,17 @@ export async function runTeamDiscussion(opts) {
122
123
  tokens += res.stats.tokens + (res.stats.nested?.tokens ?? 0);
123
124
  turns += res.stats.turns + (res.stats.nested?.turns ?? 0);
124
125
  costMicroUsd += (res.stats.costMicroUsd ?? 0) + (res.stats.nested?.costMicroUsd ?? 0);
125
- if (isDurablePause(res.status)) {
126
- const m = mapNestedSuspend(res);
127
- pendingPause = m;
126
+ if (isDurablePause(res.terminal)) {
127
+ pendingPause = { ...res, terminal: res.terminal };
128
+ const m = terminalProjection(mapNestedSuspend(res).terminal);
128
129
  return { text: `[unavailable: ${m.status}/${m.errorCode}]`, ok: false };
129
130
  }
130
131
  await opts.runner.sessions.release(res.sessionId).catch(() => undefined);
131
- if (res.status === "completed" && res.result.trim()) {
132
+ if (res.terminal.kind === "completed" && res.result.trim()) {
132
133
  return { text: res.result, ok: true };
133
134
  }
134
- last = `[unavailable: ${res.status}${res.errorCode ? `/${res.errorCode}` : ""}${res.errorMessage ? ` — ${res.errorMessage}` : ""}]`;
135
+ const face = terminalProjection(res.terminal);
136
+ last = `[unavailable: ${face.status}${face.errorCode ? `/${face.errorCode}` : ""}${face.errorMessage ? ` — ${face.errorMessage}` : ""}]`;
135
137
  }
136
138
  return { text: last || "[unavailable]", ok: false };
137
139
  };
@@ -1,8 +1,7 @@
1
1
  import { Type, type Static } from "typebox";
2
- import type { Runner, ResumeTaskConfig } from "../core/runner/runtask.js";
3
- import type { RunInternals } from "../core/runner/prepare-task.js";
2
+ import type { ResumeTaskConfig, RunInternals, RunnerSelfSeat } from "../core/runner/contracts.js";
4
3
  import type { CheckpointGate, CheckpointToken, ResumeOutcome } from "../core/checkpoint-store.js";
5
- import type { ModelRef, TaskResult, TaskSpec, ToolSpec } from "../core/types.js";
4
+ import type { ModelRef, TaskResult, TaskSpec, TaskStatus, ToolSpec } from "../core/types.js";
6
5
  /**
7
6
  * Verification gate (developer mode, design/28 §4). An **independent falsification-style verifier** runs after
8
7
  * an implementation task and tries to BREAK it — read-only, evidence-required, returning a structured
@@ -131,7 +130,7 @@ export interface VerifyConfig {
131
130
  */
132
131
  onVerifierPause?: (info: {
133
132
  round: number;
134
- status: TaskResult["status"];
133
+ status: TaskStatus;
135
134
  sessionId: string;
136
135
  checkpointToken?: CheckpointToken;
137
136
  checkpointGate?: CheckpointGate;
@@ -200,7 +199,7 @@ export interface VerificationOutcome {
200
199
  */
201
200
  verifierPause?: {
202
201
  /** The verifier run's terminal pause status (`"suspended"` or `"needs_review"`). */
203
- status: TaskResult["status"];
202
+ status: TaskStatus;
204
203
  /** The paused verifier's own session id (independent of the implementation's). */
205
204
  sessionId: string;
206
205
  /** The resume capability for the verifier's checkpoint. Undefined only if the pause carried none. */
@@ -242,7 +241,7 @@ export interface VerificationResult extends TaskResult {
242
241
  * override is given, the verifier uses {@link STATIC_VERIFICATION_PROMPT} (read the diff/results; don't try
243
242
  * to execute — that was L2's job), avoiding the "PARTIAL on everything" failure in a read-only sandbox.
244
243
  */
245
- export declare function verifyCompleted(runner: Runner, result: TaskResult, specBase: ResumeTaskConfig, objective: string, config: VerifyConfig,
244
+ export declare function verifyCompleted(runner: RunnerSelfSeat, result: TaskResult, specBase: ResumeTaskConfig, objective: string, config: VerifyConfig,
246
245
  /**
247
246
  * The TRUSTED, run-scoped internals channel — the same object `Runner.runTask` takes as its second
248
247
  * argument — forwarded to EVERY leg this gate drives under the composition forward contract (identity,
@@ -303,7 +302,7 @@ internals?: RunInternals): Promise<VerificationResult>;
303
302
  * failed-with-token (verdict `unverified`); the caller approves and calls {@link resumeWithVerification}
304
303
  * to resume AND verify (design/51 P1-b: the durable + HITL + verify integration).
305
304
  */
306
- export declare function runWithVerification(runner: Runner, implSpec: TaskSpec, config?: VerifyConfig,
305
+ export declare function runWithVerification(runner: RunnerSelfSeat, implSpec: TaskSpec, config?: VerifyConfig,
307
306
  /** The trusted run-scoped internals channel, forwarded UNCHANGED to every leg this entry drives —
308
307
  * the implementation run, then (on completion) the verifier + fix legs of the gate. Full contract,
309
308
  * including the attribution and forwarded-whole rules, on {@link verifyCompleted}. Absent ⇒ every
@@ -324,7 +323,7 @@ internals?: RunInternals): Promise<VerificationResult>;
324
323
  * after approval, so a pre-suspend diff would grade stale code. The verifier already verifies the current working
325
324
  * tree (verifierObjective), so dropping the stale diff closes the timing window.
326
325
  */
327
- export declare function resumeWithVerification(runner: Runner, token: CheckpointToken, outcome: ResumeOutcome, taskConfig: ResumeTaskConfig, objective: string, config?: VerifyConfig,
326
+ export declare function resumeWithVerification(runner: RunnerSelfSeat, token: CheckpointToken, outcome: ResumeOutcome, taskConfig: ResumeTaskConfig, objective: string, config?: VerifyConfig,
328
327
  /** The trusted run-scoped internals channel, forwarded UNCHANGED to every leg this entry drives —
329
328
  * `Runner.resume`'s own internals argument for the RESUMED implementation leg, then (on completion)
330
329
  * the verifier + fix legs of the gate. Note that this seat carries the resume-path duty of the same
@@ -344,7 +343,7 @@ export interface DeveloperTaskConfig extends VerifyConfig {
344
343
  * Pair with a role map that gives a strong implementation model and cheaper helper/verifier models for
345
344
  * "auto model selection" (design/28 §3.2): `roles: { default: strong, subagent: cheap, verifier: strong }`.
346
345
  */
347
- export declare function runDeveloperTask(runner: Runner, spec: TaskSpec, config?: DeveloperTaskConfig,
346
+ export declare function runDeveloperTask(runner: RunnerSelfSeat, spec: TaskSpec, config?: DeveloperTaskConfig,
348
347
  /** The trusted run-scoped internals channel, forwarded UNCHANGED to every leg this entry drives — in
349
348
  * BOTH arms: the lone implementation run when `verify: false`, and the whole gate otherwise. The two
350
349
  * arms differ in ONE respect: the gate's door refusal of `isolation` (see {@link verifyCompleted})
@@ -1,7 +1,7 @@
1
1
  import { Type } from "typebox";
2
2
  import { CODE_AGENT_PROMPT } from "../prompts/default.js";
3
3
  import { releaseSession } from "./session-util.js";
4
- import { mapNestedSuspend, isDurablePause } from "./suspend-guard.js";
4
+ import { mapNestedSuspend, isDurablePause, pauseFamily } from "./suspend-guard.js";
5
5
  import { delimitUntrusted, sanitizeUntrustedText } from "../core/untrusted-text.js";
6
6
  import { createSafeNotifier } from "../core/safe-notify.js";
7
7
  import { createCumulativeStatsTracker } from "./cumulative-stats.js";
@@ -102,7 +102,7 @@ function verifierObjective(originalObjective, impl, evidence) {
102
102
  }
103
103
  export async function verifyCompleted(runner, result, specBase, objective, config, internals) {
104
104
  refuseUnhonorableInternals(internals, "door");
105
- if (result.status !== "completed") {
105
+ if (result.terminal.kind !== "completed") {
106
106
  return { ...result, verification: { verdict: "unverified", unverifiedReason: "impl_incomplete", rounds: 0, findings: [] } };
107
107
  }
108
108
  const maxRounds = Number.isFinite(config.maxRounds) ? Math.max(1, Math.floor(config.maxRounds)) : 2;
@@ -168,10 +168,10 @@ export async function verifyCompleted(runner, result, specBase, objective, confi
168
168
  }, legInternals());
169
169
  try {
170
170
  const cost = (v.stats.costMicroUsd ?? 0) + (v.stats.nested?.costMicroUsd ?? 0);
171
- return { verdict: v.structuredOutput, cost, ...(isDurablePause(v.status) ? { paused: v } : {}) };
171
+ return { verdict: v.structuredOutput, cost, ...(isDurablePause(v.terminal) ? { paused: { ...v, terminal: v.terminal } } : {}) };
172
172
  }
173
173
  finally {
174
- if (!isDurablePause(v.status))
174
+ if (!isDurablePause(v.terminal))
175
175
  await releaseSession(runner, v.sessionId);
176
176
  }
177
177
  };
@@ -202,18 +202,18 @@ export async function verifyCompleted(runner, result, specBase, objective, confi
202
202
  findings: [],
203
203
  verifierCost: verifierSpend,
204
204
  verifierPause: {
205
- status: v.paused.status,
206
- sessionId: v.paused.sessionId,
207
- ...(v.paused.checkpointToken ? { checkpointToken: v.paused.checkpointToken } : {}),
208
- ...(v.paused.checkpointGate ? { checkpointGate: v.paused.checkpointGate } : {}),
205
+ status: pauseFamily(paused.terminal),
206
+ sessionId: paused.sessionId,
207
+ checkpointToken: paused.terminal.token,
208
+ checkpointGate: paused.terminal.gate,
209
209
  },
210
210
  };
211
211
  notifier.notify(() => config.onVerifierPause?.({
212
212
  round,
213
- status: paused.status,
213
+ status: pauseFamily(paused.terminal),
214
214
  sessionId: paused.sessionId,
215
- ...(paused.checkpointToken ? { checkpointToken: paused.checkpointToken } : {}),
216
- ...(paused.checkpointGate ? { checkpointGate: paused.checkpointGate } : {}),
215
+ checkpointToken: paused.terminal.token,
216
+ checkpointGate: paused.terminal.gate,
217
217
  }), VERIFY_ON_VERIFIER_PAUSE_SITE);
218
218
  notifier.notify(() => config.onRound?.({ round, verdict: outcome.verdict, findings: outcome.findings }), VERIFY_ON_ROUND_SITE);
219
219
  break;
@@ -248,10 +248,10 @@ export async function verifyCompleted(runner, result, specBase, objective, confi
248
248
  }, legInternals());
249
249
  spend += (current.stats.costMicroUsd ?? 0) + (current.stats.nested?.costMicroUsd ?? 0);
250
250
  implAccount.add(current.stats);
251
- if (isDurablePause(current.status)) {
251
+ if (isDurablePause(current.terminal)) {
252
252
  return { ...mapNestedSuspend(carryFrozenPosture(withImplAccount(current))), verification: outcome };
253
253
  }
254
- if (current.status !== "completed")
254
+ if (current.terminal.kind !== "completed")
255
255
  break;
256
256
  }
257
257
  return { ...carryFrozenPosture(withImplAccount(current)), verification: outcome };
@@ -259,8 +259,8 @@ export async function verifyCompleted(runner, result, specBase, objective, confi
259
259
  export async function runWithVerification(runner, implSpec, config = {}, internals) {
260
260
  refuseUnhonorableInternals(internals, "door");
261
261
  const result = await runner.runTask(implSpec, internals);
262
- if (isDurablePause(result.status)) {
263
- return { ...mapNestedSuspend(result), verification: { verdict: "unverified", unverifiedReason: result.status === "needs_review" ? "needs_review" : "suspended", rounds: 0, findings: [] } };
262
+ if (isDurablePause(result.terminal)) {
263
+ return { ...mapNestedSuspend(result), verification: { verdict: "unverified", unverifiedReason: pauseFamily(result.terminal), rounds: 0, findings: [] } };
264
264
  }
265
265
  refuseUnhonorableInternals(internals, "before_leg");
266
266
  return verifyCompleted(runner, result, implSpec, implSpec.objective, config, internals);
@@ -268,8 +268,8 @@ export async function runWithVerification(runner, implSpec, config = {}, interna
268
268
  export async function resumeWithVerification(runner, token, outcome, taskConfig, objective, config = {}, internals) {
269
269
  refuseUnhonorableInternals(internals, "door");
270
270
  const result = await runner.resume(token, outcome, taskConfig, internals);
271
- if (isDurablePause(result.status)) {
272
- return { ...mapNestedSuspend(result), verification: { verdict: "unverified", unverifiedReason: result.status === "needs_review" ? "needs_review" : "suspended", rounds: 0, findings: [] } };
271
+ if (isDurablePause(result.terminal)) {
272
+ return { ...mapNestedSuspend(result), verification: { verdict: "unverified", unverifiedReason: pauseFamily(result.terminal), rounds: 0, findings: [] } };
273
273
  }
274
274
  refuseUnhonorableInternals(internals, "before_leg");
275
275
  return verifyCompleted(runner, result, taskConfig, objective, config, internals);
package/dist/core/a2a.js CHANGED
@@ -1,7 +1,8 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import { Type } from "typebox";
3
3
  import { A2A_TASK_STATES } from "./a2a-task-state.js";
4
- import { describeHttpTransportFailure, resolveProtocolHttpHeaders } from "./mcp.js";
4
+ import { resolveProtocolHttpHeaders } from "./mcp.js";
5
+ import { describeHttpTransportFailure } from "./mcp-failure.js";
5
6
  import { findNamespacePrefixCollision, mintNamespacePrefix, mintNamespacedToolName } from "./protocol-naming.js";
6
7
  import { A2A_NAMESPACE } from "./protocol-table.js";
7
8
  import { delimitUntrusted, inlineUntrusted } from "./untrusted-text.js";
@@ -0,0 +1,172 @@
1
+ /**
2
+ * A delegated AGENT's definition and the skills it may mount: `AgentDefinition` (the agent a subagent
3
+ * tool spawns), `SkillManifest` (a skill's on-disk front matter) and `SkillSpec` (the wired form the
4
+ * runner mounts). Layer 0 vocabulary; `types.ts` re-exports the three, so no consumer's import changes.
5
+ */
6
+ import type { ThinkingLevel } from "../internal/harness.js";
7
+ import type { ModelRef } from "./model-seat.js";
8
+ import type { TaskSpec } from "./task-spec.js";
9
+ /**
10
+ * A declarative, reusable named sub-agent (design/38 1B). Hand a set to {@link createSubagentTool} and the
11
+ * model picks one (by `whenToUse`) to delegate to; the chosen definition's config builds the child task.
12
+ *
13
+ * **Deployment-side config, NOT user input** — `systemPrompt`/`allowTools` shape what the sub-agent can do
14
+ * and see, so they must be set by the deployment, never taken from an end-user message.
15
+ */
16
+ export interface AgentDefinition {
17
+ /** Stable identifier + the value the delegation tool's `agent` parameter selects. Must be unique. */
18
+ name: string;
19
+ /** Guidance shown to the model for WHEN to pick this agent (listed in the delegation tool's description). */
20
+ whenToUse?: string;
21
+ /**
22
+ * F4 (CC 2.1.198 parity, `whenToUseLean` — 锚 pretty.js:409174 `rbm` + selection `tIl` :453029
23
+ * `(lean && e.whenToUseLean) || e.whenToUse`): a SHORT variant of {@link whenToUse} preferred on
24
+ * lean-prompt surfaces. sema posture: the engine runs the CC lean-prompt arm wholesale (web.ts
25
+ * lean-card precedent), so roster listings/descriptions prefer this text whenever present;
26
+ * {@link whenToUse} remains the fallback. Optional — a single-text agent behaves exactly as before.
27
+ */
28
+ whenToUseLean?: string;
29
+ /** Tool allowlist over the delegation pool (`["*"]` = all). Replaces (does not merge with) the tool's
30
+ * own `allowTools`/`denyTools` when this agent is selected. F1 (CC parity): unlike the TOOL-level
31
+ * allow/deny (which never filters the nested delegation tool — that stays `maxDepth`-governed), a
32
+ * PER-AGENT allow/deny ALSO governs the nested delegation tool: an agent whose allow/deny excludes
33
+ * the delegation tool's name cannot re-delegate (CC Explore/Plan `disallowedTools` include Agent,
34
+ * pretty.js:409189/486948). */
35
+ allowTools?: string[];
36
+ /** Tool denylist (a deny always wins). See {@link allowTools} for the per-agent delegation-tool note. */
37
+ denyTools?: string[];
38
+ /**
39
+ * F7 (CC 2.1.198 parity, per-agent `skills` — 锚 pretty.js:422126 `e.skills ?? []`, GA/ungated in 198:
40
+ * CC preloads each named skill's prompt content into the child's opening messages, warning on misses).
41
+ * sema shape: full {@link SkillSpec} objects (sema has no ambient skill registry to resolve bare names
42
+ * against) threaded into the chosen child's `TaskSpec.skills` — disclosed in its `<skills>` block +
43
+ * invocable via the `skill` tool. Deployment-trusted, same trust tier as `systemPrompt`.
44
+ */
45
+ skills?: SkillSpec[];
46
+ /**
47
+ * F7 (CC 2.1.198 parity, per-agent `background` — 锚 pretty.js:453611 `H.background===!0` OR-ed into
48
+ * the background predicate, GA/ungated): `true` = this agent ALWAYS launches on the background lane
49
+ * (an omitted `run_in_background` no longer means sync, and the per-call parameter cannot force sync —
50
+ * CC-same OR semantics). Requires the delegation tool's `background` surface; without one the child
51
+ * runs synchronously (CC-same quiet fallback — CC's env kill-switch arm, :453611 `&& !sJt`).
52
+ */
53
+ background?: boolean;
54
+ /**
55
+ * F7 (CC 2.1.198 parity, per-agent `isolation` — 锚 pretty.js:453599 `z = a ?? H.isolation`, GA):
56
+ * default isolation for this agent; an explicit per-call `isolation` argument wins. sema supports
57
+ * `"worktree"` (CC's `"remote"` arm is a claude.ai-session concern, not ported).
58
+ */
59
+ isolation?: "worktree";
60
+ /** Model for this agent. **Omit to INHERIT the caller's current model** (design/38 1B finding 7) — the
61
+ * recommended default; a concrete `ModelRef` pins it instead. */
62
+ model?: ModelRef;
63
+ /** Thinking level for this agent. **Omit to INHERIT the caller's current thinking level** (the model
64
+ * companion above — a mid-run degrade/boost on the parent carries into new children); a concrete
65
+ * level pins it instead. */
66
+ thinking?: ThinkingLevel;
67
+ /** System prompt / persona for this agent. Falls back to the delegation tool's `systemPrompt`. */
68
+ systemPrompt?: string;
69
+ /** Per-agent turn cap. Falls back to the delegation tool's `limits.maxTurns`. Must be POSITIVE: a
70
+ * non-positive/non-finite value is ignored (CLS-A-3) rather than folded in, because downstream an
71
+ * explicit `0` DISABLES the turn cap — the opposite of the tightening this field expresses. */
72
+ maxTurns?: number;
73
+ /** Long-term memory scope for this agent (same shape as {@link TaskSpec.memory}). */
74
+ memory?: TaskSpec["memory"];
75
+ /**
76
+ * This agent's own {@link TaskSpec.memoryPersistenceCapable} declaration — the same three-state
77
+ * statement, made per definition: an agent whose closure tools persist memory declares `true`
78
+ * (suppressing the read-only-memory disclosure no roster inference can clear), one whose roster
79
+ * looks write-capable but reaches no durable store declares `false` (forcing the disclosure).
80
+ * Arbitration with the spawning run's declaration: a parent's explicit `false` is a FLOOR and wins
81
+ * over a definition `true` (the disclosure is about the deployment's storage, which choosing this
82
+ * agent does not change); otherwise a declared definition value wins over the parent's; absent
83
+ * both, the child spec carries no value and the child's own roster inference decides.
84
+ * On the workflow lane (`agent(…, {agentType})`) the definition value fills an ABSENT governed-spec
85
+ * value only — a base/spec declaration of either polarity wins (the lane's spec-pinned-fields rule).
86
+ */
87
+ memoryPersistenceCapable?: boolean;
88
+ /**
89
+ * Observer agents (CC 2.1.206 parity — 逐字锚 docs/CC206-OBSERVER-ANCHORS-2026-07-11.md, schema
90
+ * 面 C @28385708): "Agent type auto-spawned as a background observer whenever this agent runs.
91
+ * The observer receives read-only activity digests and reports via the ObserverReport tool; it
92
+ * never participates in the task." Resolution is warn-and-unobserved on an unknown type and
93
+ * refuses chaining (an observer's own `observer` declaration is ignored). Gated by
94
+ * {@link RuntimeCaps.allowObservers} — EXPLICIT opt-in, default OFF (the sema seat of CC's
95
+ * default-unset env `CLAUDE_CODE_EXPERIMENTAL_OBSERVER_AGENTS` + default-true statsig pair).
96
+ */
97
+ observer?: string;
98
+ /**
99
+ * CC 面 C: "Supplemental postamble appended (after the harness-owned default) to each activity
100
+ * digest sent to the observer. Blank values are ignored."
101
+ */
102
+ observerMessage?: string;
103
+ }
104
+ /**
105
+ * A per-skill runtime capability manifest (design/77 §3 Gate-3). When present, loading the skill
106
+ * SCOPES the rest of the invocation to ONLY these tools/paths via runtime deny-narrowing: a tool call
107
+ * not in `allowTools` (intersected across nested skill frames) is DENIED, and a write whose canonical
108
+ * target falls outside `allowPaths` is DENIED — regardless of how permissive the task policy is.
109
+ *
110
+ * 🔴 **Safe-by-construction:** a manifest can only SUBTRACT capability. The narrowing composes via the
111
+ * `deny > ask > allow` fold (`combinePolicies`) where `deny` short-circuits, so a manifest can never
112
+ * *grant* a tool the task policy withheld — whoever sets a manifest can only restrict further.
113
+ * Worst case (the model never pops the frame) = no narrowing, never widening — still ⊆ task policy.
114
+ *
115
+ * The manifest is CALLER-provided here (same trust level as the rest of `SkillSpec`); deriving it
116
+ * automatically (the auto-promote pipeline) is a separate, deferred concern (design/77 §5).
117
+ */
118
+ export interface SkillManifest {
119
+ /** The ONLY tool names the invocation may call while this skill's frame is active. */
120
+ allowTools: string[];
121
+ /**
122
+ * Optional path allowlist for WRITE tools (`write_file`/`edit_file`): a write whose canonicalized
123
+ * target is not inside one of these (path-segment-aware) prefixes is DENIED. Omit = no path narrowing
124
+ * (only `allowTools` applies). Reads are never path-narrowed (this gate restricts capability, not visibility).
125
+ *
126
+ * 🔴 **Fail-closed (MAJOR-2):** `allowPaths` can only confine tools whose write target is a parseable `path`
127
+ * argument (`write_file`/`edit_file`). While an `allowPaths` constraint is active, any OTHER mutating
128
+ * (effect:write/idempotent) allowlisted tool — notably `bash`, whose command can write anywhere — is DENIED
129
+ * outright, because its target cannot be path-confined. So a manifest that lists `bash` together with
130
+ * `allowPaths` does NOT let bash write/egress anywhere: bash is denied while the path constraint is active.
131
+ * Confine writes to specific paths via `write_file`/`edit_file` only.
132
+ */
133
+ allowPaths?: string[];
134
+ /** Reserved (design/77 §3): whether egress is permitted under this manifest. Carried through; v1 does
135
+ * not act on it beyond the allowTools intersection (egress tools must also be in `allowTools`). */
136
+ egress?: boolean;
137
+ /** Stable lineage id of the artifact this manifest came from — used in deny reasons + as the scope key. */
138
+ lineageId: string;
139
+ }
140
+ /** A skill made available to this task (passed as an object; not loaded from disk). */
141
+ export interface SkillSpec {
142
+ name: string;
143
+ description: string;
144
+ content: string;
145
+ /** Optional Gate-3 runtime capability manifest (design/77 §3) — see {@link SkillManifest}. */
146
+ manifest?: SkillManifest;
147
+ /**
148
+ * Optional attachments bundled with the skill (additive) — the CC bundled-skill shape (a SKILL.md whose
149
+ * body references sibling files by relative path) translated to sema's brain-outside posture, where the
150
+ * ruling is: **core never writes to disk.** CC extracts a bundle and lets the body reference files on
151
+ * the filesystem; sema core has no filesystem authority of its own, so materializing attachments into a
152
+ * working directory (making `Read`-by-path work) is the DEPLOYMENT's half. What core owns is exactly two
153
+ * faces, both bounded:
154
+ * 1. **disclosure** — each attachment's relative `path` is listed on the skill's `<skills>`-block line
155
+ * (metadata only, capped like the rest of the block), so the model knows the material exists before
156
+ * it loads the skill;
157
+ * 2. **delivery** — a `Skill` invoke appends an `## Attachments` section after the body (which is
158
+ * itself delivered untruncated): each file as `### <path>` + its content, under a SEPARATE total
159
+ * cap (`SKILL_ATTACHMENTS_MAX_CHARS`, 50k chars — an attachments-face budget; truncation/omission
160
+ * is always disclosed in-band). Without deployment-side materialization this inline delivery is the
161
+ * attachment's ONLY reachable face — that is the deliberate minimal shape, not an oversight.
162
+ *
163
+ * `path` is a relative, display-oriented identifier (how the body refers to the file); core never
164
+ * resolves it against a filesystem. `content` carries the SAME trust contract as {@link content}: skill
165
+ * material is CALLER-trusted instructions by design (see the skills module note) — a caller forwarding
166
+ * end-user skills is responsible for vetting attachments exactly like the body.
167
+ */
168
+ files?: Array<{
169
+ path: string;
170
+ content: string;
171
+ }>;
172
+ }
@@ -0,0 +1 @@
1
+ export {};