macp-sdk-typescript 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/CHANGELOG.md +124 -1
  2. package/README.md +99 -3
  3. package/dist/agent/cancel-callback.d.ts +23 -0
  4. package/dist/agent/cancel-callback.js +108 -0
  5. package/dist/agent/index.d.ts +1 -0
  6. package/dist/agent/index.js +1 -0
  7. package/dist/agent/participant.d.ts +58 -0
  8. package/dist/agent/participant.js +143 -45
  9. package/dist/agent/runner.d.ts +34 -1
  10. package/dist/agent/runner.js +53 -0
  11. package/dist/agent/strategies.d.ts +2 -0
  12. package/dist/agent/strategies.js +8 -0
  13. package/dist/agent/transports.js +5 -0
  14. package/dist/base-session.d.ts +64 -0
  15. package/dist/base-session.js +112 -0
  16. package/dist/client.d.ts +25 -0
  17. package/dist/client.js +100 -10
  18. package/dist/constants.d.ts +21 -16
  19. package/dist/constants.js +24 -17
  20. package/dist/decision.d.ts +2 -1
  21. package/dist/decision.js +2 -1
  22. package/dist/envelope.d.ts +25 -3
  23. package/dist/envelope.js +42 -11
  24. package/dist/errors.d.ts +17 -4
  25. package/dist/errors.js +19 -10
  26. package/dist/handoff.d.ts +2 -9
  27. package/dist/handoff.js +2 -17
  28. package/dist/index.d.ts +3 -1
  29. package/dist/index.js +10 -2
  30. package/dist/logging.d.ts +21 -0
  31. package/dist/logging.js +64 -0
  32. package/dist/policy.d.ts +47 -42
  33. package/dist/policy.js +6 -6
  34. package/dist/projections/base.d.ts +20 -0
  35. package/dist/projections/base.js +37 -0
  36. package/dist/projections.d.ts +1 -0
  37. package/dist/projections.js +1 -0
  38. package/dist/proposal.d.ts +2 -1
  39. package/dist/proposal.js +2 -1
  40. package/dist/quorum.d.ts +2 -1
  41. package/dist/quorum.js +2 -1
  42. package/dist/task.d.ts +6 -5
  43. package/dist/task.js +6 -5
  44. package/dist/types.d.ts +11 -2
  45. package/dist/watchers.d.ts +23 -1
  46. package/dist/watchers.js +59 -1
  47. package/package.json +2 -2
@@ -4,12 +4,15 @@ exports.Participant = void 0;
4
4
  const constants_1 = require("../constants");
5
5
  const decision_1 = require("../decision");
6
6
  const handoff_1 = require("../handoff");
7
+ const logging_1 = require("../logging");
7
8
  const decision_2 = require("../projections/decision");
8
9
  const proposal_1 = require("../proposal");
9
10
  const quorum_1 = require("../quorum");
10
11
  const task_1 = require("../task");
12
+ const cancel_callback_1 = require("./cancel-callback");
11
13
  const dispatcher_1 = require("./dispatcher");
12
14
  const transports_1 = require("./transports");
15
+ const TERMINAL_PHASES = new Set(['Committed', 'Accepted', 'Declined', 'Cancelled', 'TerminalRejected']);
13
16
  class Participant {
14
17
  participantId;
15
18
  sessionId;
@@ -21,8 +24,15 @@ class Participant {
21
24
  dispatcher;
22
25
  session;
23
26
  transport;
27
+ initiatorConfig;
28
+ participants;
29
+ modeVersion;
30
+ configurationVersion;
31
+ policyVersion;
24
32
  running = false;
25
33
  lastPhase;
34
+ cancelCallbackServer;
35
+ cancelCallbackConfig;
26
36
  constructor(config) {
27
37
  this.participantId = config.participantId;
28
38
  this.sessionId = config.sessionId;
@@ -43,6 +53,12 @@ class Participant {
43
53
  this.lastPhase = projection.phase;
44
54
  this.actions = this.buildActions();
45
55
  this.transport = config.transport ?? new transports_1.GrpcTransportAdapter(config.client, config.sessionId, config.auth);
56
+ this.initiatorConfig = config.initiator;
57
+ this.participants = config.participants ?? [];
58
+ this.modeVersion = config.modeVersion;
59
+ this.configurationVersion = config.configurationVersion;
60
+ this.policyVersion = config.policyVersion;
61
+ this.cancelCallbackConfig = config.cancelCallback;
46
62
  }
47
63
  createModeSession(mode, client, opts) {
48
64
  switch (mode) {
@@ -154,68 +170,150 @@ class Participant {
154
170
  this.dispatcher.onTerminal(handler);
155
171
  return this;
156
172
  }
173
+ /** Whether {@link stop} has been called (or the loop has exited). */
174
+ get isStopped() {
175
+ return !this.running;
176
+ }
157
177
  async run() {
158
178
  if (this.running)
159
179
  return;
160
180
  this.running = true;
161
- const sessionInfo = {
162
- sessionId: this.sessionId,
163
- mode: this.mode,
164
- participants: [],
165
- policyVersion: undefined,
166
- };
181
+ if (this.cancelCallbackConfig && !this.cancelCallbackServer) {
182
+ this.cancelCallbackServer = await (0, cancel_callback_1.startCancelCallbackServer)({
183
+ host: this.cancelCallbackConfig.host,
184
+ port: this.cancelCallbackConfig.port,
185
+ path: this.cancelCallbackConfig.path,
186
+ onCancel: () => {
187
+ void this.stop();
188
+ },
189
+ });
190
+ }
191
+ if (this.initiatorConfig && this.session) {
192
+ await this.emitInitiatorEnvelopes();
193
+ }
167
194
  try {
168
195
  for await (const message of this.transport.start()) {
169
196
  if (!this.running)
170
197
  break;
171
- const ctx = {
172
- participant: this,
173
- projection: this.projection,
174
- actions: this.actions,
175
- session: sessionInfo,
176
- log: (msg, details) => {
177
- // eslint-disable-next-line no-console
178
- console.log(`[${this.participantId}] ${msg}`, details ?? '');
179
- },
180
- };
181
- // Apply envelope to projection if we have a mode session
182
- if (this.session && message.raw) {
183
- const applyMethod = this.session
184
- .projection.applyEnvelope;
185
- if (typeof applyMethod === 'function') {
186
- applyMethod.call(this.session.projection, message.raw, this.client.protoRegistry);
187
- }
188
- }
189
- // Dispatch message
190
- await this.dispatcher.dispatch(message, ctx);
191
- // Check for phase change
192
- const currentPhase = this.projection.phase;
193
- if (currentPhase !== this.lastPhase) {
194
- this.lastPhase = currentPhase;
195
- await this.dispatcher.dispatchPhaseChange(currentPhase, ctx);
196
- // Check for terminal state
197
- if (currentPhase === 'Committed' ||
198
- currentPhase === 'Accepted' ||
199
- currentPhase === 'Declined' ||
200
- currentPhase === 'Cancelled' ||
201
- currentPhase === 'TerminalRejected') {
202
- const terminalResult = {
203
- state: currentPhase,
204
- commitment: this.projection.commitment,
205
- };
206
- await this.dispatcher.dispatchTerminal(terminalResult);
207
- break;
208
- }
209
- }
198
+ const reachedTerminal = await this.processMessage(message);
199
+ if (reachedTerminal)
200
+ break;
210
201
  }
211
202
  }
212
203
  finally {
213
204
  this.running = false;
214
205
  }
215
206
  }
207
+ /**
208
+ * Manually process a single envelope. Parity with python-sdk's
209
+ * {@code Participant.process_event}. Useful for deterministic unit tests
210
+ * and for driving the participant from a foreign event loop without
211
+ * installing a {@link TransportAdapter}.
212
+ */
213
+ async processEvent(envelope) {
214
+ const payload = this.client.protoRegistry.decodeKnownPayload(envelope.mode, envelope.messageType, envelope.payload) ?? {};
215
+ const message = {
216
+ messageType: envelope.messageType,
217
+ sender: envelope.sender,
218
+ payload,
219
+ proposalId: payload.proposalId ?? payload.proposal_id,
220
+ raw: envelope,
221
+ seq: 0,
222
+ };
223
+ await this.processMessage(message);
224
+ }
225
+ buildHandlerContext() {
226
+ const sessionInfo = {
227
+ sessionId: this.sessionId,
228
+ mode: this.mode,
229
+ participants: this.participants,
230
+ modeVersion: this.modeVersion,
231
+ configurationVersion: this.configurationVersion,
232
+ policyVersion: this.policyVersion,
233
+ };
234
+ return {
235
+ participant: this,
236
+ projection: this.projection,
237
+ actions: this.actions,
238
+ session: sessionInfo,
239
+ log: (msg, details) => {
240
+ logging_1.logger.debug(`[${this.participantId}] ${msg}`, details ?? '');
241
+ },
242
+ };
243
+ }
244
+ /** Returns true if a terminal state was reached. */
245
+ async processMessage(message) {
246
+ const ctx = this.buildHandlerContext();
247
+ if (this.session && message.raw) {
248
+ const applyMethod = this.session.projection
249
+ .applyEnvelope;
250
+ if (typeof applyMethod === 'function') {
251
+ applyMethod.call(this.session.projection, message.raw, this.client.protoRegistry);
252
+ }
253
+ }
254
+ await this.dispatcher.dispatch(message, ctx);
255
+ const currentPhase = this.projection.phase;
256
+ if (currentPhase !== this.lastPhase) {
257
+ this.lastPhase = currentPhase;
258
+ await this.dispatcher.dispatchPhaseChange(currentPhase, ctx);
259
+ if (TERMINAL_PHASES.has(currentPhase)) {
260
+ const terminalResult = {
261
+ state: currentPhase,
262
+ commitment: this.projection.commitment,
263
+ };
264
+ await this.dispatcher.dispatchTerminal(terminalResult);
265
+ return true;
266
+ }
267
+ }
268
+ return false;
269
+ }
216
270
  async stop() {
217
271
  this.running = false;
218
272
  await this.transport.stop();
273
+ if (this.cancelCallbackServer) {
274
+ const srv = this.cancelCallbackServer;
275
+ this.cancelCallbackServer = undefined;
276
+ try {
277
+ await srv.close();
278
+ }
279
+ catch (err) {
280
+ logging_1.logger.debug('cancel_callback close failed', err);
281
+ }
282
+ }
283
+ }
284
+ /**
285
+ * Attach a running cancel-callback HTTP server to this participant.
286
+ * The server is closed automatically when {@link stop} is called.
287
+ * Parity with python-sdk's `Participant.attach_cancel_callback_server`.
288
+ */
289
+ attachCancelCallbackServer(server) {
290
+ this.cancelCallbackServer = server;
291
+ }
292
+ async emitInitiatorEnvelopes() {
293
+ if (!this.initiatorConfig || !this.session)
294
+ return;
295
+ const ss = this.initiatorConfig.sessionStart;
296
+ if ('start' in this.session && typeof this.session.start === 'function') {
297
+ await this.session.start({
298
+ intent: ss.intent,
299
+ participants: ss.participants,
300
+ ttlMs: ss.ttlMs,
301
+ contextId: ss.contextId,
302
+ extensions: ss.extensions,
303
+ roots: ss.roots,
304
+ });
305
+ }
306
+ const kickoff = this.initiatorConfig.kickoff;
307
+ if (!kickoff)
308
+ return;
309
+ if (kickoff.messageType === 'Proposal' && this.session instanceof decision_1.DecisionSession) {
310
+ const payload = kickoff.payload;
311
+ await this.session.propose({
312
+ proposalId: String(payload.proposalId ?? payload.proposal_id ?? `${this.sessionId}-kickoff`),
313
+ option: String(payload.option ?? 'decide'),
314
+ rationale: payload.rationale !== undefined ? String(payload.rationale) : undefined,
315
+ });
316
+ }
219
317
  }
220
318
  }
221
319
  exports.Participant = Participant;
@@ -11,8 +11,41 @@ export interface BootstrapPayload {
11
11
  auth_token?: string;
12
12
  agent_id?: string;
13
13
  secure?: boolean;
14
- /** Mirror of {@link MacpClientOptions.allowInsecure}; required when `secure` is false. */
15
14
  allow_insecure?: boolean;
16
15
  participants?: string[];
16
+ initiator?: {
17
+ session_start: {
18
+ intent: string;
19
+ participants: string[];
20
+ ttl_ms: number;
21
+ mode_version?: string;
22
+ configuration_version?: string;
23
+ policy_version?: string;
24
+ context?: Record<string, unknown>;
25
+ context_id?: string;
26
+ extensions?: Record<string, unknown>;
27
+ roots?: Array<{
28
+ uri: string;
29
+ name?: string;
30
+ }>;
31
+ };
32
+ kickoff?: {
33
+ message_type: string;
34
+ payload_type?: string;
35
+ payload: Record<string, unknown>;
36
+ };
37
+ };
38
+ metadata?: Record<string, unknown>;
39
+ /**
40
+ * Bind a cancel-callback HTTP endpoint (RFC-0001 §7.2 Option A). The
41
+ * orchestrator POSTs `{runId, reason}` to `http://host:port{path}` to
42
+ * request a clean shutdown. The runner starts the endpoint before
43
+ * entering the event loop and closes it when the participant stops.
44
+ */
45
+ cancel_callback?: {
46
+ host: string;
47
+ port: number;
48
+ path: string;
49
+ };
17
50
  }
18
51
  export declare function fromBootstrap(bootstrapPath?: string): Participant;
@@ -69,6 +69,26 @@ function fromBootstrap(bootstrapPath) {
69
69
  allowInsecure: payload.allow_insecure,
70
70
  auth,
71
71
  });
72
+ let initiator;
73
+ if (payload.initiator) {
74
+ const ss = payload.initiator.session_start;
75
+ initiator = {
76
+ sessionStart: {
77
+ intent: ss.intent,
78
+ participants: ss.participants,
79
+ ttlMs: ss.ttl_ms,
80
+ contextId: ss.context_id,
81
+ extensions: encodeExtensions(ss.extensions),
82
+ roots: ss.roots,
83
+ },
84
+ kickoff: payload.initiator.kickoff
85
+ ? {
86
+ messageType: payload.initiator.kickoff.message_type,
87
+ payload: payload.initiator.kickoff.payload,
88
+ }
89
+ : undefined,
90
+ };
91
+ }
72
92
  const config = {
73
93
  participantId: payload.participant_id,
74
94
  sessionId: payload.session_id,
@@ -79,6 +99,39 @@ function fromBootstrap(bootstrapPath) {
79
99
  modeVersion: payload.mode_version,
80
100
  configurationVersion: payload.configuration_version,
81
101
  policyVersion: payload.policy_version ?? constants_1.DEFAULT_POLICY_VERSION,
102
+ initiator,
103
+ cancelCallback: payload.cancel_callback
104
+ ? {
105
+ host: payload.cancel_callback.host,
106
+ port: payload.cancel_callback.port,
107
+ path: payload.cancel_callback.path,
108
+ }
109
+ : undefined,
82
110
  };
83
111
  return new participant_1.Participant(config);
84
112
  }
113
+ /**
114
+ * Serialise a JSON-native extensions map (as delivered in the bootstrap
115
+ * payload) into the `Record<string, Buffer>` form the SessionStart envelope
116
+ * expects. Each value is encoded as UTF-8 JSON so arbitrary structures
117
+ * round-trip through the protobuf `bytes` field. `Buffer` / `Uint8Array`
118
+ * values are passed through untouched so callers that already hold raw bytes
119
+ * don't pay a double-encode.
120
+ */
121
+ function encodeExtensions(extensions) {
122
+ if (!extensions)
123
+ return undefined;
124
+ const out = {};
125
+ for (const [key, value] of Object.entries(extensions)) {
126
+ if (Buffer.isBuffer(value)) {
127
+ out[key] = value;
128
+ }
129
+ else if (value instanceof Uint8Array) {
130
+ out[key] = Buffer.from(value);
131
+ }
132
+ else {
133
+ out[key] = Buffer.from(JSON.stringify(value), 'utf8');
134
+ }
135
+ }
136
+ return out;
137
+ }
@@ -19,6 +19,7 @@ export interface VotingStrategy {
19
19
  decideVote(projection: DecisionProjection): Promise<VoteResult>;
20
20
  }
21
21
  export declare function votingHandler(strategy: VotingStrategy): MessageHandler;
22
+ export declare function functionVoter(shouldVote: (projection: DecisionProjection) => boolean, decideVote: (projection: DecisionProjection) => Promise<VoteResult>): VotingStrategy;
22
23
  export declare function majorityVoter(options?: {
23
24
  positiveThreshold?: number;
24
25
  }): VotingStrategy;
@@ -33,6 +34,7 @@ export interface CommitmentStrategy {
33
34
  decideCommitment(projection: DecisionProjection): Promise<CommitmentResult>;
34
35
  }
35
36
  export declare function commitmentHandler(strategy: CommitmentStrategy): MessageHandler;
37
+ export declare function functionCommitter(shouldCommit: (projection: DecisionProjection) => boolean, decideCommitment: (projection: DecisionProjection) => Promise<CommitmentResult>): CommitmentStrategy;
36
38
  export declare function majorityCommitter(options?: {
37
39
  quorumSize?: number;
38
40
  action?: string;
@@ -3,8 +3,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.evaluationHandler = evaluationHandler;
4
4
  exports.functionEvaluator = functionEvaluator;
5
5
  exports.votingHandler = votingHandler;
6
+ exports.functionVoter = functionVoter;
6
7
  exports.majorityVoter = majorityVoter;
7
8
  exports.commitmentHandler = commitmentHandler;
9
+ exports.functionCommitter = functionCommitter;
8
10
  exports.majorityCommitter = majorityCommitter;
9
11
  const envelope_1 = require("../envelope");
10
12
  function evaluationHandler(strategy) {
@@ -43,6 +45,9 @@ function votingHandler(strategy) {
43
45
  }
44
46
  };
45
47
  }
48
+ function functionVoter(shouldVote, decideVote) {
49
+ return { shouldVote, decideVote };
50
+ }
46
51
  function majorityVoter(options) {
47
52
  const threshold = options?.positiveThreshold ?? 0.5;
48
53
  return {
@@ -80,6 +85,9 @@ function commitmentHandler(strategy) {
80
85
  }
81
86
  };
82
87
  }
88
+ function functionCommitter(shouldCommit, decideCommitment) {
89
+ return { shouldCommit, decideCommitment };
90
+ }
83
91
  function majorityCommitter(options) {
84
92
  const quorum = options?.quorumSize ?? 1;
85
93
  const action = options?.action ?? 'commit';
@@ -25,6 +25,11 @@ class GrpcTransportAdapter {
25
25
  }
26
26
  async *start() {
27
27
  this.stream = this.client.openStream({ auth: this.auth });
28
+ // RFC-MACP-0006-A1: Subscribe to the session with history replay.
29
+ // The runtime replays accepted envelopes then switches to live broadcast.
30
+ // This ensures non-initiator agents receive the SessionStart + Proposal
31
+ // regardless of spawn order or connection timing.
32
+ await this.stream.sendSubscribe(this.sessionId);
28
33
  for await (const envelope of this.stream.responses()) {
29
34
  if (envelope.sessionId !== this.sessionId)
30
35
  continue;
@@ -0,0 +1,64 @@
1
+ import { type AuthConfig } from './auth';
2
+ import type { MacpClient, MacpStream } from './client';
3
+ import type { BaseProjection } from './projections/base';
4
+ import type { Ack, Envelope, Root, SessionMetadata } from './types';
5
+ export interface BaseSessionOptions {
6
+ sessionId?: string;
7
+ modeVersion?: string;
8
+ configurationVersion?: string;
9
+ policyVersion?: string;
10
+ auth?: AuthConfig;
11
+ }
12
+ /**
13
+ * Abstract base class for mode session helpers — parity with python-sdk's
14
+ * `macp_sdk.base_session.BaseSession`. Provides the shared lifecycle
15
+ * (start, commit, cancel, metadata, openStream) and the two identity-guarded
16
+ * helpers (`senderFor`, `sendAndTrack`) that every mode session needs.
17
+ *
18
+ * Concrete session classes (DecisionSession, etc.) pre-date this base class
19
+ * and implement their own equivalents; BaseSession is the recommended
20
+ * extension point for custom modes registered via `registerExtMode`.
21
+ */
22
+ export declare abstract class BaseSession<P extends BaseProjection> {
23
+ readonly client: MacpClient;
24
+ readonly sessionId: string;
25
+ readonly modeVersion: string;
26
+ readonly configurationVersion: string;
27
+ readonly policyVersion: string;
28
+ readonly auth?: AuthConfig;
29
+ readonly projection: P;
30
+ protected abstract readonly mode: string;
31
+ constructor(client: MacpClient, options?: BaseSessionOptions);
32
+ /** Return a new projection instance. Subclasses construct their mode-specific projection here. */
33
+ protected abstract createProjection(): P;
34
+ /** Resolve the envelope sender, raising {@link MacpIdentityMismatchError} on conflict. */
35
+ protected senderFor(sender: string | undefined, auth?: AuthConfig): string;
36
+ /** Send an envelope and, on ACK, feed it to the projection. */
37
+ protected sendAndTrack(envelope: Envelope, auth?: AuthConfig): Promise<Ack>;
38
+ /** Send SessionStart and begin tracking via the projection. */
39
+ start(input: {
40
+ intent: string;
41
+ participants: string[];
42
+ ttlMs: number;
43
+ contextId?: string;
44
+ extensions?: Record<string, Buffer>;
45
+ roots?: Root[];
46
+ sender?: string;
47
+ auth?: AuthConfig;
48
+ }): Promise<Ack>;
49
+ /** Send a Commitment envelope to resolve the session. */
50
+ commit(input: {
51
+ action: string;
52
+ authorityScope: string;
53
+ reason: string;
54
+ commitmentId?: string;
55
+ outcomePositive?: boolean;
56
+ sender?: string;
57
+ auth?: AuthConfig;
58
+ }): Promise<Ack>;
59
+ metadata(auth?: AuthConfig): Promise<{
60
+ metadata: SessionMetadata;
61
+ }>;
62
+ cancel(reason?: string, auth?: AuthConfig): Promise<Ack>;
63
+ openStream(auth?: AuthConfig): MacpStream;
64
+ }
@@ -0,0 +1,112 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BaseSession = void 0;
4
+ const auth_1 = require("./auth");
5
+ const constants_1 = require("./constants");
6
+ const envelope_1 = require("./envelope");
7
+ const logging_1 = require("./logging");
8
+ const validation_1 = require("./validation");
9
+ /**
10
+ * Abstract base class for mode session helpers — parity with python-sdk's
11
+ * `macp_sdk.base_session.BaseSession`. Provides the shared lifecycle
12
+ * (start, commit, cancel, metadata, openStream) and the two identity-guarded
13
+ * helpers (`senderFor`, `sendAndTrack`) that every mode session needs.
14
+ *
15
+ * Concrete session classes (DecisionSession, etc.) pre-date this base class
16
+ * and implement their own equivalents; BaseSession is the recommended
17
+ * extension point for custom modes registered via `registerExtMode`.
18
+ */
19
+ class BaseSession {
20
+ client;
21
+ sessionId;
22
+ modeVersion;
23
+ configurationVersion;
24
+ policyVersion;
25
+ auth;
26
+ projection;
27
+ constructor(client, options = {}) {
28
+ this.client = client;
29
+ if (options.sessionId)
30
+ (0, validation_1.validateSessionId)(options.sessionId);
31
+ this.sessionId = options.sessionId ?? (0, envelope_1.newSessionId)();
32
+ this.modeVersion = options.modeVersion ?? constants_1.DEFAULT_MODE_VERSION;
33
+ this.configurationVersion = options.configurationVersion ?? constants_1.DEFAULT_CONFIGURATION_VERSION;
34
+ this.policyVersion = options.policyVersion ?? constants_1.DEFAULT_POLICY_VERSION;
35
+ this.auth = options.auth;
36
+ this.projection = this.createProjection();
37
+ }
38
+ /** Resolve the envelope sender, raising {@link MacpIdentityMismatchError} on conflict. */
39
+ senderFor(sender, auth) {
40
+ const effectiveAuth = auth ?? this.auth ?? this.client.auth;
41
+ (0, auth_1.assertSenderMatchesIdentity)(effectiveAuth, sender);
42
+ return sender ?? (0, auth_1.authSender)(effectiveAuth) ?? '';
43
+ }
44
+ /** Send an envelope and, on ACK, feed it to the projection. */
45
+ async sendAndTrack(envelope, auth) {
46
+ logging_1.logger.debug('send', {
47
+ session: envelope.sessionId,
48
+ messageType: envelope.messageType,
49
+ sender: envelope.sender,
50
+ });
51
+ const ack = await this.client.send(envelope, { auth: auth ?? this.auth });
52
+ if (ack.ok)
53
+ this.projection.applyEnvelope(envelope, this.client.protoRegistry);
54
+ else
55
+ logging_1.logger.warn('nack', { session: envelope.sessionId, code: ack.error?.code });
56
+ return ack;
57
+ }
58
+ /** Send SessionStart and begin tracking via the projection. */
59
+ async start(input) {
60
+ (0, validation_1.validateParticipantCount)(input.participants.length);
61
+ const payload = (0, envelope_1.buildSessionStartPayload)({
62
+ intent: input.intent,
63
+ participants: input.participants,
64
+ ttlMs: input.ttlMs,
65
+ contextId: input.contextId,
66
+ extensions: input.extensions,
67
+ roots: input.roots,
68
+ modeVersion: this.modeVersion,
69
+ configurationVersion: this.configurationVersion,
70
+ policyVersion: this.policyVersion,
71
+ });
72
+ const envelope = (0, envelope_1.buildEnvelope)({
73
+ mode: this.mode,
74
+ messageType: 'SessionStart',
75
+ sessionId: this.sessionId,
76
+ sender: this.senderFor(input.sender, input.auth),
77
+ payload: this.client.protoRegistry.encodeKnownPayload(this.mode, 'SessionStart', (0, envelope_1.toProtoPayload)(payload)),
78
+ });
79
+ return this.sendAndTrack(envelope, input.auth ?? this.auth);
80
+ }
81
+ /** Send a Commitment envelope to resolve the session. */
82
+ async commit(input) {
83
+ const payload = (0, envelope_1.buildCommitmentPayload)({
84
+ action: input.action,
85
+ authorityScope: input.authorityScope,
86
+ reason: input.reason,
87
+ commitmentId: input.commitmentId,
88
+ outcomePositive: input.outcomePositive,
89
+ modeVersion: this.modeVersion,
90
+ configurationVersion: this.configurationVersion,
91
+ policyVersion: this.policyVersion,
92
+ });
93
+ const envelope = (0, envelope_1.buildEnvelope)({
94
+ mode: this.mode,
95
+ messageType: 'Commitment',
96
+ sessionId: this.sessionId,
97
+ sender: this.senderFor(input.sender, input.auth),
98
+ payload: this.client.protoRegistry.encodeKnownPayload(this.mode, 'Commitment', (0, envelope_1.toProtoPayload)(payload)),
99
+ });
100
+ return this.sendAndTrack(envelope, input.auth);
101
+ }
102
+ metadata(auth) {
103
+ return this.client.getSession(this.sessionId, { auth: auth ?? this.auth });
104
+ }
105
+ cancel(reason = '', auth) {
106
+ return this.client.cancelSession(this.sessionId, reason, { auth: auth ?? this.auth });
107
+ }
108
+ openStream(auth) {
109
+ return this.client.openStream({ auth: auth ?? this.auth });
110
+ }
111
+ }
112
+ exports.BaseSession = BaseSession;
package/dist/client.d.ts CHANGED
@@ -35,7 +35,27 @@ export declare class MacpStream {
35
35
  constructor(call: grpc.ClientDuplexStream<any, any>);
36
36
  onInlineError(callback: InlineErrorCallback): void;
37
37
  send(envelope: Envelope): Promise<void>;
38
+ /**
39
+ * RFC-MACP-0006-A1: Send a subscribe-only frame to receive session history
40
+ * + live broadcast. The runtime replays accepted envelopes from
41
+ * `afterSequence` onwards, then continues with live broadcast.
42
+ */
43
+ sendSubscribe(sessionId: string, afterSequence?: number): Promise<void>;
38
44
  responses(): AsyncGenerator<Envelope, void, void>;
45
+ /**
46
+ * Read a single envelope from the stream. Parity with python-sdk's
47
+ * {@code MacpStream.read(timeout)}.
48
+ *
49
+ * @param timeoutMs Maximum time to wait for the next envelope. If omitted,
50
+ * blocks indefinitely until an envelope arrives, the stream
51
+ * ends, or an error occurs.
52
+ * @returns The next envelope, or {@code null} if the stream has
53
+ * ended.
54
+ * @throws MacpTimeoutError If {@code timeoutMs} elapses before an envelope
55
+ * arrives.
56
+ * @throws MacpTransportError If the underlying stream errored.
57
+ */
58
+ read(timeoutMs?: number): Promise<Envelope | null>;
39
59
  close(): void;
40
60
  }
41
61
  export declare class MacpClient {
@@ -125,6 +145,11 @@ export declare class MacpClient {
125
145
  auth?: AuthConfig;
126
146
  deadlineMs?: number;
127
147
  }): Promise<PolicyDescriptor[]>;
148
+ listSessions(options?: {
149
+ auth?: AuthConfig;
150
+ deadlineMs?: number;
151
+ }): Promise<SessionMetadata[]>;
152
+ watchSessions(auth?: AuthConfig): grpc.ClientReadableStream<any>;
128
153
  watchPolicies(auth?: AuthConfig): grpc.ClientReadableStream<any>;
129
154
  /** @deprecated Use {@link watchPolicies}. Scheduled for removal in 0.4.0. */
130
155
  _watchPolicies(auth?: AuthConfig): grpc.ClientReadableStream<any>;