macp-sdk-typescript 0.5.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +57 -0
- package/README.md +14 -4
- package/dist/commitment-hash.d.ts +41 -0
- package/dist/commitment-hash.js +198 -0
- package/dist/envelope.d.ts +9 -2
- package/dist/envelope.js +16 -2
- package/dist/index.d.ts +2 -0
- package/dist/index.js +10 -1
- package/dist/validation.d.ts +1 -0
- package/dist/validation.js +7 -0
- package/dist/version.d.ts +7 -0
- package/dist/version.js +10 -0
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,63 @@ 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.7.0](https://github.com/multiagentcoordinationprotocol/macp-sdk-typescript/compare/v0.6.0...v0.7.0) (2026-08-30)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
### ⚠ BREAKING CHANGES
|
|
11
|
+
|
|
12
|
+
* RFC-MACP-0013 canonical commitment hash ([#45](https://github.com/multiagentcoordinationprotocol/macp-sdk-typescript/issues/45))
|
|
13
|
+
|
|
14
|
+
### Features
|
|
15
|
+
|
|
16
|
+
* RFC-MACP-0013 canonical commitment hash ([#45](https://github.com/multiagentcoordinationprotocol/macp-sdk-typescript/issues/45)) ([2af3c51](https://github.com/multiagentcoordinationprotocol/macp-sdk-typescript/commit/2af3c5156e5831a4d2767175fd8f3154e34ec1af))
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
### Bug Fixes
|
|
20
|
+
|
|
21
|
+
* reject non-object supersedes instead of silently dropping it ([#49](https://github.com/multiagentcoordinationprotocol/macp-sdk-typescript/issues/49)) ([b4a68e3](https://github.com/multiagentcoordinationprotocol/macp-sdk-typescript/commit/b4a68e3f6059d968dabc3f6b43c5e8033fb4957c))
|
|
22
|
+
|
|
23
|
+
## [0.6.0](https://github.com/multiagentcoordinationprotocol/macp-sdk-typescript/compare/v0.5.0...v0.6.0) (2026-07-10)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
### Features
|
|
27
|
+
|
|
28
|
+
* export package VERSION constant ([#37](https://github.com/multiagentcoordinationprotocol/macp-sdk-typescript/issues/37)) ([4ae5cda](https://github.com/multiagentcoordinationprotocol/macp-sdk-typescript/commit/4ae5cda23255b82339490f740a30ca0b7fee0d5a))
|
|
29
|
+
|
|
30
|
+
## [Unreleased]
|
|
31
|
+
|
|
32
|
+
### Changed
|
|
33
|
+
|
|
34
|
+
- **BREAKING: requires Node.js >= 20** (`engines.node`). Node 18 reached
|
|
35
|
+
end-of-life in April 2025 and was never exercised in CI; the CI matrix now
|
|
36
|
+
tests Node 20, 22, and 24.
|
|
37
|
+
- CI installs with `npm ci` against the committed lockfile (regenerated so all
|
|
38
|
+
platform-specific optional binaries are recorded), with npm caching and a
|
|
39
|
+
concurrency group that cancels superseded PR runs. The advisory-only
|
|
40
|
+
`npm audit` job is replaced by Dependabot (weekly grouped npm and
|
|
41
|
+
github-actions updates). All workflow actions are pinned by commit SHA.
|
|
42
|
+
- Publishing now runs the full `prepublishOnly` gate (check + lint +
|
|
43
|
+
format:check + test + build) against the exact tree being published —
|
|
44
|
+
the `--ignore-scripts` workaround is gone.
|
|
45
|
+
- Pull requests get a coverage summary comment from CI.
|
|
46
|
+
|
|
47
|
+
### Added
|
|
48
|
+
|
|
49
|
+
- Extensive unit coverage for previously integration-only surface: the
|
|
50
|
+
`MacpStream` data path (envelope unwrapping, inline errors, timeouts,
|
|
51
|
+
end-of-stream semantics), the entire `MacpClient` unary RPC surface and its
|
|
52
|
+
metadata/deadline dispatch, the `watch*` stream factories, session
|
|
53
|
+
`cancel`/`suspend`/`resume` delegation for all five modes, `BaseSession`/
|
|
54
|
+
`BaseProjection` extension-point behavior, and the `Participant` run loop
|
|
55
|
+
(terminal dispatch, cancel-callback wiring, initiator kickoff defaults).
|
|
56
|
+
Coverage thresholds raised to lines 94 / branches 88 / functions 90 /
|
|
57
|
+
statements 94.
|
|
58
|
+
- Conformance: `ext.multi_round.v1` fixtures now replay with real assertions
|
|
59
|
+
(previously silently skipped); fixtures for unmapped modes fail loudly; and
|
|
60
|
+
reject-path fixtures are contract-checked (canonical `expected_error_code`,
|
|
61
|
+
resolvable `payload_type`) with explicit skip markers pointing at the
|
|
62
|
+
runtime conformance oracle for the NACK behavior itself.
|
|
63
|
+
|
|
7
64
|
## [0.5.0] - 2026-07-06
|
|
8
65
|
|
|
9
66
|
Absorbs `macp-runtime` 0.5.0 and `@multiagentcoordinationprotocol/proto` 0.1.6
|
package/README.md
CHANGED
|
@@ -2,8 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
TypeScript SDK for the [Multi-Agent Coordination Protocol (MACP)](https://github.com/multiagentcoordinationprotocol/multiagentcoordinationprotocol) runtime. Connects TypeScript/Node.js applications to the Rust MACP runtime over gRPC.
|
|
4
4
|
|
|
5
|
+
SDK documentation lives in [`docs/`](docs/index.md) and covers the TypeScript API.
|
|
6
|
+
Protocol semantics are normative in the [spec repo's docs and RFCs](https://github.com/multiagentcoordinationprotocol/multiagentcoordinationprotocol/tree/main/rfcs);
|
|
7
|
+
runtime behavior is documented in the [runtime repo's docs](https://github.com/multiagentcoordinationprotocol/macp-runtime/blob/main/docs/getting-started.md).
|
|
8
|
+
|
|
5
9
|
## Install
|
|
6
10
|
|
|
11
|
+
Requires Node.js >= 20.
|
|
12
|
+
|
|
7
13
|
```bash
|
|
8
14
|
npm install macp-sdk-typescript
|
|
9
15
|
```
|
|
@@ -287,7 +293,7 @@ reference: [`docs/api/strategies.md`](docs/api/strategies.md).
|
|
|
287
293
|
### Cancel Callback
|
|
288
294
|
|
|
289
295
|
Long-running agents can expose an HTTP endpoint that an orchestrator POSTs to
|
|
290
|
-
in order to request a clean shutdown (RFC-MACP-0001 §7.2 Option A). Bootstrap
|
|
296
|
+
in order to request a clean shutdown ([RFC-MACP-0001 (Core)](https://github.com/multiagentcoordinationprotocol/multiagentcoordinationprotocol/blob/main/rfcs/RFC-MACP-0001-core.md) §7.2 Option A). Bootstrap
|
|
291
297
|
JSON with a `cancel_callback: { host, port, path }` block auto-wires the
|
|
292
298
|
server via `fromBootstrap()`; for the manual path use
|
|
293
299
|
`startCancelCallbackServer(...)` + `Participant.attachCancelCallbackServer(...)`.
|
|
@@ -323,7 +329,7 @@ await session.vote({
|
|
|
323
329
|
|
|
324
330
|
## TLS
|
|
325
331
|
|
|
326
|
-
TLS is on by default (RFC-MACP-0006 §3). To connect to an insecure runtime
|
|
332
|
+
TLS is on by default ([RFC-MACP-0006 (Transport Bindings)](https://github.com/multiagentcoordinationprotocol/multiagentcoordinationprotocol/blob/main/rfcs/RFC-MACP-0006-transport-bindings.md) §3). To connect to an insecure runtime
|
|
327
333
|
during local development, you must opt out explicitly:
|
|
328
334
|
|
|
329
335
|
```typescript
|
|
@@ -405,10 +411,14 @@ npm run lint # ESLint
|
|
|
405
411
|
npm run format # Prettier
|
|
406
412
|
npm test # Run unit + conformance tests
|
|
407
413
|
npm run test:watch # Watch mode
|
|
408
|
-
npm run test:coverage # With coverage
|
|
409
|
-
npm run test:integration # Integration tests (requires Docker runtime)
|
|
414
|
+
npm run test:coverage # With coverage (CI enforces thresholds from vitest.config.ts)
|
|
415
|
+
npm run test:integration # Integration tests (requires Docker runtime; not run in CI)
|
|
410
416
|
```
|
|
411
417
|
|
|
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
|
|
420
|
+
request, and posts a coverage summary comment on PRs.
|
|
421
|
+
|
|
412
422
|
### Integration Tests
|
|
413
423
|
|
|
414
424
|
Run the full SDK against a live MACP runtime:
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { CommitmentPayload } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Projects a `CommitmentPayload` (this SDK's camelCase shape) to its RFC
|
|
4
|
+
* 8785 canonical JSON string (snake_case, D2.1) and serializes it directly
|
|
5
|
+
* — no intermediate object graph — in the fixed member order below.
|
|
6
|
+
*
|
|
7
|
+
* Member order is RFC 8785 §3.2.3: object members sorted lexicographically
|
|
8
|
+
* by UTF-16 code unit on the member name. Every member name here is ASCII
|
|
9
|
+
* and the field set is frozen (it mirrors `CommitmentPayload`, a stable
|
|
10
|
+
* wire type), so this order is hard-coded rather than sorted at runtime:
|
|
11
|
+
* action, authority_scope, commitment_id, configuration_version,
|
|
12
|
+
* mode_version, outcome_positive, policy_version, reason, then supersedes
|
|
13
|
+
* last (only when present). Do not "simplify" this to `.localeCompare()` —
|
|
14
|
+
* that is locale-sensitive and not equivalent to code-unit ordering; it is
|
|
15
|
+
* moot only because the names above are already in the right order.
|
|
16
|
+
*
|
|
17
|
+
* `supersedes` is omitted entirely when `payload.supersedes === undefined`
|
|
18
|
+
* (D2.3) — including when the payload itself is missing/malformed, `payload
|
|
19
|
+
* ?? {}` never manufactures a `supersedes` key. An explicit
|
|
20
|
+
* `supersedes: { sessionId: '', commitmentHash: '' }` is NOT collapsed into
|
|
21
|
+
* omission; it is present with two empty-string members, `commitment_hash`
|
|
22
|
+
* before `session_id` (also alphabetical).
|
|
23
|
+
*
|
|
24
|
+
* Exported separately from `commitmentHash` so a vector-runner test can
|
|
25
|
+
* localize a mismatch to projection/JCS (compare this string) vs. digest
|
|
26
|
+
* (compare the final `sha256:` hash).
|
|
27
|
+
*/
|
|
28
|
+
export declare function canonicalizeCommitmentPayload(payload: CommitmentPayload): string;
|
|
29
|
+
/**
|
|
30
|
+
* Computes the RFC-MACP-0013 canonical commitment hash for `payload`:
|
|
31
|
+
* `sha256:` followed by 64 lowercase hex digits of
|
|
32
|
+
* `SHA-256(LABEL + ':' + JCS(project(payload)))`.
|
|
33
|
+
*
|
|
34
|
+
* Never throws (D3), for any input — well-formed or not. Every step that
|
|
35
|
+
* could plausibly throw on malformed input (unknown-typed field reads,
|
|
36
|
+
* string iteration, control-character formatting) is guarded in
|
|
37
|
+
* `canonicalizeCommitmentPayload` / `jcsString` / `jcsBoolean` instead of
|
|
38
|
+
* being caught here, so a genuine bug in this file shows up as a test
|
|
39
|
+
* failure rather than being silently swallowed by a catch-all.
|
|
40
|
+
*/
|
|
41
|
+
export declare function commitmentHash(payload: CommitmentPayload): string;
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.canonicalizeCommitmentPayload = canonicalizeCommitmentPayload;
|
|
4
|
+
exports.commitmentHash = commitmentHash;
|
|
5
|
+
/**
|
|
6
|
+
* RFC-MACP-0013 canonical commitment hashing.
|
|
7
|
+
*
|
|
8
|
+
* Computes a deterministic content hash for a `CommitmentPayload` by
|
|
9
|
+
* projecting it to a frozen snake_case shape, serializing that shape with a
|
|
10
|
+
* hand-written subset of RFC 8785 (JSON Canonicalization Scheme, "JCS"), and
|
|
11
|
+
* hashing the labeled preimage with SHA-256.
|
|
12
|
+
*
|
|
13
|
+
* Dependency decision (deliberate, not an oversight): this module does NOT
|
|
14
|
+
* depend on a JCS library. RFC 8785's hardest requirement — canonical
|
|
15
|
+
* ECMAScript float formatting for JSON numbers — is unreachable here: the
|
|
16
|
+
* `CommitmentPayload` projection below is a frozen 9-field shape containing
|
|
17
|
+
* only strings, one boolean, and one optional 2-string nested object. No
|
|
18
|
+
* JSON numbers, no JSON arrays. That leaves only object-member ordering and
|
|
19
|
+
* string escaping, both of which are small enough to hand-write and test
|
|
20
|
+
* directly rather than pull in this SDK's first non-gRPC/proto runtime
|
|
21
|
+
* dependency. `node:crypto` is a Node builtin, not a dependency.
|
|
22
|
+
*/
|
|
23
|
+
const node_crypto_1 = require("node:crypto");
|
|
24
|
+
/**
|
|
25
|
+
* Domain-separation label mixed into the hash preimage (RFC-MACP-0013 §4).
|
|
26
|
+
* Changing this string changes every hash this module produces — that is a
|
|
27
|
+
* protocol MINOR version bump, not a routine edit.
|
|
28
|
+
*/
|
|
29
|
+
const LABEL = 'macp-commitment-hash/1';
|
|
30
|
+
/**
|
|
31
|
+
* Escapes a single JSON string value per RFC 8785 §3.2.2.2:
|
|
32
|
+
* - short-form escapes for backspace/tab/newline/formfeed/carriage-return,
|
|
33
|
+
* `"`, and `\`
|
|
34
|
+
* - `\u00xx` (lowercase hex) for other C0 control characters (0x00-0x1F)
|
|
35
|
+
* - every other character, including non-ASCII and astral-plane
|
|
36
|
+
* codepoints, is emitted literally (NOT escaped).
|
|
37
|
+
*
|
|
38
|
+
* Lone surrogates: a bare UTF-16 surrogate code unit not paired into a valid
|
|
39
|
+
* codepoint is out-of-contract input for this function — a `CommitmentPayload`
|
|
40
|
+
* should never legitimately contain one, since this SDK's strings originate
|
|
41
|
+
* from valid UTF-16 sources (JS string literals, JSON-decoded wire payloads,
|
|
42
|
+
* etc.). If one appears anyway, this function does not detect or escape it
|
|
43
|
+
* specially: `for (const ch of str)` below yields it as its own one-code-unit
|
|
44
|
+
* step and it is emitted literally, unescaped, like any other character. The
|
|
45
|
+
* actual loss happens one step later, in `commitmentHash`, when the finished
|
|
46
|
+
* JCS string is encoded to bytes via `Buffer.from(preimage, 'utf8')` — Node's
|
|
47
|
+
* standard lossy UTF-8 encoder silently substitutes a lone surrogate with
|
|
48
|
+
* U+FFFD (the Unicode replacement character) before hashing. The observable
|
|
49
|
+
* consequence: a field containing a lone surrogate (e.g. `'\uD800'`) hashes
|
|
50
|
+
* IDENTICALLY to the same field containing a literal U+FFFD in that position
|
|
51
|
+
* — pinned by the "lone surrogate" test in commitment-hash.test.ts. This is
|
|
52
|
+
* an accepted consequence of D3 (never throw on malformed input), not a
|
|
53
|
+
* claimed point of compatibility (or divergence) with any other
|
|
54
|
+
* implementation of RFC-MACP-0013.
|
|
55
|
+
*
|
|
56
|
+
* `value` is typed `unknown`, not `string`, on purpose: this function is the
|
|
57
|
+
* single choke point that keeps `commitmentHash` from ever throwing (D3) —
|
|
58
|
+
* every field of a `CommitmentPayload` flows through here, and at runtime a
|
|
59
|
+
* decoded or hand-built payload is not guaranteed to actually match its
|
|
60
|
+
* static type. Non-string input is coerced via `String()` (never a template
|
|
61
|
+
* literal or `+`, both of which throw on a bare `Symbol`) rather than
|
|
62
|
+
* rejected — this file does not "fix" a malformed payload into a valid one,
|
|
63
|
+
* it only guarantees serialization cannot crash.
|
|
64
|
+
*
|
|
65
|
+
* Iterates with `for (const ch of str)`, which walks Unicode codepoints, not
|
|
66
|
+
* UTF-16 code units. Indexing by code unit would split an astral-plane
|
|
67
|
+
* codepoint's surrogate pair (e.g. U+1F702) and emit half of it — iterating
|
|
68
|
+
* by codepoint is what keeps a full character, including a surrogate pair,
|
|
69
|
+
* together as a single loop step so it can be appended whole.
|
|
70
|
+
*/
|
|
71
|
+
function jcsString(value) {
|
|
72
|
+
const str = typeof value === 'string' ? value : String(value ?? '');
|
|
73
|
+
let out = '"';
|
|
74
|
+
for (const ch of str) {
|
|
75
|
+
switch (ch) {
|
|
76
|
+
case '"':
|
|
77
|
+
out += '\\"';
|
|
78
|
+
break;
|
|
79
|
+
case '\\':
|
|
80
|
+
out += '\\\\';
|
|
81
|
+
break;
|
|
82
|
+
case '\b':
|
|
83
|
+
out += '\\b';
|
|
84
|
+
break;
|
|
85
|
+
case '\t':
|
|
86
|
+
out += '\\t';
|
|
87
|
+
break;
|
|
88
|
+
case '\n':
|
|
89
|
+
out += '\\n';
|
|
90
|
+
break;
|
|
91
|
+
case '\f':
|
|
92
|
+
out += '\\f';
|
|
93
|
+
break;
|
|
94
|
+
case '\r':
|
|
95
|
+
out += '\\r';
|
|
96
|
+
break;
|
|
97
|
+
default: {
|
|
98
|
+
// Non-null assertion is safe: `for (const ch of str)` always yields
|
|
99
|
+
// a non-empty code-point substring, so `codePointAt(0)` can never
|
|
100
|
+
// actually be `undefined` here — a `?? 0` fallback would be dead
|
|
101
|
+
// code that misleadingly reads as if codepoints can be absent.
|
|
102
|
+
const code = ch.codePointAt(0);
|
|
103
|
+
if (code < 0x20) {
|
|
104
|
+
// Other C0 control character with no short form (e.g. U+0000).
|
|
105
|
+
out += '\\u' + code.toString(16).padStart(4, '0');
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
// Printable ASCII, non-ASCII, and astral-plane codepoints alike:
|
|
109
|
+
// JCS emits these as literal UTF-8, never escaped.
|
|
110
|
+
out += ch;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return out + '"';
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Emits `value` as a JCS boolean literal. `??` (not `||`) materializes an
|
|
119
|
+
* absent `outcomePositive` as `false` rather than omitting the key (D2.2) —
|
|
120
|
+
* `||` would also fold an explicit falsy-but-not-nullish value incorrectly
|
|
121
|
+
* for fields added in the future, so `??` documents the intent even though
|
|
122
|
+
* `boolean` only has two values today. A non-boolean value (possible only
|
|
123
|
+
* for a malformed/untyped payload at runtime) is coerced via a truthy check
|
|
124
|
+
* rather than thrown on, keeping D3 intact.
|
|
125
|
+
*/
|
|
126
|
+
function jcsBoolean(value) {
|
|
127
|
+
return (value ?? false) ? 'true' : 'false';
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Projects a `CommitmentPayload` (this SDK's camelCase shape) to its RFC
|
|
131
|
+
* 8785 canonical JSON string (snake_case, D2.1) and serializes it directly
|
|
132
|
+
* — no intermediate object graph — in the fixed member order below.
|
|
133
|
+
*
|
|
134
|
+
* Member order is RFC 8785 §3.2.3: object members sorted lexicographically
|
|
135
|
+
* by UTF-16 code unit on the member name. Every member name here is ASCII
|
|
136
|
+
* and the field set is frozen (it mirrors `CommitmentPayload`, a stable
|
|
137
|
+
* wire type), so this order is hard-coded rather than sorted at runtime:
|
|
138
|
+
* action, authority_scope, commitment_id, configuration_version,
|
|
139
|
+
* mode_version, outcome_positive, policy_version, reason, then supersedes
|
|
140
|
+
* last (only when present). Do not "simplify" this to `.localeCompare()` —
|
|
141
|
+
* that is locale-sensitive and not equivalent to code-unit ordering; it is
|
|
142
|
+
* moot only because the names above are already in the right order.
|
|
143
|
+
*
|
|
144
|
+
* `supersedes` is omitted entirely when `payload.supersedes === undefined`
|
|
145
|
+
* (D2.3) — including when the payload itself is missing/malformed, `payload
|
|
146
|
+
* ?? {}` never manufactures a `supersedes` key. An explicit
|
|
147
|
+
* `supersedes: { sessionId: '', commitmentHash: '' }` is NOT collapsed into
|
|
148
|
+
* omission; it is present with two empty-string members, `commitment_hash`
|
|
149
|
+
* before `session_id` (also alphabetical).
|
|
150
|
+
*
|
|
151
|
+
* Exported separately from `commitmentHash` so a vector-runner test can
|
|
152
|
+
* localize a mismatch to projection/JCS (compare this string) vs. digest
|
|
153
|
+
* (compare the final `sha256:` hash).
|
|
154
|
+
*/
|
|
155
|
+
function canonicalizeCommitmentPayload(payload) {
|
|
156
|
+
// D3: a malformed/absent payload must not throw. `{}` lets every field
|
|
157
|
+
// read below fall through to `jcsString`'s/`jcsBoolean`'s own defensive
|
|
158
|
+
// coercion instead of dereferencing a null/undefined payload.
|
|
159
|
+
const p = payload ?? {};
|
|
160
|
+
const members = [
|
|
161
|
+
`"action":${jcsString(p.action)}`,
|
|
162
|
+
`"authority_scope":${jcsString(p.authorityScope)}`,
|
|
163
|
+
`"commitment_id":${jcsString(p.commitmentId)}`,
|
|
164
|
+
`"configuration_version":${jcsString(p.configurationVersion)}`,
|
|
165
|
+
`"mode_version":${jcsString(p.modeVersion)}`,
|
|
166
|
+
`"outcome_positive":${jcsBoolean(p.outcomePositive)}`,
|
|
167
|
+
`"policy_version":${jcsString(p.policyVersion ?? '')}`,
|
|
168
|
+
`"reason":${jcsString(p.reason)}`,
|
|
169
|
+
];
|
|
170
|
+
if (p.supersedes !== undefined) {
|
|
171
|
+
// Optional chaining tolerates `supersedes` being present but not
|
|
172
|
+
// actually an object (null, a primitive, ...) on a malformed payload —
|
|
173
|
+
// it never throws, it just yields `undefined` for the sub-fields, which
|
|
174
|
+
// `jcsString` then coerces to `""` the same way it handles any other
|
|
175
|
+
// missing string field.
|
|
176
|
+
const sup = p.supersedes;
|
|
177
|
+
members.push(`"supersedes":{"commitment_hash":${jcsString(sup?.commitmentHash)},"session_id":${jcsString(sup?.sessionId)}}`);
|
|
178
|
+
}
|
|
179
|
+
return `{${members.join(',')}}`;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Computes the RFC-MACP-0013 canonical commitment hash for `payload`:
|
|
183
|
+
* `sha256:` followed by 64 lowercase hex digits of
|
|
184
|
+
* `SHA-256(LABEL + ':' + JCS(project(payload)))`.
|
|
185
|
+
*
|
|
186
|
+
* Never throws (D3), for any input — well-formed or not. Every step that
|
|
187
|
+
* could plausibly throw on malformed input (unknown-typed field reads,
|
|
188
|
+
* string iteration, control-character formatting) is guarded in
|
|
189
|
+
* `canonicalizeCommitmentPayload` / `jcsString` / `jcsBoolean` instead of
|
|
190
|
+
* being caught here, so a genuine bug in this file shows up as a test
|
|
191
|
+
* failure rather than being silently swallowed by a catch-all.
|
|
192
|
+
*/
|
|
193
|
+
function commitmentHash(payload) {
|
|
194
|
+
const jcs = canonicalizeCommitmentPayload(payload);
|
|
195
|
+
const preimage = `${LABEL}:${jcs}`;
|
|
196
|
+
const digest = (0, node_crypto_1.createHash)('sha256').update(Buffer.from(preimage, 'utf8')).digest('hex');
|
|
197
|
+
return `sha256:${digest}`;
|
|
198
|
+
}
|
package/dist/envelope.d.ts
CHANGED
|
@@ -24,7 +24,10 @@ export declare function inferOutcomePositive(action: string): boolean;
|
|
|
24
24
|
/**
|
|
25
25
|
* Build a {@link CommitmentRef} pointing at a prior accepted commitment, for
|
|
26
26
|
* use as `buildCommitmentPayload({ supersedes })` (cross-session supersession,
|
|
27
|
-
* RFC-MACP-0001 §7.3).
|
|
27
|
+
* RFC-MACP-0001 §7.3). Use `commitmentHash()` (from `./commitment-hash`) to
|
|
28
|
+
* produce a valid `commitmentHash` value — this function validates the hash
|
|
29
|
+
* shape (`sha256:<64 lowercase hex>`) and throws `MacpSessionError` if it
|
|
30
|
+
* doesn't match. Parity with python-sdk `build_commitment_ref`.
|
|
28
31
|
*/
|
|
29
32
|
export declare function buildCommitmentRef(input: {
|
|
30
33
|
sessionId: string;
|
|
@@ -56,7 +59,11 @@ export declare function buildCommitmentPayload(input: {
|
|
|
56
59
|
/**
|
|
57
60
|
* Cross-session supersession (RFC-MACP-0001 §7.3): reference to the prior
|
|
58
61
|
* commitment this one supersedes. Distinct from proposal-mode
|
|
59
|
-
* `supersedesProposalId`. Absent by default. Backward-compatible.
|
|
62
|
+
* `supersedesProposalId`. Absent by default. Backward-compatible. A
|
|
63
|
+
* non-`undefined` value that isn't a `CommitmentRef` object (e.g. `null`,
|
|
64
|
+
* from a caller bypassing the type system) throws `MacpSessionError`
|
|
65
|
+
* rather than being silently dropped — see RFC-MACP-0013 reconcile,
|
|
66
|
+
* 2026-08-30.
|
|
60
67
|
*/
|
|
61
68
|
supersedes?: CommitmentRef;
|
|
62
69
|
}): CommitmentPayload;
|
package/dist/envelope.js
CHANGED
|
@@ -16,6 +16,8 @@ exports.toProtoPayload = toProtoPayload;
|
|
|
16
16
|
exports.buildEnvelope = buildEnvelope;
|
|
17
17
|
const node_crypto_1 = require("node:crypto");
|
|
18
18
|
const constants_1 = require("./constants");
|
|
19
|
+
const errors_1 = require("./errors");
|
|
20
|
+
const validation_1 = require("./validation");
|
|
19
21
|
function newSessionId() {
|
|
20
22
|
return (0, node_crypto_1.randomUUID)();
|
|
21
23
|
}
|
|
@@ -52,9 +54,13 @@ function inferOutcomePositive(action) {
|
|
|
52
54
|
/**
|
|
53
55
|
* Build a {@link CommitmentRef} pointing at a prior accepted commitment, for
|
|
54
56
|
* use as `buildCommitmentPayload({ supersedes })` (cross-session supersession,
|
|
55
|
-
* RFC-MACP-0001 §7.3).
|
|
57
|
+
* RFC-MACP-0001 §7.3). Use `commitmentHash()` (from `./commitment-hash`) to
|
|
58
|
+
* produce a valid `commitmentHash` value — this function validates the hash
|
|
59
|
+
* shape (`sha256:<64 lowercase hex>`) and throws `MacpSessionError` if it
|
|
60
|
+
* doesn't match. Parity with python-sdk `build_commitment_ref`.
|
|
56
61
|
*/
|
|
57
62
|
function buildCommitmentRef(input) {
|
|
63
|
+
(0, validation_1.validateCommitmentHash)(input.commitmentHash);
|
|
58
64
|
return { sessionId: input.sessionId, commitmentHash: input.commitmentHash };
|
|
59
65
|
}
|
|
60
66
|
/**
|
|
@@ -82,8 +88,16 @@ function buildCommitmentPayload(input) {
|
|
|
82
88
|
policyVersion: input.policyVersion ?? constants_1.DEFAULT_POLICY_VERSION,
|
|
83
89
|
outcomePositive: input.outcomePositive ?? inferOutcomePositive(input.action),
|
|
84
90
|
};
|
|
85
|
-
if (input.supersedes)
|
|
91
|
+
if (input.supersedes !== undefined) {
|
|
92
|
+
if (typeof input.supersedes !== 'object' || input.supersedes === null) {
|
|
93
|
+
// `String()`, not `JSON.stringify()`: the latter throws on a bigint,
|
|
94
|
+
// which would replace this deliberate `MacpSessionError` with an
|
|
95
|
+
// unrelated `TypeError` for that one input shape.
|
|
96
|
+
throw new errors_1.MacpSessionError(`supersedes must be a CommitmentRef object, got: ${String(input.supersedes)}`);
|
|
97
|
+
}
|
|
98
|
+
(0, validation_1.validateCommitmentHash)(input.supersedes.commitmentHash);
|
|
86
99
|
payload.supersedes = input.supersedes;
|
|
100
|
+
}
|
|
87
101
|
return payload;
|
|
88
102
|
}
|
|
89
103
|
function buildRoot(uri, name = '') {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export { Auth, type AuthConfig, type BearerAuthOptions } from './auth';
|
|
2
2
|
export * from './base-session';
|
|
3
3
|
export * from './client';
|
|
4
|
+
export { commitmentHash } from './commitment-hash';
|
|
4
5
|
export * from './constants';
|
|
5
6
|
export * from './decision';
|
|
6
7
|
export * from './envelope';
|
|
@@ -16,5 +17,6 @@ export * from './quorum';
|
|
|
16
17
|
export * from './task';
|
|
17
18
|
export * from './types';
|
|
18
19
|
export * from './validation';
|
|
20
|
+
export { VERSION } from './version';
|
|
19
21
|
export * from './watchers';
|
|
20
22
|
export * as agent from './agent';
|
package/dist/index.js
CHANGED
|
@@ -36,7 +36,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
36
36
|
};
|
|
37
37
|
})();
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
-
exports.agent = exports.Auth = void 0;
|
|
39
|
+
exports.agent = exports.VERSION = exports.commitmentHash = exports.Auth = void 0;
|
|
40
40
|
// Auth: export only the public constructors and types. The helpers
|
|
41
41
|
// `validateAuth`, `authSender`, `assertSenderMatchesIdentity`, and
|
|
42
42
|
// `metadataFromAuth` are SDK-internal — importable from the `./auth`
|
|
@@ -46,6 +46,13 @@ var auth_1 = require("./auth");
|
|
|
46
46
|
Object.defineProperty(exports, "Auth", { enumerable: true, get: function () { return auth_1.Auth; } });
|
|
47
47
|
__exportStar(require("./base-session"), exports);
|
|
48
48
|
__exportStar(require("./client"), exports);
|
|
49
|
+
// Commitment hash: export only `commitmentHash`. `canonicalizeCommitmentPayload`
|
|
50
|
+
// is the projection/JCS half of the algorithm — importable from the
|
|
51
|
+
// `./commitment-hash` submodule (as the vector-runner tests do) but kept off
|
|
52
|
+
// the top-level surface to match python-sdk's public API, which does not
|
|
53
|
+
// expose its `canonical_projection` equivalent either.
|
|
54
|
+
var commitment_hash_1 = require("./commitment-hash");
|
|
55
|
+
Object.defineProperty(exports, "commitmentHash", { enumerable: true, get: function () { return commitment_hash_1.commitmentHash; } });
|
|
49
56
|
__exportStar(require("./constants"), exports);
|
|
50
57
|
__exportStar(require("./decision"), exports);
|
|
51
58
|
__exportStar(require("./envelope"), exports);
|
|
@@ -61,5 +68,7 @@ __exportStar(require("./quorum"), exports);
|
|
|
61
68
|
__exportStar(require("./task"), exports);
|
|
62
69
|
__exportStar(require("./types"), exports);
|
|
63
70
|
__exportStar(require("./validation"), exports);
|
|
71
|
+
var version_1 = require("./version");
|
|
72
|
+
Object.defineProperty(exports, "VERSION", { enumerable: true, get: function () { return version_1.VERSION; } });
|
|
64
73
|
__exportStar(require("./watchers"), exports);
|
|
65
74
|
exports.agent = __importStar(require("./agent"));
|
package/dist/validation.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ export declare function validateTtlMs(ttlMs: number): void;
|
|
|
9
9
|
export declare function validateMaxSuspendMs(maxSuspendMs: number): void;
|
|
10
10
|
export declare function validateParticipants(participants: string[]): void;
|
|
11
11
|
export declare function validateRequiredField(fieldName: string, value: string): void;
|
|
12
|
+
export declare function validateCommitmentHash(value: string, field?: string): void;
|
|
12
13
|
export declare function validateSessionStart(input: {
|
|
13
14
|
intent: string;
|
|
14
15
|
participants: string[];
|
package/dist/validation.js
CHANGED
|
@@ -11,6 +11,7 @@ exports.validateTtlMs = validateTtlMs;
|
|
|
11
11
|
exports.validateMaxSuspendMs = validateMaxSuspendMs;
|
|
12
12
|
exports.validateParticipants = validateParticipants;
|
|
13
13
|
exports.validateRequiredField = validateRequiredField;
|
|
14
|
+
exports.validateCommitmentHash = validateCommitmentHash;
|
|
14
15
|
exports.validateSessionStart = validateSessionStart;
|
|
15
16
|
const errors_1 = require("./errors");
|
|
16
17
|
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
@@ -91,6 +92,12 @@ function validateRequiredField(fieldName, value) {
|
|
|
91
92
|
throw new errors_1.MacpSessionError(`${fieldName} must be non-empty`);
|
|
92
93
|
}
|
|
93
94
|
}
|
|
95
|
+
const COMMITMENT_HASH_RE = /^sha256:[0-9a-f]{64}$/;
|
|
96
|
+
function validateCommitmentHash(value, field = 'commitmentHash') {
|
|
97
|
+
if (!COMMITMENT_HASH_RE.test(value)) {
|
|
98
|
+
throw new errors_1.MacpSessionError(`${field} must be 'sha256:' followed by 64 lowercase hex characters, got: ${value}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
94
101
|
function validateSessionStart(input) {
|
|
95
102
|
validateRequiredField('intent', input.intent);
|
|
96
103
|
validateParticipants(input.participants);
|
package/dist/version.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.VERSION = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* The installed `macp-sdk-typescript` package version.
|
|
6
|
+
*
|
|
7
|
+
* Kept in sync with package.json by release-please (see the
|
|
8
|
+
* `x-release-please-version` annotation).
|
|
9
|
+
*/
|
|
10
|
+
exports.VERSION = '0.7.0'; // x-release-please-version
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "macp-sdk-typescript",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "TypeScript SDK for the MACP Rust runtime",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "commonjs",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"prepublishOnly": "npm run check && npm run lint && npm run format:check && npm test && npm run build"
|
|
40
40
|
},
|
|
41
41
|
"engines": {
|
|
42
|
-
"node": ">=
|
|
42
|
+
"node": ">=20"
|
|
43
43
|
},
|
|
44
44
|
"keywords": [
|
|
45
45
|
"macp",
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
"dependencies": {
|
|
56
56
|
"@grpc/grpc-js": "^1.14.3",
|
|
57
57
|
"@grpc/proto-loader": "^0.8.0",
|
|
58
|
-
"@multiagentcoordinationprotocol/proto": "^0.1.
|
|
58
|
+
"@multiagentcoordinationprotocol/proto": "^0.1.8",
|
|
59
59
|
"protobufjs": "^8.6.5"
|
|
60
60
|
},
|
|
61
61
|
"devDependencies": {
|