macp-sdk-typescript 0.11.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 (44) hide show
  1. package/CHANGELOG.md +371 -0
  2. package/README.md +1 -1
  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/projections/base.d.ts +71 -14
  24. package/dist/projections/base.js +83 -12
  25. package/dist/projections/decision.js +23 -3
  26. package/dist/projections/handoff.js +39 -16
  27. package/dist/projections/proposal.js +16 -2
  28. package/dist/projections/quorum.js +13 -0
  29. package/dist/projections/task.d.ts +27 -4
  30. package/dist/projections/task.js +44 -13
  31. package/dist/proposal.d.ts +1 -0
  32. package/dist/proposal.js +2 -2
  33. package/dist/proto-registry.d.ts +69 -3
  34. package/dist/proto-registry.js +83 -5
  35. package/dist/quorum.d.ts +1 -0
  36. package/dist/quorum.js +4 -4
  37. package/dist/task.d.ts +1 -0
  38. package/dist/task.js +24 -11
  39. package/dist/types.d.ts +14 -7
  40. package/dist/validation.d.ts +14 -0
  41. package/dist/validation.js +39 -5
  42. package/dist/version.d.ts +1 -1
  43. package/dist/version.js +1 -1
  44. package/package.json +3 -2
@@ -170,7 +170,13 @@ class ProtoRegistry {
170
170
  * Decode an `ext.multi_round.v1` `Contribute` payload, accepting both wire
171
171
  * formats: legacy JSON (`{"value":"..."}`, still replayed verbatim from
172
172
  * pre-proto histories) and canonical protobuf (`ContributePayload`). Both
173
- * normalize to `{ value: string }`.
173
+ * normalize to `{ value }`, but only the protobuf path's `value` is always
174
+ * a `string` — the legacy-JSON path passes `parsed.value` through exactly
175
+ * as `JSON.parse` produced it (issue #124: no `String(...)` coercion, no
176
+ * `?? ''` fallback for an absent key), so `value` may be `undefined`,
177
+ * `null`, a number, a boolean, or an object/array for malformed-but-valid
178
+ * legacy JSON. This method's own contract is observational, not a gate —
179
+ * see the `contribute_acceptance` note below.
174
180
  *
175
181
  * Parse-then-fallback (issue #93), not a first-byte shortcut: JSON is always
176
182
  * attempted first, and only a `JSON.parse` failure falls through to
@@ -178,8 +184,27 @@ class ProtoRegistry {
178
184
  * not generalize — e.g. legacy JSON with leading whitespace has neither byte
179
185
  * first, and used to reach `decodeMessage` and throw. Parity with
180
186
  * `macp-sdk-python`'s `_decode_json_first_then_proto`, which has always
181
- * worked this way. Safe because a canonical proto payload's bytes are
182
- * essentially never also syntactically valid JSON.
187
+ * worked this way.
188
+ *
189
+ * **A successful `JSON.parse` is not proof the bytes are legacy JSON**
190
+ * (issue #104 — this method's prior "essentially never also syntactically
191
+ * valid JSON" claim was false, not merely imprecise): the canonical proto
192
+ * tag byte for field 1 (`0x0A`) is itself insignificant JSON whitespace, so
193
+ * for specific value byte-lengths the length varint — or, when the varint
194
+ * is itself whitespace, the value's own leading byte — becomes the first
195
+ * significant character `JSON.parse` sees, and a genuine `ContributePayload`
196
+ * silently misreads as a JSON number/string/object. Verified directly
197
+ * (encode-then-decode sweep, value lengths 1-127, seven value shapes): every
198
+ * one of lengths 9, 10, 13, 32, 34, 45, 48, 49-57, and 123 corrupted before
199
+ * `isCanonicalProto` below, most to total silent data loss
200
+ * (`String(undefined ?? '') === ''`), zero corrupt after.
201
+ *
202
+ * `isCanonicalProto` resolves the ambiguity by tie-break, not by refusing
203
+ * to try JSON first: bytes that parse as JSON *and* are the exact canonical
204
+ * proto encoding are treated as proto. This has a narrow, priced cost on
205
+ * the reverse direction (legacy JSON misread as proto) that it introduces
206
+ * rather than merely fails to close — see `isCanonicalProto`'s own
207
+ * docblock.
183
208
  *
184
209
  * An empty payload falls through the same path: `JSON.parse('')` throws, so
185
210
  * it reaches `decodeMessage` on zero bytes, which yields `{}` (proto3
@@ -192,10 +217,63 @@ class ProtoRegistry {
192
217
  decodeMultiRoundContribute(payload) {
193
218
  try {
194
219
  const parsed = JSON.parse(payload.toString('utf8'));
195
- return { value: typeof parsed.value === 'string' ? parsed.value : String(parsed.value ?? '') };
220
+ if (!this.isCanonicalProto(MULTI_ROUND_CONTRIBUTE, payload)) {
221
+ return { value: parsed.value };
222
+ }
223
+ }
224
+ catch {
225
+ // Not JSON at all — fall through to the protobuf decode below.
226
+ }
227
+ return this.decodeMessage(MULTI_ROUND_CONTRIBUTE, payload);
228
+ }
229
+ /**
230
+ * True iff `payload` is the exact canonical protobuf encoding of
231
+ * `typeName` — used to break the JSON/proto ambiguity in
232
+ * `decodeMultiRoundContribute` (issue #104): bytes that both parse as JSON
233
+ * *and* round-trip byte-identically through the proto message are treated
234
+ * as proto, not JSON.
235
+ *
236
+ * `macp-sdk-python`'s equivalent (`_is_canonical_proto`) needs an explicit
237
+ * `DiscardUnknownFields()` step before comparing, because Python's protobuf
238
+ * runtime preserves unknown fields verbatim through
239
+ * `ParseFromString`/`SerializeToString` by default — without it, a plain
240
+ * round-trip check misclassified several whitespace-prefixed legacy-JSON
241
+ * payloads as canonical proto. **`protobufjs` needs no equivalent step**:
242
+ * verified directly (a synthetic payload with a declared field-1 string
243
+ * plus an undeclared field 2) that `Type.decode()` silently drops fields
244
+ * not in the schema rather than preserving them, so `Type.encode()` can
245
+ * never reproduce bytes it never captured — a byte-exact round-trip here
246
+ * is already proof the payload is *exactly* the single-field encoding, no
247
+ * unknown fields possible. Confirmed this closes the same
248
+ * whitespace-prefixed false-positive class `DiscardUnknownFields` closes
249
+ * in Python (tab/space-prefixed legacy JSON at several lengths).
250
+ *
251
+ * One narrow, symmetric residual survives regardless — genuinely
252
+ * irreducible, not a gap this check merely fails to close: a payload whose
253
+ * first byte is a literal `0x0A` (`\n`) and whose remainder forms a
254
+ * complete, well-formed proto field-1 string with nothing left over has no
255
+ * unknown field for either implementation to expose a mismatch on, because
256
+ * it *is*, byte for byte, both a legal JSON reading and the canonical
257
+ * proto encoding of some (possibly nonsensical) string. Verified instances
258
+ * (this SDK's own `JSON.stringify`, which — unlike Python's `json.dumps`
259
+ * — omits the space after `:`, shifting the colliding lengths by one byte
260
+ * from `macp-sdk-python`'s pinned cases): `'\n' + JSON.stringify({value:
261
+ * 'z'.repeat(112)})`, and with one extra whitespace byte inserted after the
262
+ * `\n`, `'\r'` at value length 1 and `' '` at value length 20. A real
263
+ * legacy-JSON sender would need to deliberately prefix with a literal
264
+ * newline byte for this to matter — this SDK's own JSON producers never
265
+ * emit one — so the trade (fixing the ordinary, naturally-reachable
266
+ * forward-direction corruption above at the cost of this adversarial,
267
+ * unreachable-by-any-known-encoder reverse case) is the right one.
268
+ */
269
+ isCanonicalProto(typeName, payload) {
270
+ try {
271
+ const type = this.lookupType(typeName);
272
+ const decoded = type.decode(payload);
273
+ return Buffer.from(type.encode(decoded).finish()).equals(payload);
196
274
  }
197
275
  catch {
198
- return this.decodeMessage(MULTI_ROUND_CONTRIBUTE, payload);
276
+ return false;
199
277
  }
200
278
  }
201
279
  lookupType(typeName) {
package/dist/quorum.d.ts CHANGED
@@ -32,6 +32,7 @@ export declare class QuorumSession {
32
32
  }[];
33
33
  maxSuspendMs?: number;
34
34
  sender?: string;
35
+ auth?: AuthConfig;
35
36
  }): Promise<Ack>;
36
37
  requestApproval(input: ApprovalRequestPayload & {
37
38
  sender?: string;
package/dist/quorum.js CHANGED
@@ -60,15 +60,15 @@ class QuorumSession {
60
60
  mode: constants_1.MODE_QUORUM,
61
61
  messageType: 'SessionStart',
62
62
  sessionId: this.sessionId,
63
- sender: this.senderFor(input.sender),
63
+ sender: this.senderFor(input.sender, input.auth),
64
64
  payload: this.client.protoRegistry.encodeKnownPayload(constants_1.MODE_QUORUM, 'SessionStart', (0, envelope_1.toProtoPayload)(payload)),
65
65
  });
66
- return this.sendAndTrack(envelope, this.auth);
66
+ return this.sendAndTrack(envelope, input.auth ?? this.auth);
67
67
  }
68
68
  async requestApproval(input) {
69
69
  (0, validation_1.validateRequiredField)('requestId', input.requestId);
70
- (0, validation_1.validateRequiredField)('action', input.action);
71
- (0, validation_1.validateRequiredField)('summary', input.summary);
70
+ // `action`/`summary` are proto3 singular string fields (implicit
71
+ // presence) -- no client-side non-empty check here (issue #124 item 3).
72
72
  const envelope = (0, envelope_1.buildEnvelope)({
73
73
  mode: constants_1.MODE_QUORUM,
74
74
  messageType: 'ApprovalRequest',
package/dist/task.d.ts CHANGED
@@ -32,6 +32,7 @@ export declare class TaskSession {
32
32
  }[];
33
33
  maxSuspendMs?: number;
34
34
  sender?: string;
35
+ auth?: AuthConfig;
35
36
  }): Promise<Ack>;
36
37
  requestTask(input: TaskRequestPayload & {
37
38
  sender?: string;
package/dist/task.js CHANGED
@@ -60,15 +60,16 @@ class TaskSession {
60
60
  mode: constants_1.MODE_TASK,
61
61
  messageType: 'SessionStart',
62
62
  sessionId: this.sessionId,
63
- sender: this.senderFor(input.sender),
63
+ sender: this.senderFor(input.sender, input.auth),
64
64
  payload: this.client.protoRegistry.encodeKnownPayload(constants_1.MODE_TASK, 'SessionStart', (0, envelope_1.toProtoPayload)(payload)),
65
65
  });
66
- return this.sendAndTrack(envelope, this.auth);
66
+ return this.sendAndTrack(envelope, input.auth ?? this.auth);
67
67
  }
68
68
  async requestTask(input) {
69
69
  (0, validation_1.validateRequiredField)('taskId', input.taskId);
70
70
  (0, validation_1.validateRequiredField)('title', input.title);
71
- (0, validation_1.validateRequiredField)('instructions', input.instructions);
71
+ // `instructions` is a proto3 singular string field (implicit presence) --
72
+ // no client-side non-empty check here (issue #124 item 3).
72
73
  const envelope = (0, envelope_1.buildEnvelope)({
73
74
  mode: constants_1.MODE_TASK,
74
75
  messageType: 'TaskRequest',
@@ -80,23 +81,31 @@ class TaskSession {
80
81
  }
81
82
  async acceptTask(input) {
82
83
  (0, validation_1.validateRequiredField)('taskId', input.taskId);
84
+ const sender = this.senderFor(input.sender, input.auth);
85
+ // Issue #108.5/4e: default assignee to the resolved sender, matching
86
+ // Python (`task.py:299,323,391,430`). `||` not `??` — an explicit empty
87
+ // string is never a meaningful assignee, and the reported gap includes
88
+ // that case, which `??` would let through unchanged.
89
+ const assignee = input.assignee || sender;
83
90
  const envelope = (0, envelope_1.buildEnvelope)({
84
91
  mode: constants_1.MODE_TASK,
85
92
  messageType: 'TaskAccept',
86
93
  sessionId: this.sessionId,
87
- sender: this.senderFor(input.sender, input.auth),
88
- payload: this.client.protoRegistry.encodeKnownPayload(constants_1.MODE_TASK, 'TaskAccept', (0, envelope_1.toProtoPayload)(input)),
94
+ sender,
95
+ payload: this.client.protoRegistry.encodeKnownPayload(constants_1.MODE_TASK, 'TaskAccept', (0, envelope_1.toProtoPayload)({ ...input, assignee })),
89
96
  });
90
97
  return this.sendAndTrack(envelope, input.auth);
91
98
  }
92
99
  async rejectTask(input) {
93
100
  (0, validation_1.validateRequiredField)('taskId', input.taskId);
101
+ const sender = this.senderFor(input.sender, input.auth);
102
+ const assignee = input.assignee || sender;
94
103
  const envelope = (0, envelope_1.buildEnvelope)({
95
104
  mode: constants_1.MODE_TASK,
96
105
  messageType: 'TaskReject',
97
106
  sessionId: this.sessionId,
98
- sender: this.senderFor(input.sender, input.auth),
99
- payload: this.client.protoRegistry.encodeKnownPayload(constants_1.MODE_TASK, 'TaskReject', (0, envelope_1.toProtoPayload)(input)),
107
+ sender,
108
+ payload: this.client.protoRegistry.encodeKnownPayload(constants_1.MODE_TASK, 'TaskReject', (0, envelope_1.toProtoPayload)({ ...input, assignee })),
100
109
  });
101
110
  return this.sendAndTrack(envelope, input.auth);
102
111
  }
@@ -113,23 +122,27 @@ class TaskSession {
113
122
  }
114
123
  async completeTask(input) {
115
124
  (0, validation_1.validateRequiredField)('taskId', input.taskId);
125
+ const sender = this.senderFor(input.sender, input.auth);
126
+ const assignee = input.assignee || sender;
116
127
  const envelope = (0, envelope_1.buildEnvelope)({
117
128
  mode: constants_1.MODE_TASK,
118
129
  messageType: 'TaskComplete',
119
130
  sessionId: this.sessionId,
120
- sender: this.senderFor(input.sender, input.auth),
121
- payload: this.client.protoRegistry.encodeKnownPayload(constants_1.MODE_TASK, 'TaskComplete', (0, envelope_1.toProtoPayload)(input)),
131
+ sender,
132
+ payload: this.client.protoRegistry.encodeKnownPayload(constants_1.MODE_TASK, 'TaskComplete', (0, envelope_1.toProtoPayload)({ ...input, assignee })),
122
133
  });
123
134
  return this.sendAndTrack(envelope, input.auth);
124
135
  }
125
136
  async failTask(input) {
126
137
  (0, validation_1.validateRequiredField)('taskId', input.taskId);
138
+ const sender = this.senderFor(input.sender, input.auth);
139
+ const assignee = input.assignee || sender;
127
140
  const envelope = (0, envelope_1.buildEnvelope)({
128
141
  mode: constants_1.MODE_TASK,
129
142
  messageType: 'TaskFail',
130
143
  sessionId: this.sessionId,
131
- sender: this.senderFor(input.sender, input.auth),
132
- payload: this.client.protoRegistry.encodeKnownPayload(constants_1.MODE_TASK, 'TaskFail', (0, envelope_1.toProtoPayload)(input)),
144
+ sender,
145
+ payload: this.client.protoRegistry.encodeKnownPayload(constants_1.MODE_TASK, 'TaskFail', (0, envelope_1.toProtoPayload)({ ...input, assignee })),
133
146
  });
134
147
  return this.sendAndTrack(envelope, input.auth);
135
148
  }
package/dist/types.d.ts CHANGED
@@ -217,12 +217,14 @@ export interface TaskRequestPayload {
217
217
  }
218
218
  export interface TaskAcceptPayload {
219
219
  taskId: string;
220
- assignee: string;
220
+ /** Defaults to the resolved sender when omitted or empty (issue #108.5). */
221
+ assignee?: string;
221
222
  reason?: string;
222
223
  }
223
224
  export interface TaskRejectPayload {
224
225
  taskId: string;
225
- assignee: string;
226
+ /** Defaults to the resolved sender when omitted or empty (issue #108.5). */
227
+ assignee?: string;
226
228
  reason?: string;
227
229
  }
228
230
  export interface TaskUpdatePayload {
@@ -234,13 +236,15 @@ export interface TaskUpdatePayload {
234
236
  }
235
237
  export interface TaskCompletePayload {
236
238
  taskId: string;
237
- assignee: string;
239
+ /** Defaults to the resolved sender when omitted or empty (issue #108.5). */
240
+ assignee?: string;
238
241
  output?: Buffer;
239
242
  summary?: string;
240
243
  }
241
244
  export interface TaskFailPayload {
242
245
  taskId: string;
243
- assignee: string;
246
+ /** Defaults to the resolved sender when omitted or empty (issue #108.5). */
247
+ assignee?: string;
244
248
  errorCode?: string;
245
249
  reason?: string;
246
250
  retryable?: boolean;
@@ -253,12 +257,14 @@ export interface HandoffOfferPayload {
253
257
  }
254
258
  export interface HandoffContextPayload {
255
259
  handoffId: string;
256
- contentType: string;
260
+ /** Defaults to 'application/octet-stream' when omitted (issue #108.6). */
261
+ contentType?: string;
257
262
  context?: Buffer;
258
263
  }
259
264
  export interface HandoffAcceptPayload {
260
265
  handoffId: string;
261
- acceptedBy: string;
266
+ /** Defaults to the resolved sender when omitted or empty (issue #108.5). */
267
+ acceptedBy?: string;
262
268
  reason?: string;
263
269
  /**
264
270
  * Runtime-emitted synthetic accepts ONLY (RFC-MACP-0010 §5.1): `true` on the
@@ -272,7 +278,8 @@ export interface HandoffAcceptPayload {
272
278
  }
273
279
  export interface HandoffDeclinePayload {
274
280
  handoffId: string;
275
- declinedBy: string;
281
+ /** Defaults to the resolved sender when omitted or empty (issue #108.5). */
282
+ declinedBy?: string;
276
283
  reason?: string;
277
284
  }
278
285
  export interface ApprovalRequestPayload {
@@ -1,3 +1,17 @@
1
+ /**
2
+ * Validate that `sid` is a lowercase UUID v4/v7 or base64url (22+ chars).
3
+ *
4
+ * Mirrors the runtime's **no-fall-through** rule (issue #108.1, and
5
+ * `macp-sdk-python`'s `validate_session_id`, `validation.py:33-58`, which
6
+ * this ports verbatim in shape): a string with the *structural* shape of a
7
+ * UUID (36 chars, hyphens at 8-13-18-23, hex-only) is validated strictly as
8
+ * a lowercase v4/v7 UUID — it is never reinterpreted as base64url, which
9
+ * would otherwise let an uppercase, wrong-version, or bad-variant UUID slip
10
+ * through since it also happens to satisfy the base64url charset. A single
11
+ * combined regex cannot express this: the absence of fall-through is itself
12
+ * the rule. Only a string that is not UUID-shaped at all reaches the
13
+ * base64url branch.
14
+ */
1
15
  export declare function validateSessionId(sid: string): void;
2
16
  export declare function validateVote(value: string): string;
3
17
  export declare function validateRecommendation(value: string): string;
@@ -15,12 +15,36 @@ exports.validateRequiredField = validateRequiredField;
15
15
  exports.validateCommitmentHash = validateCommitmentHash;
16
16
  exports.validateSessionStart = validateSessionStart;
17
17
  const errors_1 = require("./errors");
18
- const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
18
+ // A string with the structural shape of a UUID (36 chars, hyphens at
19
+ // 8-13-18-23, hex-only otherwise) — case-insensitive, any version/variant.
20
+ const UUID_SHAPE_RE = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
21
+ // Strict lowercase UUID v4/v7 (version nibble 4 or 7, RFC 9562 variant 8/9/a/b).
22
+ const UUID_V4V7_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[47][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
19
23
  const BASE64URL_RE = /^[A-Za-z0-9_-]{22,}$/;
24
+ /**
25
+ * Validate that `sid` is a lowercase UUID v4/v7 or base64url (22+ chars).
26
+ *
27
+ * Mirrors the runtime's **no-fall-through** rule (issue #108.1, and
28
+ * `macp-sdk-python`'s `validate_session_id`, `validation.py:33-58`, which
29
+ * this ports verbatim in shape): a string with the *structural* shape of a
30
+ * UUID (36 chars, hyphens at 8-13-18-23, hex-only) is validated strictly as
31
+ * a lowercase v4/v7 UUID — it is never reinterpreted as base64url, which
32
+ * would otherwise let an uppercase, wrong-version, or bad-variant UUID slip
33
+ * through since it also happens to satisfy the base64url charset. A single
34
+ * combined regex cannot express this: the absence of fall-through is itself
35
+ * the rule. Only a string that is not UUID-shaped at all reaches the
36
+ * base64url branch.
37
+ */
20
38
  function validateSessionId(sid) {
21
- if (!UUID_RE.test(sid) && !BASE64URL_RE.test(sid)) {
22
- throw new errors_1.MacpSessionError(`session_id must be UUID v4/v7 or base64url (22+ chars), got: ${sid}`);
39
+ if (UUID_SHAPE_RE.test(sid)) {
40
+ if (!UUID_V4V7_RE.test(sid)) {
41
+ throw new errors_1.MacpSessionError(`session_id is UUID-shaped but not a lowercase v4/v7 UUID (no fall-through to base64url), got: ${sid}`);
42
+ }
43
+ return;
23
44
  }
45
+ if (BASE64URL_RE.test(sid))
46
+ return;
47
+ throw new errors_1.MacpSessionError(`session_id must be a lowercase UUID v4/v7 or base64url (22+ chars), got: ${sid}`);
24
48
  }
25
49
  const VALID_VOTES = new Set(['APPROVE', 'REJECT', 'ABSTAIN']);
26
50
  function validateVote(value) {
@@ -39,7 +63,13 @@ function validateRecommendation(value) {
39
63
  return normalized;
40
64
  }
41
65
  function validateConfidence(value) {
42
- if (value < 0.0 || value > 1.0) {
66
+ // `Number.isFinite` (matching this file's own `ttlMs`/`maxSuspendMs`
67
+ // idiom) rather than two range comparisons alone: `NaN < 0` and
68
+ // `NaN > 1.0` are both `false`, so a bare range check silently *accepts*
69
+ // `NaN` (issue #108.2) — verified by execution before this fix. `Infinity`
70
+ // was already rejected incidentally (`Infinity > 1.0`); this also rejects
71
+ // it explicitly rather than by accident.
72
+ if (!Number.isFinite(value) || value < 0.0 || value > 1.0) {
43
73
  throw new errors_1.MacpSessionError(`confidence must be in [0.0, 1.0], got ${value}`);
44
74
  }
45
75
  }
@@ -131,7 +161,11 @@ function validateCommitmentHash(value, field = 'commitmentHash') {
131
161
  }
132
162
  }
133
163
  function validateSessionStart(input) {
134
- validateRequiredField('intent', input.intent);
164
+ // `intent` is a proto3 singular string field (implicit presence -- an
165
+ // omitted field and an explicit "" serialize identically on the wire), and
166
+ // RFC-MACP-0001 §7.1 states a runtime "MUST NOT reject a SessionStart
167
+ // solely because intent is empty" -- no client-side non-empty check here
168
+ // (issue #124 item 3).
135
169
  validateParticipants(input.participants);
136
170
  validateTtlMs(input.ttlMs);
137
171
  if (input.maxSuspendMs !== undefined)
package/dist/version.d.ts CHANGED
@@ -4,4 +4,4 @@
4
4
  * Kept in sync with package.json by release-please (see the
5
5
  * `x-release-please-version` annotation).
6
6
  */
7
- export declare const VERSION = "0.11.0";
7
+ export declare const VERSION = "0.12.0";
package/dist/version.js CHANGED
@@ -7,4 +7,4 @@ exports.VERSION = void 0;
7
7
  * Kept in sync with package.json by release-please (see the
8
8
  * `x-release-please-version` annotation).
9
9
  */
10
- exports.VERSION = '0.11.0'; // x-release-please-version
10
+ exports.VERSION = '0.12.0'; // x-release-please-version
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "macp-sdk-typescript",
3
- "version": "0.11.0",
3
+ "version": "0.12.0",
4
4
  "description": "TypeScript SDK for the MACP Rust runtime",
5
5
  "license": "Apache-2.0",
6
6
  "type": "commonjs",
@@ -20,7 +20,8 @@
20
20
  ],
21
21
  "scripts": {
22
22
  "build": "tsc -p tsconfig.json",
23
- "check": "tsc -p tsconfig.json --noEmit",
23
+ "check": "tsc -p tsconfig.json --noEmit && npm run check:examples",
24
+ "check:examples": "tsc -p tsconfig.examples.json",
24
25
  "lint": "eslint src/",
25
26
  "lint:fix": "eslint src/ --fix",
26
27
  "format": "prettier --write 'src/**/*.ts' 'tests/**/*.ts' --ignore-unknown",