@canonmsg/agent-sdk 1.1.4 → 1.2.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.
@@ -32,6 +32,7 @@ export declare class CanonAgent {
32
32
  private contactRemovedHandler;
33
33
  private interruptHandler;
34
34
  private stopAndDropHandler;
35
+ private newSessionHandler;
35
36
  /** Contact-graph operations (`agent.contacts.*`). Initialized in the constructor. */
36
37
  readonly contacts: AgentContactsAPI;
37
38
  /** Block/unblock operations (`agent.users.*`). Initialized in the constructor. */
@@ -53,6 +54,7 @@ export declare class CanonAgent {
53
54
  on(event: 'contactRemoved', handler: ContactRemovedHandler): void;
54
55
  on(event: 'interrupt', handler: RuntimeSignalHandler): void;
55
56
  on(event: 'stopAndDrop', handler: RuntimeSignalHandler): void;
57
+ on(event: 'newSession', handler: RuntimeSignalHandler): void;
56
58
  /**
57
59
  * Resolve admission live for a target user (typically read off a shared
58
60
  * contact card) and route into either an immediate message or a contact
@@ -94,6 +96,7 @@ export declare class CanonAgent {
94
96
  stop(): Promise<void>;
95
97
  private hasInterruptSupport;
96
98
  private hasStopAndDropSupport;
99
+ private hasNewSessionSupport;
97
100
  private hasRuntimeSignalSupport;
98
101
  private buildRuntimeDescriptor;
99
102
  private buildRuntimeCapabilities;
@@ -1,4 +1,4 @@
1
- import { CanonClient, createRuntimeStatePublisher, FINAL_MESSAGE_HANDOFF_MS, initRTDBAuth, rtdbRead, rtdbWrite, normalizeTurnMetadata, reachOutToCanonContact, } from '@canonmsg/core';
1
+ import { CanonClient, createRuntimeStatePublisher, FINAL_MESSAGE_HANDOFF_MS, RUNTIME_NEW_SESSION_ACTION, RUNTIME_STOP_ACTION, RUNTIME_STOP_AND_DROP_ACTION, initRTDBAuth, rtdbRead, rtdbWrite, normalizeTurnMetadata, reachOutToCanonContact, } from '@canonmsg/core';
2
2
  import { randomUUID } from 'node:crypto';
3
3
  import { AuthManager } from './auth.js';
4
4
  import { Debouncer } from './debouncer.js';
@@ -18,26 +18,6 @@ const DEFAULT_SDK_RUNTIME_DESCRIPTOR = {
18
18
  supportsInterrupt: false,
19
19
  streamingTextMode: 'snapshot',
20
20
  };
21
- const SDK_STOP_ACTION = {
22
- id: 'stop',
23
- label: 'Stop',
24
- description: 'Interrupt the current SDK agent turn.',
25
- aliases: ['stop'],
26
- category: 'turn',
27
- placements: ['composer_slash', 'command_palette'],
28
- availability: ['busy'],
29
- dispatch: { kind: 'signal', signal: 'interrupt' },
30
- };
31
- const SDK_STOP_AND_DROP_ACTION = {
32
- id: 'stop-and-clear-queue',
33
- label: 'Stop & clear queue',
34
- description: 'Interrupt the current SDK agent turn and drop queued Canon messages.',
35
- aliases: ['stop-clear', 'clear-queue'],
36
- category: 'turn',
37
- placements: ['composer_slash', 'command_palette', 'session_strip'],
38
- availability: ['busy_with_queue'],
39
- dispatch: { kind: 'signal', signal: 'stop_and_drop' },
40
- };
41
21
  function sleep(ms) {
42
22
  return new Promise((resolve) => setTimeout(resolve, ms));
43
23
  }
@@ -55,6 +35,7 @@ export class CanonAgent {
55
35
  contactRemovedHandler = null;
56
36
  interruptHandler = null;
57
37
  stopAndDropHandler = null;
38
+ newSessionHandler = null;
58
39
  /** Contact-graph operations (`agent.contacts.*`). Initialized in the constructor. */
59
40
  contacts;
60
41
  /** Block/unblock operations (`agent.users.*`). Initialized in the constructor. */
@@ -100,6 +81,7 @@ export class CanonAgent {
100
81
  }
101
82
  this.interruptHandler = options.runtimeControls?.onInterrupt ?? null;
102
83
  this.stopAndDropHandler = options.runtimeControls?.onStopAndDrop ?? null;
84
+ this.newSessionHandler = options.runtimeControls?.onNewSession ?? null;
103
85
  }
104
86
  on(event, handler) {
105
87
  if (event === 'message') {
@@ -138,6 +120,16 @@ export class CanonAgent {
138
120
  void this.publishAgentRuntime().catch(() => { });
139
121
  return;
140
122
  }
123
+ if (event === 'newSession') {
124
+ this.newSessionHandler = handler;
125
+ if (this.running) {
126
+ void this.baselineRuntimeControlSignals(this.cachedConversationIds)
127
+ .then(() => this.startRuntimeControlPolling())
128
+ .catch(() => { });
129
+ }
130
+ void this.publishAgentRuntime().catch(() => { });
131
+ return;
132
+ }
141
133
  this.contactRemovedHandler = handler;
142
134
  }
143
135
  /**
@@ -366,13 +358,17 @@ export class CanonAgent {
366
358
  hasStopAndDropSupport() {
367
359
  return Boolean(this.stopAndDropHandler);
368
360
  }
361
+ hasNewSessionSupport() {
362
+ return Boolean(this.newSessionHandler);
363
+ }
369
364
  hasRuntimeSignalSupport() {
370
- return this.hasInterruptSupport() || this.hasStopAndDropSupport();
365
+ return this.hasInterruptSupport() || this.hasStopAndDropSupport() || this.hasNewSessionSupport();
371
366
  }
372
367
  buildRuntimeDescriptor() {
373
368
  const source = this.options.runtimeDescriptor ?? DEFAULT_SDK_RUNTIME_DESCRIPTOR;
374
369
  const hasInterrupt = this.hasInterruptSupport();
375
370
  const hasStopAndDrop = this.hasStopAndDropSupport();
371
+ const hasNewSession = this.hasNewSessionSupport();
376
372
  const actions = [...(source.actions ?? [])].filter((action) => {
377
373
  if (action.dispatch.kind !== 'signal')
378
374
  return true;
@@ -380,15 +376,21 @@ export class CanonAgent {
380
376
  return hasInterrupt;
381
377
  if (action.dispatch.signal === 'stop_and_drop')
382
378
  return hasStopAndDrop;
379
+ if (action.dispatch.signal === 'new_session')
380
+ return hasNewSession;
383
381
  return false;
384
382
  });
385
383
  const hasInterruptAction = actions.some((action) => action.dispatch.kind === 'signal' && action.dispatch.signal === 'interrupt');
386
384
  const hasStopAndDropAction = actions.some((action) => action.dispatch.kind === 'signal' && action.dispatch.signal === 'stop_and_drop');
385
+ const hasNewSessionAction = actions.some((action) => action.dispatch.kind === 'signal' && action.dispatch.signal === 'new_session');
387
386
  if (hasInterrupt && !hasInterruptAction) {
388
- actions.push(SDK_STOP_ACTION);
387
+ actions.push(RUNTIME_STOP_ACTION);
389
388
  }
390
389
  if (hasStopAndDrop && this.sessionManager && !hasStopAndDropAction) {
391
- actions.push(SDK_STOP_AND_DROP_ACTION);
390
+ actions.push(RUNTIME_STOP_AND_DROP_ACTION);
391
+ }
392
+ if (hasNewSession && !hasNewSessionAction) {
393
+ actions.push(RUNTIME_NEW_SESSION_ACTION);
392
394
  }
393
395
  return {
394
396
  ...source,
@@ -476,23 +478,27 @@ export class CanonAgent {
476
478
  if (!this.agentId)
477
479
  return;
478
480
  const signal = raw.type;
479
- if (signal !== 'interrupt' && signal !== 'stop_and_drop')
481
+ if (signal !== 'interrupt' && signal !== 'stop_and_drop' && signal !== 'new_session')
480
482
  return;
481
483
  const timestamp = Number(raw.updatedAt ?? 0);
482
484
  if (timestamp <= (this.lastSeenSignal.get(conversationId) ?? 0))
483
485
  return;
484
486
  this.lastSeenSignal.set(conversationId, timestamp);
485
- const handler = signal === 'stop_and_drop'
486
- ? this.stopAndDropHandler
487
- : this.interruptHandler;
487
+ const handler = signal === 'new_session'
488
+ ? this.newSessionHandler
489
+ : signal === 'stop_and_drop'
490
+ ? this.stopAndDropHandler
491
+ : this.interruptHandler;
488
492
  if (!handler) {
489
493
  await Promise.resolve(rtdbWrite(`/control/${conversationId}/${this.agentId}/signal`, null)).catch(() => { });
490
494
  return;
491
495
  }
492
496
  const abortSignal = this.abortActiveTurns(conversationId);
493
- const droppedMessages = signal === 'stop_and_drop'
494
- ? this.sessionManager?.dropQueued(conversationId) ?? []
495
- : [];
497
+ const droppedMessages = signal === 'new_session'
498
+ ? this.sessionManager?.resetSession(conversationId) ?? []
499
+ : signal === 'stop_and_drop'
500
+ ? this.sessionManager?.dropQueued(conversationId) ?? []
501
+ : [];
496
502
  const droppedMessageIds = droppedMessages.map((message) => message.id);
497
503
  await Promise.all(droppedMessages.map((message) => {
498
504
  if (message.metadata?.inboundDisposition !== 'queued')
@@ -65,6 +65,8 @@ export declare class SessionManager {
65
65
  getQueueDepth(conversationId: string): number;
66
66
  /** Drop queued, not-yet-running batches for a conversation. */
67
67
  dropQueued(conversationId: string): CanonMessage[];
68
+ /** Drop queued work and clear retained context for a conversation. */
69
+ resetSession(conversationId: string): CanonMessage[];
68
70
  /** Clean up all state */
69
71
  destroy(): void;
70
72
  }
@@ -213,6 +213,14 @@ export class SessionManager {
213
213
  }
214
214
  return droppedMessages;
215
215
  }
216
+ /** Drop queued work and clear retained context for a conversation. */
217
+ resetSession(conversationId) {
218
+ const droppedMessages = this.dropQueued(conversationId);
219
+ this.sessions.delete(conversationId);
220
+ this.seenMessages.delete(conversationId);
221
+ this.seededSessions.delete(conversationId);
222
+ return droppedMessages;
223
+ }
216
224
  /** Clean up all state */
217
225
  destroy() {
218
226
  if (this.sweepTimer) {
package/dist/types.d.ts CHANGED
@@ -115,6 +115,7 @@ export type RuntimeSignalHandler = (context: RuntimeSignalContext) => void | Pro
115
115
  export interface RuntimeControlHandlers {
116
116
  onInterrupt?: RuntimeSignalHandler;
117
117
  onStopAndDrop?: RuntimeSignalHandler;
118
+ onNewSession?: RuntimeSignalHandler;
118
119
  }
119
120
  export interface CanonAgentOptions {
120
121
  apiKey: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@canonmsg/agent-sdk",
3
- "version": "1.1.4",
3
+ "version": "1.2.0",
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.15.5"
31
+ "@canonmsg/core": "^0.16.0"
32
32
  },
33
33
  "publishConfig": {
34
34
  "access": "public"