macp-sdk-typescript 0.10.0 → 0.12.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.
Files changed (50) hide show
  1. package/CHANGELOG.md +465 -0
  2. package/README.md +2 -2
  3. package/dist/agent/index.d.ts +1 -1
  4. package/dist/agent/index.js +2 -1
  5. package/dist/agent/participant.d.ts +47 -4
  6. package/dist/agent/participant.js +88 -10
  7. package/dist/agent/strategies.js +23 -7
  8. package/dist/agent/transports.d.ts +19 -1
  9. package/dist/agent/transports.js +108 -66
  10. package/dist/auth.d.ts +15 -4
  11. package/dist/auth.js +23 -7
  12. package/dist/base-session.js +13 -3
  13. package/dist/client.js +58 -15
  14. package/dist/commitment-hash.d.ts +33 -3
  15. package/dist/commitment-hash.js +39 -5
  16. package/dist/decision.d.ts +1 -0
  17. package/dist/decision.js +11 -9
  18. package/dist/envelope.js +9 -1
  19. package/dist/handoff.d.ts +1 -0
  20. package/dist/handoff.js +19 -6
  21. package/dist/index.d.ts +2 -2
  22. package/dist/index.js +18 -7
  23. package/dist/policy.d.ts +54 -7
  24. package/dist/policy.js +143 -19
  25. package/dist/projections/base.d.ts +71 -14
  26. package/dist/projections/base.js +106 -16
  27. package/dist/projections/decision.d.ts +5 -49
  28. package/dist/projections/decision.js +30 -85
  29. package/dist/projections/handoff.d.ts +5 -48
  30. package/dist/projections/handoff.js +44 -93
  31. package/dist/projections/proposal.d.ts +5 -48
  32. package/dist/projections/proposal.js +21 -79
  33. package/dist/projections/quorum.d.ts +5 -49
  34. package/dist/projections/quorum.js +20 -82
  35. package/dist/projections/task.d.ts +31 -51
  36. package/dist/projections/task.js +49 -90
  37. package/dist/proposal.d.ts +1 -0
  38. package/dist/proposal.js +2 -2
  39. package/dist/proto-registry.d.ts +85 -5
  40. package/dist/proto-registry.js +101 -15
  41. package/dist/quorum.d.ts +1 -0
  42. package/dist/quorum.js +4 -4
  43. package/dist/task.d.ts +1 -0
  44. package/dist/task.js +24 -11
  45. package/dist/types.d.ts +14 -7
  46. package/dist/validation.d.ts +14 -0
  47. package/dist/validation.js +39 -5
  48. package/dist/version.d.ts +1 -1
  49. package/dist/version.js +1 -1
  50. package/package.json +6 -5
package/CHANGELOG.md CHANGED
@@ -4,6 +4,471 @@ All notable changes to `macp-sdk-typescript` are documented here. The format
4
4
  follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the
5
5
  project uses [Semantic Versioning](https://semver.org/).
6
6
 
7
+ ## [0.12.0](https://github.com/multiagentcoordinationprotocol/macp-sdk-typescript/compare/v0.11.0...v0.12.0) (2026-09-30)
8
+
9
+
10
+ ### ⚠ BREAKING CHANGES
11
+
12
+ * **auth:** Auth.devAgent sets expectedSender to enforce sender identity client-side ([#133](https://github.com/multiagentcoordinationprotocol/macp-sdk-typescript/issues/133))
13
+
14
+ ### Features
15
+
16
+ * **parity:** add isCanonicalCommitmentHash + anomaly constant exports ([bbde8d2](https://github.com/multiagentcoordinationprotocol/macp-sdk-typescript/commit/bbde8d2f72b6fa2d1dc9024bafa587a21243d051))
17
+ * **projections:** rename TaskCompletionRecord/TaskFailureRecord ([#142](https://github.com/multiagentcoordinationprotocol/macp-sdk-typescript/issues/142)) ([84008e1](https://github.com/multiagentcoordinationprotocol/macp-sdk-typescript/commit/84008e1700304814fe17093ae35c9046aab553ea))
18
+ * **projections:** rename TaskProjection.isComplete to isCompleted ([#141](https://github.com/multiagentcoordinationprotocol/macp-sdk-typescript/issues/141)) ([3239ae6](https://github.com/multiagentcoordinationprotocol/macp-sdk-typescript/commit/3239ae63df50b25064b51bdee93d397af2ed904e))
19
+
20
+
21
+ ### Bug Fixes
22
+
23
+ * **agent:** Participant terminal-on-cancel, teardown on every run() exit, tri-state isStopped, guarded rollback pop ([#113](https://github.com/multiagentcoordinationprotocol/macp-sdk-typescript/issues/113)) ([e1a7de4](https://github.com/multiagentcoordinationprotocol/macp-sdk-typescript/commit/e1a7de486c9ce6d23c249811a98ba99a15814a9b))
24
+ * **agent:** retry a transient NOT_FOUND on subscribe ([#100](https://github.com/multiagentcoordinationprotocol/macp-sdk-typescript/issues/100)) ([#101](https://github.com/multiagentcoordinationprotocol/macp-sdk-typescript/issues/101)) ([ffb0251](https://github.com/multiagentcoordinationprotocol/macp-sdk-typescript/commit/ffb02514aaaeb9351bf465c00e808833536d3568))
25
+ * **auth:** Auth.devAgent sets expectedSender to enforce sender identity client-side ([#133](https://github.com/multiagentcoordinationprotocol/macp-sdk-typescript/issues/133)) ([cc0f344](https://github.com/multiagentcoordinationprotocol/macp-sdk-typescript/commit/cc0f344f2cbb4cf4d59e17fbe4d043cc6f9eebac))
26
+ * **client:** map stream inline errors and registry gRPC statuses to MacpAckError ([#118](https://github.com/multiagentcoordinationprotocol/macp-sdk-typescript/issues/118)) ([28d4c52](https://github.com/multiagentcoordinationprotocol/macp-sdk-typescript/commit/28d4c527c324469848ce90a9199b7318970dd071))
27
+ * **commitment-hash:** supersedes: null now hashes as absent, matching macp-sdk-python ([#114](https://github.com/multiagentcoordinationprotocol/macp-sdk-typescript/issues/114)) ([6e15b9f](https://github.com/multiagentcoordinationprotocol/macp-sdk-typescript/commit/6e15b9fbc2fc61699970ed30276fd2e249152f08))
28
+ * **index:** drop _resetLoggingForTests from public surface; correct pagination/ListRoots docs ([#112](https://github.com/multiagentcoordinationprotocol/macp-sdk-typescript/issues/112)) ([99c2f4a](https://github.com/multiagentcoordinationprotocol/macp-sdk-typescript/commit/99c2f4a67c5200a60f3d7facfbec0ab0dc08eb85))
29
+ * **parity:** re-vendor contract.json for contract_version 1.1.1 ([#129](https://github.com/multiagentcoordinationprotocol/macp-sdk-typescript/issues/129)) ([459e795](https://github.com/multiagentcoordinationprotocol/macp-sdk-typescript/commit/459e7959a5eabaa32008b8750bbf33c972e151a3)), closes [#125](https://github.com/multiagentcoordinationprotocol/macp-sdk-typescript/issues/125)
30
+ * **parity:** re-vendor contract.json for contract_version 1.2.0 ([#136](https://github.com/multiagentcoordinationprotocol/macp-sdk-typescript/issues/136)) ([8ef1ab1](https://github.com/multiagentcoordinationprotocol/macp-sdk-typescript/commit/8ef1ab1861e8e871cf3284ee3e125fceba7e2691)), closes [#135](https://github.com/multiagentcoordinationprotocol/macp-sdk-typescript/issues/135)
31
+ * **projections:** gate TaskComplete/TaskFail phase transitions on task existence ([#120](https://github.com/multiagentcoordinationprotocol/macp-sdk-typescript/issues/120)) ([41f9010](https://github.com/multiagentcoordinationprotocol/macp-sdk-typescript/commit/41f90109df032c8072052232cd4501d2bbda0fed)), closes [#111](https://github.com/multiagentcoordinationprotocol/macp-sdk-typescript/issues/111)
32
+ * **projections:** gate three more phase transitions on entity existence ([#123](https://github.com/multiagentcoordinationprotocol/macp-sdk-typescript/issues/123)) ([866490a](https://github.com/multiagentcoordinationprotocol/macp-sdk-typescript/commit/866490a332353049064596371dd1e05b4598062f)), closes [#119](https://github.com/multiagentcoordinationprotocol/macp-sdk-typescript/issues/119)
33
+ * **projections:** guard QuorumProjection.setBallot on request existence ([#127](https://github.com/multiagentcoordinationprotocol/macp-sdk-typescript/issues/127)) ([e3b75aa](https://github.com/multiagentcoordinationprotocol/macp-sdk-typescript/commit/e3b75aa265e019610cfd7b0d0446a38a93437d01)), closes [#121](https://github.com/multiagentcoordinationprotocol/macp-sdk-typescript/issues/121)
34
+ * **projections:** unfreeze 5 of 6 ProjectionAnomalyKind sites ([#126](https://github.com/multiagentcoordinationprotocol/macp-sdk-typescript/issues/126), [#128](https://github.com/multiagentcoordinationprotocol/macp-sdk-typescript/issues/128)) ([#134](https://github.com/multiagentcoordinationprotocol/macp-sdk-typescript/issues/134)) ([a99668c](https://github.com/multiagentcoordinationprotocol/macp-sdk-typescript/commit/a99668cf8977309c91c0390f5c0967ad80c2786b))
35
+ * **proto-registry:** stop coercing legacy-JSON Contribute value ([#124](https://github.com/multiagentcoordinationprotocol/macp-sdk-typescript/issues/124)) ([#130](https://github.com/multiagentcoordinationprotocol/macp-sdk-typescript/issues/130)) ([02bbe90](https://github.com/multiagentcoordinationprotocol/macp-sdk-typescript/commit/02bbe90c48824e82e0436d315dd0582d057ffec2))
36
+ * **proto-registry:** stop misdecoding canonical Contribute payloads as JSON ([#110](https://github.com/multiagentcoordinationprotocol/macp-sdk-typescript/issues/110)) ([26dddb6](https://github.com/multiagentcoordinationprotocol/macp-sdk-typescript/commit/26dddb61eb7ff163c23ecbd6e96c2abedfab150c))
37
+ * **sessions,agent,auth:** thread auth through start(), default identities, validate strategy output ([#116](https://github.com/multiagentcoordinationprotocol/macp-sdk-typescript/issues/116)) ([4c633e0](https://github.com/multiagentcoordinationprotocol/macp-sdk-typescript/commit/4c633e02f11bc5f6a8995346b9b4cdc0213003c0))
38
+ * **strategies:** exclude REVIEW evaluations from majorityVoter ratio ([#124](https://github.com/multiagentcoordinationprotocol/macp-sdk-typescript/issues/124)) ([#131](https://github.com/multiagentcoordinationprotocol/macp-sdk-typescript/issues/131)) ([b52c7f2](https://github.com/multiagentcoordinationprotocol/macp-sdk-typescript/commit/b52c7f2744bd5841a023461a4009c411a9514b75))
39
+ * **validation:** enforce no-fall-through session-id rule, reject NaN confidence, normalize decision.ts wire case ([#115](https://github.com/multiagentcoordinationprotocol/macp-sdk-typescript/issues/115)) ([df1c6a2](https://github.com/multiagentcoordinationprotocol/macp-sdk-typescript/commit/df1c6a2c966f2f5f88937b3b2fcc2a160edbd987))
40
+ * **validation:** stop rejecting empty/omitted intent, instructions, action, summary ([#124](https://github.com/multiagentcoordinationprotocol/macp-sdk-typescript/issues/124)) ([#132](https://github.com/multiagentcoordinationprotocol/macp-sdk-typescript/issues/132)) ([aa157ee](https://github.com/multiagentcoordinationprotocol/macp-sdk-typescript/commit/aa157eeff8d284c1cc727c7cb0d73a86f41ab35b))
41
+
42
+ ## [0.11.0](https://github.com/multiagentcoordinationprotocol/macp-sdk-typescript/compare/v0.10.0...v0.11.0) (2026-09-22)
43
+
44
+
45
+ ### ⚠ BREAKING CHANGES
46
+
47
+ * **policy:** buildDecisionPolicy's default schemaVersion is now 3 (was 2). A call that omits options.schemaVersion and uses a binding voting algorithm now fails closed on an empty decisive tally instead of silently sealing on zero ballots. Pass { schemaVersion: 1 } or { schemaVersion: 2 } explicitly to keep the old fail-open behavior.
48
+
49
+ ### Features
50
+
51
+ * **policy:** flip Decision-policy schemaVersion default from 2 to 3 ([#97](https://github.com/multiagentcoordinationprotocol/macp-sdk-typescript/issues/97)) ([a82972a](https://github.com/multiagentcoordinationprotocol/macp-sdk-typescript/commit/a82972ae43550f4b953a6bd0e32bfb387fd25806))
52
+
53
+ ## [Unreleased]
54
+
55
+ ### ⚠ BREAKING CHANGES
56
+
57
+ * **policy:** `QuorumThreshold.type` no longer accepts `'weighted'`. The
58
+ identifier was removed from the canonical `quorum-rules.schema.json`
59
+ without ever having defined semantics (no weights vocabulary, no
60
+ electorate rule) and every descriptor that used it was already refused by
61
+ the runtime at `RegisterPolicy` — so this is a compile-time break for
62
+ callers whose descriptors could never register in the first place.
63
+ `buildQuorumPolicy` now also rejects it at runtime (for JS callers or an
64
+ `as` cast), naming the reservation.
65
+ * **policy:** `buildQuorumPolicy(id, desc, {})` (an omitted `threshold`) now
66
+ emits `threshold: { value: 1 }` instead of `value: 0`. A zero approval bar
67
+ is trivially satisfied by any ballot set, so the canonical schema declares
68
+ `value` with `exclusiveMinimum: 0`; `buildQuorumPolicy` now enforces that
69
+ bound for every `threshold.type`, not just `'percentage'`, and rejects a
70
+ non-integer or non-positive `value` with `MacpSessionError`.
71
+ * **policy:** `buildDecisionPolicy` now enforces the canonical
72
+ `decision-rules.schema.json` voting constraints client-side, so calls that
73
+ previously returned an unregistrable descriptor now throw `MacpSessionError`
74
+ instead:
75
+ - `voting.algorithm` must be one of the six canonical values.
76
+ - `voting.threshold` must be `0 < threshold <= 1`.
77
+ - `algorithm: 'majority'` requires `threshold >= 0.5` (inclusive — a `0.3`
78
+ "majority" now throws).
79
+ - `algorithm: 'supermajority'` **at its own default `threshold: 0.5`** now
80
+ throws — this is the change most likely to break an existing idiomatic
81
+ call (`buildDecisionPolicy(id, desc, { voting: { algorithm: 'supermajority' } })`);
82
+ pass an explicit `threshold` above `0.5` (e.g. `0.67`).
83
+ - `algorithm: 'weighted'` with no `weights` now throws.
84
+ - `voting.weights`, if supplied at all, is now validated **unconditionally
85
+ at every algorithm, not only `'weighted'`**: it must be non-empty, and
86
+ every value must be `> 0`. `weights: { a: 0 }` now throws — a weight-0
87
+ participant must be expressed by omission from the map.
88
+ * **policy:** all five `build*Policy` functions (`buildDecisionPolicy`,
89
+ `buildQuorumPolicy`, `buildProposalPolicy`, `buildTaskPolicy`,
90
+ `buildHandoffPolicy`) now throw `MacpSessionError` when
91
+ `commitment.authority` is `'designated_role'` and `designatedRoles` is
92
+ omitted or `[]`. An authority rule that names no one is unsatisfiable, and
93
+ every canonical rule schema already refused this combination at
94
+ `RegisterPolicy` — this call previously returned a descriptor the runtime
95
+ would reject.
96
+ * **policy:** `buildDecisionPolicy`'s default `schemaVersion` is now `3`
97
+ (was `2`), decided jointly with `macp-sdk-python`'s identical default flip
98
+ (issue #85, see `macp-sdk-python`#65). Any `buildDecisionPolicy` call that
99
+ omits `options.schemaVersion` and uses a binding voting algorithm
100
+ (`majority`, `unanimous`, etc.) now fails closed on an empty decisive
101
+ tally instead of silently sealing on zero ballots. Not retroactive — a
102
+ stored policy always evaluates under its own recorded `schemaVersion`
103
+ forever (RFC-MACP-0012 §8); this only changes what new callers who don't
104
+ pass `schemaVersion` explicitly get going forward. Pass
105
+ `{ schemaVersion: 1 }` or `{ schemaVersion: 2 }` explicitly to keep the
106
+ old fail-open behavior.
107
+ * **auth:** `Auth.devAgent(agentId)` now also sets `expectedSender: agentId`
108
+ on the returned `AuthConfig` (issue #124 item 4). The runtime's dev
109
+ fallback already authenticates the gRPC connection as the bearer value
110
+ (`agentId`) and unconditionally refuses a mismatched `sender` for dev
111
+ credentials (`macp-runtime/src/server.rs:229-231`, no insecure/dev
112
+ exemption) — this SDK now enforces the same guarantee client-side instead
113
+ of surfacing the runtime's rejection late and confusingly. A bootstrap or
114
+ caller whose `agent_id`/credential diverges from the `sender`/
115
+ `participant_id` it actually sends now fails on the first typed
116
+ handler-driven action (e.g. `acceptTask`, `vote`) with
117
+ `MacpIdentityMismatchError`, instead of only failing server-side. The
118
+ initiator's `SessionStart`/kickoff path is unaffected (it passes no
119
+ explicit `sender`), as is the generic `actions.send` escape hatch (which
120
+ still only fails server-side). A caller that genuinely needs one
121
+ credential reused across multiple senders should use
122
+ `Auth.bearer(token, senderHint)` (the legacy two-arg form) instead.
123
+
124
+ ### Features
125
+
126
+ * **policy:** `buildDecisionPolicy` accepts a new fourth `options` argument,
127
+ `{ schemaVersion?: 1 | 2 | 3 }`, to select a Decision policy's
128
+ RFC-MACP-0012 empty-tally semantics — `3` (the default) fails closed
129
+ (§4.1's "vacuous participation floor"); `1`/`2` fail open. An out-of-range
130
+ value throws `MacpSessionError`.
131
+
132
+ ### Bug Fixes
133
+
134
+ * **policy:** `buildQuorumPolicy`, `buildProposalPolicy`, `buildTaskPolicy`,
135
+ and `buildHandoffPolicy` no longer emit `require_vote_quorum` into their
136
+ `commitment` block. Only `decision-rules.schema.json`'s commitment object
137
+ declares that key; the other four canonical rule schemas close with
138
+ `additionalProperties: false` and do not, so the previous output was
139
+ spec-nonconformant with no CI signal (today's runtime does not enforce
140
+ `additionalProperties`, but a stricter or future validator would reject
141
+ it). Passing `commitment.requireVoteQuorum` to these four builders is now
142
+ silently dropped, matching its existing "ignored for other modes"
143
+ documentation, rather than leaking an unrecognized key onto the wire.
144
+ `buildDecisionPolicy` is unaffected.
145
+ * **projections:** all six `applyEnvelope` entry points now roll back
146
+ `transcript` and the `message_id` redelivery-dedup set on a failed payload
147
+ decode, instead of leaving a partial application in place. Previously, a
148
+ failed decode still marked the envelope's `message_id` "seen," so a
149
+ legitimate retry with a corrected payload was silently absorbed as a
150
+ redelivery and the envelope's effect was lost forever.
151
+
152
+ ### Added
153
+
154
+ - **All five mode sessions' `start()` now accept a per-call
155
+ `auth?: AuthConfig`** (issue #108, part 2 of 2 — 4B), matching every other
156
+ action method on these classes. Previously `start()` alone ignored a
157
+ per-call `auth` on `DecisionSession`/`ProposalSession`/`TaskSession`/
158
+ `HandoffSession`/`QuorumSession`, resolving the sender only against the
159
+ session/client-level credential — so a caller passing a conflicting
160
+ per-call `auth.expectedSender` never got the identity-mismatch guard
161
+ (RFC-MACP-0004 §4) that every other method already enforced.
162
+ `BaseSession.start()` already accepted and threaded a per-call `auth`
163
+ before this change; see the `BaseSession.start()` validation entry below
164
+ for what actually changed there in 4B.
165
+ - **`ProjectionAnomalyKind` gains two new, cross-SDK-agreed kinds:
166
+ `duplicate_task_accept` and `settled_handoff`** (issue #126/#128;
167
+ `macp-sdk-python` landed its half in PR #95). `TaskProjection` now records
168
+ a `duplicate_task_accept` anomaly when a `TaskAccept` for a task it has a
169
+ `TaskRequest` on file for is discarded because another task already holds
170
+ the session's one assignee slot (RFC-MACP-0009 §5 rules 3/3a).
171
+ `HandoffProjection` now records a `settled_handoff` anomaly when a
172
+ `HandoffAccept`/`HandoffDecline` targets a `handoff_id` that already
173
+ settled as accepted or declined (RFC-MACP-0010 §5 rule 4). Both new
174
+ constants (`ANOMALY_DUPLICATE_TASK_ACCEPT`/`ANOMALY_SETTLED_HANDOFF`)
175
+ export from the package root alongside the existing pair. A `TaskAccept`/
176
+ `HandoffAccept`/`HandoffDecline` for an *unknown* `task_id`/`handoff_id`
177
+ still records no anomaly — that determination (not caller misuse) is
178
+ unchanged by this release. This is purely additive observability: no
179
+ change to `phase`, `tasks`, or `handoffs` state itself at any of these
180
+ sites. `DecisionProjection`'s late-`Vote`-after-`Commitment` site remains
181
+ the one still-open, undecided case (per #128's own explicit scoping) —
182
+ unaffected by this change.
183
+
184
+ ### Changed
185
+
186
+ - **BREAKING (test-only surface): `_resetLoggingForTests` is no longer part
187
+ of the package's public export surface.** (issue #109) It was reachable
188
+ only via `src/index.ts`'s `export * from './logging'` wildcard; that line
189
+ is now a named re-export listing `logger`, `configureLogging`, `LogLevel`,
190
+ and `LogSink`. Because `package.json`'s `exports` map exposes only `"."`,
191
+ this makes the helper unreachable from the published package entirely (no
192
+ submodule fallback) — accepted, since it is test-only by name and by
193
+ purpose, and `configureLogging` already covers every legitimate caller
194
+ need.
195
+ - **`validateSessionId` now enforces the runtime's actual no-fall-through
196
+ rule** (issue #108, part 1 of 2 — 4A). A UUID-**shaped** string (36 chars,
197
+ hyphens at 8-13-18-23, hex-only) is now validated strictly as a lowercase
198
+ v4/v7 UUID and never reinterpreted as base64url. An uppercase UUID, a
199
+ lowercase v1 UUID, the nil UUID, or a UUID with a bad variant nibble now
200
+ throws `MacpSessionError` client-side — all were already rejected by the
201
+ runtime, so nothing that previously *worked* stops working, but a caller
202
+ relying on the old, looser client-side check now gets the rejection
203
+ earlier. Ports `macp-sdk-python`'s two-regex no-fall-through structure.
204
+ - **`DecisionSession.evaluate`/`raiseObjection`/`vote` now put the
205
+ normalized (uppercased/lowercased) value on the wire, not the caller's
206
+ original case** (issue #108, part 1 of 2 — 4A).
207
+ `validateVote`/`validateRecommendation`/`validateSeverity` each return a
208
+ normalized string; the three call sites in `decision.ts` previously
209
+ discarded it. A consumer that string-compares an un-normalized value
210
+ (e.g. `'approve'` instead of `'APPROVE'`) sees a different value on the
211
+ wire; every in-repo projection already normalizes case itself, so no
212
+ projection assertion is affected by this change.
213
+ - **An empty/missing `bearerToken` now throws `MacpSdkError`, not a bare
214
+ `Error`** (issue #108, part 2 of 2 — 4B). `validateAuth` (and therefore
215
+ `metadataFromAuth`, reached from every authenticated RPC) previously threw
216
+ a plain `Error('bearerToken is required')`, which is not `instanceof
217
+ MacpSdkError` — escaping the documented `catch (e) { if (e instanceof
218
+ MacpSdkError) ... }` pattern from both SDKs' error-handling docs. Message
219
+ text unchanged.
220
+ - **`BaseSession.start()` now runs the same full `validateSessionStart`
221
+ check as the five built-in mode sessions, not a partial check** (issue
222
+ #108, part 2 of 2 — 4B). Previously this documented ext-mode extension
223
+ point validated only participant count and `maxSuspendMs`, so an empty
224
+ `intent`, an empty/duplicate `participants` array, or an out-of-range
225
+ `ttlMs` reached the wire unvalidated from a custom mode built on
226
+ `BaseSession` — the weakest-validated mode in the SDK. All five now
227
+ reject client-side, matching every built-in mode session.
228
+ - **BREAKING (narrows an exception type): `send()` now throws `MacpAckError`,
229
+ not `MacpTransportError`, for a gRPC `ALREADY_EXISTS`/`FAILED_PRECONDITION`;
230
+ same for `registerExtMode`/`unregisterExtMode`/`promoteMode`/
231
+ `registerPolicy`/`unregisterPolicy` on `FAILED_PRECONDITION`** (issue #107).
232
+ These are application-level rejections the runtime couldn't express as a
233
+ normal Ack, not transport faults — `MacpAckError` (`failure.code`
234
+ `'SESSION_ALREADY_EXISTS'` / `'POLICY_DENIED'` / `'FAILED_PRECONDITION'`
235
+ respectively) is the correct, Python-parity model
236
+ (`client.py:442-452`, `_map_registry_mutation_error` at `client.py:347-362`).
237
+ Code today catching `MacpTransportError` around these calls for these
238
+ specific statuses stops matching (`MacpAckError` extends `MacpSdkError`
239
+ directly, not `MacpTransportError`); every other gRPC status on these RPCs,
240
+ and every status on every other RPC, is unaffected. A knock-on effect on
241
+ `retrySend()`: a `FAILED_PRECONDITION` `send()` was previously retried to
242
+ exhaustion (no code filter on `MacpTransportError`) and surfaced as
243
+ `MacpRetryError`; it now surfaces immediately as `MacpAckError`, since
244
+ `POLICY_DENIED` is not in `DEFAULT_RETRY_POLICY.retryableCodes` — strictly
245
+ more correct (neither condition is retryable) and no longer masks the real
246
+ error behind a generic "retries exhausted."
247
+ - **`TaskProjection.isComplete(taskId)` renamed to `isCompleted(taskId)`**
248
+ (issue #138, cross-SDK naming decision at
249
+ multiagentcoordinationprotocol#135). Every other `is*` predicate in this
250
+ SDK is past-participle (`isFailed`, `isAccepted`, `isTerminallyRejected`,
251
+ `isDeclined`); `isComplete` was the outlier, and `macp-sdk-python` has
252
+ always spelled this `is_completed`. The old name remains available — see
253
+ `### Deprecated` below.
254
+ - **`TaskCompletionRecord` renamed to `TaskCompleteRecord`, and
255
+ `TaskFailureRecord` renamed to `TaskFailRecord`** (issue #138, cross-SDK
256
+ naming decision at multiagentcoordinationprotocol#135). Both now mirror
257
+ their triggering `TaskComplete`/`TaskFail` message types the way their
258
+ sibling `TaskUpdateRecord` already mirrors `TaskUpdate`, and match
259
+ `macp-sdk-python` and `macp-runtime`'s own names for the same records. The
260
+ old names remain available — see `### Deprecated` below.
261
+
262
+ ### Deprecated
263
+
264
+ - **`TaskProjection.isComplete(taskId)`** remains available as a
265
+ `@deprecated` alias for `isCompleted(taskId)`; use `isCompleted()` instead.
266
+ Scheduled for removal in 0.13.0.
267
+ - **`TaskCompletionRecord`/`TaskFailureRecord`** remain available as
268
+ `@deprecated` type aliases for `TaskCompleteRecord`/`TaskFailRecord`
269
+ respectively. Scheduled for removal in 0.13.0.
270
+
271
+ ### Fixed
272
+
273
+ - **`validateConfidence` now rejects `NaN`** (issue #108, part 1 of 2 — 4A).
274
+ `NaN < 0` and `NaN > 1.0` are both `false`, so a bare range check silently
275
+ accepted `NaN` — verified by execution before this fix. `±Infinity` was
276
+ already rejected (incidentally); this also rejects it explicitly via
277
+ `Number.isFinite`, matching this file's own `ttlMs`/`maxSuspendMs` idiom.
278
+ - **`commitmentHash`/`canonicalizeCommitmentPayload`: `supersedes: null` now
279
+ hashes as absent, matching `macp-sdk-python`** (issue #105). `supersedes`
280
+ is a proto3 message-typed field with only present/absent wire states;
281
+ `null` is the idiomatic in-language representation of "absent" (e.g. under
282
+ a `defaults: true` protobufjs/proto-loader decode, or a payload
283
+ round-tripped through JSON). Previously `supersedes: null` took the
284
+ *present* branch and hashed identically to an explicit
285
+ `{ sessionId: '', commitmentHash: '' }`, producing a different digest than
286
+ `macp-sdk-python` computes for the same logical commitment. The predicate
287
+ changed from `p.supersedes !== undefined` to `p.supersedes != null`
288
+ (deliberately loose — catches `null` and `undefined` only, so `0`, `''`,
289
+ `false`, and other falsy-but-not-nullish values still take the present
290
+ branch, unaffected). Not a protocol MINOR bump: the hashing `LABEL` and
291
+ frozen field set are unchanged — this corrects a projection bug, not the
292
+ algorithm. Any digest a caller already computed by feeding `null` was
293
+ already wrong (Python never reproduced it), so there is no
294
+ correct-value regression, but the fix is a security/audit-relevant
295
+ behavior change on this SDK's own output.
296
+ - **`Participant` lifecycle: `SessionCancel` now reaches `onTerminal`, and
297
+ teardown runs on every exit from `run()`, not just an explicit `stop()`**
298
+ (issue #106). Three independent bugs in `src/agent/participant.ts`:
299
+ - A runtime-issued `SessionCancel` never fired `onTerminal` at all — no
300
+ built-in projection maps it to a terminal phase, so the phase-driven
301
+ dispatch path had nothing to trigger on. `Participant` now recognizes
302
+ `SessionCancel` directly as a fallback when the phase path doesn't fire,
303
+ dispatching `{ state: 'Cancelled' }`. Against the current `macp-runtime`,
304
+ this fallback's practical reach is `processEvent()` callers and
305
+ cross-SDK parity with `macp-sdk-python`'s identical fallback — the
306
+ streamed `run()` path never observes a `SessionCancel` at all today,
307
+ since `cancel_session` stores it as `EntryKind::Internal` and
308
+ `get_incoming_after` filters strictly to `EntryKind::Incoming`;
309
+ cancellation surfaces via `session_lifecycle_bus`/`WatchSessions`
310
+ instead.
311
+ - `run()`'s `finally` only cleared an internal flag; the transport
312
+ subscription and the cancel-callback HTTP listener were torn down
313
+ exclusively inside `stop()`. A normal terminal exit, or a transport
314
+ simply running out of messages, leaked both — a long-lived agent
315
+ process running many sessions sequentially leaked one gRPC stream
316
+ subscription and one listening TCP socket per completed session.
317
+ Teardown now runs on every exit from `run()`.
318
+ - `isStopped` computed `!running`, so it read `true` immediately after
319
+ construction, before `run()` had ever been called. Fixed by tracking
320
+ three independent states (`running` / `terminal` / `stopRequested`)
321
+ instead of one flag — deliberately **not** a single latch copied from
322
+ `macp-sdk-python`, because that would make a `Participant` single-use
323
+ after any `stop()`, breaking this SDK's own documented and tested
324
+ restart contract (issue #66): `stop()` mid-stream, then a second
325
+ `run()` on the same instance, must resume rather than refuse. Only a
326
+ genuinely *terminal* outcome is single-use; a `stop()`-only exit is a
327
+ resumable pause.
328
+ - **`BaseProjection`'s rollback pop is now guarded by reference identity**
329
+ (issue #106, item 4): it no longer unconditionally pops the last
330
+ `transcript` entry on a failed decode. `BaseProjection` is documented
331
+ subclassable API; a third-party subclass whose `applyMode` pushes its own
332
+ entry onto `transcript` before throwing would previously have had that
333
+ entry silently removed instead of the envelope actually under
334
+ application — data corruption strictly worse than the bug the rollback
335
+ itself fixes. Mirrors `macp-sdk-python`'s identical guard and its own
336
+ documented trade-off in this exact scenario.
337
+ - **Identity fields on `Task`/`Handoff` actions now default to the resolved
338
+ sender when omitted or empty, matching `macp-sdk-python`** (issue #108,
339
+ part 2 of 2 — 4B): `TaskSession.acceptTask`/`rejectTask`/`completeTask`/
340
+ `failTask`'s `assignee`, and `HandoffSession.acceptHandoff`'s `acceptedBy`/
341
+ `decline`'s `declinedBy`, are now optional and fall back to the caller's
342
+ authenticated identity (`input.field || resolvedSender` — `||`, not `??`,
343
+ since an explicit empty string is also meant to fall back). Previously
344
+ these were required fields passed straight through with no default, unlike
345
+ the Python SDK (`task.py:299,323,391,430`, `handoff.py:247,271`).
346
+ - **`HandoffSession.addContext`'s `contentType` now defaults to
347
+ `'application/octet-stream'` when omitted**, matching `macp-sdk-python`
348
+ (`handoff.py:210`) (issue #108, part 2 of 2 — 4B). Previously `contentType`
349
+ was a required field with no default.
350
+ - **`evaluationHandler`/`votingHandler` now validate a strategy's output
351
+ before dispatching it** (issue #108, part 2 of 2 — 4B). Both previously
352
+ passed a custom `EvaluationStrategy`/`VotingStrategy`'s raw
353
+ `recommendation`/`confidence`/`vote` straight to `ctx.actions.*` with no
354
+ validation, so a buggy strategy (an out-of-enum recommendation, a `NaN`
355
+ confidence, an out-of-enum vote) failed later and less legibly, from
356
+ inside the session rather than at the handler that produced the bad
357
+ value. Both handlers now reuse this SDK's own `validateRecommendation`/
358
+ `validateConfidence`/`validateVote` (throwing the documented
359
+ `MacpSessionError`) and dispatch the validators' normalized return value.
360
+ - **`MacpStream`'s inline-error path was reading a shape a real decode never
361
+ produces, silently dropping every inline application-level error** (issue
362
+ #107). `StreamSessionResponse.response` is a proto3 `oneof`; under this
363
+ file's own `@grpc/proto-loader` options (`oneofs: true`), the decoded
364
+ object exposes the payload directly as `chunk.envelope`/`chunk.error`, with
365
+ `chunk.response` set to the oneof's *arm name as a string* (`'envelope'` or
366
+ `'error'`) — not a nested object. The old code read
367
+ `chunk?.response?.error`, which indexes a string and is always `undefined`
368
+ on every message that has ever crossed this stream; the corresponding unit
369
+ test only ever passed because it emitted the same fictional
370
+ `{ response: { error } }` shape by hand rather than a real decode. Fixed to
371
+ read `chunk.error` directly, and each inline error is now also logged at
372
+ `warn` (it previously produced no log line anywhere).
373
+ - **`TaskProjection.phase` no longer advances to `'Completed'`/`'Failed'` for
374
+ a `TaskComplete`/`TaskFail` naming an unknown `task_id`** (issue #111).
375
+ Both `this.phase` assignments sat outside the `if (task) { ... }` guard
376
+ that already gated every other per-task field mutation in the same
377
+ `applyMode` cases, so a message for a `task_id` this projection never saw
378
+ a `TaskRequest` for still flipped the session-level `phase` to a terminal
379
+ state even though no task actually completed or failed. Moved both
380
+ assignments inside their sibling `if (task)` block, matching the guard
381
+ already correct on `TaskAccept` (issue #71/#70) and on
382
+ `HandoffProjection`'s analogous `phase` transitions.
383
+ - **Three more `phase`-transition-outside-guard sites, same bug shape as
384
+ issue #111, found during that fix's verification** (issue #119):
385
+ - `ProposalProjection`'s `Reject` case moved `this.phase =
386
+ 'TerminalRejected'` inside the sibling `if (proposal) { ... }` guard. A
387
+ terminal `Reject` for an unknown `proposalId` no longer advances `phase`
388
+ — which matters beyond bookkeeping, since `'TerminalRejected'` is a
389
+ `TERMINAL_PHASES` member in `src/agent/participant.ts`, so the bug could
390
+ end a live `Participant`'s `run()` loop for a session that never
391
+ actually terminated.
392
+ - `HandoffProjection`'s `HandoffContext` case moved its `phase` transition
393
+ (`'OfferPending'` → `'ContextSharing'`) inside the sibling `if (handoff)
394
+ { ... }` guard, matching `HandoffAccept`/`HandoffDecline`, which were
395
+ already correct.
396
+ - `DecisionProjection`'s `Vote` case gained a new existence guard (none
397
+ existed before): `if (!this.proposals.has(record.proposalId)) break;`,
398
+ per RFC-MACP-0007 §5 rule 2 ("`Evaluation`, `Objection`, and `Vote` MUST
399
+ reference an existing `proposal_id`"). Previously a `Vote` for an unknown
400
+ `proposalId` fabricated a `votes` `Map` entry and flipped `phase` to
401
+ `'Voting'` — the fabricated entry also inflated `voteTotals()`'s and
402
+ `majorityWinner()`'s denominators, able to flip a real proposal from
403
+ winner to non-winner in a close tally.
404
+
405
+ All three sites' `rejections`/audit-array-style writes remain unconditional
406
+ (append-only logs, matching the runtime), so query methods reading from
407
+ them (`isTerminallyRejected`, `hasTerminalRejection`) can still report
408
+ `true` for an unknown id even though `phase` no longer moves — intentional,
409
+ not a residual gap.
410
+ - **`QuorumProjection.setBallot` no longer fabricates a `ballots` entry for an
411
+ unknown `request_id`** (issue #121, found during issue #119's plan-review
412
+ sweep). RFC-MACP-0011 §5 rule 3 requires a ballot (`Approve`/`Reject`/
413
+ `Abstain`) to reference the Session's accepted `request_id`; there was no
414
+ check against `this.requests` before creating or extending a `ballots` Map
415
+ entry, so an unfiltered/multi-session transcript could make
416
+ `votedSenders()` and `approvalCount()`/`rejectionCount()`/
417
+ `abstentionCount()` report on a `request_id` that was never opened, and a
418
+ second such ballot from the same sender could fire a spurious
419
+ `duplicate_ballot` anomaly against it. Unlike the three sites above, this
420
+ bug never touched `phase` — `QuorumProjection.phase` is set only by
421
+ `ApprovalRequest` and by `Commitment` handling in `BaseProjection` — so the
422
+ fix is a single existence guard at the top of `setBallot`, with no phase
423
+ component.
424
+ - **`ProtoRegistry.decodeMultiRoundContribute` no longer coerces a legacy-JSON
425
+ `Contribute` payload's `value` to a string** (issue #124). Previously
426
+ `String(parsed.value ?? '')` made an absent `value` key indistinguishable
427
+ from a genuine empty string, and stringified any non-string JSON value
428
+ (`123` decoded to `'123'`, `null` decoded to `''`). The legacy-JSON decode
429
+ path now passes `parsed.value` through exactly as `JSON.parse` produced
430
+ it — `undefined` stays `undefined`, `null` stays `null`, a number stays a
431
+ number. The canonical-protobuf decode path is unaffected (its `value` is
432
+ always a `string`, per the proto schema).
433
+ - **`majorityVoter` no longer counts `REVIEW` evaluations in its approval
434
+ ratio** (issue #124). RFC-MACP-0007 §4 states `REVIEW` evaluations "do not
435
+ block or approve a proposal; they serve as informational analysis records
436
+ only" — a `REVIEW` was nonetheless lowering the ratio's denominator, so a
437
+ proposal with one `APPROVE` and one `REVIEW` voted at ratio `0.5` instead
438
+ of `1.0`. The ratio's denominator is now the `decisive` evaluations
439
+ (everything except `REVIEW`, case-insensitive); `shouldVote` now returns
440
+ `false` for an evaluation set of only `REVIEW`s (no decisive evaluation to
441
+ vote on) instead of voting `REJECT` at ratio `0`. `BLOCK` and `REJECT`
442
+ remain in the denominator — they are stances, not informational-only
443
+ records.
444
+ - **`intent`, `instructions`, `action`, and `summary` no longer rejected when
445
+ empty or omitted** (issue #124). `SessionStart.intent`,
446
+ `TaskSession.requestTask`'s `instructions`, and
447
+ `QuorumSession.requestApproval`'s `action`/`summary` are proto3 singular
448
+ `string` fields — implicit presence means an omitted value and an explicit
449
+ `''` serialize to identical bytes, so a client-side "required, non-empty"
450
+ check rejected a payload the runtime would accept and no runtime-side check
451
+ could ever distinguish. `intent` also has an explicit normative rule
452
+ (RFC-MACP-0001 §7.1: a runtime "MUST NOT reject a SessionStart solely
453
+ because intent is empty"). Every other `validateRequiredField` call
454
+ site — `taskId`, `title`, `requestId`, `proposalId`, `option`, `handoffId`,
455
+ `targetParticipant`, `modeVersion`, `configurationVersion` — is unaffected
456
+ and still rejects an empty value.
457
+
458
+ ### Documentation
459
+
460
+ - **Pagination memory caveat** (issue #109): `docs/api/client.md`'s
461
+ `listSessions` and `listSessionsPage` entries now state plainly that while
462
+ `macp-runtime` ≥ 0.7.0 (current: 0.8.3) implements real server-side
463
+ pagination, `listSessions()` itself still accumulates every page into one
464
+ in-memory array regardless of `pageSize` — callers who need bounded
465
+ memory should call `listSessionsPage()` directly.
466
+ - **`ListRoots` wording** (issue #109): `docs/guides/streaming.md` no
467
+ longer says the runtime "serves `ListRoots`," which reads as "returns
468
+ real data" — reworded to state plainly that the runtime always returns an
469
+ empty root list. `docs/api/client.md`'s `listRoots` entry gains the same
470
+ statement, which it previously lacked entirely.
471
+
7
472
  ## [0.10.0](https://github.com/multiagentcoordinationprotocol/macp-sdk-typescript/compare/v0.9.0...v0.10.0) (2026-09-01)
8
473
 
9
474
 
package/README.md CHANGED
@@ -158,7 +158,7 @@ await session.commit({ action: 'task.completed', authorityScope: 'lead', reason:
158
158
 
159
159
  // Projection queries
160
160
  session.projection.progressOf('t1'); // 1.0
161
- session.projection.isComplete('t1'); // true
161
+ session.projection.isCompleted('t1'); // true
162
162
  session.projection.activeTasks(); // []
163
163
  ```
164
164
 
@@ -416,7 +416,7 @@ npm run test:integration # Integration tests (requires Docker runtime; not run
416
416
  ```
417
417
 
418
418
  CI (GitHub Actions) runs type-check, lint, format check, the coverage-gated
419
- test suite, and the build on Node 20, 22, and 24 for every push and pull
419
+ test suite, and the build on Node 22 and 24 for every push and pull
420
420
  request, and posts a coverage summary comment on PRs.
421
421
 
422
422
  ### Integration Tests
@@ -4,4 +4,4 @@ export * from './dispatcher';
4
4
  export * from './participant';
5
5
  export * from './strategies';
6
6
  export * from './runner';
7
- export { GrpcTransportAdapter, HttpTransportAdapter, type TransportAdapter, type HttpPollingConfig, } from './transports';
7
+ export { GrpcTransportAdapter, HttpTransportAdapter, DEFAULT_SUBSCRIBE_RETRY_POLICY, type TransportAdapter, type HttpPollingConfig, type SubscribeRetryPolicy, } from './transports';
@@ -14,7 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.HttpTransportAdapter = exports.GrpcTransportAdapter = void 0;
17
+ exports.DEFAULT_SUBSCRIBE_RETRY_POLICY = exports.HttpTransportAdapter = exports.GrpcTransportAdapter = void 0;
18
18
  __exportStar(require("./types"), exports);
19
19
  __exportStar(require("./cancel-callback"), exports);
20
20
  __exportStar(require("./dispatcher"), exports);
@@ -24,3 +24,4 @@ __exportStar(require("./runner"), exports);
24
24
  var transports_1 = require("./transports");
25
25
  Object.defineProperty(exports, "GrpcTransportAdapter", { enumerable: true, get: function () { return transports_1.GrpcTransportAdapter; } });
26
26
  Object.defineProperty(exports, "HttpTransportAdapter", { enumerable: true, get: function () { return transports_1.HttpTransportAdapter; } });
27
+ Object.defineProperty(exports, "DEFAULT_SUBSCRIBE_RETRY_POLICY", { enumerable: true, get: function () { return transports_1.DEFAULT_SUBSCRIBE_RETRY_POLICY; } });
@@ -39,8 +39,10 @@ export interface ParticipantConfig {
39
39
  /**
40
40
  * Bind a cancel-callback HTTP endpoint (RFC-0001 §7.2 Option A) when
41
41
  * this participant's event loop starts. The server is closed
42
- * automatically when {@link Participant.stop} is called. Parity with
43
- * python-sdk's bootstrap `cancel_callback` field.
42
+ * automatically on any exit from {@link Participant.run} — an explicit
43
+ * {@link Participant.stop}, a terminal outcome, or the transport running
44
+ * out of messages. Parity with python-sdk's bootstrap `cancel_callback`
45
+ * field.
44
46
  */
45
47
  cancelCallback?: {
46
48
  host: string;
@@ -64,7 +66,23 @@ export declare class Participant {
64
66
  private readonly modeVersion?;
65
67
  private readonly configurationVersion?;
66
68
  private readonly policyVersion?;
69
+ /** Whether the event loop is currently inside {@link run}. */
67
70
  private running;
71
+ /**
72
+ * Latching: set once a terminal result is dispatched (a terminal phase,
73
+ * or the {@code SessionCancel} fallback in {@link processMessage}). A
74
+ * terminal `Participant` is single-use — {@link run} refuses to
75
+ * re-enter, because the projection is resolved and the runtime will
76
+ * accept nothing further for this session.
77
+ */
78
+ private terminal;
79
+ /**
80
+ * The cooperative cancel flag {@link stop} sets and {@link run} clears
81
+ * on entry — the loop's break predicate. Distinct from {@link terminal}:
82
+ * a `stop()`-exited, non-terminal `Participant` is a resumable pause
83
+ * (see {@link isStopped}), not single-use.
84
+ */
85
+ private stopRequested;
68
86
  private lastPhase;
69
87
  private cancelCallbackServer?;
70
88
  private readonly cancelCallbackConfig?;
@@ -74,7 +92,12 @@ export declare class Participant {
74
92
  on(messageType: string, handler: MessageHandler): Participant;
75
93
  onPhaseChange(phase: string, handler: PhaseChangeHandler): Participant;
76
94
  onTerminal(handler: TerminalHandler): Participant;
77
- /** Whether {@link stop} has been called (or the loop has exited). */
95
+ /**
96
+ * `true` after a terminal outcome (single-use from here on), or while a
97
+ * `stop()` request is pending/has taken effect on a `Participant` whose
98
+ * loop has not (yet) been resumed by a later {@link run}. A `stop()`
99
+ * exit is resumable — see {@link run} and {@link terminal}'s own doc.
100
+ */
78
101
  get isStopped(): boolean;
79
102
  run(): Promise<void>;
80
103
  /**
@@ -87,10 +110,30 @@ export declare class Participant {
87
110
  private buildHandlerContext;
88
111
  /** Returns true if a terminal state was reached. */
89
112
  private processMessage;
113
+ /**
114
+ * Idempotent teardown of everything {@link run} started: the transport
115
+ * subscription and the cancel-callback HTTP listener, if any. Called
116
+ * from both {@link stop} and every exit from {@link run}'s loop (issue
117
+ * #106.2) — a normal terminal exit or a transport running out of
118
+ * messages used to leak both. A rejecting `transport.stop()` is caught
119
+ * rather than propagated, so a transport fault on teardown cannot mask
120
+ * a clean session outcome as a thrown error from {@link run}.
121
+ */
122
+ private teardown;
123
+ /**
124
+ * Request that {@link run}'s loop stop. This SDK's {@link TransportAdapter}
125
+ * has no `cancel()` — `stop()` on the transport is the only thing that can
126
+ * wake a loop suspended awaiting the next envelope, so teardown (including
127
+ * the transport) runs here unconditionally, not just the flag. Safe to
128
+ * call whether or not `run()` is currently active; does not itself flip
129
+ * {@link running} — only `run()`'s own loop exit does that, since it is
130
+ * the only place that actually knows the loop has left.
131
+ */
90
132
  stop(): Promise<void>;
91
133
  /**
92
134
  * Attach a running cancel-callback HTTP server to this participant.
93
- * The server is closed automatically when {@link stop} is called.
135
+ * The server is closed automatically on any exit from {@link run} — see
136
+ * {@link teardown}.
94
137
  * Parity with python-sdk's `Participant.attach_cancel_callback_server`.
95
138
  */
96
139
  attachCancelCallbackServer(server: CancelCallbackServer): void;