@canonmsg/agent-sdk 1.3.0 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
- import { type AddMemberResult, type CanonContact, type ContactCardPayload, type CreateContactRequestResult } from '@canonmsg/core';
2
- import type { CanonAgentOptions, ContactAddedHandler, ContactRemovedHandler, CreateConversationOptions, MessageHandler, ReachOutOptions, ReachOutResult, ContactRequestHandler, RuntimeSignalHandler } from './types.js';
1
+ import { type AddMemberResult, type CanonContact, type CanonRuntimePrimitiveId, type ContactCardPayload, type CreateContactRequestResult } from '@canonmsg/core';
2
+ import type { CanonAgentOptions, ContactAddedHandler, ContactRemovedHandler, CreateConversationOptions, MessageHandler, ReachOutOptions, ReachOutResult, ContactRequestHandler, RuntimeSignalHandler, RuntimePrimitiveHandler } from './types.js';
3
3
  /**
4
4
  * Contact-graph operations exposed under `agent.contacts`. Wraps the REST
5
5
  * endpoints in CanonClient — the same surface a human user would hit through
@@ -33,6 +33,8 @@ export declare class CanonAgent {
33
33
  private interruptHandler;
34
34
  private stopAndDropHandler;
35
35
  private newSessionHandler;
36
+ private readonly primitiveHandlers;
37
+ private primitiveFallbackHandler;
36
38
  /** Contact-graph operations (`agent.contacts.*`). Initialized in the constructor. */
37
39
  readonly contacts: AgentContactsAPI;
38
40
  /** Block/unblock operations (`agent.users.*`). Initialized in the constructor. */
@@ -45,6 +47,7 @@ export declare class CanonAgent {
45
47
  private runtimeHeartbeatTimer;
46
48
  private runtimeControlPollTimer;
47
49
  private readonly lastSeenSignal;
50
+ private readonly primitiveRequestDedupe;
48
51
  private readonly activeAbortControllers;
49
52
  private readonly conversationMemberIds;
50
53
  private readonly pendingMembershipChanges;
@@ -57,6 +60,7 @@ export declare class CanonAgent {
57
60
  on(event: 'interrupt', handler: RuntimeSignalHandler): void;
58
61
  on(event: 'stopAndDrop', handler: RuntimeSignalHandler): void;
59
62
  on(event: 'newSession', handler: RuntimeSignalHandler): void;
63
+ onPrimitive(primitive: CanonRuntimePrimitiveId | '*', handler: RuntimePrimitiveHandler): void;
60
64
  /**
61
65
  * Resolve admission live for a target user (typically read off a shared
62
66
  * contact card) and route into either an immediate message or a contact
@@ -100,6 +104,8 @@ export declare class CanonAgent {
100
104
  private hasStopAndDropSupport;
101
105
  private hasNewSessionSupport;
102
106
  private hasRuntimeSignalSupport;
107
+ private hasRuntimePrimitiveSupport;
108
+ private hasRuntimeControlSupport;
103
109
  private buildRuntimeDescriptor;
104
110
  private buildRuntimeCapabilities;
105
111
  private publishAgentRuntime;
@@ -113,7 +119,10 @@ export declare class CanonAgent {
113
119
  private baselineRuntimeControlSignals;
114
120
  private startRuntimeControlPolling;
115
121
  private stopRuntimeControlPolling;
116
- private pollRuntimeControlSignals;
122
+ private pollRuntimeControls;
123
+ private handleRuntimePrimitiveRequests;
124
+ private clearRuntimePrimitiveRequest;
125
+ private prunePrimitiveRequestDedupe;
117
126
  private handleRuntimeSignal;
118
127
  private abortActiveTurns;
119
128
  private resolveBatchDeliveryIntent;
@@ -5,6 +5,8 @@ import { Debouncer } from './debouncer.js';
5
5
  import { materializeMessageMedia, sendMediaFileMessage, uploadMediaFile, } from './media.js';
6
6
  import { SessionManager } from './session-manager.js';
7
7
  const AGENT_RUNTIME_HEARTBEAT_MS = 30_000;
8
+ const RUNTIME_PRIMITIVE_DEDUPE_TTL_MS = 5 * 60 * 1000;
9
+ const RUNTIME_PRIMITIVE_DEDUPE_MAX = 1_000;
8
10
  const SDK_RUNTIME_CAPABILITIES = {
9
11
  supportsInterrupt: false,
10
12
  supportsQueue: true,
@@ -18,9 +20,133 @@ const DEFAULT_SDK_RUNTIME_DESCRIPTOR = {
18
20
  supportsInterrupt: false,
19
21
  streamingTextMode: 'snapshot',
20
22
  };
23
+ const STANDARD_PRIMITIVE_COMMANDS = {
24
+ 'runtime.status': {
25
+ id: 'runtime-status',
26
+ label: 'Runtime status',
27
+ description: 'Ask the runtime for its current status.',
28
+ primitive: 'runtime.status',
29
+ aliases: ['status'],
30
+ category: 'runtime',
31
+ placements: ['composer_slash', 'command_palette'],
32
+ availability: ['always'],
33
+ dispatch: { kind: 'primitive', primitive: 'runtime.status' },
34
+ },
35
+ 'runtime.reasoning.set': {
36
+ id: 'thinking-level',
37
+ label: 'Thinking level',
38
+ description: 'Set the runtime reasoning or effort level.',
39
+ primitive: 'runtime.reasoning.set',
40
+ aliases: ['think', 'effort'],
41
+ category: 'runtime',
42
+ placements: ['composer_slash', 'command_palette'],
43
+ availability: ['always'],
44
+ args: [{
45
+ id: 'level',
46
+ label: 'Level',
47
+ kind: 'enum',
48
+ required: true,
49
+ choices: [
50
+ { value: 'low', label: 'Low' },
51
+ { value: 'medium', label: 'Medium' },
52
+ { value: 'high', label: 'High' },
53
+ ],
54
+ }],
55
+ dispatch: { kind: 'primitive', primitive: 'runtime.reasoning.set' },
56
+ },
57
+ 'runtime.verbosity.set': {
58
+ id: 'verbosity',
59
+ label: 'Verbosity',
60
+ description: 'Set runtime verbosity.',
61
+ primitive: 'runtime.verbosity.set',
62
+ aliases: ['verbose'],
63
+ category: 'runtime',
64
+ placements: ['composer_slash', 'command_palette'],
65
+ availability: ['always'],
66
+ args: [{
67
+ id: 'level',
68
+ label: 'Level',
69
+ kind: 'enum',
70
+ required: true,
71
+ choices: [
72
+ { value: 'off', label: 'Off' },
73
+ { value: 'on', label: 'On' },
74
+ { value: 'full', label: 'Full' },
75
+ ],
76
+ }],
77
+ dispatch: { kind: 'primitive', primitive: 'runtime.verbosity.set' },
78
+ },
79
+ 'runtime.usage': {
80
+ id: 'usage',
81
+ label: 'Usage',
82
+ description: 'Show or change runtime usage reporting.',
83
+ primitive: 'runtime.usage',
84
+ aliases: ['usage'],
85
+ category: 'runtime',
86
+ placements: ['composer_slash', 'command_palette'],
87
+ availability: ['always'],
88
+ dispatch: { kind: 'primitive', primitive: 'runtime.usage' },
89
+ },
90
+ 'context.compact': {
91
+ id: 'compact-context',
92
+ label: 'Compact context',
93
+ description: 'Ask the runtime to compact its conversation context.',
94
+ primitive: 'context.compact',
95
+ aliases: ['compact'],
96
+ category: 'session',
97
+ placements: ['composer_slash', 'command_palette'],
98
+ availability: ['always'],
99
+ dispatch: { kind: 'primitive', primitive: 'context.compact' },
100
+ },
101
+ 'session.new': {
102
+ id: 'new-session-primitive',
103
+ label: 'New session',
104
+ description: 'Ask the runtime to start a fresh session.',
105
+ primitive: 'session.new',
106
+ aliases: ['new'],
107
+ category: 'session',
108
+ placements: ['composer_slash', 'command_palette', 'session_strip'],
109
+ availability: ['always'],
110
+ dispatch: { kind: 'primitive', primitive: 'session.new' },
111
+ },
112
+ 'session.reset': {
113
+ id: 'reset-session',
114
+ label: 'Reset session',
115
+ description: 'Ask the runtime to reset the current session.',
116
+ primitive: 'session.reset',
117
+ aliases: ['reset'],
118
+ category: 'session',
119
+ placements: ['composer_slash', 'command_palette'],
120
+ availability: ['always'],
121
+ dispatch: { kind: 'primitive', primitive: 'session.reset' },
122
+ },
123
+ };
21
124
  function sleep(ms) {
22
125
  return new Promise((resolve) => setTimeout(resolve, ms));
23
126
  }
127
+ function isRuntimePrimitiveId(value) {
128
+ return value === 'runtime.status'
129
+ || value === 'runtime.reasoning.set'
130
+ || value === 'runtime.verbosity.set'
131
+ || value === 'runtime.usage'
132
+ || value === 'context.compact'
133
+ || value === 'session.new'
134
+ || value === 'session.reset';
135
+ }
136
+ function normalizePrimitiveArgs(value) {
137
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
138
+ return {};
139
+ }
140
+ const args = {};
141
+ for (const [key, rawValue] of Object.entries(value)) {
142
+ if (!/^[A-Za-z0-9_-]+$/.test(key))
143
+ continue;
144
+ if (typeof rawValue === 'boolean' || typeof rawValue === 'string') {
145
+ args[key] = rawValue;
146
+ }
147
+ }
148
+ return args;
149
+ }
24
150
  export class CanonAgent {
25
151
  options;
26
152
  apiClient;
@@ -36,6 +162,8 @@ export class CanonAgent {
36
162
  interruptHandler = null;
37
163
  stopAndDropHandler = null;
38
164
  newSessionHandler = null;
165
+ primitiveHandlers = new Map();
166
+ primitiveFallbackHandler = null;
39
167
  /** Contact-graph operations (`agent.contacts.*`). Initialized in the constructor. */
40
168
  contacts;
41
169
  /** Block/unblock operations (`agent.users.*`). Initialized in the constructor. */
@@ -48,6 +176,7 @@ export class CanonAgent {
48
176
  runtimeHeartbeatTimer = null;
49
177
  runtimeControlPollTimer = null;
50
178
  lastSeenSignal = new Map();
179
+ primitiveRequestDedupe = new Map();
51
180
  activeAbortControllers = new Map();
52
181
  conversationMemberIds = new Map();
53
182
  pendingMembershipChanges = new Map();
@@ -84,6 +213,14 @@ export class CanonAgent {
84
213
  this.interruptHandler = options.runtimeControls?.onInterrupt ?? null;
85
214
  this.stopAndDropHandler = options.runtimeControls?.onStopAndDrop ?? null;
86
215
  this.newSessionHandler = options.runtimeControls?.onNewSession ?? null;
216
+ for (const [primitive, handler] of Object.entries(options.runtimePrimitives ?? {})) {
217
+ if (primitive === '*') {
218
+ this.primitiveFallbackHandler = handler;
219
+ }
220
+ else if (isRuntimePrimitiveId(primitive)) {
221
+ this.primitiveHandlers.set(primitive, handler);
222
+ }
223
+ }
87
224
  }
88
225
  on(event, handler) {
89
226
  if (event === 'message') {
@@ -134,6 +271,18 @@ export class CanonAgent {
134
271
  }
135
272
  this.contactRemovedHandler = handler;
136
273
  }
274
+ onPrimitive(primitive, handler) {
275
+ if (primitive === '*') {
276
+ this.primitiveFallbackHandler = handler;
277
+ }
278
+ else {
279
+ this.primitiveHandlers.set(primitive, handler);
280
+ }
281
+ if (this.running) {
282
+ this.startRuntimeControlPolling();
283
+ }
284
+ void this.publishAgentRuntime().catch(() => { });
285
+ }
137
286
  /**
138
287
  * Resolve admission live for a target user (typically read off a shared
139
288
  * contact card) and route into either an immediate message or a contact
@@ -371,6 +520,12 @@ export class CanonAgent {
371
520
  hasRuntimeSignalSupport() {
372
521
  return this.hasInterruptSupport() || this.hasStopAndDropSupport() || this.hasNewSessionSupport();
373
522
  }
523
+ hasRuntimePrimitiveSupport() {
524
+ return this.primitiveHandlers.size > 0 || Boolean(this.primitiveFallbackHandler);
525
+ }
526
+ hasRuntimeControlSupport() {
527
+ return this.hasRuntimeSignalSupport() || this.hasRuntimePrimitiveSupport();
528
+ }
374
529
  buildRuntimeDescriptor() {
375
530
  const source = this.options.runtimeDescriptor ?? DEFAULT_SDK_RUNTIME_DESCRIPTOR;
376
531
  const hasInterrupt = this.hasInterruptSupport();
@@ -399,9 +554,23 @@ export class CanonAgent {
399
554
  if (hasNewSession && !hasNewSessionAction) {
400
555
  actions.push(RUNTIME_NEW_SESSION_ACTION);
401
556
  }
557
+ const commands = [...(source.commands ?? [])].filter((command) => {
558
+ if (command.dispatch.kind !== 'primitive')
559
+ return true;
560
+ return this.primitiveHandlers.has(command.dispatch.primitive)
561
+ || Boolean(this.primitiveFallbackHandler);
562
+ });
563
+ const hasCommandForPrimitive = (primitive) => commands.some((command) => (command.primitive === primitive
564
+ || (command.dispatch.kind === 'primitive' && command.dispatch.primitive === primitive)));
565
+ for (const primitive of this.primitiveHandlers.keys()) {
566
+ if (!hasCommandForPrimitive(primitive)) {
567
+ commands.push(STANDARD_PRIMITIVE_COMMANDS[primitive]);
568
+ }
569
+ }
402
570
  return {
403
571
  ...source,
404
572
  supportsInterrupt: hasInterrupt,
573
+ commands,
405
574
  actions,
406
575
  };
407
576
  }
@@ -493,10 +662,10 @@ export class CanonAgent {
493
662
  }));
494
663
  }
495
664
  startRuntimeControlPolling() {
496
- if (!this.agentId || this.runtimeControlPollTimer || !this.hasRuntimeSignalSupport())
665
+ if (!this.agentId || this.runtimeControlPollTimer || !this.hasRuntimeControlSupport())
497
666
  return;
498
667
  this.runtimeControlPollTimer = setInterval(() => {
499
- void this.pollRuntimeControlSignals();
668
+ void this.pollRuntimeControls();
500
669
  }, 2_000);
501
670
  this.runtimeControlPollTimer.unref?.();
502
671
  }
@@ -506,16 +675,97 @@ export class CanonAgent {
506
675
  clearInterval(this.runtimeControlPollTimer);
507
676
  this.runtimeControlPollTimer = null;
508
677
  }
509
- async pollRuntimeControlSignals() {
510
- if (!this.agentId || !this.hasRuntimeSignalSupport())
678
+ async pollRuntimeControls() {
679
+ if (!this.agentId || !this.hasRuntimeControlSupport())
511
680
  return;
512
681
  await Promise.all(this.cachedConversationIds.map(async (conversationId) => {
513
- const raw = await Promise.resolve(rtdbRead(`/control/${conversationId}/${this.agentId}/signal`)).catch(() => null);
514
- if (!raw || typeof raw !== 'object')
515
- return;
516
- await this.handleRuntimeSignal(conversationId, raw);
682
+ if (this.hasRuntimeSignalSupport()) {
683
+ const raw = await Promise.resolve(rtdbRead(`/control/${conversationId}/${this.agentId}/signal`)).catch(() => null);
684
+ if (raw && typeof raw === 'object') {
685
+ await this.handleRuntimeSignal(conversationId, raw);
686
+ }
687
+ }
688
+ if (this.hasRuntimePrimitiveSupport()) {
689
+ const raw = await Promise.resolve(rtdbRead(`/control/${conversationId}/${this.agentId}/primitive`)).catch(() => null);
690
+ if (raw && typeof raw === 'object') {
691
+ await this.handleRuntimePrimitiveRequests(conversationId, raw);
692
+ }
693
+ }
517
694
  }));
518
695
  }
696
+ async handleRuntimePrimitiveRequests(conversationId, raw) {
697
+ if (!this.agentId)
698
+ return;
699
+ this.prunePrimitiveRequestDedupe();
700
+ const requests = Object.entries(raw)
701
+ .map(([requestId, value]) => ({ requestId, value }))
702
+ .filter((entry) => (Boolean(entry.requestId)
703
+ && Boolean(entry.value)
704
+ && typeof entry.value === 'object'
705
+ && !Array.isArray(entry.value)))
706
+ .sort((a, b) => Number(a.value.updatedAt ?? 0) - Number(b.value.updatedAt ?? 0));
707
+ for (const { requestId, value } of requests) {
708
+ const requestKey = `${conversationId}:${requestId}`;
709
+ if (this.primitiveRequestDedupe.has(requestKey))
710
+ continue;
711
+ this.primitiveRequestDedupe.set(requestKey, Date.now());
712
+ let cleared = false;
713
+ try {
714
+ const primitive = value.id;
715
+ if (!isRuntimePrimitiveId(primitive)) {
716
+ cleared = await this.clearRuntimePrimitiveRequest(conversationId, requestId);
717
+ continue;
718
+ }
719
+ const handler = this.primitiveHandlers.get(primitive) ?? this.primitiveFallbackHandler;
720
+ if (!handler) {
721
+ cleared = await this.clearRuntimePrimitiveRequest(conversationId, requestId);
722
+ continue;
723
+ }
724
+ const args = normalizePrimitiveArgs(value.args);
725
+ await Promise.resolve(handler({
726
+ conversationId,
727
+ primitive,
728
+ args,
729
+ requestId,
730
+ updatedAt: typeof value.updatedAt === 'number' ? value.updatedAt : undefined,
731
+ rawText: typeof value.rawText === 'string' ? value.rawText : undefined,
732
+ alias: typeof value.alias === 'string' ? value.alias : undefined,
733
+ })).catch((error) => {
734
+ console.error(`[canon-sdk] Runtime primitive ${primitive} handler failed for ${conversationId}:`, error);
735
+ });
736
+ cleared = await this.clearRuntimePrimitiveRequest(conversationId, requestId);
737
+ }
738
+ finally {
739
+ if (cleared) {
740
+ this.primitiveRequestDedupe.delete(requestKey);
741
+ }
742
+ }
743
+ }
744
+ }
745
+ async clearRuntimePrimitiveRequest(conversationId, requestId) {
746
+ if (!this.agentId)
747
+ return false;
748
+ try {
749
+ await Promise.resolve(rtdbWrite(`/control/${conversationId}/${this.agentId}/primitive/${requestId}`, null));
750
+ return true;
751
+ }
752
+ catch {
753
+ return false;
754
+ }
755
+ }
756
+ prunePrimitiveRequestDedupe(now = Date.now()) {
757
+ for (const [key, timestamp] of this.primitiveRequestDedupe) {
758
+ if (now - timestamp >= RUNTIME_PRIMITIVE_DEDUPE_TTL_MS) {
759
+ this.primitiveRequestDedupe.delete(key);
760
+ }
761
+ }
762
+ while (this.primitiveRequestDedupe.size > RUNTIME_PRIMITIVE_DEDUPE_MAX) {
763
+ const oldestKey = this.primitiveRequestDedupe.keys().next().value;
764
+ if (!oldestKey)
765
+ break;
766
+ this.primitiveRequestDedupe.delete(oldestKey);
767
+ }
768
+ }
519
769
  async handleRuntimeSignal(conversationId, raw) {
520
770
  if (!this.agentId)
521
771
  return;
package/dist/index.d.ts CHANGED
@@ -7,4 +7,4 @@ export { DEFAULT_MEDIA_CACHE_DIR, getCodexImagePath, getMessageAttachments, infe
7
7
  export type { AnthropicImageBlock, AnthropicImageMimeType, MaterializeMediaOptions, MaterializedCanonAttachment, ReplyWithFileOptions, UploadMediaFileOptions, } from './media.js';
8
8
  export type { SessionConfig, Session } from './session-manager.js';
9
9
  export type { AgentContext, CanonGroupContext, CanonKnownRecentParticipant, CanonMembershipChange, CanonContactRequest, CanonMessage, CanonConversation, CanonSelfContext, SendContextualMessageOptions, SendContextualMessageResult, SendContextualSelfContextInput, SendMessageOptions, CreateConversationOptions, } from '@canonmsg/core';
10
- export type { CanonAgentOptions, ContactAddedHandler, ContactRemovedHandler, ContactRequestHandler, MessageHandler, MessageHandlerContext, ProgressMessageOptions, ProgressMessageResult, ReachOutOptions, ReachOutResult, SessionInfo, SessionOptions, DeliveryMode, } from './types.js';
10
+ export type { CanonAgentOptions, ContactAddedHandler, ContactRemovedHandler, ContactRequestHandler, MessageHandler, MessageHandlerContext, ProgressMessageOptions, ProgressMessageResult, ReachOutOptions, ReachOutResult, RuntimePrimitiveContext, RuntimePrimitiveHandler, RuntimePrimitiveHandlers, SessionInfo, SessionOptions, DeliveryMode, } from './types.js';
package/dist/types.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- export type { AddMemberResult, AgentClientType, CanonGroupContext, CanonRuntimeDescriptor, CanonMessage, CanonConversation, CanonContact, CanonContactRequest, CanonResolveAdmissionResult, ContactAddedPayload, ContactRemovedPayload, ContactSource, AgentContext, ResolvedAdmissionState, ResolvedAdmissionTargetSummary, ResolvedTargetAdmissionPayload, CanonSelfContext, SendContextualMessageOptions, SendContextualMessageResult, SendContextualSelfContextInput, SendMessageOptions, SessionConfig, CreateConversationOptions, TurnLifecycleState, } from '@canonmsg/core';
2
- import type { AddMemberResult, CanonGroupContext, CanonMessage, CanonConversation, ContactCardPayload, CanonRuntimeActionDispatch, SendMessageOptions, SendContextualSelfContextInput, SessionConfig } from '@canonmsg/core';
1
+ export type { AddMemberResult, AgentClientType, CanonGroupContext, CanonRuntimeDescriptor, CanonRuntimePrimitiveId, CanonMessage, CanonConversation, CanonContact, CanonContactRequest, CanonResolveAdmissionResult, ContactAddedPayload, ContactRemovedPayload, ContactSource, AgentContext, ResolvedAdmissionState, ResolvedAdmissionTargetSummary, ResolvedTargetAdmissionPayload, CanonSelfContext, SendContextualMessageOptions, SendContextualMessageResult, SendContextualSelfContextInput, SendMessageOptions, SessionConfig, CreateConversationOptions, TurnLifecycleState, } from '@canonmsg/core';
2
+ import type { AddMemberResult, CanonGroupContext, CanonMessage, CanonConversation, ContactCardPayload, CanonRuntimeActionDispatch, CanonRuntimePrimitiveId, SendMessageOptions, SendContextualSelfContextInput, SessionConfig } from '@canonmsg/core';
3
3
  import type { MaterializeMediaOptions, MaterializedCanonAttachment, ReplyWithFileOptions, UploadMediaFileOptions } from './media.js';
4
4
  export interface ProgressMessageOptions extends SendMessageOptions {
5
5
  /**
@@ -123,6 +123,19 @@ export interface RuntimeControlHandlers {
123
123
  onStopAndDrop?: RuntimeSignalHandler;
124
124
  onNewSession?: RuntimeSignalHandler;
125
125
  }
126
+ export interface RuntimePrimitiveContext {
127
+ conversationId: string;
128
+ primitive: CanonRuntimePrimitiveId;
129
+ args: Record<string, string | boolean>;
130
+ rawText?: string;
131
+ alias?: string;
132
+ requestId?: string;
133
+ updatedAt?: number;
134
+ }
135
+ export type RuntimePrimitiveHandler = (context: RuntimePrimitiveContext) => void | Promise<void>;
136
+ export type RuntimePrimitiveHandlers = Partial<Record<CanonRuntimePrimitiveId, RuntimePrimitiveHandler>> & {
137
+ '*'?: RuntimePrimitiveHandler;
138
+ };
126
139
  export interface CanonAgentOptions {
127
140
  apiKey: string;
128
141
  baseUrl?: string;
@@ -141,6 +154,8 @@ export interface CanonAgentOptions {
141
154
  runtimeDescriptor?: import('@canonmsg/core').CanonRuntimeDescriptor;
142
155
  /** Optional Canon runtime signal handlers. Enables interrupt controls when provided. */
143
156
  runtimeControls?: RuntimeControlHandlers;
157
+ /** Optional typed runtime primitive handlers. Enables descriptor-backed command controls when provided. */
158
+ runtimePrimitives?: RuntimePrimitiveHandlers;
144
159
  /**
145
160
  * Enable RTDB session-state reporting. Off by default.
146
161
  * Turn-state reporting is automatic while handlers run.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@canonmsg/agent-sdk",
3
- "version": "1.3.0",
3
+ "version": "1.3.1",
4
4
  "description": "Canon Agent SDK — build AI agents that participate in Canon conversations",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -28,7 +28,7 @@
28
28
  "node": ">=18.0.0"
29
29
  },
30
30
  "dependencies": {
31
- "@canonmsg/core": "^0.17.0"
31
+ "@canonmsg/core": "^0.17.2"
32
32
  },
33
33
  "publishConfig": {
34
34
  "access": "public"